From 89194da1490492e78ef273119c0c1cc360d03f93 Mon Sep 17 00:00:00 2001 From: Emagi Date: Mon, 22 Jul 2024 08:52:28 -0400 Subject: [PATCH] EQ2Emu Source Base July 2024 --- source/LoginServer/Character.cpp | 20 + source/LoginServer/Character.h | 25 + source/LoginServer/EQ2 Login.sln | 25 + source/LoginServer/EQ2 Login.suo | Bin 0 -> 58880 bytes source/LoginServer/LWorld.cpp | 1546 ++ source/LoginServer/LWorld.h | 253 + source/LoginServer/Login.dsp | 447 + source/LoginServer/Login.dsw | 29 + source/LoginServer/Login.vcproj | 542 + source/LoginServer/Login.vcxproj | 154 + source/LoginServer/Login.vcxproj.filters | 277 + source/LoginServer/Login.vcxproj.user | 3 + source/LoginServer/LoginAccount.cpp | 58 + source/LoginServer/LoginAccount.h | 54 + source/LoginServer/LoginDatabase.cpp | 1070 ++ source/LoginServer/LoginDatabase.h | 95 + source/LoginServer/PacketHeaders.cpp | 88 + source/LoginServer/PacketHeaders.h | 61 + source/LoginServer/Web/LoginWeb.cpp | 64 + source/LoginServer/Web/LoginWeb.o | Bin 0 -> 5302112 bytes source/LoginServer/client.cpp | 813 + source/LoginServer/client.h | 131 + source/LoginServer/login_opcodes.h | 52 + source/LoginServer/login_structs.h | 61 + source/LoginServer/makefile | 32 + source/LoginServer/net.cpp | 363 + source/LoginServer/net.h | 147 + .../WorldServer/Achievements/Achievements.cpp | 332 + .../WorldServer/Achievements/Achievements.h | 176 + .../Achievements/AchievementsDB.cpp | 241 + .../AltAdvancement/AltAdvancement.cpp | 1707 ++ .../AltAdvancement/AltAdvancement.h | 118 + .../AltAdvancement/AltAdvancementDB.cpp | 102 + source/WorldServer/Appearances.h | 87 + source/WorldServer/Bots/Bot.cpp | 732 + source/WorldServer/Bots/Bot.h | 95 + source/WorldServer/Bots/BotBrain.cpp | 205 + source/WorldServer/Bots/BotBrain.h | 20 + source/WorldServer/Bots/BotCommands.cpp | 830 + source/WorldServer/Bots/BotDB.cpp | 467 + source/WorldServer/Chat/Chat.cpp | 372 + source/WorldServer/Chat/Chat.h | 119 + source/WorldServer/Chat/ChatChannel.cpp | 227 + source/WorldServer/Chat/ChatChannel.h | 79 + source/WorldServer/Chat/ChatDB.cpp | 46 + source/WorldServer/ClientPacketFunctions.cpp | 475 + source/WorldServer/ClientPacketFunctions.h | 92 + .../WorldServer/Collections/Collections.cpp | 317 + source/WorldServer/Collections/Collections.h | 107 + .../WorldServer/Collections/CollectionsDB.cpp | 295 + source/WorldServer/Combat.cpp | 1980 +++ source/WorldServer/Combat.h | 39 + source/WorldServer/Commands/Commands.cpp | 12380 ++++++++++++++ source/WorldServer/Commands/Commands.h | 976 ++ source/WorldServer/Commands/CommandsDB.cpp | 316 + .../WorldServer/Commands/ConsoleCommands.cpp | 549 + source/WorldServer/Commands/ConsoleCommands.h | 62 + source/WorldServer/Entity.cpp | 3986 +++++ source/WorldServer/Entity.h | 2145 +++ source/WorldServer/Factions.cpp | 183 + source/WorldServer/Factions.h | 139 + source/WorldServer/GroundSpawn.cpp | 575 + source/WorldServer/GroundSpawn.h | 86 + source/WorldServer/Guilds/Guild.cpp | 2347 +++ source/WorldServer/Guilds/Guild.h | 450 + source/WorldServer/Guilds/GuildDB.cpp | 582 + source/WorldServer/HeroicOp/HeroicOp.cpp | 315 + source/WorldServer/HeroicOp/HeroicOp.h | 156 + source/WorldServer/HeroicOp/HeroicOpDB.cpp | 79 + .../WorldServer/HeroicOp/HeroicOpPackets.cpp | 158 + source/WorldServer/Housing/HousingDB.cpp | 131 + source/WorldServer/Housing/HousingPackets.cpp | 454 + source/WorldServer/Items/Items.cpp | 4648 +++++ source/WorldServer/Items/Items.h | 1223 ++ source/WorldServer/Items/ItemsDB.cpp | 1419 ++ source/WorldServer/Items/Items_CoE.h | 817 + source/WorldServer/Items/Items_DoV.h | 916 + source/WorldServer/Items/Items_ToV.h | 211 + source/WorldServer/Items/Loot.cpp | 134 + source/WorldServer/Items/Loot.h | 23 + source/WorldServer/Items/LootDB.cpp | 210 + source/WorldServer/Languages.cpp | 153 + source/WorldServer/Languages.h | 76 + source/WorldServer/LoginServer.cpp | 667 + source/WorldServer/LoginServer.h | 88 + source/WorldServer/LuaFunctions.cpp | 14167 ++++++++++++++++ source/WorldServer/LuaFunctions.h | 660 + source/WorldServer/LuaInterface.cpp | 2774 +++ source/WorldServer/LuaInterface.h | 357 + source/WorldServer/MutexHelper.h | 202 + source/WorldServer/MutexList.h | 277 + source/WorldServer/MutexMap.h | 304 + source/WorldServer/MutexVector.h | 202 + source/WorldServer/NPC.cpp | 1075 ++ source/WorldServer/NPC.h | 217 + source/WorldServer/NPC_AI.cpp | 902 + source/WorldServer/NPC_AI.h | 198 + source/WorldServer/Object.cpp | 99 + source/WorldServer/Object.h | 49 + source/WorldServer/Player.cpp | 7549 ++++++++ source/WorldServer/Player.h | 1258 ++ source/WorldServer/PlayerGroups.cpp | 1000 ++ source/WorldServer/PlayerGroups.h | 221 + source/WorldServer/Quests.cpp | 1867 ++ source/WorldServer/Quests.h | 447 + source/WorldServer/RaceTypes/RaceTypes.cpp | 120 + source/WorldServer/RaceTypes/RaceTypes.h | 321 + source/WorldServer/RaceTypes/RaceTypesDB.cpp | 43 + source/WorldServer/Recipes/Recipe.cpp | 778 + source/WorldServer/Recipes/Recipe.h | 272 + source/WorldServer/Recipes/RecipeDB.cpp | 296 + source/WorldServer/Rules/Rules.cpp | 532 + source/WorldServer/Rules/Rules.h | 363 + source/WorldServer/Rules/RulesDB.cpp | 109 + source/WorldServer/Sign.cpp | 319 + source/WorldServer/Sign.h | 91 + source/WorldServer/Skills.cpp | 591 + source/WorldServer/Skills.h | 180 + source/WorldServer/Spawn.cpp | 5316 ++++++ source/WorldServer/Spawn.h | 1568 ++ source/WorldServer/SpawnLists.h | 108 + source/WorldServer/SpellProcess.cpp | 3039 ++++ source/WorldServer/SpellProcess.h | 423 + source/WorldServer/Spells.cpp | 2453 +++ source/WorldServer/Spells.h | 446 + source/WorldServer/Titles.cpp | 162 + source/WorldServer/Titles.h | 83 + source/WorldServer/Trade.cpp | 604 + source/WorldServer/Trade.h | 57 + .../WorldServer/Tradeskills/Tradeskills.cpp | 869 + source/WorldServer/Tradeskills/Tradeskills.h | 153 + .../WorldServer/Tradeskills/TradeskillsDB.cpp | 64 + .../Tradeskills/TradeskillsPackets.cpp | 610 + source/WorldServer/Traits/Traits.cpp | 932 + source/WorldServer/Traits/Traits.h | 94 + source/WorldServer/Transmute.cpp | 339 + source/WorldServer/Transmute.h | 35 + source/WorldServer/Variables.h | 92 + source/WorldServer/VisualStates.h | 282 + source/WorldServer/Web/WorldWeb.cpp | 84 + source/WorldServer/Widget.cpp | 468 + source/WorldServer/Widget.h | 133 + source/WorldServer/World.cpp | 2989 ++++ source/WorldServer/World.h | 729 + source/WorldServer/WorldDatabase.cpp | 8563 ++++++++++ source/WorldServer/WorldDatabase.h | 664 + source/WorldServer/WorldTCPConnection.h | 33 + source/WorldServer/Zone/ChestTrap.cpp | 285 + source/WorldServer/Zone/ChestTrap.h | 134 + source/WorldServer/Zone/map.cpp | 983 ++ source/WorldServer/Zone/map.h | 151 + .../WorldServer/Zone/mob_movement_manager.cpp | 1279 ++ .../WorldServer/Zone/mob_movement_manager.h | 111 + .../WorldServer/Zone/pathfinder_interface.cpp | 21 + .../WorldServer/Zone/pathfinder_interface.h | 81 + .../WorldServer/Zone/pathfinder_nav_mesh.cpp | 514 + source/WorldServer/Zone/pathfinder_nav_mesh.h | 43 + source/WorldServer/Zone/pathfinder_null.cpp | 26 + source/WorldServer/Zone/pathfinder_null.h | 14 + .../WorldServer/Zone/pathfinder_waypoint.cpp | 517 + source/WorldServer/Zone/pathfinder_waypoint.h | 36 + source/WorldServer/Zone/position.cpp | 255 + source/WorldServer/Zone/position.h | 67 + source/WorldServer/Zone/raycast_mesh.cpp | 973 ++ source/WorldServer/Zone/raycast_mesh.h | 73 + source/WorldServer/Zone/region_map.cpp | 125 + source/WorldServer/Zone/region_map.h | 130 + source/WorldServer/Zone/region_map_v1.cpp | 868 + source/WorldServer/Zone/region_map_v1.h | 90 + source/WorldServer/classes.cpp | 199 + source/WorldServer/classes.h | 119 + source/WorldServer/client.cpp | 13140 ++++++++++++++ source/WorldServer/client.h | 767 + source/WorldServer/makefile | 100 + source/WorldServer/makefile.discord | 97 + source/WorldServer/net.cpp | 948 ++ source/WorldServer/net.h | 143 + source/WorldServer/races.cpp | 147 + source/WorldServer/races.h | 62 + source/WorldServer/zoneserver.cpp | 9104 ++++++++++ source/WorldServer/zoneserver.h | 1168 ++ source/common/CRC16.cpp | 328 + source/common/CRC16.h | 25 + source/common/Common_Defines.h | 32 + source/common/Condition.cpp | 133 + source/common/Condition.h | 48 + source/common/ConfigReader.cpp | 302 + source/common/ConfigReader.h | 52 + source/common/Crypto.cpp | 47 + source/common/Crypto.h | 66 + source/common/DataBuffer.h | 207 + source/common/DatabaseNew.cpp | 422 + source/common/DatabaseNew.h | 48 + source/common/DatabaseResult.cpp | 234 + source/common/DatabaseResult.h | 57 + source/common/EQ2_Common_Structs.h | 361 + source/common/EQEMuError.cpp | 131 + source/common/EQEMuError.h | 39 + source/common/EQPacket.cpp | 652 + source/common/EQPacket.h | 209 + source/common/EQStream.cpp | 1908 +++ source/common/EQStream.h | 373 + source/common/EQStreamFactory.cpp | 444 + source/common/EQStreamFactory.h | 86 + source/common/GlobalHeaders.h | 58 + source/common/JsonParser.cpp | 97 + source/common/JsonParser.h | 33 + source/common/Log.cpp | 615 + source/common/Log.h | 69 + source/common/LogTypes.h | 519 + source/common/MiscFunctions.cpp | 980 ++ source/common/MiscFunctions.h | 187 + source/common/Mutex.cpp | 361 + source/common/Mutex.h | 103 + source/common/PacketStruct.cpp | 2738 +++ source/common/PacketStruct.h | 513 + source/common/RC4.cpp | 93 + source/common/RC4.h | 38 + source/common/TCPConnection.cpp | 1729 ++ source/common/TCPConnection.h | 277 + source/common/Web/WebServer.cpp | 336 + source/common/Web/WebServer.h | 56 + source/common/Web/WebServer.o | Bin 0 -> 10894152 bytes source/common/database.cpp | 567 + source/common/database.h | 183 + source/common/dbcore.cpp | 368 + source/common/dbcore.h | 80 + source/common/debug.cpp | 336 + source/common/debug.h | 143 + source/common/emu_opcodes.cpp | 39 + source/common/emu_opcodes.h | 56 + source/common/emu_oplist.h | 505 + source/common/linked_list.h | 445 + source/common/login_oplist.h | 61 + source/common/md5.cpp | 281 + source/common/md5.h | 64 + source/common/misc.cpp | 305 + source/common/misc.h | 65 + source/common/op_codes.h | 44 + source/common/opcodemgr.cpp | 350 + source/common/opcodemgr.h | 162 + source/common/packet_dump.cpp | 195 + source/common/packet_dump.h | 41 + source/common/packet_functions.cpp | 537 + source/common/packet_functions.h | 45 + source/common/queue.h | 128 + source/common/seperator.h | 165 + source/common/servertalk.h | 754 + source/common/sha512.cpp | 155 + source/common/sha512.h | 71 + source/common/string_util.cpp | 529 + source/common/string_util.h | 193 + source/common/timer.cpp | 207 + source/common/timer.h | 88 + source/common/types.h | 191 + source/common/unix.cpp | 45 + source/common/unix.h | 34 + source/common/version.h | 56 + source/common/xmlParser.cpp | 2974 ++++ source/common/xmlParser.h | 732 + 260 files changed, 180526 insertions(+) create mode 100644 source/LoginServer/Character.cpp create mode 100644 source/LoginServer/Character.h create mode 100644 source/LoginServer/EQ2 Login.sln create mode 100644 source/LoginServer/EQ2 Login.suo create mode 100644 source/LoginServer/LWorld.cpp create mode 100644 source/LoginServer/LWorld.h create mode 100644 source/LoginServer/Login.dsp create mode 100644 source/LoginServer/Login.dsw create mode 100644 source/LoginServer/Login.vcproj create mode 100644 source/LoginServer/Login.vcxproj create mode 100644 source/LoginServer/Login.vcxproj.filters create mode 100644 source/LoginServer/Login.vcxproj.user create mode 100644 source/LoginServer/LoginAccount.cpp create mode 100644 source/LoginServer/LoginAccount.h create mode 100644 source/LoginServer/LoginDatabase.cpp create mode 100644 source/LoginServer/LoginDatabase.h create mode 100644 source/LoginServer/PacketHeaders.cpp create mode 100644 source/LoginServer/PacketHeaders.h create mode 100644 source/LoginServer/Web/LoginWeb.cpp create mode 100644 source/LoginServer/Web/LoginWeb.o create mode 100644 source/LoginServer/client.cpp create mode 100644 source/LoginServer/client.h create mode 100644 source/LoginServer/login_opcodes.h create mode 100644 source/LoginServer/login_structs.h create mode 100644 source/LoginServer/makefile create mode 100644 source/LoginServer/net.cpp create mode 100644 source/LoginServer/net.h create mode 100644 source/WorldServer/Achievements/Achievements.cpp create mode 100644 source/WorldServer/Achievements/Achievements.h create mode 100644 source/WorldServer/Achievements/AchievementsDB.cpp create mode 100644 source/WorldServer/AltAdvancement/AltAdvancement.cpp create mode 100644 source/WorldServer/AltAdvancement/AltAdvancement.h create mode 100644 source/WorldServer/AltAdvancement/AltAdvancementDB.cpp create mode 100644 source/WorldServer/Appearances.h create mode 100644 source/WorldServer/Bots/Bot.cpp create mode 100644 source/WorldServer/Bots/Bot.h create mode 100644 source/WorldServer/Bots/BotBrain.cpp create mode 100644 source/WorldServer/Bots/BotBrain.h create mode 100644 source/WorldServer/Bots/BotCommands.cpp create mode 100644 source/WorldServer/Bots/BotDB.cpp create mode 100644 source/WorldServer/Chat/Chat.cpp create mode 100644 source/WorldServer/Chat/Chat.h create mode 100644 source/WorldServer/Chat/ChatChannel.cpp create mode 100644 source/WorldServer/Chat/ChatChannel.h create mode 100644 source/WorldServer/Chat/ChatDB.cpp create mode 100644 source/WorldServer/ClientPacketFunctions.cpp create mode 100644 source/WorldServer/ClientPacketFunctions.h create mode 100644 source/WorldServer/Collections/Collections.cpp create mode 100644 source/WorldServer/Collections/Collections.h create mode 100644 source/WorldServer/Collections/CollectionsDB.cpp create mode 100644 source/WorldServer/Combat.cpp create mode 100644 source/WorldServer/Combat.h create mode 100644 source/WorldServer/Commands/Commands.cpp create mode 100644 source/WorldServer/Commands/Commands.h create mode 100644 source/WorldServer/Commands/CommandsDB.cpp create mode 100644 source/WorldServer/Commands/ConsoleCommands.cpp create mode 100644 source/WorldServer/Commands/ConsoleCommands.h create mode 100644 source/WorldServer/Entity.cpp create mode 100644 source/WorldServer/Entity.h create mode 100644 source/WorldServer/Factions.cpp create mode 100644 source/WorldServer/Factions.h create mode 100644 source/WorldServer/GroundSpawn.cpp create mode 100644 source/WorldServer/GroundSpawn.h create mode 100644 source/WorldServer/Guilds/Guild.cpp create mode 100644 source/WorldServer/Guilds/Guild.h create mode 100644 source/WorldServer/Guilds/GuildDB.cpp create mode 100644 source/WorldServer/HeroicOp/HeroicOp.cpp create mode 100644 source/WorldServer/HeroicOp/HeroicOp.h create mode 100644 source/WorldServer/HeroicOp/HeroicOpDB.cpp create mode 100644 source/WorldServer/HeroicOp/HeroicOpPackets.cpp create mode 100644 source/WorldServer/Housing/HousingDB.cpp create mode 100644 source/WorldServer/Housing/HousingPackets.cpp create mode 100644 source/WorldServer/Items/Items.cpp create mode 100644 source/WorldServer/Items/Items.h create mode 100644 source/WorldServer/Items/ItemsDB.cpp create mode 100644 source/WorldServer/Items/Items_CoE.h create mode 100644 source/WorldServer/Items/Items_DoV.h create mode 100644 source/WorldServer/Items/Items_ToV.h create mode 100644 source/WorldServer/Items/Loot.cpp create mode 100644 source/WorldServer/Items/Loot.h create mode 100644 source/WorldServer/Items/LootDB.cpp create mode 100644 source/WorldServer/Languages.cpp create mode 100644 source/WorldServer/Languages.h create mode 100644 source/WorldServer/LoginServer.cpp create mode 100644 source/WorldServer/LoginServer.h create mode 100644 source/WorldServer/LuaFunctions.cpp create mode 100644 source/WorldServer/LuaFunctions.h create mode 100644 source/WorldServer/LuaInterface.cpp create mode 100644 source/WorldServer/LuaInterface.h create mode 100644 source/WorldServer/MutexHelper.h create mode 100644 source/WorldServer/MutexList.h create mode 100644 source/WorldServer/MutexMap.h create mode 100644 source/WorldServer/MutexVector.h create mode 100644 source/WorldServer/NPC.cpp create mode 100644 source/WorldServer/NPC.h create mode 100644 source/WorldServer/NPC_AI.cpp create mode 100644 source/WorldServer/NPC_AI.h create mode 100644 source/WorldServer/Object.cpp create mode 100644 source/WorldServer/Object.h create mode 100644 source/WorldServer/Player.cpp create mode 100644 source/WorldServer/Player.h create mode 100644 source/WorldServer/PlayerGroups.cpp create mode 100644 source/WorldServer/PlayerGroups.h create mode 100644 source/WorldServer/Quests.cpp create mode 100644 source/WorldServer/Quests.h create mode 100644 source/WorldServer/RaceTypes/RaceTypes.cpp create mode 100644 source/WorldServer/RaceTypes/RaceTypes.h create mode 100644 source/WorldServer/RaceTypes/RaceTypesDB.cpp create mode 100644 source/WorldServer/Recipes/Recipe.cpp create mode 100644 source/WorldServer/Recipes/Recipe.h create mode 100644 source/WorldServer/Recipes/RecipeDB.cpp create mode 100644 source/WorldServer/Rules/Rules.cpp create mode 100644 source/WorldServer/Rules/Rules.h create mode 100644 source/WorldServer/Rules/RulesDB.cpp create mode 100644 source/WorldServer/Sign.cpp create mode 100644 source/WorldServer/Sign.h create mode 100644 source/WorldServer/Skills.cpp create mode 100644 source/WorldServer/Skills.h create mode 100644 source/WorldServer/Spawn.cpp create mode 100644 source/WorldServer/Spawn.h create mode 100644 source/WorldServer/SpawnLists.h create mode 100644 source/WorldServer/SpellProcess.cpp create mode 100644 source/WorldServer/SpellProcess.h create mode 100644 source/WorldServer/Spells.cpp create mode 100644 source/WorldServer/Spells.h create mode 100644 source/WorldServer/Titles.cpp create mode 100644 source/WorldServer/Titles.h create mode 100644 source/WorldServer/Trade.cpp create mode 100644 source/WorldServer/Trade.h create mode 100644 source/WorldServer/Tradeskills/Tradeskills.cpp create mode 100644 source/WorldServer/Tradeskills/Tradeskills.h create mode 100644 source/WorldServer/Tradeskills/TradeskillsDB.cpp create mode 100644 source/WorldServer/Tradeskills/TradeskillsPackets.cpp create mode 100644 source/WorldServer/Traits/Traits.cpp create mode 100644 source/WorldServer/Traits/Traits.h create mode 100644 source/WorldServer/Transmute.cpp create mode 100644 source/WorldServer/Transmute.h create mode 100644 source/WorldServer/Variables.h create mode 100644 source/WorldServer/VisualStates.h create mode 100644 source/WorldServer/Web/WorldWeb.cpp create mode 100644 source/WorldServer/Widget.cpp create mode 100644 source/WorldServer/Widget.h create mode 100644 source/WorldServer/World.cpp create mode 100644 source/WorldServer/World.h create mode 100644 source/WorldServer/WorldDatabase.cpp create mode 100644 source/WorldServer/WorldDatabase.h create mode 100644 source/WorldServer/WorldTCPConnection.h create mode 100644 source/WorldServer/Zone/ChestTrap.cpp create mode 100644 source/WorldServer/Zone/ChestTrap.h create mode 100644 source/WorldServer/Zone/map.cpp create mode 100644 source/WorldServer/Zone/map.h create mode 100644 source/WorldServer/Zone/mob_movement_manager.cpp create mode 100644 source/WorldServer/Zone/mob_movement_manager.h create mode 100644 source/WorldServer/Zone/pathfinder_interface.cpp create mode 100644 source/WorldServer/Zone/pathfinder_interface.h create mode 100644 source/WorldServer/Zone/pathfinder_nav_mesh.cpp create mode 100644 source/WorldServer/Zone/pathfinder_nav_mesh.h create mode 100644 source/WorldServer/Zone/pathfinder_null.cpp create mode 100644 source/WorldServer/Zone/pathfinder_null.h create mode 100644 source/WorldServer/Zone/pathfinder_waypoint.cpp create mode 100644 source/WorldServer/Zone/pathfinder_waypoint.h create mode 100644 source/WorldServer/Zone/position.cpp create mode 100644 source/WorldServer/Zone/position.h create mode 100644 source/WorldServer/Zone/raycast_mesh.cpp create mode 100644 source/WorldServer/Zone/raycast_mesh.h create mode 100644 source/WorldServer/Zone/region_map.cpp create mode 100644 source/WorldServer/Zone/region_map.h create mode 100644 source/WorldServer/Zone/region_map_v1.cpp create mode 100644 source/WorldServer/Zone/region_map_v1.h create mode 100644 source/WorldServer/classes.cpp create mode 100644 source/WorldServer/classes.h create mode 100644 source/WorldServer/client.cpp create mode 100644 source/WorldServer/client.h create mode 100644 source/WorldServer/makefile create mode 100644 source/WorldServer/makefile.discord create mode 100644 source/WorldServer/net.cpp create mode 100644 source/WorldServer/net.h create mode 100644 source/WorldServer/races.cpp create mode 100644 source/WorldServer/races.h create mode 100644 source/WorldServer/zoneserver.cpp create mode 100644 source/WorldServer/zoneserver.h create mode 100644 source/common/CRC16.cpp create mode 100644 source/common/CRC16.h create mode 100644 source/common/Common_Defines.h create mode 100644 source/common/Condition.cpp create mode 100644 source/common/Condition.h create mode 100644 source/common/ConfigReader.cpp create mode 100644 source/common/ConfigReader.h create mode 100644 source/common/Crypto.cpp create mode 100644 source/common/Crypto.h create mode 100644 source/common/DataBuffer.h create mode 100644 source/common/DatabaseNew.cpp create mode 100644 source/common/DatabaseNew.h create mode 100644 source/common/DatabaseResult.cpp create mode 100644 source/common/DatabaseResult.h create mode 100644 source/common/EQ2_Common_Structs.h create mode 100644 source/common/EQEMuError.cpp create mode 100644 source/common/EQEMuError.h create mode 100644 source/common/EQPacket.cpp create mode 100644 source/common/EQPacket.h create mode 100644 source/common/EQStream.cpp create mode 100644 source/common/EQStream.h create mode 100644 source/common/EQStreamFactory.cpp create mode 100644 source/common/EQStreamFactory.h create mode 100644 source/common/GlobalHeaders.h create mode 100644 source/common/JsonParser.cpp create mode 100644 source/common/JsonParser.h create mode 100644 source/common/Log.cpp create mode 100644 source/common/Log.h create mode 100644 source/common/LogTypes.h create mode 100644 source/common/MiscFunctions.cpp create mode 100644 source/common/MiscFunctions.h create mode 100644 source/common/Mutex.cpp create mode 100644 source/common/Mutex.h create mode 100644 source/common/PacketStruct.cpp create mode 100644 source/common/PacketStruct.h create mode 100644 source/common/RC4.cpp create mode 100644 source/common/RC4.h create mode 100644 source/common/TCPConnection.cpp create mode 100644 source/common/TCPConnection.h create mode 100644 source/common/Web/WebServer.cpp create mode 100644 source/common/Web/WebServer.h create mode 100644 source/common/Web/WebServer.o create mode 100644 source/common/database.cpp create mode 100644 source/common/database.h create mode 100644 source/common/dbcore.cpp create mode 100644 source/common/dbcore.h create mode 100644 source/common/debug.cpp create mode 100644 source/common/debug.h create mode 100644 source/common/emu_opcodes.cpp create mode 100644 source/common/emu_opcodes.h create mode 100644 source/common/emu_oplist.h create mode 100644 source/common/linked_list.h create mode 100644 source/common/login_oplist.h create mode 100644 source/common/md5.cpp create mode 100644 source/common/md5.h create mode 100644 source/common/misc.cpp create mode 100644 source/common/misc.h create mode 100644 source/common/op_codes.h create mode 100644 source/common/opcodemgr.cpp create mode 100644 source/common/opcodemgr.h create mode 100644 source/common/packet_dump.cpp create mode 100644 source/common/packet_dump.h create mode 100644 source/common/packet_functions.cpp create mode 100644 source/common/packet_functions.h create mode 100644 source/common/queue.h create mode 100644 source/common/seperator.h create mode 100644 source/common/servertalk.h create mode 100644 source/common/sha512.cpp create mode 100644 source/common/sha512.h create mode 100644 source/common/string_util.cpp create mode 100644 source/common/string_util.h create mode 100644 source/common/timer.cpp create mode 100644 source/common/timer.h create mode 100644 source/common/types.h create mode 100644 source/common/unix.cpp create mode 100644 source/common/unix.h create mode 100644 source/common/version.h create mode 100644 source/common/xmlParser.cpp create mode 100644 source/common/xmlParser.h diff --git a/source/LoginServer/Character.cpp b/source/LoginServer/Character.cpp new file mode 100644 index 0000000..67423d8 --- /dev/null +++ b/source/LoginServer/Character.cpp @@ -0,0 +1,20 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Character.h" \ No newline at end of file diff --git a/source/LoginServer/Character.h b/source/LoginServer/Character.h new file mode 100644 index 0000000..a0e89a8 --- /dev/null +++ b/source/LoginServer/Character.h @@ -0,0 +1,25 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _EQ2_CHARACTER_ +#define _EQ2_CHARACTER_ +class Character{ + +}; +#endif diff --git a/source/LoginServer/EQ2 Login.sln b/source/LoginServer/EQ2 Login.sln new file mode 100644 index 0000000..48169c7 --- /dev/null +++ b/source/LoginServer/EQ2 Login.sln @@ -0,0 +1,25 @@ +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EQ2 Login", "Login.vcxproj", "{BE2C1914-FCCC-4F65-A7DD-105142B36104}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + EQ2Login|Win32 = EQ2Login|Win32 + MiniLogin Release|Win32 = MiniLogin Release|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Debug|Win32.ActiveCfg = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Debug|Win32.Build.0 = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.EQ2Login|Win32.ActiveCfg = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.EQ2Login|Win32.Build.0 = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.MiniLogin Release|Win32.ActiveCfg = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.MiniLogin Release|Win32.Build.0 = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Release|Win32.ActiveCfg = EQ2Login|Win32 + {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Release|Win32.Build.0 = EQ2Login|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/source/LoginServer/EQ2 Login.suo b/source/LoginServer/EQ2 Login.suo new file mode 100644 index 0000000000000000000000000000000000000000..d090a9e694dfe1f02c09befe49c158489ad874fb GIT binary patch literal 58880 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 literal 0 HcmV?d00001 diff --git a/source/LoginServer/LWorld.cpp b/source/LoginServer/LWorld.cpp new file mode 100644 index 0000000..aa80ad4 --- /dev/null +++ b/source/LoginServer/LWorld.cpp @@ -0,0 +1,1546 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" +#include +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include + +#include "../common/unix.h" + +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 + +extern int errno; +#endif + +#include "../common/servertalk.h" +#include "LWorld.h" +#include "net.h" +#include "client.h" +#include "../common/packet_dump.h" +#include "login_opcodes.h" +#include "login_structs.h" +#include "LoginDatabase.h" +#include "PacketHeaders.h" +#include "../common/ConfigReader.h" + +#ifdef WIN32 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +extern ClientList client_list; +extern NetConnection net; +extern LWorldList world_list; +extern LoginDatabase database; +extern ConfigReader configReader; +extern volatile bool RunLoops; + +#include "../common/Log.h" +using namespace std; +LWorld::LWorld(TCPConnection* in_con, bool in_OutgoingLoginUplink, int32 iIP, int16 iPort, bool iNeverKick) { + Link = in_con; + RemoteID = 0; + LinkWorldID = 0; + if (iIP) + ip = iIP; + else + ip = in_con->GetrIP(); + + struct in_addr in; + in.s_addr = in_con->GetrIP(); + char* ipadd = inet_ntoa(in); + if(ipadd) + strncpy(IPAddr,ipadd,64); + + if (iPort) + port = iPort; + else + port = in_con->GetrPort(); + ID = 0; + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = 0; + accountid = 0; + admin_id = 0; + IsInit = false; + kicked = false; + pNeverKick = iNeverKick; + pPlaceholder = false; + pshowdown = false; + pConnected = in_con->Connected(); + pReconnectTimer = 0; + pStatsTimer = NULL; + isAuthenticated = false; + + if (in_OutgoingLoginUplink) { + pClientPort = port; + ptype = Login; + OutgoingUplink = true; + if (net.GetLoginMode() == Mesh) { + pReconnectTimer = new Timer(INTERSERVER_TIMER); + pReconnectTimer->Trigger(); + } + } + else { + ptype = UnknownW; + OutgoingUplink = false; + } + + in.s_addr = GetIP(); + strcpy(address, inet_ntoa(in)); + isaddressip = true; + + num_players = 0; + num_zones = 0; +} + +LWorld::LWorld(int32 in_accountid, char* in_accountname, char* in_worldname, int32 in_admin_id) { + pPlaceholder = true; + Link = 0; + ip = 0; + port = 0; + ID = 0; + strcpy(IPAddr,""); + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = 0; + accountid = in_accountid; + admin_id = in_admin_id; + IsInit = false; + kicked = false; + pNeverKick = false; + pshowdown = true; + RemoteID = 0; + LinkWorldID = 0; + OutgoingUplink = false; + pReconnectTimer = 0; + pConnected = false; + + pStatsTimer = NULL; + + ptype = World; + strcpy(account, in_accountname); + strcpy(worldname, in_worldname); + + strcpy(address, "none"); + + isaddressip = true; + + num_players = 0; + num_zones = 0; +} + +LWorld::LWorld(TCPConnection* in_RemoteLink, int32 in_ip, int32 in_RemoteID, int32 in_accountid, char* in_accountname, char* in_worldname, char* in_address, sint32 in_status, int32 in_adminid, bool in_showdown, int8 in_authlevel, bool in_placeholder, int32 iLinkWorldID) { + Link = in_RemoteLink; + RemoteID = in_RemoteID; + LinkWorldID = iLinkWorldID; + ip = in_ip; + + struct in_addr in; + if(in_RemoteLink) + in.s_addr = in_RemoteLink->GetrIP(); + else if (in_ip) + in.s_addr = in_ip; + char* ipadd = inet_ntoa(in); + if(ipadd) + strncpy(IPAddr,ipadd,64); + + port = 0; + ID = 0; + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = in_status; + accountid = in_accountid; + admin_id = in_adminid; + IsInit = true; + kicked = false; + pNeverKick = false; + pPlaceholder = in_placeholder; + pshowdown = in_showdown; + OutgoingUplink = false; + pReconnectTimer = 0; + pConnected = true; + pStatsTimer = NULL; + + ptype = World; + strcpy(account, in_accountname); + strcpy(worldname, in_worldname); + + strcpy(address, in_address); + + isaddressip = false; + + num_players = 0; + num_zones = 0; +} + +LWorld::~LWorld() { + + safe_delete ( pStatsTimer ); + num_zones = 0; + num_players = 0; + database.UpdateWorldServerStats(this, -4); + + if (ptype == World && RemoteID == 0) { + if (net.GetLoginMode() != Mesh || (!pPlaceholder && IsInit)) { + ServerPacket* pack = new ServerPacket(ServerOP_WorldListRemove, sizeof(int32)); + *((int32*) pack->pBuffer) = GetID(); + world_list.SendPacketLogin(pack); + delete pack; + } + } + + if (Link != 0 && RemoteID == 0) { + world_list.RemoveByLink(Link, 0, this); + if (OutgoingUplink) + delete Link; + else + Link->Free(); + } + Link = 0; + safe_delete(pReconnectTimer); + + world_list.RemoveByID ( this->GetID ( ) ); +} + +bool LWorld::Process() { + bool ret = true; + if (Link == 0) + return true; + if (Link->Connected()) { + if (!pConnected) { + pConnected = true; + } + } + else { + pConnected = false; + if (pReconnectTimer) { + if (pReconnectTimer->Check() && Link->ConnectReady()) { + pReconnectTimer->Start(pReconnectTimer->GetTimerTime() + (rand()%15000), false); + } + return true; + } + return false; + } + if (RemoteID != 0) + return true; + + if(pStatsTimer && pStatsTimer->Check()) + { + if(isAuthenticated && (database.IsServerAccountDisabled(account) || database.IsIPBanned(IPAddr))) + { + this->Kick(ERROR_BADPASSWORD); + return false; + } + + database.UpdateWorldServerStats(this, GetStatus()); + } + + ServerPacket* pack = 0; + while (ret && (pack = Link->PopPacket())) { + + // this stops connections from sending invalid packets without first authenticating + // with the login server to show it is a legit server + if(!isAuthenticated && pack->opcode != ServerOP_LSInfo) + { + Kick("This connection has not authenticated."); + break; + } + + switch(pack->opcode) { + case 0: + break; + case ServerOP_KeepAlive: { + // ignore this + break; + } + case ServerOP_LSFatalError: { + net.Uplink_WrongVersion = true; + ret = false; + kicked = true; + break; + } + case ServerOP_CharacterCreate: { + WorldCharNameFilterResponse_Struct* wcnfr = (WorldCharNameFilterResponse_Struct*) pack->pBuffer; + + Client* client = client_list.FindByLSID(wcnfr->account_id); + if(!client){ + if(wcnfr->account_id == 0){ + client_list.FindByCreateRequest(); + } + break; + } + if(wcnfr->response == 1) + { + client->CharacterApproved(GetID(),wcnfr->char_id); + } + else + { + client->CharacterRejected(wcnfr->response); + } + break; + } + case ServerOP_UsertoWorldReq: { + UsertoWorldRequest_Struct* ustwr = (UsertoWorldRequest_Struct*) pack->pBuffer; + if (ustwr->ToID) { + LWorld* world = world_list.FindByID(ustwr->ToID); + if (!world) { + break; + } + if (this->GetType() != Login) { + break; + } + ustwr->FromID = this->GetID(); + world->SendPacket(pack); + } + break; + } + case ServerOP_UsertoWorldResp: { + if (pack->size != sizeof(UsertoWorldResponse_Struct)) + break; + + UsertoWorldResponse_Struct* seps = (UsertoWorldResponse_Struct*) pack->pBuffer; + if (seps->ToID) { + LWorld* world = world_list.FindByID(seps->ToID); + + if (this->GetType() != Login) { + break; + } + if (world) { + seps->ToID = world->GetRemoteID(); + world->SendPacket(pack); + } + } + else { + Client* client = 0; + client = client_list.FindByLSID(seps->lsaccountid); + if(client == 0) + break; + if(this->GetID() != seps->worldid && this->GetType() != Login) + break; + + client->WorldResponse(GetID(),seps->response, seps->ip_address, seps->port, seps->access_key); + } + break; + } + case ServerOP_CharTimeStamp: { // This is being sent to synch a new timestamp on the login server + if(pack->size != sizeof(CharacterTimeStamp_Struct)) + break; + + CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) pack->pBuffer; + + if(!database.UpdateCharacterTimeStamp(cts->account_id,cts->char_id,cts->unix_timestamp,GetAccountID())) + printf("TimeStamp update error with character id %i of account id %i on server %i\n",cts->char_id,cts->account_id,GetAccountID()); + + //Todo: Synch with the other login servers + + break; + } + case ServerOP_GetTableData:{ + Kick("This is not an update server."); + break; + } + case ServerOP_GetTableQuery:{ + Kick("This is not an update server."); + break; + } + case ServerOP_GetLatestTables:{ + Kick("This is not an update server."); + break; + } + case ServerOP_ZoneUpdate:{ + if(pack->size > CHARZONESTRUCT_MAXSIZE) + break; + CharZoneUpdate_Struct* czu = (CharZoneUpdate_Struct*)pack->pBuffer; + database.UpdateCharacterZone(czu->account_id, czu->char_id, czu->zone_id, GetAccountID()); + break; + } + case ServerOP_RaceUpdate: { + + if(pack->size != sizeof(RaceUpdate_Struct)) + break; + + RaceUpdate_Struct* ru = (RaceUpdate_Struct*) pack->pBuffer; + database.UpdateCharacterRace(ru->account_id , ru->char_id , ru->model_type , ru->race , this->GetAccountID ( )); + break; + } + case ServerOP_BasicCharUpdate: { + if(pack->size != sizeof(CharDataUpdate_Struct)) + break; + + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*) pack->pBuffer; + + switch(cdu->update_field) + { + case LEVEL_UPDATE_FLAG: + { + database.UpdateCharacterLevel(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case CLASS_UPDATE_FLAG: + { + database.UpdateCharacterClass(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case GENDER_UPDATE_FLAG: + { + database.UpdateCharacterGender(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case DELETE_UPDATE_FLAG: + { + if(cdu->update_field == 1) + database.DeleteCharacter(cdu->account_id,cdu->char_id,this->GetAccountID()); + break; + } + } + break; + } + case ServerOP_LSInfo: { + if (pack->size != sizeof(ServerLSInfo_Struct)) { + this->Kick(ERROR_BADVERSION); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else { + ServerLSInfo_Struct* lsi = (ServerLSInfo_Struct*) pack->pBuffer; + if (strcmp(lsi->protocolversion, EQEMU_PROTOCOL_VERSION) != 0 || !database.CheckVersion(lsi->serverversion)) { + cout << "ERROR - KICK BAD VERSION: Got versions: protocol: '" << lsi->protocolversion << "', database version: " << lsi->serverversion << endl; + cout << "To allow all world server versions to login, run query on your login database (alternatively replacing * with the database version if preferred): insert into login_versions set version = '*';" << endl; + this->Kick(ERROR_BADVERSION); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else if (!SetupWorld(lsi->name, lsi->address, lsi->account, lsi->password, lsi->serverversion)) { + this->Kick(ERROR_BADPASSWORD); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else{ + isAuthenticated = true; + devel_server = (lsi->servertype == 4); + } + } + break; + } + case ServerOP_LSStatus: { + ServerLSStatus_Struct* lss = (ServerLSStatus_Struct*) pack->pBuffer; + + if(lss->num_players > 5000 || lss->num_zones > 500) { + this->Kick("Your server has exceeded a number of players and/or zone limit."); + ret = false; + break; + } + UpdateStatus(lss->status, lss->num_players, lss->num_zones, lss->world_max_level); + break; + } + case ServerOP_SystemwideMessage: { + if (this->GetType() == Login) { + // no looping plz =p + //world_list.SendPacket(pack, this); + } + else if (this->GetType() == Chat) { + world_list.SendPacket(pack); + } + else { + } + break; + } + case ServerOP_ListWorlds: { + if (pack->size <= 1 || pack->pBuffer[pack->size - 1] != 0) { + break; + } + world_list.SendWorldStatus(this, (char*) pack->pBuffer); + break; + } + case ServerOP_WorldListUpdate: { + break; + } + case ServerOP_WorldListRemove: { + if (this->GetType() != Login) { + // cout << "Error: ServerOP_WorldListRemove from a non-login connection? WTF!" << endl; + break; + } + if (pack->size != sizeof(int32)) { + // cout << "Wrong size on ServerOP_WorldListRemove. Got: " << pack->size << ", Expected: " << sizeof(int32) << endl; + break; + } + cout << "Got world remove for remote #" << *((int32*) pack->pBuffer) << endl; + if ((*((int32*) pack->pBuffer)) > 0) { + LWorld* world = world_list.FindByLink(this->GetLink(), *((int32*) pack->pBuffer)); + if (world && world->GetRemoteID() != 0) { + *((int32*) pack->pBuffer) = world->GetID(); + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + world_list.RemoveByID(*((int32*) pack->pBuffer)); + } + } + else { + // cout << "Error: ServerOP_WorldListRemove: ID = 0? ops!" << endl; + } + break; + } + case ServerOP_TriggerWorldListRefresh: { + world_list.UpdateWorldList(); + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + break; + } + case ServerOP_ZoneUpdates:{ + pack->Inflate(); + ZoneUpdateList_Struct* updates = 0; + if(pack->size >= sizeof(ZoneUpdateList_Struct) && ((ZoneUpdateList_Struct*)pack->pBuffer)->total_updates <= MAX_UPDATE_COUNT){ + updates = (ZoneUpdateList_Struct*)pack->pBuffer; + ZoneUpdate_Struct* zone = 0; + int32 pos = sizeof(ZoneUpdateList_Struct); + sint16 num_updates = 0; + map zone_updates; + LoginZoneUpdate update; + while(pos < pack->size && num_updates < updates->total_updates){ + zone = (ZoneUpdate_Struct*)(pack->pBuffer+pos); + if((pos + zone->zone_name_length + zone->zone_desc_length + sizeof(ZoneUpdate_Struct)) <= pack->size){ + update.name = string(zone->data, zone->zone_name_length); + update.description = string(zone->data + zone->zone_name_length, zone->zone_desc_length); + pos += sizeof(ZoneUpdate_Struct) + zone->zone_name_length + zone->zone_desc_length; + num_updates++; + zone_updates[zone->zone_id] = update; + } + else + break; + } + if(zone_updates.size() == updates->total_updates) + world_list.AddServerZoneUpdates(this, zone_updates); + else + cout << "Error processing zone updates for server: " << GetAccount() << endl; + } + else + Kick("Possible Hacking Attempt"); + break; + } + + case ServerOP_LoginEquipment: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode %04X (%i): ServerOP_LoginEquipment", pack->opcode, pack->opcode); + + pack->Inflate(); + EquipmentUpdateList_Struct* updates = 0; + if(pack->size >= sizeof(EquipmentUpdateList_Struct) && ((EquipmentUpdateList_Struct*)pack->pBuffer)->total_updates <= MAX_LOGIN_APPEARANCE_COUNT){ + updates = (EquipmentUpdateList_Struct*)pack->pBuffer; + EquipmentUpdate_Struct* equip = 0; + int32 pos = sizeof(EquipmentUpdateList_Struct); + sint16 num_updates = 0; + map equip_updates; + LoginEquipmentUpdate update; + while(pos < pack->size && num_updates < updates->total_updates){ + equip = (EquipmentUpdate_Struct*)(pack->pBuffer+pos); + update.world_char_id = equip->world_char_id; + update.equip_type = equip->equip_type; + update.red = equip->red; + update.green = equip->green; + update.blue = equip->blue; + update.highlight_red = equip->highlight_red; + update.highlight_green = equip->highlight_green; + update.highlight_blue = equip->highlight_blue; + update.slot = equip->slot; + pos += sizeof(EquipmentUpdate_Struct); + num_updates++; + equip_updates[equip->id] = update; // JohnAdams: I think I need item_appearances.id from World here? + } + + LogWrite(LOGIN__DEBUG, 1, "Login", "Processing %i Login Appearance Updates...", num_updates); + if(equip_updates.size() == updates->total_updates) + { + world_list.AddServerEquipmentUpdates(this, equip_updates); + } + else + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error processing login appearance updates for server: %s\n\t%s, function %s, line %i", GetAccount(), __FILE__, __FUNCTION__, __LINE__); + } + } + else + { + LogWrite(LOGIN__ERROR, 0, "Login", "World ID '%i', Possible Hacking Attempt (func: %s, line: %i", GetAccountID(), __FUNCTION__, __LINE__); + Kick("Possible Hacking Attempt"); + } + break; + } + case ServerOP_BugReport:{ + if(pack->size == sizeof(BugReport)){ + BugReport* report = (BugReport*)pack->pBuffer; + database.SaveBugReport(GetAccountID(), report->category, report->subcategory, report->causes_crash, report->reproducible, report->summary, report->description, report->version, report->player, report->account_id, report->spawn_name, report->spawn_id, report->zone_id); + } + break; + } + case ServerOP_EncapPacket: { + if (this->GetType() != Login) { + // cout << "Error: ServerOP_EncapPacket from a non-login connection? WTF!" << endl; + break; + } + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) pack->pBuffer; + if (seps->ToID == 0xFFFFFFFF) { // Broadcast + ServerPacket* inpack = new ServerPacket(seps->opcode); + inpack->size = seps->size; + // Little trick here to save a memcpy, be careful if you change this any + inpack->pBuffer = seps->data; + world_list.SendPacketLocal(inpack, this); + inpack->pBuffer = 0; + delete inpack; + } + else { + LWorld* world = world_list.FindByID(seps->ToID); + if (world) { + ServerPacket* inpack = new ServerPacket(seps->opcode); + inpack->size = seps->size; + // Little trick here to save a memcpy, be careful if you change this any + inpack->pBuffer = seps->data; + world->SendPacket(inpack); + inpack->pBuffer = 0; + delete inpack; + } + } + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + break; + } + default: + { + cout << "Unknown LoginSOPcode: 0x" << hex << (int)pack->opcode << dec; + cout << " size:" << pack->size << " from " << GetAccount() << endl; + DumpPacket(pack->pBuffer, pack->size); + //Kick("Possible Hacking Attempt"); + break; + } + } + delete pack; + } + return ret; +} + +void LWorld::SendPacket(ServerPacket* pack) { + if (Link == 0) + return; + if (RemoteID) { + ServerPacket* outpack = new ServerPacket(ServerOP_EncapPacket, sizeof(ServerEncapPacket_Struct) + pack->size); + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) outpack->pBuffer; + seps->ToID = RemoteID; + seps->opcode = pack->opcode; + seps->size = pack->size; + memcpy(seps->data, pack->pBuffer, pack->size); + Link->SendPacket(outpack); + delete outpack; + } + else { + Link->SendPacket(pack); + } +} + +void LWorld::Message(const char* to, const char* message, ...) { + va_list argptr; + char buffer[256]; + + va_start(argptr, message); + vsnprintf(buffer, 256, message, argptr); + va_end(argptr); + + ServerPacket* pack = new ServerPacket(ServerOP_EmoteMessage, sizeof(ServerEmoteMessage_Struct) + strlen(buffer) + 1); + ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*) pack->pBuffer; + strcpy(sem->to, to); + strcpy(sem->message, buffer); + SendPacket(pack); + delete pack; +} + +void LWorld::Kick(const char* message, bool iSetKickedFlag) { + if (iSetKickedFlag) + kicked = true; + if (message) { + ServerPacket* pack = new ServerPacket(ServerOP_LSFatalError, strlen(message) + 1); + strcpy((char*) pack->pBuffer, message); + SendPacket(pack); + delete pack; + } + if (Link && GetRemoteID() == 0) + Link->Disconnect(); +} +bool LWorld::CheckServerName(const char* name) { + if (strlen(name) < 10) + return false; + for (size_t i=0; i= 'a' && name[i] <= 'z') || (name[i] >= 'A' && name[i] <= 'Z') || (name[i] >= '0' && name[i] <= '9') || name[i] == ' ' || name[i] == '\'' || name[i] == '-' || name[i] == '(' || name[i] == ')' || name[i] == '[' || name[i] == ']' || name[i] == '/' || name[i] == '.' || name[i] == ',' || name[i] == '_' || name[i] == '+' || name[i] == '=' || name[i] == ':' || name[i] == '~')) + return false; + } + return true; +} +bool LWorld::SetupWorld(char* in_worldname, char* in_worldaddress, char* in_account, char* in_password, char* in_version) { + if (strlen(in_worldaddress) > 3) { + isaddressip = false; + strcpy(address, in_worldaddress); + } + if (strlen(in_worldname) > 3) { + char tmpAccount[30]; + memcpy(tmpAccount, in_account, 29); + tmpAccount[29] = '\0'; + + int32 id = database.CheckServerAccount(tmpAccount, in_password); + + if(id == 0) + return false; + if(database.IsServerAccountDisabled(tmpAccount) || database.IsIPBanned(IPAddr) || (isaddressip && database.IsIPBanned(address))) + return false; + + LWorld* world = world_list.FindByID(id); + if(world) + world->Kick("Ghost Kick!"); + + ID = id; + accountid = id; + strncpy(account,tmpAccount,30); + char* name = database.GetServerAccountName(id); + if(name) + snprintf(worldname, (sizeof(worldname)) - 1, "%s", name); + else{ //failed to get account + account[0] = 0; + IsInit = false; + this->Kick ( "Could not load server information." ); + return false; + } + //world_list.KickGhostIP(GetIP(), this); + IsInit = true; + ptype = World; + world_list.SendWorldChanged(id, true); + } + else { + // name too short + account[0] = 0; + IsInit = false; + return false; + } + + database.UpdateWorldVersion(GetAccountID(), in_version); + pStatsTimer = new Timer ( 60000 ); + pStatsTimer->Start ( 60000 ); + + return true; +} +void LWorldList::SendWorldChanged(int32 server_id, bool sendtoallclients, Client* sendto){ + EQ2Packet* outapp = new EQ2Packet(OP_WorldStatusChangeMsg, 0, sizeof(LS_WorldStatusChanged)); + LS_WorldStatusChanged* world_changed = (LS_WorldStatusChanged*)outapp->pBuffer; + + world_changed->server_id = server_id; + LWorld* world = world_list.FindByID(server_id); + if(!world || world->ShowDown()) + world_changed->up = 0; + else + world_changed->up = 1; + if(sendtoallclients || sendto == 0) + client_list.SendPacketToAllClients(outapp); + else + sendto->QueuePacket(outapp); + world_list.SetUpdateServerList(true); +} +void LWorld::UpdateWorldList(LWorld* to) { + world_list.SetUpdateServerList( true ); +} + +void LWorld::ChangeToPlaceholder() { + ip = 0; + status = -1; + pPlaceholder = true; + if (Link != 0 && RemoteID == 0) { + Link->Disconnect(); + } + UpdateWorldList(); +} + +void LWorld::SetRemoteInfo(int32 in_ip, int32 in_accountid, char* in_account, char* in_name, char* in_address, int32 in_status, int32 in_adminid, sint32 in_players, sint32 in_zones) { + ip = in_ip; + accountid = in_accountid; +// strcpy(account, in_account); + strcpy(worldname, in_name); + strcpy(address, in_address); + status = in_status; + admin_id = in_adminid; + num_players = in_players; + num_zones = in_zones; +} + + +LWorldList::LWorldList() { + server_update_thread = true; + NextID = 1; + tcplistener = new TCPServer(net.GetPort(), true); + if (net.GetLoginMode() == Slave) + OutLink = new TCPConnection(true); + else + OutLink = 0; + + UpdateServerList = true; + #ifdef WIN32 + _beginthread(ServerUpdateLoop, 0, this); + #else + pthread_t thread; + pthread_create(&thread, NULL, &ServerUpdateLoop, this); + #endif +} + +LWorldList::~LWorldList() { + server_update_thread = false; + while(!server_update_thread){ + Sleep(100); + } + safe_delete(tcplistener); + safe_delete(OutLink); +} + +void LWorldList::Shutdown() { + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) + { + iterator.RemoveCurrent ( ); + } + + safe_delete(tcplistener); +} + +void LWorldList::Add(LWorld* worldserver) { + LWorld* worldExist = FindByID(worldserver->GetID ( ) ); + if( worldExist ) + { + worldExist->Kick(); + MWorldMap.writelock(); + worldmap.erase(worldExist->GetID()); + MWorldMap.releasewritelock(); + safe_delete(worldExist); + } + MWorldMap.writelock(); + worldmap[worldserver->GetID()] = worldserver; + MWorldMap.releasewritelock(); + database.ResetWorldServerStatsConnectedTime(worldserver); + database.UpdateWorldIPAddress(worldserver->GetID(), worldserver->GetIP()); +} + +void LWorldList::AddInitiateWorld ( LWorld* world ) +{ + list.Insert ( world ); +} + +void LWorldList::KickGhostIP(int32 ip, LWorld* NotMe, int16 iClientPort) { + if (ip == 0) + return; + + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (!world->IsKicked() && world->GetIP() == ip && world != NotMe) { + if ((iClientPort == 0 && world->GetType() == World) || (iClientPort != 0 && world->GetClientPort() == iClientPort)) { + struct in_addr in; + in.s_addr = world->GetIP(); + // cout << "Removing GhostIP LWorld(" << world->GetID() << ") from ip: " << inet_ntoa(in) << " port: " << (int16)(world->GetPort()); + if (!world->Connected()) + { + // cout << " (it wasnt connected)"; + // cout << endl; + if (NotMe) { + in.s_addr = NotMe->GetIP(); + cout << "NotMe(" << NotMe->GetID() << ") = " << inet_ntoa(in) << ":" << NotMe->GetPort() << " (" << NotMe->GetClientPort() << ")" << endl; + } + world->Kick("Ghost IP kick"); + } + } + } + } + MWorldMap.releasereadlock(); +} + +void LWorldList::KickGhost(ConType in_type, int32 in_accountid, LWorld* ButNotMe) { + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (!world->IsKicked() && world->GetType() == in_type && world != ButNotMe && (in_accountid == 0 || world->GetAccountID() == in_accountid)) { + if (world->GetIP() != 0) { + //struct in_addr in; + //in.s_addr = world->GetIP(); + // cout << "Removing GhostAcc LWorld(" << world->GetID() << ") from ip: " << inet_ntoa(in) << " port: " << (int16)(world->GetPort()) << endl; + } + if (world->GetType() == Login && world->IsOutgoingUplink()) { + world->Kick("Ghost Acc Kick", false); + // cout << "softkick" << endl; + } + else + world->Kick("Ghost Acc Kick"); + } + } + MWorldMap.releasereadlock(); +} + +void LWorldList::UpdateWorldStats(){ + map::iterator map_list; + MWorldMap.readlock(); + for(map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if(world && world->GetAccountID() > 0) + database.UpdateWorldServerStats(world, world->GetStatus()); + } + MWorldMap.releasereadlock(); +} + +void LWorldList::Process() { + TCPConnection* newtcp = 0; + LWorld* newworld = 0; + + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) + { + if(iterator.GetData( )->GetID ( ) > 0 ) + { + LWorld* world = iterator.GetData ( ); + iterator.RemoveCurrent ( false ); + Add( world ); + } + else + { + if(! iterator.GetData ( )->Process ( ) ) + iterator.RemoveCurrent ( ); + else + iterator.Advance(); + } + } + + while ((newtcp = tcplistener->NewQueuePop())) { + newworld = new LWorld(newtcp); + newworld->SetID(0); + AddInitiateWorld(newworld); + struct in_addr in; + in.s_addr = newtcp->GetrIP(); + LogWrite(LOGIN__INFO, 0, "Login", "New Server connection: %s port %i", inet_ntoa(in), ntohs(newtcp->GetrPort())); + net.numservers++; + net.UpdateWindowTitle(); + world_list.UpdateWorldList(); + } + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); ) { + LWorld* world = map_list->second; + + int32 account_id = world->GetAccountID(); + + if (world->IsKicked() && !world->IsNeverKick()) { + map_list++; + worldmap.erase ( account_id ); + net.numservers--; + net.UpdateWindowTitle(); + safe_delete ( world ); + continue; + } + else if (!world->Process()) { + //struct in_addr in; + //in.s_addr = world->GetIP(); + if (world->GetAccountID() == 0 || !(world->ShowDown()) || world->GetType() == Chat) { + map_list++; + worldmap.erase ( account_id ); + + net.numservers--; + net.UpdateWindowTitle(); + if(account_id > 0){ + LWorld* world2 = FindByID(account_id); + if(world2) + world2->ShowDownActive(true); + } + SendWorldChanged(account_id, true); + safe_delete ( world ); + continue; + } + else { + world->ChangeToPlaceholder(); + } + } + map_list++; + } +} + +// Sends packet to all World and Chat servers, local and remote (but not to remote login server's ::Process()) +void LWorldList::SendPacket(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != butnotme) { + if (world->GetType() == Login) { + ServerPacket* outpack = new ServerPacket(ServerOP_EncapPacket, sizeof(ServerEncapPacket_Struct) + pack->size); + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) outpack->pBuffer; + seps->ToID = 0xFFFFFFFF; + seps->opcode = pack->opcode; + seps->size = pack->size; + memcpy(seps->data, pack->pBuffer, pack->size); + world->SendPacket(outpack); + delete outpack; + } + else if (world->GetRemoteID() == 0) { + world->SendPacket(pack); + } + } + } +} + +// Sends a packet to every local TCP Connection, all types +void LWorldList::SendPacketLocal(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != butnotme && world->GetRemoteID() == 0) { + world->SendPacket(pack); + } + } +} + +// Sends the packet to all login servers +void LWorldList::SendPacketLogin(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (world != butnotme && world->GetType() == Login) { + world->SendPacket(pack); + } + } +} + +void LWorldList::UpdateWorldList(LWorld* to) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (net.GetLoginMode() != Mesh || world->GetRemoteID() == 0) + world->UpdateWorldList(to); + } +} + +LWorld* LWorldList::FindByID(int32 LWorldID) { + if(worldmap.count(LWorldID) > 0) + return worldmap[LWorldID]; + return 0; +} + +LWorld* LWorldList::FindByIP(int32 ip) { + map::iterator map_list; + LWorld* world = 0; + LWorld* ret = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetIP() == ip){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByAddress(char* address) { + map::iterator map_list; + LWorld* world = 0; + LWorld* ret = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (strcasecmp(world->GetAddress(), address) == 0){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByLink(TCPConnection* in_link, int32 in_id) { + if (in_link == 0) + return 0; + LWorld* world = 0; + LWorld* ret = 0; + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetLink() == in_link && world->GetRemoteID() == in_id){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByAccount(int32 in_accountid, ConType in_type) { + if (in_accountid == 0) + return 0; + LWorld* world = 0; + LWorld* ret = 0; + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetAccountID() == in_accountid && world->GetType() == in_type){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +int8 LWorld::GetWorldStatus(){ + if(IsDevelServer() && IsLocked() == false) + return 1; + else if(IsInit && IsLocked() == false) + return 0; + else + return 2; +} + +void LWorld::SendDeleteCharacter ( int32 char_id , int32 account_id ) +{ + ServerPacket* outpack = new ServerPacket(ServerOP_BasicCharUpdate, sizeof(CharDataUpdate_Struct)); + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*)outpack->pBuffer; + cdu->account_id = account_id; + cdu->char_id = char_id; + cdu->update_field = DELETE_UPDATE_FLAG; + cdu->update_data = 1; + SendPacket(outpack); +} + +vector* LWorldList::GetServerListUpdate(int16 version){ + vector* ret = new vector; + map::iterator map_list; + PacketStruct* packet = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + packet = configReader.getStruct("LS_WorldUpdate", version); + if(packet){ + packet->setDataByName("server_id", world->GetID()); + packet->setDataByName("up", 1); + if(world->IsLocked()) + packet->setDataByName("locked", 1); + ret->push_back(packet); + } + } + } + MWorldMap.releasereadlock(); + return ret; +} + +EQ2Packet* LWorldList::MakeServerListPacket(int8 lsadmin, int16 version) { + + // if the latest world list has already been loaded, just return the string + MWorldMap.readlock(); + if (!UpdateServerList && ServerListData.count(version)) + { + MWorldMap.releasereadlock(); + return ServerListData[version]; + } + + //LWorld* world = 0; + int32 ServerNum = 0; + /* while(iterator.MoreElements()){ + world = iterator.GetData(); + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) + ServerNum++; + iterator.Advance(); + } + ServerNum+=3; + */ + uint32 tmpCount = 0; + map::iterator map_list; + for (map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + tmpCount++; + } + } + + PacketStruct* packet = configReader.getStruct("LS_WorldList", version); + packet->setArrayLengthByName("num_worlds", tmpCount); + + string world_data; + for (map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + ServerNum++; + packet->setArrayDataByName("id", world->GetID(), ServerNum - 1); + + if (version <= 283) { + packet->setArrayDataByName("name", world->GetName(), ServerNum - 1); + if (!world->ShowDown()) + packet->setArrayDataByName("online", 1, ServerNum - 1); + if (world->IsLocked()) + packet->setArrayDataByName("locked", 1, ServerNum - 1); + packet->setArrayDataByName("unknown2", 1, ServerNum - 1); + packet->setArrayDataByName("unknown3", 1, ServerNum - 1); + packet->setArrayDataByName("load", world->GetWorldStatus(), ServerNum - 1); + } + else + { + if (version < 1212) + packet->setArrayDataByName("allowed_races", 0xFFFFFFFF, ServerNum - 1); + else if (version < 60006) + packet->setArrayDataByName("allowed_races", 0x000FFFFF, ServerNum - 1); // + Freeblood + else + packet->setArrayDataByName("allowed_races", 0x001FFFFF, ServerNum - 1); // + Aerakyn + + packet->setArrayDataByName("number_online_flag", 1, ServerNum - 1); + packet->setArrayDataByName("num_players", world->GetPlayerNum(), ServerNum - 1); + packet->setArrayDataByName("name", world->GetName(), ServerNum - 1); + packet->setArrayDataByName("name2", world->GetName(), ServerNum - 1); + packet->setArrayDataByName("feature_set", 0, ServerNum - 1); + + packet->setArrayDataByName("load", world->GetWorldStatus(), ServerNum - 1); + if (world->IsLocked()) + packet->setArrayDataByName("locked", 1, ServerNum - 1); + + if (world->ShowDown()) + packet->setArrayDataByName("tag", 0, ServerNum - 1); + else + packet->setArrayDataByName("tag", 1, ServerNum - 1); + + if (version < 1212) + packet->setArrayDataByName("unknown", ServerNum, ServerNum - 1); + } + } + } + + EQ2Packet* pack = packet->serialize(); + #ifdef DEBUG + //Only dump these for people trying to debug this... + printf("WorldList:\n"); + DumpPacket(pack->pBuffer, pack->size); + #endif + if (ServerListData.count(version)) + { + map::iterator it = ServerListData.find(version); + EQ2Packet* tmpPack = ServerListData[version]; + safe_delete(tmpPack); + ServerListData.erase(it); + } + ServerListData.insert(make_pair(version, pack)); + MWorldMap.releasereadlock(); + + SetUpdateServerList(false); + + return ServerListData[version]; +} + +void LWorldList::SendWorldStatus(LWorld* chat, char* adminname) { + struct in_addr in; + + int32 count = 0; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world->GetIP() != 0 && world->GetType() == World) { + chat->Message(adminname, "Name: %s", world->GetName()); + in.s_addr = world->GetIP(); + if (world->GetAccountID() != 0) { + chat->Message(adminname, " Account: %s", world->GetAccount()); + } + chat->Message(adminname, " Number of Zones: %i", world->GetZoneNum()); + chat->Message(adminname, " Number of Players: %i", world->GetPlayerNum()); + chat->Message(adminname, " IP: %s", inet_ntoa(in)); + if (!world->IsAddressIP()) { + chat->Message(adminname, " Address: %s", world->GetAddress()); + } + count++; + } + } + chat->Message(adminname, "%i worlds listed.", count); +} + +void LWorldList::RemoveByLink(TCPConnection* in_link, int32 in_id, LWorld* ButNotMe) { + if (in_link == 0) + return; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != ButNotMe && world->GetLink() == in_link && (in_id == 0 || world->GetRemoteID() == in_id)) { + // world->Link = 0; + map_list++; + worldmap.erase ( world->GetID ( ) ); + safe_delete ( world ); + continue; + } + } +} + +void LWorldList::RemoveByID(int32 in_id) { + if (in_id == 0) + return; + + LWorld* existWorld = FindByID(in_id); + if ( existWorld != NULL ) + { + MWorldMap.writelock(); + worldmap.erase ( in_id ); + MWorldMap.releasewritelock(); + safe_delete ( existWorld ); + } +} + +bool LWorldList::Init() { + + database.ResetWorldStats ( ); + + if (!tcplistener->IsOpen()) { + return tcplistener->Open(net.GetPort()); + } + + return false; +} + +void LWorldList::InitWorlds(){ + vector server_list; + database.GetServerAccounts(&server_list); + vector::iterator iter; + int i = 0; + for(iter = server_list.begin(); iter != server_list.end(); iter++, i++){ + LWorld* world = FindByID(server_list[i]->GetAccountID()); + if(!world){ + server_list[i]->ShowDown(true); + server_list[i]->ShowDownActive(true); + server_list[i]->SetID ( server_list[i]->GetAccountID ( ) ); + Add ( server_list[i] ); + } + } +} + +int32 LWorldList::GetCount(ConType type) { + int32 count = 0; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world->GetType() == type) { + count++; + } + } + return count; +} + +void LWorldList::ListWorldsToConsole() { + struct in_addr in; + + cout << "World List:" << endl; + cout << "============================" << endl; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + in.s_addr = world->GetIP(); + if (world->GetType() == World) { + if (world->GetRemoteID() == 0) + cout << "ID: " << world->GetID() << ", Name: " << world->GetName() << ", Local, IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", Status: " << world->GetStatus() << ", AccID: " << world->GetAccountID() << endl; + else + cout << "ID: " << world->GetID() << ", Name: " << world->GetName() << ", RemoteID: " << world->GetRemoteID() << ", LinkWorldID: " << world->GetLinkWorldID() << ", IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", Status: " << world->GetStatus() << ", AccID: " << world->GetAccountID() << endl; + } + else if (world->GetType() == Chat) { + cout << "ID: " << world->GetID() << ", Chat Server, IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + else if (world->GetType() == Login) { + if (world->IsOutgoingUplink()) { + if (world->Connected()) + cout << "ID: " << world->GetID() << ", Login Server (out), IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + else + cout << "ID: " << world->GetID() << ", Login Server (nc), IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + else + cout << "ID: " << world->GetID() << ", Login Server (in), IP: " << inet_ntoa(in) << ":" << world->GetPort() << " (" << world->GetClientPort() << "), AccID: " << world->GetAccountID() << endl; + } + else { + cout << "ID: " << world->GetID() << ", Unknown Type, Name: " << world->GetName() << ", IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + } + cout << "============================" << endl; +} + +void LWorldList::AddServerZoneUpdates(LWorld* world, map updates){ + int32 server_id = world->GetID(); + map::iterator itr; + for(itr = updates.begin(); itr != updates.end(); itr++){ + if(zone_updates_already_used.size() >= 1500 || zone_updates_already_used[server_id].count(itr->first) > 0){ + world->Kick("Hacking attempt."); + return; + } + zone_updates_already_used[server_id][itr->first] = true; + } + server_zone_updates.Put(server_id, updates); +} +//devn00b temp + +void LWorldList::AddServerEquipmentUpdates(LWorld* world, map updates){ + int32 server_id = world->GetID(); + map::iterator itr; + for(itr = updates.begin(); itr != updates.end(); itr++){ + LogWrite(MISC__TODO, 1, "TODO", "JA: Until we learn what this does, can't risk worlds being kicked performing login appearance updates...\n%s, func: %s, line: %i", __FILE__, __FUNCTION__, __LINE__); + + /*if(equip_updates_already_used.size() >= 1500 || equip_updates_already_used[server_id].count(itr->first) > 0) + { + LogWrite(LOGIN__ERROR, 0, "Login", "World ID '%i': Hacking attempt. (function: %s, line: %i", world->GetAccountID(), __FUNCTION__, __LINE__); + world->Kick("Hacking attempt."); + return; + }*/ + equip_updates_already_used[server_id][itr->first] = true; + } + server_equip_updates.Put(server_id, updates); +} + +void LWorldList::RequestServerUpdates(LWorld* world){ + if(world){ + ServerPacket* pack = new ServerPacket(ServerOP_ZoneUpdates, sizeof(ZoneUpdateRequest_Struct)); + ZoneUpdateRequest_Struct* request = (ZoneUpdateRequest_Struct*)pack->pBuffer; + request->max_per_batch = MAX_UPDATE_COUNT; + world->SendPacket(pack); + delete pack; + zone_update_timeouts.Put(world->GetID(), Timer::GetCurrentTime2() + 30000); + } +} + +void LWorldList::ProcessServerUpdates(){ + MutexMap >::iterator itr = server_zone_updates.begin(); + while(itr.Next()){ + if(itr->second.size() > 0){ + database.SetServerZoneDescriptions(itr->first, itr->second); + if(itr->second.size() == MAX_UPDATE_COUNT) + awaiting_zone_update.Put(itr->first, Timer::GetCurrentTime2() + 10000); //only process 20 updates in a 10 second period to avoid network problems + server_zone_updates.erase(itr->first); + } + if(zone_update_timeouts.count(itr->first) == 0 || zone_update_timeouts.Get(itr->first) <= Timer::GetCurrentTime2()){ + zone_update_timeouts.erase(itr->first); + server_zone_updates.erase(itr->first); + } + } + LWorld* world = 0; + MWorldMap.readlock(); + map::iterator map_itr; + for(map_itr = worldmap.begin(); map_itr != worldmap.end(); map_itr++){ + world = map_itr->second; + if(world && world->GetID()){ + if(last_updated.count(world) == 0 || last_updated.Get(world) <= Timer::GetCurrentTime2()){ + zone_updates_already_used[world->GetID()].clear(); + RequestServerUpdates(world); + last_updated.Put(world, Timer::GetCurrentTime2() + 21600000); + } + if(awaiting_zone_update.count(world->GetID()) > 0 && awaiting_zone_update.Get(world->GetID()) <= Timer::GetCurrentTime2()){ + awaiting_zone_update.erase(world->GetID()); + RequestServerUpdates(world); + } + } + } + ProcessLSEquipUpdates(); + MWorldMap.releasereadlock(); + + +} +void LWorldList::RequestServerEquipUpdates(LWorld* world) +{ + if(world) + { + ServerPacket *pack_equip = new ServerPacket(ServerOP_LoginEquipment, sizeof(EquipmentUpdateRequest_Struct)); + EquipmentUpdateRequest_Struct *request_equip = (EquipmentUpdateRequest_Struct *)pack_equip->pBuffer; + request_equip->max_per_batch = MAX_LOGIN_APPEARANCE_COUNT; // item appearance data smaller, request more at a time? + LogWrite(LOGIN__DEBUG, 1, "Login", "Sending equipment update requests to world: (%s)... (Batch Size: %i)", world->GetName(), request_equip->max_per_batch); + world->SendPacket(pack_equip); + delete pack_equip; + equip_update_timeouts.Put(world->GetID(), Timer::GetCurrentTime2() + 30000); + } +} +void LWorldList::ProcessLSEquipUpdates() +{ + // process login_equipment updates + MutexMap >::iterator itr_equip = server_equip_updates.begin(); + while(itr_equip.Next()) + { + if(itr_equip->second.size() > 0) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Setting Login Appearances..."); + database.SetServerEquipmentAppearances(itr_equip->first, itr_equip->second); + if(itr_equip->second.size() == MAX_LOGIN_APPEARANCE_COUNT) + awaiting_equip_update.Put(itr_equip->first, Timer::GetCurrentTime2() + 10000); //only process 100 updates in a 10 second period to avoid network problems + server_equip_updates.erase(itr_equip->first); + } + if(equip_update_timeouts.count(itr_equip->first) == 0 || equip_update_timeouts.Get(itr_equip->first) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Clearing Login Appearances Update Timers..."); + equip_update_timeouts.erase(itr_equip->first); + server_equip_updates.erase(itr_equip->first); + } + } + LWorld* world = 0; + MWorldMap.readlock(); + map::iterator map_itr; + for(map_itr = worldmap.begin(); map_itr != worldmap.end(); map_itr++) + { + world = map_itr->second; + if(world && world->GetID()) + { + if(last_equip_updated.count(world) == 0 || last_equip_updated.Get(world) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Clearing Login Appearances Update Counters..."); + equip_updates_already_used[world->GetID()].clear(); + RequestServerEquipUpdates(world); + last_equip_updated.Put(world, Timer::GetCurrentTime2() + 900000); // every 15 mins + } + if( awaiting_equip_update.count(world->GetID()) > 0 && awaiting_equip_update.Get(world->GetID()) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Erase awaiting equip updates..."); + awaiting_equip_update.erase(world->GetID()); + RequestServerEquipUpdates(world); + } + } + } + MWorldMap.releasereadlock(); +} + + +ThreadReturnType ServerUpdateLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ServerUpdateLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + LWorldList* worldList = (LWorldList*) tmp; + while (worldList->ContinueServerUpdates()) { + Sleep(1000); + worldList->ProcessServerUpdates(); + } + worldList->ResetServerUpdates(); + THREAD_RETURN(NULL); +} diff --git a/source/LoginServer/LWorld.h b/source/LoginServer/LWorld.h new file mode 100644 index 0000000..d9e3361 --- /dev/null +++ b/source/LoginServer/LWorld.h @@ -0,0 +1,253 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef LWORLD_H +#define LWORLD_H + +#include "../common/Mutex.h" + +#define ERROR_BADPASSWORD "Bad password" +#define INVALID_ACCOUNT "Invalid Server Account." +#define ERROR_BADVERSION "Incorrect version" +#define ERROR_UNNAMED "Unnamed servers not allowed to connect to full login servers" +#define ERROR_NOTMASTER "Not a master server" +#define ERROR_NOTMESH "Not a mesh server" +#define ERROR_GHOST "Ghost kick" +#define ERROR_UnknownServerType "Unknown Server Type" +#define ERROR_BADNAME_SERVER "Bad server name, name may not contain the word \"Server\" (case sensitive)" +#define ERROR_BADNAME "Bad server name. Unknown reason." + +#define WORLD_NAME_SUFFIX " Server" + +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from + +#include "../common/linked_list.h" +#include "../WorldServer/MutexList.h" +#include "../WorldServer/MutexMap.h" +#include "../common/timer.h" +#include "../common/types.h" +#include "../common/queue.h" +#include "../common/servertalk.h" +#include "../common/TCPConnection.h" +#include "client.h" + +#define MAX_UPDATE_COUNT 20 +#define MAX_LOGIN_APPEARANCE_COUNT 100 + +#ifdef WIN32 + void ServerUpdateLoop(void* tmp); +#else + void* ServerUpdateLoop(void* tmp); +#endif + +enum ConType { UnknownW, World, Chat, Login }; + +class LWorld +{ +public: + LWorld(TCPConnection* in_con, bool OutgoingLoginUplink = false, int32 iIP = 0, int16 iPort = 0, bool iNeverKick = false); + LWorld(int32 in_accountid, char* in_accountname, char* in_worldname, int32 in_admin_id); + LWorld(TCPConnection* in_RemoteLink, int32 in_ip, int32 in_RemoteID, int32 in_accountid, char* in_accountname, char* in_worldname, char* in_address, sint32 in_status, int32 in_adminid, bool in_showdown, int8 in_authlevel, bool in_placeholder, int32 iLinkWorldID); + ~LWorld(); + + static bool CheckServerName(const char* name); + + bool Process(); + void SendPacket(ServerPacket* pack); + void Message(const char* to, const char* message, ...); + + bool SetupWorld(char* in_worldname, char* in_worldaddress, char* in_account, char* in_password, char* in_version); + void UpdateStatus(sint32 in_status, sint32 in_players, sint32 in_zones, int8 in_level) { + // we don't want the server list to update unless something has changed + if(status != in_status || num_players != in_players || num_zones != in_zones || world_max_level != in_level) + { + status = in_status; + num_players = in_players; + num_zones = in_zones; + world_max_level = in_level; + UpdateWorldList(); + } + } + void UpdateWorldList(LWorld* to = 0); + void SetRemoteInfo(int32 in_ip, int32 in_accountid, char* in_account, char* in_name, char* in_address, int32 in_status, int32 in_adminid, sint32 in_players, sint32 in_zones); + + inline bool IsPlaceholder() { return pPlaceholder; } + inline int32 GetAccountID() { return accountid; } + inline char* GetAccount() { return account; } + inline char* GetAddress() { return address; } + inline int16 GetClientPort() { return pClientPort; } + inline bool IsAddressIP() { return isaddressip; } + inline char* GetName() { return worldname; } + inline sint32 GetStatus() { return status; } + bool IsLocked() { return status==-2; } + inline int32 GetIP() { return ip; } + inline int16 GetPort() { return port; } + inline int32 GetID() { return ID; } + inline int32 GetAdmin() { return admin_id; } + inline bool ShowDown() { return pshowdown; } + inline bool ShowDownActive(){ return show_down_active; } + void ShowDownActive(bool show){ show_down_active = show; } + void ShowDown(bool show){ pshowdown = show; } + inline bool Connected() { return pConnected; } + int8 GetWorldStatus(); + + void ChangeToPlaceholder(); + void Kick(const char* message = ERROR_GHOST, bool iSetKickedFlag = true); + inline bool IsKicked() { return kicked; } + inline bool IsNeverKick() { return pNeverKick; } + inline ConType GetType() { return ptype; } + inline bool IsOutgoingUplink() { return OutgoingUplink; } + inline TCPConnection* GetLink() { return Link; } + inline int32 GetRemoteID() { return RemoteID; } + inline int32 GetLinkWorldID() { return LinkWorldID; } + inline sint32 GetZoneNum() { return num_zones; } + inline sint32 GetPlayerNum() { return num_players; } + void SetID(int32 new_id) { ID = new_id; } + + void SendDeleteCharacter( int32 char_id, int32 account_id ); + bool IsDevelServer(){ return devel_server; } + + inline int8 GetMaxWorldLevel() { return world_max_level; } + + bool IsInit; +protected: + friend class LWorldList; + TCPConnection* Link; + Timer* pReconnectTimer; + Timer* pStatsTimer; +private: + int32 ID; + int32 ip; + char IPAddr[64]; + int16 port; + bool kicked; + bool pNeverKick; + bool pPlaceholder; + bool devel_server; + + int32 accountid; + char account[30]; + char address[250]; + bool isAuthenticated; + int16 pClientPort; + bool isaddressip; + char worldname[200]; + sint32 status; + int32 admin_id; + bool pshowdown; + bool show_down_active; + ConType ptype; + bool OutgoingUplink; + bool pConnected; + sint32 num_players; + sint32 num_zones; + int32 RemoteID; + int32 LinkWorldID; + int8 world_max_level; + +}; + +class LWorldList +{ +public: + + LWorldList(); + ~LWorldList(); + + LWorld* FindByID(int32 WorldID); + LWorld* FindByIP(int32 ip); + LWorld* FindByAddress(char* address); + LWorld* FindByLink(TCPConnection* in_link, int32 in_id); + LWorld* FindByAccount(int32 in_accountid, ConType in_type = World); + + void Add(LWorld* worldserver); + void AddInitiateWorld ( LWorld* world ); + void Process(); + void ReceiveData(); + void SendPacket(ServerPacket* pack, LWorld* butnotme = 0); + void SendPacketLocal(ServerPacket* pack, LWorld* butnotme = 0); + void SendPacketLogin(ServerPacket* pack, LWorld* butnotme = 0); + void SendWorldChanged(int32 server_id, bool sendtoallclients=false, Client* sendto = 0); + vector* GetServerListUpdate(int16 version); + EQ2Packet* MakeServerListPacket(int8 lsadmin, int16 version); + + void UpdateWorldList(LWorld* to = 0); + void UpdateWorldStats(); + void KickGhost(ConType in_type, int32 in_accountid = 0, LWorld* ButNotMe = 0); + void KickGhostIP(int32 ip, LWorld* NotMe = 0, int16 iClientPort = 0); + void RemoveByLink(TCPConnection* in_link, int32 in_id = 0, LWorld* ButNotMe = 0); + void RemoveByID(int32 in_id); + + void SendWorldStatus(LWorld* chat, char* adminname); + + void ConnectUplink(); + bool Init(); + void InitWorlds(); + void Shutdown(); + bool WriteXML(); + + int32 GetCount(ConType type); + void PopulateWorldList(http::response& res); + + void ListWorldsToConsole(); + //devn00b temp + void AddServerEquipmentUpdates(LWorld* world, map updates); + void ProcessLSEquipUpdates(); + void RequestServerEquipUpdates(LWorld* world); + + void SetUpdateServerList ( bool var ) { UpdateServerList = var; } + bool ContinueServerUpdates(){ return server_update_thread; } + void ResetServerUpdates(){server_update_thread = true;} + void ProcessServerUpdates(); + void RequestServerUpdates(LWorld* world); + void AddServerZoneUpdates(LWorld* world, map updates); + +protected: + friend class LWorld; + int32 GetNextID() { return NextID++; } + +private: + Mutex MWorldMap; + map > zone_updates_already_used; //used to determine if someone is trying to DOS us + MutexMap zone_update_timeouts; + MutexMap awaiting_zone_update; + MutexMap last_updated; + MutexMap > server_zone_updates; + bool server_update_thread; + int32 NextID; + + LinkedList list; + + map worldmap; + + TCPServer* tcplistener; + TCPConnection* OutLink; + + //devn00b temp + // JohnAdams: login appearances, copied from above + map > equip_updates_already_used; + MutexMap equip_update_timeouts; + MutexMap awaiting_equip_update; + MutexMap last_equip_updated; + MutexMap > server_equip_updates; + // + /// + + // holds the world server list so we don't have to create it for every character + // logging in + map ServerListData; + bool UpdateServerList; +}; +#endif diff --git a/source/LoginServer/Login.dsp b/source/LoginServer/Login.dsp new file mode 100644 index 0000000..cf66e51 --- /dev/null +++ b/source/LoginServer/Login.dsp @@ -0,0 +1,447 @@ +# Microsoft Developer Studio Project File - Name="Login" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=Login - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Login.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Login.mak" CFG="Login - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Login - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "Login - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE "Login - Win32 MiniLogin" (based on "Win32 (x86) Console Application") +!MESSAGE "Login - Win32 PublicLogin" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Login - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Build" +# PROP Intermediate_Dir "../Build/Login" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /w /W0 /GX /Zi /O2 /Ob2 /D "LOGINCRYPTO" /D "INVERSEXY" /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo /o"../Build/Login/Login.bsc" +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /map:"../Build/Login.map" /debug /machine:I386 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Login - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Login___Win32_Debug" +# PROP BASE Intermediate_Dir "Login___Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "../build/login/Debug" +# PROP Intermediate_Dir "../build/login/debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /Gm /GX /ZI /Od /D "LOGINCRYPTO" /D "INVERSEXY" /D _WIN32_WINNT=0x0400 /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"LIBCMT" /out:"../build/login/Debug/LoginDebug.exe" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Login___Win32_MiniLogin" +# PROP BASE Intermediate_Dir "Login___Win32_MiniLogin" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Build" +# PROP Intermediate_Dir "../Build/MiniLogin" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "BUILD_FOR_WINDOWS" /FR /YX /FD /c +# ADD CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "MINILOGIN" /FR /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo /o"../Build/Login/Login.bsc" +# ADD BSC32 /nologo /o"../Build/MiniLogin/Login.bsc" +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 /out:"../Build/MiniLogin.exe" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Login___Win32_PublicLogin" +# PROP BASE Intermediate_Dir "Login___Win32_PublicLogin" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Build" +# PROP Intermediate_Dir "../Build/PublicLogin" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "BUILD_FOR_WINDOWS" /FR /YX /FD /c +# ADD CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "PUBLICLOGIN" /FR /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo /o"../Build/Login/Login.bsc" +# ADD BSC32 /nologo /o"../Build/Login/Login.bsc" +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 /out:"../Build/PublicLogin.exe" +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "Login - Win32 Release" +# Name "Login - Win32 Debug" +# Name "Login - Win32 MiniLogin" +# Name "Login - Win32 PublicLogin" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\client.cpp +# End Source File +# Begin Source File + +SOURCE=.\EQCrypto.cpp + +!IF "$(CFG)" == "Login - Win32 Release" + +!ELSEIF "$(CFG)" == "Login - Win32 Debug" + +!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin" + +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\logindatabase.cpp + +!IF "$(CFG)" == "Login - Win32 Release" + +!ELSEIF "$(CFG)" == "Login - Win32 Debug" + +!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\LWorld.cpp +# End Source File +# Begin Source File + +SOURCE=.\net.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\EQCrypto.h +# End Source File +# Begin Source File + +SOURCE=.\login_opcodes.h +# End Source File +# Begin Source File + +SOURCE=.\login_structs.h +# End Source File +# Begin Source File + +SOURCE=.\LWorld.h +# End Source File +# Begin Source File + +SOURCE=.\net.h +# End Source File +# End Group +# Begin Group "Common Source Files" + +# PROP Default_Filter ".cpp" +# Begin Source File + +SOURCE=..\common\crc32.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\database.cpp + +!IF "$(CFG)" == "Login - Win32 Release" + +!ELSEIF "$(CFG)" == "Login - Win32 Debug" + +!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\dbcore.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\DBMemLeak.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\debug.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\EQNetwork.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\md5.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\MiscFunctions.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\Mutex.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\packet_dump.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\packet_functions.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\TCPConnection.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\timer.cpp +# End Source File +# End Group +# Begin Group "Common Header Files" + +# PROP Default_Filter ".h" +# Begin Source File + +SOURCE=..\common\classes.h +# End Source File +# Begin Source File + +SOURCE=..\common\crc32.h +# End Source File +# Begin Source File + +SOURCE=..\common\database.h +# End Source File +# Begin Source File + +SOURCE=..\common\DBMemLeak.h +# End Source File +# Begin Source File + +SOURCE=..\common\debug.h +# End Source File +# Begin Source File + +SOURCE=..\common\deity.h +# End Source File +# Begin Source File + +SOURCE=..\common\eq_opcodes.h +# End Source File +# Begin Source File + +SOURCE=..\common\eq_packet_structs.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQCheckTable.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQFragment.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQNetwork.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQOpcodes.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQPacket.h +# End Source File +# Begin Source File + +SOURCE=..\common\EQPacketManager.h +# End Source File +# Begin Source File + +SOURCE=..\common\errmsg.h +# End Source File +# Begin Source File + +SOURCE=..\common\Guilds.h +# End Source File +# Begin Source File + +SOURCE=..\common\linked_list.h +# End Source File +# Begin Source File + +SOURCE=..\common\md5.h +# End Source File +# Begin Source File + +SOURCE=..\common\MiscFunctions.h +# End Source File +# Begin Source File + +SOURCE=..\common\moremath.h +# End Source File +# Begin Source File + +SOURCE=..\common\Mutex.h +# End Source File +# Begin Source File + +SOURCE=..\common\packet_dump.h +# End Source File +# Begin Source File + +SOURCE=..\common\packet_dump_file.h +# End Source File +# Begin Source File + +SOURCE=..\common\packet_functions.h +# End Source File +# Begin Source File + +SOURCE=..\common\queue.h +# End Source File +# Begin Source File + +SOURCE=..\common\queues.h +# End Source File +# Begin Source File + +SOURCE=..\common\races.h +# End Source File +# Begin Source File + +SOURCE=..\common\Seperator.h +# End Source File +# Begin Source File + +SOURCE=..\common\servertalk.h +# End Source File +# Begin Source File + +SOURCE=..\common\TCPConnection.h +# End Source File +# Begin Source File + +SOURCE=..\common\timer.h +# End Source File +# Begin Source File + +SOURCE=..\common\types.h +# End Source File +# Begin Source File + +SOURCE=..\common\version.h +# End Source File +# End Group +# Begin Group "Text Files" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\Protocol.txt +# End Source File +# Begin Source File + +SOURCE=.\Tables.txt +# End Source File +# Begin Source File + +SOURCE=.\ThanksTo.txt +# End Source File +# End Group +# End Target +# End Project diff --git a/source/LoginServer/Login.dsw b/source/LoginServer/Login.dsw new file mode 100644 index 0000000..4ed0adf --- /dev/null +++ b/source/LoginServer/Login.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Login"=.\Login.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/source/LoginServer/Login.vcproj b/source/LoginServer/Login.vcproj new file mode 100644 index 0000000..7c1f7a6 --- /dev/null +++ b/source/LoginServer/Login.vcproj @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/LoginServer/Login.vcxproj b/source/LoginServer/Login.vcxproj new file mode 100644 index 0000000..5622e75 --- /dev/null +++ b/source/LoginServer/Login.vcxproj @@ -0,0 +1,154 @@ + + + + + EQ2Login + x64 + + + + EQ2Login + {BE2C1914-FCCC-4F65-A7DD-105142B36104} + EQ2 Login + 10.0 + + + + v142 + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + $(SolutionDir)..\source\depends\mariadb-10.1.19\include;$(SolutionDir)..\source\depends\zlib\include;$(SolutionDir)..\source\depends\recastnavigation\Detour\Include;$(SolutionDir)..\source\depends\boost_1_72_0\;$(SolutionDir)..\source\depends\glm\;$(VC_IncludePath);$(WindowsSDK_IncludePath); + $(SolutionDir)..\source\depends\recastnavigation\RecastDemo\Build\vs2019\lib\Debug;$(SolutionDir)..\source\depends\mariadb-10.1.19\lib\64-debug;$(SolutionDir)..\source\depends\zlib\lib;$(SolutionDir)..\source\depends\boost_1_72_0\lib64-msvc-14.2;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64 + false + $(SolutionDir)loginserver\ + .\$(ProjectName)__Debug64\ + $(ProjectName)__Debug64 + + + + Disabled + AnySuitable + _WIN32_WINNT=0x0400;WIN32;NDEBUG;_CONSOLE;LOGIN; EQ2; EQN_DEBUG;_CRT_SECURE_NO_DEPRECATE;_HAS_STD_BYTE=0 +;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + false + false + + + $(IntDir) + + + 4996;%(DisableSpecificWarnings) + stdcpp17 + + + odbc32.lib;odbccp32.lib;ws2_32.lib;zlib.lib;mysqlclient.lib;DebugUtils.lib;Detour.lib;DetourCrowd.lib;DetourTileCache.lib;Recast.lib;%(AdditionalDependencies) + LIBCMT;LIBC;%(IgnoreSpecificDefaultLibraries) + true + $(IntDir)$(TargetName).pdb + true + true + Default + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/LoginServer/Login.vcxproj.filters b/source/LoginServer/Login.vcxproj.filters new file mode 100644 index 0000000..fae3865 --- /dev/null +++ b/source/LoginServer/Login.vcxproj.filters @@ -0,0 +1,277 @@ + + + + + {bfe8d6b0-594f-4b55-9f95-101bbcf4069c} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {d65b2760-468c-4206-a19a-48323a50ba5a} + h;hpp;hxx;hm;inl + + + {27b769a5-0972-4e9e-b78c-09ad3341579c} + .cpp + + + {11757e5a-691c-49c9-a627-df027ad58326} + .h + + + {99e7f9f9-abcd-4abf-8200-a4b5a467788c} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + Common Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + World Files + + + World Files + + + World Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + Common Header Files + + + \ No newline at end of file diff --git a/source/LoginServer/Login.vcxproj.user b/source/LoginServer/Login.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/source/LoginServer/Login.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/source/LoginServer/LoginAccount.cpp b/source/LoginServer/LoginAccount.cpp new file mode 100644 index 0000000..228f0b5 --- /dev/null +++ b/source/LoginServer/LoginAccount.cpp @@ -0,0 +1,58 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "LoginAccount.h" + +LoginAccount::LoginAccount(){ +} +LoginAccount::~LoginAccount(){ + vector::iterator iter; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + safe_delete(*iter); + } +} + +void LoginAccount::flushCharacters ( ) +{ + vector::iterator iter; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + safe_delete(*iter); + } + + charlist.clear ( ); +} + +CharSelectProfile* LoginAccount::getCharacter(char* name){ + vector::iterator char_iterator; + CharSelectProfile* profile = 0; + EQ2_16BitString temp; + for(char_iterator = charlist.begin(); char_iterator != charlist.end(); char_iterator++){ + profile = *char_iterator; + temp = profile->packet->getType_EQ2_16BitString_ByName("name"); + if(strcmp(temp.data.c_str(), name)==0) + return profile; + } + return 0; +} +void LoginAccount::removeCharacter(char* name, int16 version){ + vector::iterator iter; + CharSelectProfile* profile = 0; + EQ2_16BitString temp; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + profile = *iter; + temp = profile->packet->getType_EQ2_16BitString_ByName("name"); + if(strcmp(temp.data.c_str(), name)==0){ + if(version <= 561) { + profile->deleted = true; // workaround for char select crash on old clients + } + else { + safe_delete(*iter); + charlist.erase(iter); + } + return; + } + } +} \ No newline at end of file diff --git a/source/LoginServer/LoginAccount.h b/source/LoginServer/LoginAccount.h new file mode 100644 index 0000000..b5bb540 --- /dev/null +++ b/source/LoginServer/LoginAccount.h @@ -0,0 +1,54 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef _LOGINACCOUNT_ +#define _LOGINACCOUNT_ + +#include +#include +#include +#include "../common/linked_list.h" +#include "PacketHeaders.h" +#include "../common/PacketStruct.h" + +using namespace std; +class LoginAccount { +public: + LoginAccount(); + LoginAccount(int32 id, const char* in_name, const char* in_pass){ + account_id = id; + strcpy(name, in_name); + strcpy(password, in_pass); + } + ~LoginAccount(); + bool SaveAccount(LoginAccount* acct); + vector charlist; + void setName(const char* in_name) { strcpy(name, in_name); } + void setPassword(const char* in_pass) { strcpy(password, in_pass); } + void setAuthenticated(bool in_auth) { authenticated=in_auth; } + void setAccountID(int32 id){ account_id = id; } + void addCharacter(CharSelectProfile* profile){ + charlist.push_back(profile); + } + void removeCharacter(PacketStruct* profile); + void removeCharacter(char* name, int16 version); + void serializeCharacter(uchar* buffer, CharSelectProfile* profile); + + void flushCharacters ( ); + + CharSelectProfile* getCharacter(char* name); + int32 getLoginAccountID(){ return account_id; } + char* getLoginName() { return name; } + char* getLoginPassword() { return password; } + bool getLoginAuthenticated() { return authenticated; } + +private: + int32 account_id; + char name[32]; + char password[32]; + bool authenticated; +}; +#endif \ No newline at end of file diff --git a/source/LoginServer/LoginDatabase.cpp b/source/LoginServer/LoginDatabase.cpp new file mode 100644 index 0000000..b750a62 --- /dev/null +++ b/source/LoginServer/LoginDatabase.cpp @@ -0,0 +1,1070 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" + +#include +#include +#include +using namespace std; + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include "../common/unix.h" +#include +#endif + +#include "../common/Log.h" +#include "../common/DatabaseNew.h" +#include "LoginDatabase.h" +#include "LoginAccount.h" +#include "../common/MiscFunctions.h" +#include "../common/packet_functions.h" +#include "../common/packet_dump.h" +#include "LWorld.h" + +extern LoginDatabase database; +extern LWorldList world_list; + + +bool LoginDatabase::ConnectNewDatabase() { + return dbLogin.Connect(); +} + +void LoginDatabase::RemoveDeletedCharacterData() +{ + dbLogin.Query("DELETE FROM login_char_colors WHERE login_characters_id IN (SELECT id FROM login_characters WHERE deleted = 1)"); + dbLogin.Query("DELETE FROM login_equipment WHERE login_characters_id IN (SELECT id FROM login_characters WHERE deleted = 1)"); +} + +void LoginDatabase::SetZoneInformation(int32 server_id, int32 zone_id, int32 version, PacketStruct* packet){ + if(packet){ + Query query; + MYSQL_RES* result = 0; + + if ( version >= 1212 ) + result = query.RunQuery2(Q_SELECT, "SELECT name, description from ls_world_zones where server_id=%i and zone_id=%i", server_id, zone_id); + + MYSQL_ROW row; + if(result && (row = mysql_fetch_row(result))) { + if (row[0]) + packet->setMediumStringByName("zone", row[0]); + else + packet->setMediumStringByName("zone", " "); + if(row[1]) + packet->setMediumStringByName("zonedesc", row[1]); + else + packet->setMediumStringByName("zonedesc", " "); + } + else{ + Query query2; + MYSQL_RES* result2 = 0; + + if (version < 1212) + result2 = query2.RunQuery2(Q_SELECT, "SELECT file, description from zones where id=%i", zone_id); + else + result2 = query2.RunQuery2(Q_SELECT, "SELECT name, description from zones where id=%i", zone_id); + + MYSQL_ROW row2; + if(result2 && (row2 = mysql_fetch_row(result2))) { + + if (version < 546 && version > 561 && version < 1212) + { + if (row2[0]) + { + int len = strlen(row2[0]); + char* zoneName = new char[len + 2]; + strncpy(zoneName, row2[0], len); + zoneName[len] = 0x2E; + zoneName[len + 1] = 0x30; + + packet->setMediumStringByName("zone", zoneName); + safe_delete_array(zoneName); + } + else + packet->setMediumStringByName("zone", ".0"); + } + else + { + if (row2[0]) + packet->setMediumStringByName("zone", row2[0]); + else + packet->setMediumStringByName("zone", " "); + } + if(row2[1]) + packet->setMediumStringByName("zonedesc", row2[1]); + else + packet->setMediumStringByName("zonedesc", " "); + } + } + packet->setMediumStringByName("zonename2"," "); + } +} + +string LoginDatabase::GetZoneDescription(char* name){ + string ret; + Query query; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT description from zones where file=substring_index('%s', '.', 1)", query.escaped_name); + MYSQL_ROW row; + if((row = mysql_fetch_row(result))) { + ret = string(row[0]); + } + return ret; +} + + +int32 LoginDatabase::GetLoginCharacterIDFromWorldCharID(int32 server_id, int32 char_id) +{ + int32 ret; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id FROM login_characters WHERE server_id = %u AND char_id = %u AND deleted = 0 LIMIT 0,1", server_id, char_id); + MYSQL_ROW row; + if((row = mysql_fetch_row(result))) { + ret = atoi(row[0]); + } + + return ret; +} + +void LoginDatabase::SetServerEquipmentAppearances(int32 server_id, map equip_updates) +{ + + if(equip_updates.size() > 0) + { + + LogWrite(LOGIN__DEBUG, 0, "Login", "Saving appearance info from world %u...", server_id); + + map::iterator equip_itr; + stringstream ss; + ss << "replace into login_equipment (login_characters_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue, slot) values"; + int count=0; + int32 char_id = 0; + + for(equip_itr = equip_updates.begin(); equip_itr != equip_updates.end(); equip_itr++) + { + char_id = GetLoginCharacterIDFromWorldCharID(server_id, (int32)equip_itr->second.world_char_id); + + if( char_id == 0 ) // invalid character/world match + continue; + + LogWrite(LOGIN__DEBUG, 5, "Login", "--Processing character %u, slot %i", char_id, (int32)equip_itr->second.slot); + + if(count > 0) + ss << ", "; + + ss << "(" << char_id << ", "; + ss << (int32)equip_itr->second.equip_type << ", "; + ss << (int32)equip_itr->second.red << ", "; + ss << (int32)equip_itr->second.green << ", "; + ss << (int32)equip_itr->second.blue << ", "; + ss << (int32)equip_itr->second.highlight_red << ", "; + ss << (int32)equip_itr->second.highlight_green << ", "; + ss << (int32)equip_itr->second.highlight_blue << ", "; + ss << (int32)equip_itr->second.slot << ")"; + + count++; + } + + Query query; + query.RunQuery2(ss.str(), Q_REPLACE); + + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LOGIN__ERROR, 0, "Login", "Error saving login_equipment data Error: ", query.GetError()); + } +} + + +void LoginDatabase::SetServerZoneDescriptions(int32 server_id, map zone_descriptions){ + if(zone_descriptions.size() > 0){ + map::iterator zone_itr; + string query_string = "replace into ls_world_zones (server_id, zone_id, name, description) values"; + int count=0; + char server_id_str[12] = {0}; + sprintf(server_id_str, "%i", server_id); + for(zone_itr = zone_descriptions.begin(); zone_itr != zone_descriptions.end(); zone_itr++, count++){ + char zone_id_str[12] = {0}; + sprintf(zone_id_str, "%i", zone_itr->first); + if(count > 0) + query_string.append(", "); + query_string.append("(").append(server_id_str).append(","); + query_string.append(zone_id_str).append(","); + query_string.append("'").append(getSafeEscapeString(zone_itr->second.name.c_str()).c_str()).append("', '"); + query_string.append(getSafeEscapeString(zone_itr->second.description.c_str()).c_str()).append("')"); + } + Query query; + query.RunQuery2(query_string, Q_REPLACE); + } +} + +//this is really just for the version that doesn't send the server id in its play request +int32 LoginDatabase::GetServer(int32 accountID, int32 charID, string name) { + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT server_id from login_characters where account_id=%i and char_id=%i and name='%s'", accountID, charID, query.escaped_name); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +void LoginDatabase::LoadCharacters(LoginAccount* acct, int16 version){ + if(acct != NULL) + acct->flushCharacters ( ); + + Query query; + Query query2; + int32 id = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT lc.char_id, lc.server_id, lc.name, lc.race, lc.class, lc.gender, lc.deity, lc.body_size, lc.body_age, lc.current_zone_id, lc.level, lc.soga_wing_type, lc.soga_chest_type, lc.soga_legs_type, lc.soga_hair_type, lc.legs_type, lc.chest_type, lc.wing_type, lc.hair_type, unix_timestamp(lc.created_date), unix_timestamp(lc.last_played), lc.id, lw.name, lc.facial_hair_type, lc.soga_facial_hair_type, lc.soga_model_type, lc.model_type from login_characters lc, login_worldservers lw where lw.id = lc.server_id and lc.account_id=%i and lc.deleted=0",acct->getLoginAccountID()); + if(result) { + MYSQL_ROW row; + MYSQL_ROW row2; + MYSQL_ROW row3; + while ((row = mysql_fetch_row(result))) { + CharSelectProfile* player = new CharSelectProfile(version); + id = atoul(row[0]); + //for (int i = 0; i < 10; i++) + // player->packet->setDataByName("hair_type", 0, i); + //player->packet->setDataByName("test23", 413); + //player->packet->setDataByName("test24", 414); + player->packet->setDataByName("charid", id); + player->packet->setDataByName("server_id", atoul(row[1])); + player->packet->setMediumStringByName("name", row[2]); + player->packet->setDataByName("race", atoi(row[3])); + player->packet->setDataByName("class", atoi(row[4])); + player->packet->setDataByName("gender", atoi(row[5])); + player->packet->setDataByName("deity", atoi(row[6])); + player->packet->setDataByName("body_size", atof(row[7])); + player->packet->setDataByName("body_age", atof(row[8])); + SetZoneInformation(atoi(row[1]), atoi(row[9]), version, player->packet); + player->packet->setDataByName("level", atoi(row[10])); + if(atoi(row[11]) > 0) + player->packet->setDataByName("soga_wing_type", atoi(row[11])); + else + player->packet->setDataByName("soga_wing_type", atoi(row[17])); + if(atoi(row[12]) > 0) + player->packet->setDataByName("soga_chest_type", atoi(row[12])); + else + player->packet->setDataByName("soga_chest_type", atoi(row[16])); + if(atoi(row[13]) > 0) + player->packet->setDataByName("soga_legs_type", atoi(row[13])); + else + player->packet->setDataByName("soga_legs_type", atoi(row[15])); + if(atoi(row[14]) > 0) + player->packet->setDataByName("soga_hair_type", atoi(row[14])); + else + player->packet->setDataByName("soga_hair_type", atoi(row[18])); + player->packet->setDataByName("legs_type", atoi(row[15])); + player->packet->setDataByName("chest_type", atoi(row[16])); + player->packet->setDataByName("wing_type", atoi(row[17])); + player->packet->setDataByName("hair_type", atoi(row[18])); + player->packet->setDataByName("created_date", atol(row[19])); + if (row[20]) + player->packet->setDataByName("last_played", atol(row[20])); + if(version == 546 || version == 561) + player->packet->setDataByName("version", 11); + else if(version >= 887) + player->packet->setDataByName("version", 6); + else + player->packet->setDataByName("version", 5); + player->packet->setDataByName("account_id", acct->getLoginAccountID()); + player->packet->setDataByName("account_id2", acct->getLoginAccountID()); + + LoadAppearanceData(atoul(row[21]), player->packet); + + if(row[22]) + player->packet->setMediumStringByName("server_name", row[22]); + player->packet->setDataByName("hair_face_type", atoi(row[23])); + if(atoi(row[24]) > 0) + player->packet->setDataByName("soga_hair_face_type", atoi(row[24])); + else + player->packet->setDataByName("soga_hair_face_type", atoi(row[23])); + if(atoi(row[25]) > 0) + player->packet->setDataByName("soga_race_type", atoi(row[25])); + else + player->packet->setDataByName("soga_race_type", atoi(row[26])); + player->packet->setDataByName("race_type", atoi(row[26])); + + player->packet->setDataByName("unknown3", 57); + player->packet->setDataByName("unknown4", 56); + player->packet->setDataByName("unknown6", 1, 1); //if not here will not display character + player->packet->setDataByName("unknown8", 15); + player->packet->setDataByName("unknown13", 212); + player->packet->setColorByName("unknown14", 0xFF, 0xFF, 0xFF); + + uchar tmp[] = {0xFF, 0xFF, 0xFF, 0x61, 0x00, 0x2C, 0x04, 0xA5, 0x09, 0x02, 0x0F, 0x00, 0x00}; + for(size_t y=0;ypacket->setDataByName("unknown11", tmp[y], y); + MYSQL_RES* result3 = query2.RunQuery2(Q_SELECT, "SELECT slot, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue from login_equipment where login_characters_id=%i order by slot",atoi(row[21])); + if(result3){ + for(int i=0;(row3 = mysql_fetch_row(result3)) && i<24; i++){ + player->packet->setEquipmentByName("equip", atoi(row3[1]), atoi(row3[2]), atoi(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoi(row3[7]), atoi(row3[0])); + } + } + player->packet->setDataByName("mount", 1377); + player->packet->setDataByName("mount_color1", 57); + /* + enum NetAppearance::NetAppearanceFlags + { + NAF_INVISIBLE=1, + NAF_SHOW_HOOD=2 + }; + */ + acct->addCharacter(player); + } + } + else + LogWrite(LOGIN__ERROR, 0, "Login", "Error in LoadCharacters query '%s': %s", query.GetQuery(), query.GetError()); + +} + +void LoginDatabase::CheckCharacterTimeStamps(LoginAccount* acct){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT char_id, unix_timestamp from login_characters where account_id=%i",acct->getLoginAccountID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + + ServerPacket* outpack = new ServerPacket(ServerOP_CharTimeStamp, sizeof(CharacterTimeStamp_Struct)); + CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) outpack->pBuffer; + cts->account_id = acct->getLoginAccountID(); + int32 server_id = 0; + LWorld* world_server = 0; + while ((row = mysql_fetch_row(result))) { + server_id = atoi(row[1]); + if(server_id != 0) + world_server = world_list.FindByAccount(server_id, World); + if(world_server) // If the pointer is 0, the world server must be down, we can't do any updates... + { + cts->char_id = atoi(row[0]); + cts->unix_timestamp = atoi(row[1]); + world_server->SendPacket(outpack); + //Reset for next character + world_server = 0; + server_id = 0; + } + } + safe_delete(outpack); + } +} + +void LoginDatabase::SaveCharacterFloats(int32 char_id, char* type, float float1, float float2, float float3,float multiplier){ + Query query; + string create_char = string("insert into login_char_colors (login_characters_id, type, red, green, blue, signed_value) values(%i,'%s',%i,%i,%i, 1)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, (sint8)(float1*multiplier), (sint8)(float2*multiplier), (sint8)(float3*multiplier)); +} + +void LoginDatabase::SaveCharacterColors(int32 char_id, char* type, EQ2_Color color){ + Query query; + string create_char = string("insert into login_char_colors (login_characters_id, type, red, green, blue) values(%i,'%s',%i,%i,%i)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, color.red, color.green, color.blue); +} + +void LoginDatabase::LoadAppearanceData(int32 char_id, PacketStruct* char_select_packet){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type, signed_value, red, green, blue from login_char_colors where login_characters_id = %i",char_id); + while((row = mysql_fetch_row(result))){ + if(atoi(row[1]) == 0) + char_select_packet->setColorByName(row[0], atoul(row[2]), atoul(row[3]), atoul(row[4])); + else{ + char_select_packet->setDataByName(row[0], atoi(row[2]), 0); + char_select_packet->setDataByName(row[0], atoi(row[3]), 1); + char_select_packet->setDataByName(row[0], atoi(row[4]), 2); + } + } +} +int16 LoginDatabase::GetAppearanceID(string name){ + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT appearance_id from appearances where name='%s'", query.escaped_name); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +void LoginDatabase::DeactivateCharID(int32 server_id, int32 char_id, int32 exception_id){ + Query query; + query.RunQuery2(Q_UPDATE, "update login_characters set deleted=1 where char_id=%u and server_id=%u and id!=%u",char_id,server_id,exception_id); +} + +int32 LoginDatabase::SaveCharacter(PacketStruct* create, LoginAccount* acct, int32 world_charid, int32 client_version){ + int32 ret_id = 0; + Query query; + string create_char = + string("Insert into login_characters (account_id, server_id, char_id, name, race, class, gender, deity, body_size, body_age, soga_wing_type, soga_chest_type, soga_legs_type, soga_hair_type, soga_facial_hair_type, legs_type, chest_type, wing_type, hair_type, facial_hair_type, soga_model_type, model_type)" + " values(%i, %i, %i, '%s', %i, %i, %i, %i, %f, %f, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), + acct->getLoginAccountID(), + create->getType_int32_ByName("server_id"), world_charid, + create->getType_EQ2_16BitString_ByName("name").data.c_str(), + create->getType_int8_ByName("race"), + create->getType_int8_ByName("class"), + create->getType_int8_ByName("gender"), + create->getType_int8_ByName("deity"), + create->getType_float_ByName("body_size"), + create->getType_float_ByName("body_age"), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_face_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("face_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_race_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("race_file").data)); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(LOGIN__ERROR, 0, "Login", "Error in SaveCharacter query '%s': %s", query.GetQuery(), query.GetError()); + return 0; + } + + int32 last_insert_id = query.GetLastInsertedID(); + + //mark any remaining characters with same id as deleted (creates problems if world deleted their db and started assigning new char ids) + DeactivateCharID(create->getType_int32_ByName("server_id"), world_charid, last_insert_id); + int32 char_id = last_insert_id; + if (client_version <= 561) { + float classic_multiplier = 250.0f; + SaveCharacterFloats(char_id, "skin_color", create->getType_float_ByName("skin_color", 0), create->getType_float_ByName("skin_color", 1), create->getType_float_ByName("skin_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "eye_color", create->getType_float_ByName("eye_color", 0), create->getType_float_ByName("eye_color", 1), create->getType_float_ByName("eye_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color1", create->getType_float_ByName("hair_color1", 0), create->getType_float_ByName("hair_color1", 1), create->getType_float_ByName("hair_color1", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color2", create->getType_float_ByName("hair_color2", 0), create->getType_float_ByName("hair_color2", 1), create->getType_float_ByName("hair_color2", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_highlight", create->getType_float_ByName("hair_highlight", 0), create->getType_float_ByName("hair_highlight", 1), create->getType_float_ByName("hair_highlight", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_color", create->getType_float_ByName("hair_face_color", 0), create->getType_float_ByName("hair_face_color", 1), create->getType_float_ByName("hair_face_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_highlight_color", create->getType_float_ByName("hair_face_highlight_color", 0), create->getType_float_ByName("hair_face_highlight_color", 1), create->getType_float_ByName("hair_face_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "shirt_color", create->getType_float_ByName("shirt_color", 0), create->getType_float_ByName("shirt_color", 1), create->getType_float_ByName("shirt_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_chest_color", create->getType_float_ByName("unknown_chest_color", 0), create->getType_float_ByName("unknown_chest_color", 1), create->getType_float_ByName("unknown_chest_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "pants_color", create->getType_float_ByName("pants_color", 0), create->getType_float_ByName("pants_color", 1), create->getType_float_ByName("pants_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_legs_color", create->getType_float_ByName("unknown_legs_color", 0), create->getType_float_ByName("unknown_legs_color", 1), create->getType_float_ByName("unknown_legs_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown9", create->getType_float_ByName("unknown9", 0), create->getType_float_ByName("unknown9", 1), create->getType_float_ByName("unknown9", 2), classic_multiplier); + } + else { + SaveCharacterColors(char_id, "skin_color", create->getType_EQ2_Color_ByName("skin_color")); + SaveCharacterColors(char_id, "model_color", create->getType_EQ2_Color_ByName("model_color")); + SaveCharacterColors(char_id, "eye_color", create->getType_EQ2_Color_ByName("eye_color")); + SaveCharacterColors(char_id, "hair_color1", create->getType_EQ2_Color_ByName("hair_color1")); + SaveCharacterColors(char_id, "hair_color2", create->getType_EQ2_Color_ByName("hair_color2")); + SaveCharacterColors(char_id, "hair_highlight", create->getType_EQ2_Color_ByName("hair_highlight")); + SaveCharacterColors(char_id, "hair_type_color", create->getType_EQ2_Color_ByName("hair_type_color")); + SaveCharacterColors(char_id, "hair_type_highlight_color", create->getType_EQ2_Color_ByName("hair_type_highlight_color")); + SaveCharacterColors(char_id, "hair_face_color", create->getType_EQ2_Color_ByName("hair_face_color")); + SaveCharacterColors(char_id, "hair_face_highlight_color", create->getType_EQ2_Color_ByName("hair_face_highlight_color")); + SaveCharacterColors(char_id, "wing_color1", create->getType_EQ2_Color_ByName("wing_color1")); + SaveCharacterColors(char_id, "wing_color2", create->getType_EQ2_Color_ByName("wing_color2")); + SaveCharacterColors(char_id, "shirt_color", create->getType_EQ2_Color_ByName("shirt_color")); + SaveCharacterColors(char_id, "unknown_chest_color", create->getType_EQ2_Color_ByName("unknown_chest_color")); + SaveCharacterColors(char_id, "pants_color", create->getType_EQ2_Color_ByName("pants_color")); + SaveCharacterColors(char_id, "unknown_legs_color", create->getType_EQ2_Color_ByName("unknown_legs_color")); + SaveCharacterColors(char_id, "unknown9", create->getType_EQ2_Color_ByName("unknown9")); + + SaveCharacterColors(char_id, "soga_skin_color", create->getType_EQ2_Color_ByName("soga_skin_color")); + SaveCharacterColors(char_id, "soga_model_color", create->getType_EQ2_Color_ByName("soga_model_color")); + SaveCharacterColors(char_id, "soga_eye_color", create->getType_EQ2_Color_ByName("soga_eye_color")); + SaveCharacterColors(char_id, "soga_hair_color1", create->getType_EQ2_Color_ByName("soga_hair_color1")); + SaveCharacterColors(char_id, "soga_hair_color2", create->getType_EQ2_Color_ByName("soga_hair_color2")); + SaveCharacterColors(char_id, "soga_hair_highlight", create->getType_EQ2_Color_ByName("soga_hair_highlight")); + SaveCharacterColors(char_id, "soga_hair_type_color", create->getType_EQ2_Color_ByName("soga_hair_type_color")); + SaveCharacterColors(char_id, "soga_hair_type_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_type_highlight_color")); + SaveCharacterColors(char_id, "soga_hair_face_color", create->getType_EQ2_Color_ByName("soga_hair_face_color")); + SaveCharacterColors(char_id, "soga_hair_face_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_face_highlight_color")); + SaveCharacterColors(char_id, "soga_wing_color1", create->getType_EQ2_Color_ByName("soga_wing_color1")); + SaveCharacterColors(char_id, "soga_wing_color2", create->getType_EQ2_Color_ByName("soga_wing_color2")); + SaveCharacterColors(char_id, "soga_shirt_color", create->getType_EQ2_Color_ByName("soga_shirt_color")); + SaveCharacterColors(char_id, "soga_unknown_chest_color", create->getType_EQ2_Color_ByName("soga_unknown_chest_color")); + SaveCharacterColors(char_id, "soga_pants_color", create->getType_EQ2_Color_ByName("soga_pants_color")); + SaveCharacterColors(char_id, "soga_unknown_legs_color", create->getType_EQ2_Color_ByName("soga_unknown_legs_color")); + SaveCharacterColors(char_id, "soga_unknown13", create->getType_EQ2_Color_ByName("soga_unknown13")); + SaveCharacterFloats(char_id, "soga_eye_type", create->getType_float_ByName("soga_eyes2", 0), create->getType_float_ByName("soga_eyes2", 1), create->getType_float_ByName("soga_eyes2", 2)); + SaveCharacterFloats(char_id, "soga_ear_type", create->getType_float_ByName("soga_ears", 0), create->getType_float_ByName("soga_ears", 1), create->getType_float_ByName("soga_ears", 2)); + SaveCharacterFloats(char_id, "soga_eye_brow_type", create->getType_float_ByName("soga_eye_brows", 0), create->getType_float_ByName("soga_eye_brows", 1), create->getType_float_ByName("soga_eye_brows", 2)); + SaveCharacterFloats(char_id, "soga_cheek_type", create->getType_float_ByName("soga_cheeks", 0), create->getType_float_ByName("soga_cheeks", 1), create->getType_float_ByName("soga_cheeks", 2)); + SaveCharacterFloats(char_id, "soga_lip_type", create->getType_float_ByName("soga_lips", 0), create->getType_float_ByName("soga_lips", 1), create->getType_float_ByName("soga_lips", 2)); + SaveCharacterFloats(char_id, "soga_chin_type", create->getType_float_ByName("soga_chin", 0), create->getType_float_ByName("soga_chin", 1), create->getType_float_ByName("soga_chin", 2)); + SaveCharacterFloats(char_id, "soga_nose_type", create->getType_float_ByName("soga_nose", 0), create->getType_float_ByName("soga_nose", 1), create->getType_float_ByName("soga_nose", 2)); + } + SaveCharacterFloats(char_id, "eye_type", create->getType_float_ByName("eyes2", 0), create->getType_float_ByName("eyes2", 1), create->getType_float_ByName("eyes2", 2)); + SaveCharacterFloats(char_id, "ear_type", create->getType_float_ByName("ears", 0), create->getType_float_ByName("ears", 1), create->getType_float_ByName("ears", 2)); + SaveCharacterFloats(char_id, "eye_brow_type", create->getType_float_ByName("eye_brows", 0), create->getType_float_ByName("eye_brows", 1), create->getType_float_ByName("eye_brows", 2)); + SaveCharacterFloats(char_id, "cheek_type", create->getType_float_ByName("cheeks", 0), create->getType_float_ByName("cheeks", 1), create->getType_float_ByName("cheeks", 2)); + SaveCharacterFloats(char_id, "lip_type", create->getType_float_ByName("lips", 0), create->getType_float_ByName("lips", 1), create->getType_float_ByName("lips", 2)); + SaveCharacterFloats(char_id, "chin_type", create->getType_float_ByName("chin", 0), create->getType_float_ByName("chin", 1), create->getType_float_ByName("chin", 2)); + SaveCharacterFloats(char_id, "nose_type", create->getType_float_ByName("nose", 0), create->getType_float_ByName("nose", 1), create->getType_float_ByName("nose", 2)); + SaveCharacterFloats(char_id, "body_size", create->getType_float_ByName("body_size", 0), 0, 0); + return ret_id; +} + +bool LoginDatabase::DeleteCharacter(int32 account_id, int32 character_id, int32 server_id){ + Query query; + string delete_char = string("delete from login_characters where char_id=%i and account_id=%i and server_id=%i"); + query.RunQuery2(Q_DELETE, delete_char.c_str(),character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + //No error just in case ppl try doing stupid stuff + return false; + } + + return true; +} + +string LoginDatabase::GetCharacterName(int32 char_id, int32 server_id, int32 account_id){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name from login_characters where char_id=%lu and server_id=%lu and account_id=%lu and deleted = 0 limit 1", char_id, server_id, account_id); + + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + return string(row[0]); + } + return string(""); +} + +bool LoginDatabase::UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update, int32 server_id){ + Query query; + string update_charts = string("update login_characters set unix_timestamp=%lu where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),timestamp_update,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterTimeStamp query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterLevel(int32 account_id, int32 character_id, int8 in_level, int32 server_id){ + Query query; + string update_charts = string("update login_characters set level=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_level,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterLevel query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterRace(int32 account_id, int32 character_id, int16 in_racetype, int8 in_race, int32 server_id){ + Query query; + string update_charts = string("update login_characters set race_type=%i, race=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_racetype,in_race,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterRace query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterZone(int32 account_id, int32 character_id, int32 zone_id, int32 server_id){ + Query query; + string update_chars = string("update login_characters set current_zone_id=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_chars.c_str(), zone_id, character_id, account_id, server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterZone query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterClass(int32 account_id, int32 character_id, int8 in_class, int32 server_id){ + Query query; + string update_charts = string("update login_characters set class=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_class,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterClass query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterGender(int32 account_id, int32 character_id, int8 in_gender, int32 server_id){ + Query query; + string update_charts = string("update login_characters set gender=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_gender,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterClass query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +LoginAccount* LoginDatabase::LoadAccount(const char* name, const char* password, bool attemptAccountCreation){ + LoginAccount* acct = NULL; + Query query; + query.escaped_name = getEscapeString(name); + query.escaped_pass = getEscapeString(password); + time_t now = time(0); //get the current epoc time + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from account where name='%s' and passwd=sha2('%s',512)", query.escaped_name, query.escaped_pass); + if(result){ + if (mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + int32 id = atol(row[0]); + + acct = new LoginAccount(id, name, password); + acct->setAuthenticated(true); + } + else if(mysql_num_rows(result) > 0) + LogWrite(LOGIN__ERROR, 0, "Login", "Error in LoginAccount: more than one account returned for '%s'", name); + else if (attemptAccountCreation && !database.GetAccountIDByName(name)) + { + Query newquery; + newquery.RunQuery2(Q_INSERT, "insert into account set name='%s',passwd=sha2('%s',512), created_date=%i", query.escaped_name, query.escaped_pass, now); + // re-run the query for select only not account creation + return LoadAccount(name, password, false); + } + + } + return acct; +} + +int32 LoginDatabase::GetAccountIDByName(const char* name) { + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from account where name='%s'", query.escaped_name); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +int32 LoginDatabase::CheckServerAccount(char* name, char* passwd){ + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT lower(password), id from login_worldservers where account='%s' and disabled = 0", query.escaped_name); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccount Account=%s\nSHA=%s", (char*)query.escaped_name, passwd); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccountResult Account=%s\nPassword=%s", (char*)query.escaped_name, (row && row[0]) ? row[0] : "(NULL)"); + + if (memcmp(row[0], passwd, strnlen(row[0], 256)) == 0) + { + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccountResultMatch Account=%s", (char*)query.escaped_name); + id = atoi(row[1]); + } + } + return id; +} + +bool LoginDatabase::IsServerAccountDisabled(char* name){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from login_worldservers where account='%s' and disabled = 1", query.escaped_name); + + LogWrite(LOGIN__DEBUG, 0, "Login", "WorldServer IsServerAccountDisabled Account=%s", (char*)query.escaped_name); + if(result && mysql_num_rows(result) > 0){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer IsServerAccountDisabled Match Account=%s", (char*)query.escaped_name); + + return true; + } + return false; +} + +bool LoginDatabase::IsIPBanned(char* ipaddr){ + if(!ipaddr) + return false; + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ip from login_bannedips where '%s' LIKE CONCAT(ip ,'%%')", ipaddr); + + LogWrite(LOGIN__DEBUG, 0, "Login", "WorldServer IsServerIPBanned IPPartial=%s", (char*)ipaddr); + if(result && mysql_num_rows(result) > 0){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer IsServerIPBanned Match IPBan=%s", row[0]); + + return true; + } + return false; +} + +void LoginDatabase::GetServerAccounts(vector* server_list){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, account, name, admin_id from login_worldservers"); + while((row = mysql_fetch_row(result))){ + LWorld* world = new LWorld(atol(row[0]), row[1], row[2], atoi(row[3])); + world->SetID(world->GetAccountID()); + server_list->push_back(world); + } +} +void LoginDatabase::SaveClientLog(const char* type, const char* message, const char* player_name, int16 version){ + Query query; + query.escaped_data1 = getEscapeString(message); + query.escaped_name = getEscapeString(player_name); + query.RunQuery2(Q_INSERT, "insert into log_messages (type, message, name, version) values('%s', '%s', '%s', %i)", type, query.escaped_data1, query.escaped_name, version); +} +bool LoginDatabase::VerifyDelete(int32 account_id, int32 character_id, const char* name){ + Query query; + query.escaped_name = getEscapeString(name); + query.RunQuery2(Q_UPDATE, "update login_characters set deleted = 1 where char_id=%i and account_id=%i and name='%s'", character_id, account_id, query.escaped_name); + if(query.GetAffectedRows() == 1) + return true; + else + return false; +} +char* LoginDatabase::GetServerAccountName(int32 id){ + Query query; + MYSQL_ROW row; + char* name = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name from login_worldservers where id=%lu", id); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + if(strlen(row[0]) > 0){ + name = new char[strlen(row[0])+1]; + strcpy(name, row[0]); + } + } + return name; +} +int32 LoginDatabase::GetRaceID(char* name){ + int32 ret = 1487; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_type from login_races where name='%s'", query.escaped_name); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + ret = atol(row[0]); + } + else if(!result || mysql_num_rows(result) == 0) + UpdateRaceID(query.escaped_name); + return ret; +} +void LoginDatabase::UpdateRaceID(char* name){ + Query query; + query.RunQuery2(Q_UPDATE, "insert into login_races (name) values('%s')", name); +} +bool LoginDatabase::CheckVersion(char* in_version){ + Query query; + query.escaped_data1 = getEscapeString(in_version); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from login_versions where version='%s' or version='*'", query.escaped_data1); + if(result && mysql_num_rows(result) > 0) + return true; + else + return false; +} +void LoginDatabase::GetLatestTableVersions(LatestTableVersions* table_versions){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, max(version) from login_table_versions group by name order by id"); + if(result && mysql_num_rows(result) > 0){ + table_versions->SetTableSize(mysql_num_rows(result)); + } + else // we need to return if theres no result, otherwise it will crash attempting to loop through rows + + return; + while((row = mysql_fetch_row(result))){ + if(VerifyDataTable(row[0])) + table_versions->AddTable(row[0], atoi(row[1]), GetDataVersion(row[0])); + else + table_versions->AddTable(row[0], atoi(row[1]), 0); + } +} +bool LoginDatabase::VerifyDataTable(char* name){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT table_name from download_tables where table_name='%s'", name); + if(result && mysql_num_rows(result) > 0) + return true; + return false; +} +string LoginDatabase::GetColumnNames(char* name){ + Query query; + MYSQL_ROW row; + string columns = "("; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "show columns from %s", name); + if(result && mysql_num_rows(result) > 0){ + int16 i = 0; + while((row = mysql_fetch_row(result))){ + if(strcmp(row[0], "table_data_version") != 0){ + if(i>0) + columns.append(","); + columns.append(row[0]); + i++; + } + } + } + columns.append(") "); + return columns; +} +TableDataQuery* LoginDatabase::GetTableDataQuery(int32 server_ip, char* name, int16 version){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + TableDataQuery* table_query = 0; + MYSQL_RES* result = 0; + string columns; + + if(VerifyDataTable(query.escaped_name)){ + result = query.RunQuery2(Q_SELECT, "SELECT * from %s where table_data_version > %i", query.escaped_name, version); + columns = GetColumnNames(query.escaped_name); + } + if(result && mysql_num_rows(result) > 0){ + table_query = new TableDataQuery(query.escaped_name); + table_query->num_queries = mysql_num_rows(result); + table_query->columns_size = columns.length() + 1; + table_query->columns = new char[table_query->columns_size + 1]; + table_query->version = GetDataVersion(query.escaped_name); + strcpy(table_query->columns, (char*)columns.c_str()); + string query_data; + MYSQL_FIELD* field; + int* int_list = new int[mysql_num_fields(result)]; + int16 ndx = 0; + while((field = mysql_fetch_field(result))){ + int_list[ndx] = IS_NUM(field->type); + if(strcmp(field->name,"table_data_version") == 0) + int_list[ndx] = 2; + ndx++; + } + ndx = 0; + while((row = mysql_fetch_row(result))){ + query_data = ""; + for(int i=0;i0) + query_data.append(","); + if(!int_list[i]){ + query_data.append("'").append(getEscapeString(row[i])).append("'"); + } + else + query_data.append(row[i]); + } + } + TableData* new_query = new TableData; + new_query->size = query_data.length() + 1; + new_query->query = new char[query_data.length() + 1]; + strcpy(new_query->query, query_data.c_str()); + table_query->queries.push_back(new_query); + ndx++; + } + safe_delete_array(int_list); + } + else{ + string query2 = string("The user tried to download the following table: ").append(query.escaped_name); + SaveClientLog("Possible Hacking Attempt", (char*)query2.c_str(), "Hacking Data", server_ip); + } + return table_query; +} +TableQuery* LoginDatabase::GetLatestTableQuery(int32 server_ip, char* name, int16 version){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + TableQuery* table_query = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT query, version from login_table_versions where name = '%s' and version>=%i order by version", query.escaped_name, version + 1); + if(result && mysql_num_rows(result) > 0){ + int16 i = 0; + table_query = new TableQuery; + while((row = mysql_fetch_row(result))){ + char* rowdata = row[0]; + if(strstr(rowdata, ";")){ + char* token = strtok(rowdata,";"); + while(token){ + char* new_query = new char[strlen(token) + 1]; + strcpy(new_query, token); + table_query->AddQuery(new_query); + token = strtok(NULL, ";"); + } + } + else + table_query->AddQuery(rowdata); + table_query->latest_version = atoi(row[1]); + } + strcpy(table_query->tablename, name); + table_query->your_version = version; + } + else{ + string query2 = string("The following was the DB Query: ").append(query.GetQuery()); + SaveClientLog("Possible Hacking Attempt", (char*)query2.c_str(), "Hacking Query", server_ip); + } + return table_query; +} +sint16 LoginDatabase::GetDataVersion(char* name){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(table_data_version) from %s", name); + sint16 ret_version = 0; + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0]) + ret_version = atoi(row[0]); + } + return ret_version; +} + +void LoginDatabase::RemoveOldWorldServerStats(){ + Query query; + query.RunQuery2(Q_DELETE, "delete from login_worldstats where (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(last_update)) > 86400"); +} + + +void LoginDatabase::UpdateWorldServerStats(LWorld* world, sint32 status) +{ + Query query; + query.RunQuery2(Q_INSERT, "insert into login_worldstats (world_id, world_status, current_players, current_zones, last_update, world_max_level) values(%u, %i, %i, %i, NOW(), %i) ON DUPLICATE KEY UPDATE current_players=%i,current_zones=%i,world_max_level=%i,world_status=%i,last_update=NOW()", + world->GetAccountID(), status, world->GetPlayerNum(), world->GetZoneNum(), world->GetMaxWorldLevel(), world->GetPlayerNum(), world->GetZoneNum(), world->GetMaxWorldLevel(), status); + + string update_stats = string("update login_worldservers set lastseen=%u where id=%i"); + query.RunQuery2(Q_UPDATE, update_stats.c_str(), Timer::GetUnixTimeStamp(), world->GetAccountID()); +} + +bool LoginDatabase::ResetWorldServerStatsConnectedTime(LWorld* world){ + if(!world || world->GetAccountID() == 0) + return false; + + Query query; + string update_stats = string("update login_worldstats set connected_time=now() where world_id=%i and (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(last_update)) > 300"); + query.RunQuery2(Q_UPDATE, update_stats.c_str(),world->GetAccountID()); + + return true; +} + +void LoginDatabase::ResetWorldStats ( ) +{ + Query query; + string update_stats = string("update login_worldstats set world_status=-4, current_players=0, current_zones=0"); + query.RunQuery2(update_stats.c_str(), Q_UPDATE); +} + +void LoginDatabase::SaveBugReport(int32 world_id, char* category, char* subcategory, char* causes_crash, char* reproducible, char* summary, char* description, char* version, char* player, int32 account_id, char* spawn_name, int32 spawn_id, int32 zone_id){ + Query query; + string bug_report = string("insert into bugs (world_id, category, subcategory, causes_crash, reproducible, summary, description, version, player, account_id, spawn_name, spawn_id, zone_id) values(%lu, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %lu, '%s', %lu, %lu)"); + query.RunQuery2(Q_INSERT, bug_report.c_str(), world_id, database.getSafeEscapeString(category).c_str(), database.getSafeEscapeString(subcategory).c_str(), + database.getSafeEscapeString(causes_crash).c_str(), database.getSafeEscapeString(reproducible).c_str(), database.getSafeEscapeString(summary).c_str(), + database.getSafeEscapeString(description).c_str(), database.getSafeEscapeString(version).c_str(), database.getSafeEscapeString(player).c_str(), account_id, + database.getSafeEscapeString(spawn_name).c_str(), spawn_id, zone_id); + FixBugReport(); +} + +void LoginDatabase::FixBugReport(){ + Query query; + string bug_report = string("update bugs set description = REPLACE(description,SUBSTRING(description,INSTR(description,'%'), 3),char(CONV(SUBSTRING(description,INSTR(description,'%')+1, 2), 16, 10))), summary = REPLACE(summary,SUBSTRING(summary,INSTR(summary,'%'), 3),char(CONV(SUBSTRING(summary,INSTR(summary,'%')+1, 2), 16, 10)))"); + query.RunQuery2(bug_report.c_str(), Q_UPDATE); +} + +void LoginDatabase::UpdateWorldIPAddress(int32 world_id, int32 address){ + struct in_addr in; + in.s_addr = address; + Query query; + query.RunQuery2(Q_UPDATE, "update login_worldservers set ip_address='%s' where id=%lu", inet_ntoa(in), world_id); +} + +void LoginDatabase::UpdateAccountIPAddress(int32 account_id, int32 address){ + struct in_addr in; + in.s_addr = address; + Query query; + query.RunQuery2(Q_UPDATE, "update account set ip_address='%s' where id=%lu", inet_ntoa(in), account_id); +} + +//devn00b: There is no rulesystem for login, so im going to use login_config for future things like this. +//devn00b: Returns the number of characters a player may create per account. This should be set by server owners -> login, +//devn00b: However, better semi-working for now than not working at all. +//devn00b: TODO: EQ2World sends max char per acct. +int8 LoginDatabase::GetMaxCharsSetting() { + //live defaults to 7 for GOLD members. + int8 max_chars = 7; + Query query; + MYSQL_ROW row; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select config_value from login_config where config_name='max_characters_per_account'"); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + if (row[0]) + max_chars = atoi(row[0]); + } + //if nothing else return the default. + return max_chars; +} + +int16 LoginDatabase::GetAccountBonus(int32 acct_id) { + int32 bonus = 0; + int16 world_id = 0; + Query query; + MYSQL_ROW row; + Query query2; + MYSQL_ROW row2; + + //get the world ID for the character. TODO: Support multi server characters. + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "select server_id from login_characters where account_id=%i", acct_id); + + if (result2 && mysql_num_rows(result2) >= 1) { + row2 = mysql_fetch_row(result2); + if (row2[0]) + world_id = atoi(row2[0]); + } + + //pull all characters greater than the max level from the server + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT COUNT(id) FROM login_characters WHERE LEVEL >= (select world_max_level from login_worldstats where world_id=%i) AND account_id=%i", world_id, acct_id); + + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + if(row[0]) + bonus = atoi(row[0]); + } + return bonus; +} + +void LoginDatabase::UpdateWorldVersion(int32 world_id, char* version) { + Query query; + query.RunQuery2(Q_UPDATE, "update login_worldservers set login_version='%s' where id=%u", version, world_id); +} + +void LoginDatabase::UpdateAccountClientDataVersion(int32 account_id, int16 version) +{ + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE account SET last_client_version='%i' WHERE id = %u", version, account_id); +} + +//devn00b todo: finish this. +void LoginDatabase::SaveCharacterPicture(int32 account_id, int32 character_id, int32 server_id, int16 picture_size, uchar* picture) { + stringstream ss_hex; + stringstream ss_query; + ss_hex.flags(ios::hex); + for (int32 i = 0; i < picture_size; i++) + ss_hex << setfill('0') << setw(2) << (int32)picture[i]; + + ss_query << "INSERT INTO `ls_character_picture` (`server_id`, `account_id`, `character_id`, `picture`) VALUES (" << server_id << ", " << account_id << ", " << character_id << ", '" << ss_hex.str() << "') ON DUPLICATE KEY UPDATE `picture` = '" << ss_hex.str() << "'"; + + if (!dbLogin.Query(ss_query.str().c_str())) + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", dbLogin.GetError(), dbLogin.GetErrorMsg()); +} \ No newline at end of file diff --git a/source/LoginServer/LoginDatabase.h b/source/LoginServer/LoginDatabase.h new file mode 100644 index 0000000..84f8d7b --- /dev/null +++ b/source/LoginServer/LoginDatabase.h @@ -0,0 +1,95 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef EQ2LOGIN_EMU_DATABASE_H +#define EQ2LOGIN_EMU_DATABASE_H + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN + #include + #include +#endif +#include +#include +#include + +#include "../common/database.h" +#include "../common/DatabaseNew.h" +#include "../common/types.h" +#include "../common/MiscFunctions.h" +#include "../common/servertalk.h" +#include "../common/Mutex.h" +#include "PacketHeaders.h" +#include "LoginAccount.h" +#include "LWorld.h" +#include "../common/PacketStruct.h" + +using namespace std; +#pragma pack() +class LoginDatabase : public Database +{ +public: + void FixBugReport(); + void UpdateAccountIPAddress(int32 account_id, int32 address); + void UpdateWorldIPAddress(int32 world_id, int32 address); + void SaveBugReport(int32 world_id, char* category, char* subcategory, char* causes_crash, char* reproducible, char* summary, char* description, char* version, char* player, int32 account_id, char* spawn_name, int32 spawn_id, int32 zone_id); + LoginAccount* LoadAccount(const char* name, const char* password, bool attemptAccountCreation=true); + int32 GetAccountIDByName(const char* name); + int32 CheckServerAccount(char* name, char* passwd); + bool IsServerAccountDisabled(char* name); + bool IsIPBanned(char* ipaddr); + void GetServerAccounts(vector* server_list); + char* GetServerAccountName(int32 id); + bool VerifyDelete(int32 account_id, int32 character_id, const char* name); + void SetServerZoneDescriptions(int32 server_id, map zone_descriptions); + int32 GetServer(int32 accountID, int32 charID, string name); + void LoadCharacters(LoginAccount* acct, int16 version); + void CheckCharacterTimeStamps(LoginAccount* acct); + string GetCharacterName(int32 char_id , int32 server_id, int32 account_id); + void SaveCharacterColors(int32 char_id, char* type, EQ2_Color color); + void SaveCharacterFloats(int32 char_id, char* type, float float1, float float2, float float3, float multiplier=100.0f); + int16 GetAppearanceID(string name); + void DeactivateCharID(int32 server_id, int32 char_id, int32 exception_id); + int32 SaveCharacter(PacketStruct* create, LoginAccount* acct, int32 world_charid, int32 client_version); + void LoadAppearanceData(int32 char_id, PacketStruct* char_select_packet); + bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update, int32 server_id); + bool UpdateCharacterLevel(int32 account_id, int32 character_id, int8 in_level, int32 server_id); + bool UpdateCharacterRace(int32 account_id, int32 character_id, int16 in_racetype, int8 in_race, int32 server_id); + bool UpdateCharacterClass(int32 account_id, int32 character_id, int8 in_class, int32 server_id); + bool UpdateCharacterZone(int32 account_id, int32 character_id, int32 zone_id, int32 server_id); + bool UpdateCharacterGender(int32 account_id, int32 character_id, int8 in_gender, int32 server_id); + int32 GetRaceID(char* name); + void UpdateRaceID(char* name); + bool DeleteCharacter(int32 account_id, int32 character_id, int32 server_id); + void SaveClientLog(const char* type, const char* message, const char* player_name, int16 version); + bool CheckVersion(char* version); + void GetLatestTableVersions(LatestTableVersions* table_versions); + TableQuery* GetLatestTableQuery(int32 server_ip, char* name, int16 version); + bool VerifyDataTable(char* name); + sint16 GetDataVersion(char* name); + void SetZoneInformation(int32 server_id, int32 zone_id, int32 version, PacketStruct* packet); + string GetZoneDescription(char* name); + string GetColumnNames(char* name); + TableDataQuery* GetTableDataQuery(int32 server_ip, char* name, int16 version); + + void UpdateWorldServerStats( LWorld* world, sint32 status); + bool ResetWorldServerStatsConnectedTime( LWorld* world ); + void RemoveOldWorldServerStats(); + void ResetWorldStats(); + //devn00b temp + bool ConnectNewDatabase(); + void SetServerEquipmentAppearances(int32 server_id, map equip_updates); // JohnAdams: login appearances + int32 GetLoginCharacterIDFromWorldCharID(int32 server_id, int32 char_id); // JohnAdams: login appearances + void RemoveDeletedCharacterData(); + int8 GetMaxCharsSetting(); + int16 GetAccountBonus(int32 acct_id); + void UpdateWorldVersion(int32 world_id, char* version); + void UpdateAccountClientDataVersion(int32 account_id, int16 version); + void SaveCharacterPicture(int32 account_id, int32 character_id, int32 server_id, int16 picture_size, uchar* picture); + + DatabaseNew dbLogin; +}; +#endif \ No newline at end of file diff --git a/source/LoginServer/PacketHeaders.cpp b/source/LoginServer/PacketHeaders.cpp new file mode 100644 index 0000000..4511f6a --- /dev/null +++ b/source/LoginServer/PacketHeaders.cpp @@ -0,0 +1,88 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "PacketHeaders.h" +#include "../common/MiscFunctions.h" +#include "LoginDatabase.h" +#include "LWorld.h" + +extern LWorldList world_list; +extern LoginDatabase database; + +void LS_DeleteCharacterRequest::loadData(EQApplicationPacket* packet){ + InitializeLoadData(packet->pBuffer, packet->size); + LoadData(character_number); + LoadData(server_id); + LoadData(spacer); + LoadDataString(name); +} + +EQ2Packet* LS_CharSelectList::serialize(int16 version){ + Clear(); + AddData(num_characters); + AddData(char_data); + if (version <= 561) { + LS_CharListAccountInfoEarlyClient account_info; + account_info.account_id = account_id; + account_info.unknown1 = 0xFFFFFFFF; + account_info.unknown2 = 0; + account_info.maxchars = 7; //live has a max of 7 on gold accounts base. + account_info.unknown4 = 0; + AddData(account_info); + } + else { + LS_CharListAccountInfo account_info; + account_info.account_id = account_id; + account_info.unknown1 = 0xFFFFFFFF; + account_info.unknown2 = 0; + account_info.maxchars = database.GetMaxCharsSetting(); + account_info.vet_adv_bonus = database.GetAccountBonus(account_id); + account_info.vet_trade_bonus = 0; + account_info.unknown4 = 0; + for (int i = 0; i < 3; i++) + account_info.unknown5[i] = 0xFFFFFFFF; + account_info.unknown5[3] = 0; + + AddData(account_info); + } + return new EQ2Packet(OP_AllCharactersDescReplyMsg, getData(), getDataSize()); +} + +void LS_CharSelectList::addChar(uchar* data, int16 size){ + char_data.append((char*)data, size); +} + +void LS_CharSelectList::loadData(int32 account, vector charlist, int16 version){ + vector::iterator itr; + account_id = account; + num_characters = 0; + char_data = ""; + CharSelectProfile* character = 0; + for(itr = charlist.begin();itr != charlist.end();itr++){ + character = *itr; + int32 serverID = character->packet->getType_int32_ByName("server_id"); + if(character->deleted) { // workaround for old clients <= 561 that crash if you delete a char (Doesn't refresh the char panel correctly) + character->packet->setDataByName("name", "(deleted)"); + character->packet->setDataByName("charid", 0xFFFFFFFF); + character->packet->setDataByName("name", 0xFFFFFFFF); + character->packet->setDataByName("server_id", 0xFFFFFFFF); + character->packet->setDataByName("created_date", 0xFFFFFFFF); + character->packet->setDataByName("unknown1", 0xFFFFFFFF); + character->packet->setDataByName("unknown2", 0xFFFFFFFF); + character->packet->setDataByName("flags", 0xFF); + } + else if(serverID == 0 || !world_list.FindByID(serverID)) + continue; + num_characters++; + character->SaveData(version); + addChar(character->getData(), character->getDataSize()); + } +} + +void CharSelectProfile::SaveData(int16 in_version){ + Clear(); + AddData(*packet->serializeString()); +} diff --git a/source/LoginServer/PacketHeaders.h b/source/LoginServer/PacketHeaders.h new file mode 100644 index 0000000..69a30c5 --- /dev/null +++ b/source/LoginServer/PacketHeaders.h @@ -0,0 +1,61 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef __PACKET_HEADERS__ +#define __PACKET_HEADERS__ + +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/EQ2_Common_Structs.h" +#include "login_structs.h" +#include "../common/DataBuffer.h" +#include "../common/GlobalHeaders.h" +#include "../common/ConfigReader.h" +#include + +extern ConfigReader configReader; + +class CharSelectProfile : public DataBuffer{ +public: + CharSelectProfile(int16 version){ + deleted = false; + packet = configReader.getStruct("CharSelectProfile",version); + for(int8 i=0;i<24;i++){ + packet->setEquipmentByName("equip",0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,i); + } + } + + ~CharSelectProfile(){ + safe_delete(packet); + } + PacketStruct* packet; + + void SaveData(int16 in_version); + void Data(); + int16 size; + bool deleted; +}; + +class LS_CharSelectList : public DataBuffer { +public: + int8 num_characters; + int32 account_id; + + EQ2Packet* serialize(int16 version); + void addChar(uchar* data, int16 size); + string char_data; + void loadData(int32 account, vector charlist, int16 version); +}; + +class LS_DeleteCharacterRequest : public DataBuffer{ +public: + int32 character_number; + int32 server_id; + int32 spacer; + EQ2_16BitString name; + void loadData(EQApplicationPacket* packet); +}; +#endif \ No newline at end of file diff --git a/source/LoginServer/Web/LoginWeb.cpp b/source/LoginServer/Web/LoginWeb.cpp new file mode 100644 index 0000000..e1e26e3 --- /dev/null +++ b/source/LoginServer/Web/LoginWeb.cpp @@ -0,0 +1,64 @@ +#include "../net.h" +#include "../LWorld.h" + +#include +#include +#include + +extern ClientList client_list; +extern LWorldList world_list; +extern NetConnection net; + +void NetConnection::Web_loginhandle_status(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree pt; + + pt.put("web_status", "online"); + pt.put("login_status", net.login_running ? "online" : "offline"); + pt.put("login_uptime", (getCurrentTimestamp() - net.login_uptime)); + auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - net.login_uptime)); + std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds)); + pt.put("login_uptime_string", uptime_str); + pt.put("world_count", world_list.GetCount(ConType::World)); + pt.put("client_count", net.numclients); + + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void NetConnection::Web_loginhandle_worlds(const http::request& req, http::response& res) { + world_list.PopulateWorldList(res); +} + +void LWorldList::PopulateWorldList(http::response& res) { + + struct in_addr in; + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree maintree; + + std::ostringstream oss; + + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + in.s_addr = world->GetIP(); + if (world->GetType() == World) { + + boost::property_tree::ptree pt; + pt.put("id", world->GetID()); + pt.put("world_name", world->GetName()); + pt.put("status", (world->GetStatus() == 1) ? "online" : "offline"); + pt.put("ip_addr", inet_ntoa(in)); + maintree.add_child("WorldServer", pt); + } + } + + boost::property_tree::write_json(oss, maintree); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + diff --git a/source/LoginServer/Web/LoginWeb.o b/source/LoginServer/Web/LoginWeb.o new file mode 100644 index 0000000000000000000000000000000000000000..29c26ff7723387c6e6c2133712dfbe0480bc828a GIT binary patch literal 5302112 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
R5X8$JREVSB0QP~y%@5QM9Q_L<-vj?Wac*3o#3 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

i>MrNC8@%c5!MHKVpew4BmS-YutHWdp&6knr? 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 z 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

CG5$B&Sdax1UeHAR*qHYS!# ziEukA0s{d4Z-uV4yRDfsE_@}q)q$mb=_<7-JM08Sv-3vcZq#iAN`*TkwSs!_N0lSup7rC37 zD^p2L>WS0RWuw!An6z_2S5Cooly3~gxCT!Q=mb{fuy&c!c*AW;Y9t+e4J@LA>cuT^4WgIG2y zf~BRQvaSx4$rG$J zXV)Mg_1e%r5K(_itk#-FQB`@%_oiq)S-1J3ia9vAOG=f92NCs!-&dl~weei_+@2#8 zA^T2}5w}EuWe%U-2)#J1rB2H?&u4U(dVXCKSFP<4fGW~_w?UsZ@qZ)VgtUbs0dQagGd+< zqqJ)ro=i2e#7D0ZgslNkhWHwg8PWaZvi=SM0%_{08ou@ZPH^ADRfI&XW%$A(7o}Zpd_wfF4W*RQ3@I%a}f~p&i6s)cwK{fP? z0y^mG6KyZTR?z`Ygu}F3ymmc2==RgxNJhIsw%A>a5 zC;ArU-Jn~hP2EwmJClJAcnw9dn{ zR);R3rVu{-DAxS@(iv15*qMa_c`TBChAbuxY0uuz$dSYhZOU7tjK)E(KgfS*+OU2@ zZbcC27AX~_wOlB+eMr!nDGyu3Ve%?4Eaz6ksb$4Z7|}sZXm}u0iW+V$1INkN=|XI@ zhWESPY_4DBjkjsVLYTTxKZhlBG`fYL-6JOi>?@+OMsF}507}L}_ea-##neR~U%Jif z4F_{l>AhgyFOBz~i$E?CcY{E;{Tvsl(=jI9K7_(&=#{FHxIv<<_bv~E-^36B61^ju z<}9)Mg55ReDit$O!8EC|_=TMr@48k$x-eJ+KD+Liyd^?)ziKC)k;r;0Ysg}q4D2ZC z8L&nd_oXlU(s=2sn5OqiDLcc~k3W3R|H8)c5nty1SgAkl60f`y4~D+2DY04s9;bPD z2n9bxsu%ak5Z$fc`E4)1=`UFL3l=UcH#m8)Wu@KVh1AkHJgl}1>)sx%bUYIl{yiEg z7-JPo3**R5U{W6e0WHgPUoZ;Es8u4}wG5HS8ackqfH7YQXgH|YHk;CA2=KSN;@Jm; z&>ml>CdU)qrr+6fZbv!__B6iSY94RX_t%Flq|`!;-T_~4z%dfPBpB5JzB^{dH|l%4 zXor{hO0OWSsHa21qU~90`kAvUCqSo0LyH!J7;{mF_-3I#I!fN(pgfQB10)@T7NHZ= zEpjBEsE|w<)D}QAVkud ze|eQ8SHn4bK)Ks_79dJM9-yhIMc7cFWIvaVe}Ss!CC09Efw5{5a&WOG zcRk0=+I9Fw!%Z?vSo$B+5)T*-XdZNGZ=Nd%B>2h{R!vrJ-lU=t&-yjHS; z7|n$*;_3c5=x~A(;g5R5PqBa=31+o+Ft?J4v_h^2oMr)??ja_)c3SuLN-+CEreNsM zi(@n%L|gnm{RDP#BiWaq@mcH-$T#fW%=?&_Azub@hl}pS4K2gN)N^}$bChd-6LYlF znxaFG$qz=25zU@Ylli2FtoF+cLhhoh3W%XJ5i63UOL3rmBU$P855FvDYWRiL*H|y< z+@VP~u^}D`n~L?5yxKmm8#qbHLXvms_o?IIt>|%{l%pQPKSYibePa zy+eXsQR-PsJOk519V$1Fek)keLh|*u=7xP5D&j%N9%wIQbQC%^@P7UBMzltGgE~3K z@R(5@pcFfLFaR7Q$U|>S5~Nf?OcUe-o}NYvZl#oZ>rIvnln61mGaW=Z#IOsHRS=#) z_sz#B9MY$59!6*HA0Lj60UmO)2Yt~S3^1R-8n=5i#{DkuD4MTpRG3R@%_*eFReIK) zdr7gsr}3kd2W2ya{mT2lbZ5=Wb4WKVYSs!0E?C#u5Ig9D(Y7txEuOwtj6jqf%0h%O zYP17ifT6XWWHv<+bT+}(gp%sIGSEUUDDKQ}u z5-}L|J^;_T-yy=LYgK`9l8hJC6Y&i^6;(eLH6lOxx76NKDvRI$z!)W0I<%O76Dwd* ziIl4J%*vVt%BovC6SfM(?J6Qc3z_VA+Cx65+n2SU$DpiyUqwz(q|}>D;r7`fVi9w| z99c#a+=1I*Lw}JP1aT@=R^S^pBnv`qL3aUUS!BSEu=_8;%?rj-r<#Kp9l?wLT)V+c7Cd14YLgBcR1>Z z`&cth5Hxzqm{&F0@^%T+qGpj?jq3ob0GbT|uHC|m0)@-knV0g(~YLv3Yi5sq{ zJxk{(53J%|TGF{{$b~4N-@Tj-MpHgbRV8?JhvGggN2nz%U4=YA$>8%0;fj>wNR~M0lSjY#;HYBpMJxplPII7^w1%~~5cva##Ea5IpI~Fg% zyM?!5iC|JNqGoj|;RK6liw`$-Dmpxv!9~7IkE!D-mYUK??z*kdWh4?Fh6Y{3QVsT( zBM4ml1r1=xm8y#bHY)U`5p+)pRY6Tl?xE3or60lH)mLv*k_UmILNwUFV|MdvtyTg9_Z>8NU1b0{~g1uKI1 zF9@p})dm0Lj0?~SgV_w>L;6pgTPS*F8!Q%5WnM0-hYK7szw+ox0eN96V=&^5PRuII z-xVpc&=8yTO+UJhc2N45xG6oHA})eTK{_Op<&6Zozyp94;_0{ybx)*|{pwp@s-ewU zSFLs%m_`(FKE);e2O*lLsaCyCmD~xK5(&I%9N+dCO z(O$pb+-KdqT-Wl;Abk3X2N(u)!Mv`a zfMDCO)BU1=j9+dDtziwNvD#dQ26oubIT4aJmmPX60C_FuxDW=;QC6JrrJjP2TxGLE zy$nz@B9V^#*Z_?+ps-*<#%6!Hb;XvH3vFUxHD~^2X*8Q137^tK?V1kbFDw)Nqt!WL z+5*zeWp9GKntw=T;S7)s{;HFvzm6iTc;vFvJAivJ{P|W=IdToB@+rYk5k(71un$bF zFz^G;MW$M0(GN+z(#^E~;+0t%`(l>^qqFh2%V;b0`q=+_iJ-QCuh(6}ib4SQ95{@8 zd52PVN4u)0jH$$GTKfcG;nUOt2I6u)#2gw(3%-Nzbg_H@neGh`yUR2ah=U)))8$G5 z7o*NSb}ZZgt+2e|4;=uqB<<2&2jc&K_TF^4jVsv}#-r$$qurq!!2&>V2z`!_rLqlc zAT6rhK7Hdx1qc*LP?#D3N%8#fvwv%4?%cI&0s)Yehx}mv#_%2 zhPISjP%BmaQu3O)wh65uhMF*xOFCAMtm0T=v)XBXvPo*h1+Hq2j;1IN0s*=>I*b>ocSa*bYyo324DhK&9UL*U=ivdISOyQ@9e`%S!PTyD$C3_ zCkAZZ_b6ae{?xj#?X2%;jR$9k^$4Nf$vHOZKw-GjU5|)4^#;8e^t`(xK*p5u>Lrf0 zbqB0yxI)NnqGo$BQbZloM~bioTp46H2)+lYOUOMWquyycN&x*JY{yOjq;>%iXON7q zPz+DB<1|QaqBdR-$N^&wfgH4@UWU_^O0iU!xHGI9LzhA({|GG2Pv&ymXg3cXDI9A| z>wGYyQ5FoJ$eG?0LzNpi$~_4U#r-lArCu7M)G?xX&Q;equ@oz(fGa2)mwuk%Z4*S{ z4r#YaZlO%Z+eq%)ZC|Tvhpg;iOp{R!C|>ldu8Z@$SA_ zavforw~E}h8}el!FQCru;?;CCo&Vk&U8N*x>toESlhr=YqN*?A7RXsUS;`nq1Pm}L zJ$S6fFA)2|%&jTFS*NQb!_|2Hcew;a6`!@h@^T4y{S*dSFI&Qm&9CXPIB<$b|IEpn z3f%@vqykZJ*BwG-(fuaf#dK;w3Qxc-&WLieTUuYl8Vfq4G^6}G%kg4yF`X};h4P%w zpGEDRo#)RE@W;2nc?vhT|3Jvtu2zxl!5|khm}H$ua1(x(%w$Ymm@6eX;b%#G2kY*k z=~;hDss8*5!v?YFu&xo%aBL^Wc@_+gUGUn);9`Z-In^~99JVZ`;Wnd-g!Mk@BH4sc z8KcTVstfXqvnfdv%D!w2&m@eHyU(FU^C4p*ZcnFmo0+C@%vmqk$n%s;nQVfK^B`94 z!oeZtYGoRIG0-t~ZLH=T^sLx5eU7-zx0_3?GAoX*<5KPuhH1N6X3W-REeOlHiu&Mq z-WB*nKVBg>&?i7uf-8i-=PLauk~93$LV%#x4*-6Fs(gT8ABQ99s2sGrrt&Pk-=O^0 zCaQz-6j~^Zv-Lq;M!2?|{+4{TT&`PAVQ>4I*y65ad!A@3F4e&j+h7`CbFPS!g#^k( z1U4F*3>zl|My<@d)r}LF33m+%R(cMmv})GL(|%z>B+sAypMz$z)!J+Bv^x8{?Y+I- z{hfVS{?ae^#MrxCElw*Ob`iC-$O8+(hZOBjCkZI3pGYN`Lkc%r6K)w=J*rO?aOkH%S^R3XK-TPy$R2ZvhdX13-;N z`!&x2yFIgITtp9Qft6DcD!6ovvyJOB`!Jf!SBAszETC1>y#_@Y@Le0KIXl%cdxiW&MR^x3BL}4@mirQq4p)F~7u~qp*?nMhU+JJ!|Kxd4aBAT7J zVBpCXJvcfIHA+}tuL|p&rbsnrN4@M=(Ikr|Y9z(EXX z+Iq{i{AIVh1093ebl#hzX$=2M(?%`B=I<<;-x%E&yb8_?U< z0lhSf%w!OgUqh{Ic>&pHRmHX}v$(GA7rAaJyMgE)+vBgL?TQmtWGv6(irOb4F!2^* znG728jIQJB%TpNy>z3$N7%GBT;R#*j7Vib3Uo2kDzR=hH1~p`TxSN*F^$_i3%;9MW z9Ovm^IETC)iq{RqfBmaDxuv(LFKGvF;Rg6AxPp-8uV3fZ+5WULxN8)T+Iav38Bd-R zjv&sAMSh|--dLi7vnIA~$8S+q8D+F25y@wD5`Sjsi{8?_hK9}Rt|H}pY$16H+ zL_~h*;oY+0r>?F0H(6}aj#{v{C_T1blF#VS65cl0XYd;>F96XcJE7@JsUXzd0bzeZ++> ztUP}I?El^jmlx0cN1pwE&%}ghNdo(5e)xZfhc8CG@#&yfI9A2iSACvhb3j7rVE_KT ztA(nQEW>a6q&qU_(t`d$0A>y+m#RaF9Rb2Y0gQurAZ-&-6c{K(7qU;{P-w|hIv*Ml z(gk(p}M7wL#|yxD;cj7S?Amb{>imxzt4ria5K)0>tn zMC>Vz;e0ebMbyWfL2q0;<1}cGI5Gp}=xSAr=7+jKlXP*_zYrJdzo48%H07XQKZ&TA zS5+Q=I}^7jhj0P zs*4Pb6NQZ~ksH;f$&ZkGgjwL}UqQ@1u_vI?w`x2F>Jnl^Z z9alYogbDlHTr!-6lFoeP)HJmh4wi2S@2v>A<&joFsC>Vva4MISMX8l_P8&|t|VJpvf0$-J>0kYI_&mTj4kisLtca2 zq{n+G`e^FJa-X(0qORcPJgp>XmS!7Rg3@dS$x_W;ecIlbyOCpbW%tJ2xktCT_Hds4 z@ZERr%d%h-{yI5@5NW#jPRU^vF~l&g?XeyXt0IVs;Pu-ZoP+UGia)IE7+tgay|Rh^ zt^#O(n`kv2549M|1S~nh2p`tG*bjp`!yTnBMZv7}RYV`&8?s$fmp&e zG&a`e;3ZPbEBb5ETw2#(iznznk^&(U%_!M?+lpS4{s`GS62?V;KAZvFm!evRY~n0( z$Z9u)ZZAV6GAb=-hgxrR)4K(0``=f+k>-cN@k&i9_aH{NymK{}0ybMgSrciA)k(7x zYvMvd8JFROj^`jX3*^hg82BzJgpEI0yQ9G9^i1e$HMwuri<+tY?uo43^cFXSt5Q@= z8?3`3JJ#IFCko;y9z13W7*G%X1tC`M%aRU-Ko!DwTso6W;>2C7-%Uf!AOo-{txVPM zCf~oMg0Vlv@T{>sHlbi|A4AO8EV1B;|1ZH zL!ta!q^%q`Mqb&?P#MQ`#5Mhn@8+LuYl5<7T~=ydqNAV2D>&_}FZ=ExXduSqj4^p&V zuNMjFyn-oNQYKH$!UsO)Bx*ZZR+joc_RiyD=&|Bhjudf6fqNGP?il!CU5gbi6Of{mi2-xl@e1KtfMwC9=z-1jq zSI~ok78X(%O++oo0e2VHz!|fUjq~C+b**(QetnNn$yTAOFPbCM8mL;5ZmCDBvG4Xpd^y;+ISs%HAKV6&93t}?vx5|%IWFJ0 zt|TBEEE?SCunkTv`fNTO`vM3bmetjBycZGmTW62k-n-OA1!fDiby0HRiUXg7!vCbl zSX}rDlBLrdJQrwsvGcln7BkIS{BDgC3QJR%+UbWq`@j4D?}vs?GO?lpip|rkm6Wui zIc!;a<*N?)H>~P0MhO)|$7kr#UCwV^e4XJsu11nS+{1mJtJ#K}@_nO`dlhO~F=Xyl z*a%68^K&>+g6*i>W#}W=RqSV=plBQXOA!QZh}@#u?)!ed1Iq=h#=YR>psPjoFkBd7 zi)R}%P_bX~F>96D2o|G7a-jxCe}p(dVI(SnS|F1n(#osglw zuYL2?i!g6?dk5;mg_D?HCNKaRcK~vLtwCh~8 z!Cvw3147FtTtfzhiHc=PTnnmituuoleOCdMXY*6^9;jqF-qE9vH#Q z%0m3xb2X7#e}7Ea)I~>68>N1uU>KacOFAzP1q#Wj7`^t9!yq(xGRSuoRiD-wXbYv> zri@fw6sC4t^O_}E+Nn35#Qiw1z~uKdU{`LfkK#G~sp;oty;nC=iA z0LaR!MQNZYVe`gpKD-{{p1T`mhYHJk1_KrJv6@M9e-*k-P%neG1(ZhxEDl9$qox3K zFZ)6|0}6Es{ac)tJDI3r2ITndxE;s+>#-utxpL1z1*mV8l{TASI#!%?N-P!3&}SWi1}gm{Qf3NFC_fIR!ny*i4>R^ouDZ=_`*Jay~c~;+Rj~@YF0}P!0k7OLfI+V6K{<^?H{{ zP6W!=Qsu=yJ0c|SNfo5csjZL_`omj7?A;@;_upZ+;wWtn2U_2OM2TNv>lbVW zq^%~;0^?)>)L_;wo0V~9FJCx)dD!~rtofXh`v0(*A3pwoOz6!VFSD1tq z$$6=W7)*t!2~c{STGC2rA4l;}YF?ztAUql1ouPfoJfMl~{Pp@|_kyU@ZikAwTwE;& z(;HY5Sv0CdJ(YoB`|l{V{jN8oW}ot`?%0J^K{xtE3mYK5Y%HfYeNn(^jCvDDcbR{WPJePW%WCl?_RYX?09(`f>|JYh^horgm#TwnX5&N1A^xIt9K#qM|fiNJ&u zG}Y&0*2jI5MA3NO3Nx5)20DGt5j7=*x|oh&KNhW|xhF5!H+k8gZ;#5D7osVJlr0G; z4ON73-AV4jzg!iFZlYlY5~gygh0|S#RvE?17+;l#)Jw`W>vurEMh2C_QJ;foF~7C` zh~T{#iUj#q%cC%^VA1yi^b$FNuwl1FH>_hRHek|*;90;s>)Ozr$x-8L@6NjU4)|gi z>WZ`w9BZst4U6<5oZg9DKnH<}$?^yZq}=9{S9|g%Wiwh=QKJ{98|X`Rq4g0Xo#?{S zOLj?0^nE7CI?=)^L$H=93haL(_QZi8S(UQ-PjHBVdYrbGz+{G;+9UlJa<_&yhwXTn&0moVxMz z+WzyASg(v_`p4zZcwc{f*eQiEs7*GqpOL?44~A2T2ks} z6Et^v*S#SID`M&t$S=UZZ)K{|yi#taxBJbrc8!l5|Lp#7spxIW^>sHP zb&r}OM#?%`l<(U<@;LPudZ;4Q6I3fOLAhMY#grcY+MkkYLlTeq;J6|cVpF|%m%~I; z6nQI_u`9OkcNd)IVH0wqCMuzr7W)C)C7th65&Ng%CbNu)s(qka_}U@ z+H>uC_yt;@=Qwc`ojl7$Al=OiACDz1Z9SEu;KQi{BxEgns5uMuRQ?n8#65qugTgY) z$k-IX!ZF21hGIBche?gtHVPoL4GnFkQX^k>(VGm=O?LM|oaf)hwpXVqIr)_PT#qK{ z#ig~zQZUhhHbt#=GOce|mQ9w0jK3c7yKe#Lq^?Kn(`N=AV05GqeoGAxuV766%q~= z?mC`N&)`D%ps0Ky{%(%Cc$)MtQjBIcnG_H#($9U=iNIXpuH#6z;GYuvg*1h*h?MWm zcWZ4KQZ;an=p)Z@n%#f-h>xw z#GCL+eZ0BNJ!AB|Qt1n2R{WUKmlRj!MpG6`|LlXdugp^AUaVNeHBSyw5M$_j1Th^X zH@$eFsU6I0}lMB zJmqny?8zkGV92Qjc_82M(Ewkzk5Xq5WnZvXS=D8f5d4EE)qB??xkz0z=KiV`to9>vfZ1wT8}FT#0-frLhb z<*0@6C%6m#bh2!M_R2zs_00L!Plz41oq`%3SXx$!xJ49jHzWTvhWr zYVJ`lXEaJj3DN?@goG9b4upWDtVp2Vh^P+V#3yo+U`kpak`h2Gz!yUwCH{%ObBJ)m z4Mhv$PI2Z9~ReWE!Kr9L?d6ggt zRG}h1BjXm!Jo+7zNn3$_#+m0GMHWg z>@Ur)(;@~xSYcE8RTD?~&MceVdF@9DTN66-Oq zNaWw*QNX5u4680#5)gGvET6J`z&(ZE8SSY0G=J9u*LO4;F48_bdxoE-Unt4OcmZsN z`=*z+NVU=vTQy}J`s@ZJl}dujAkjv*pbMsL87TJSjPp6Rek8lYc{^|3jA2g(u&$jlt+Er#9rZb-CaVCOZ?kqsA^X$r4nmOS?psgb8WGo@InoKfL9(GKe+eo z$CIPP5kUiFBMcOS` zt`oBd%bj+WaMH^0AGyVo>uOxL+G@-C5|0g03DxGV3)D?2092#4cnqf?PzW zeIC&sVX9n{2V}nYZB}E^3T@+*vektF#-X#$g$BHF(uR)Pm#a)7L&XtT*pYR|_p#Uz zHOXZdo?k4-Q-qUedLRm!zedRA4Phip)IBIw>p&^11P<9t57QH$kL-mD5F_Mv8VURq;*fI;ATpM1@!0@Laa-wr3usJ++TZ*|)H@!O&Q#~zBuvU}kNCl-Yk zp{ic>RGi?^6mPVmI=hI^LXjO@S@D^16n3{_c8(^64pFSAXLrs}F*H8VstC_gvbEbV zw+OlR**?gEl_rO8Oq!v&ivZ)*#c(tr6Xu^7_x*B-Xnd9`$G4kJ-I2^I?K8v|j<5ZaaLgIYacR-Te>j72S3w>zjNTV#PofR7;>eSL>((3W!c|h!HQz1YS`N=B6fpy<`sI<9GASBYQzAwIqN&q! z``{?@x%j1mobR>9G>BK^n6)mriacNdG#xZ9##qrU{gwwMs=3t%tVuP^M{AAnc0asrbN<7LJAU=7QQ01bQ)QnA_ z7pOTg_r?o|nQnUp)GcxBO zCAygYH1!G&IIa2B0twLJVa_($UJqVBz0gU<_R=Y}gfXuRG@ipll5J`*ht-ZQ}XNYQAIaEvpI zG)pr%;^1xnx!26644K?XZ^?4b&3X?W~vK8m?pxjzScBs{Q%n4^2m5>KcXwNK~?m{@`b|fWfL?%72!4-tQTsj z{;sUv?U?+o@XbJT&xJv-<;JY4qP;aCx0fiXE!uIe_fcGb#xbdCMD%Vy`|dTWj~SPM zQMLK6B?IC|Zr0+}@F(mBr8yBXJ$rxsyuA%Y%2h0`1OQL6X?QRJe86A^#zn*qD0 z5>9i=)MXg~Ij)pXR1*a0>T}JYRe29STA%lXJ!DxtZAzZ4`{p1IB(|pNggaE$R)99% zMS(aU*Sc&21ZrOj5hit?DseDxu0|p;D$3(-k!_O*T*hxt!Slu+JpXt4C1mrQUa>uF zffglI0byrS?-ateA^Eim6>0*XHyU^#peA_^PF}i@AhU^_MmmjT4OQFS+*HkNu8n=s zmc2+{gAlgJ(uKJxJ9n7Ak@Kts3v$CGZ8$>~=}Ckjb;>Yuuo;m&mbmW5&m1-Te!7I& zZD{Y8s{M+;A*pX3HSwO4WpBRZKLsbnY3rO~Y%sk!S{B(wd1A4Ec)0YLRCtTZq6f0L z_#dNwA5i1nBaB}{9u^)!TG4Q(lHfM7NCWArMj3L8g8tj^F1Wi`zQtd5A}1c2gobo^ zzmvHQU1%31upC0wt$fyn{fyKK6h=+D7(mR}{>dBH zSX@DMdy#^U#@m)lO0!}y_X)^G8*to|WXi=joC8N+l0f+Q;2kl5I5=+mf@RHmXZ+mk0PmKsf7G+b2VtJ#BOo zM$cwAlX)jNrOmg3cU*wCuyD-mJQeCu&(3bGnFIQ1hF#8Iw?(rWN)j6N+_9Fnlz*Si zRNU7&=!zoHet$ZA0iqfk1epQps**I{rN+$` zL}?)Hf##94e+3t7hChnj3ydQr{eA7mA64FHX@i4>%ID3)xG9})m|gD`*}v8K+_u*# zPfmbB$8&FPWMQuRjpH}35c0nK`0+z>(*5~8hfrzI@npe;0{eBSRvO~Kej^M7BfS(o z>12`2Np285fw1gC%(`qDtrS8DVr<+!V}RVllp1sp@jP^6W+`;k5*>mCfq1P~-K&`Q zK#BDAdo1d_Uiz5M@tQjZ^krCU!MQl!K^adjm#Ruzi*AMWSK?p>U@-=X(bpbOUSQO_P3K)u?_#`1l0x*ap-~bh_ViW& zj{~2M%RgJF6m;LW@f4Hhq!)voxZMv=M83~cJhS01uN@eqA3KKnde+tE>An6)#O=F~fwPhRH z&bQRu+Tw>Bdxh=%mA3PzqV3%3o0zc3S9ve?GaZe&m7jBNg-paPu13CtOPHMZeVYJD zOe&tR;oW(!o%wQUK<^qO^S>+NT?7t?m=~~?p&9gI`xSCU@Za#koKu`YjLj%pF zgWk0?-~KO}sXb0*AUg)iDZ9Yw^xOx?5Xy|$2rMfu#9FpLFj*`?W&7jrd~Y?`zn<&Q zSE{N0>i`Iu)v6i;gwqE^{wYl?@_Q62gsDrq&S*$$2xX&^zu>f+wm%Q!(<1=|G>-){ zTsAYCqyW3EyfXm2sDYe8EY}a_5v0R)=3{lYU_w^a#=6vI}Ev{`&o&$=kL5(@}hX+~% z->y}}oW$`ZCJ0|DAHA6AG@GTtx9XA9UI(>sgXNB|r51{gm)ShMR@NBUg!29!z2;W&e>kU>@G;GMu9yN^B+<~g9%)CqA}|2EYj*y6Flf= zOADSLoUaSvjIo4&CxioDtl&7X0xXERDQRE_BQlv>B@oj)JfwWgd<;YD1(99IuA_Wy z4lINAd4f#(Dm8bPf5WQD>tMoVgV!-OSjsc&JbQ;)4qR}?&`%SKlLu_y33YA zzC&Hu1F|yfZ?U_d{Gdfqa*)z0bBESKL&ms8Ov5Vu0#AN87=js0P6PHHDh%AA{9Et* z>7hZvYq@x_^3J-O^mq^LyKG9$4+u=pdKj}H^O<1=)$`h(MyQ%2ahG}2um?u!P)#gy zBoQNAEF0q)x|BfNMYv_h8_%NX@DMMM9<55;IlwmXt={}f<9Ai=qwEW5? z4x)j9FuFo7Wf>owrOWmWLnaUHiC?+#{!Sb_%KoibhYaO z{NCwbL0M{yjFZKIv^TEm7bl(fI_y>+O6qd*$9oJtXAuqk?E}mqXe@h+Po)Y@K)l(Q z4Y@`EM-sFQ6fLxK8Gv?4fD-qa01bc+X06i5vq>)@R_S#Qx~{7u8CnnvH|mW#Tz0tE zLqUMhJb6rmOslxwO0~ZNJf}4c2$;KZBTi$Kc=}ECWsF$xKlxty8&mh=3RU9Mh8V@&U5dy{x^8Hl- zC(k^KIDu*z(U|;8EIa9K(sSZi=@6&O`dL&J@9@w=r83dh#AW+>hUmho4s;1>CJDw5?%lm<@}9ZcD~Ku z<3ti$Wps0MclN2O+w8pjR(F}Zdi~RL*?G3*z8$x=p0*yKa`ns|(&R@eq|{JU1YMic zz@+%cR3u%}Hv*%@zy%E17`V^egRSir-|AX2$aJE~A?q7|g=z6UDKJQ79Tr@~O)8RK zVvZm1u>dsm0(zx30FJD(A`G=}U@BPnhT1&uqU-pR_)gbxRs9SX3aAL5EZ5JZh|Z@J zm_QLESGb5}{j0OAmUn4pAAKn#!lOf1AwnWl6)g~ekx(V>Uyt=&IS29}SgGi-$-Jnf zcl8;!YK06xZeJr{46=}+>$CK<)3S_o;8vQBbFbZ2Nv}_BYuCM9GbLutwyP{q*{mtG zu2**WxdbEI17s+mYrneN-|Q@UU6OQq#v4@~BW}?6b=)WE1YSbzfin(@x>OO}iavJB zGOLk5jrE-uS6Mmdr9h6e5iuAyfeVqqITsY&+d^++3ZfQkdLBc-X}w8r$vVgXywXt~ z+0(mAILhu7YY)PcbQWgafn{O-S7`Hao^xyZrF4t9gZQg;)?jI6*g9Ok^X2Z|v zNJ_7PxiQu!s>V*5${8M``_qth`pi90DxltM6Ek;+t-{+%e>ElwtN%;{=LmQqg`eRFrUZ3mk%c zNQ{w!vKj{AEKvW&zt}Z|6TbccS#XyXF}NN{OdD*W5>QclgK}3Uk{?;JlrO>thywB! z7S43e7|7Ag?tM6w2`I=dK|m)hZ~x0VEYwD@!QOxSUT{CPW*kY36yE*g&da&m8WaX| z4U5OGG?{oWnU9zkvTLF%I1ZQG#;Dfm4~MuxGvK=Mr}H`J5Zt7G7Y<|iy8#uh z+Ln8*hy0LFHhcMFMo{RQRE-H&AE(i)-GE2 zSHwIBP`s67Y8(bO%$k4XnlWb75vT`Qdce5V&w#|YbrmZhGdOtCGIke1JTCt-(XlYpuVOupt25DGR14{48R$y4{C4C#SohwAX%-G}<5g!k}I zMi+m02E+$PgTOA9E`v5K7Icw^9q3zSoExuZyF2+v&npRM#b$uki7jnZ%0vsLv>&z( zx&%h1@8;YyjZxQ$IuN*G#J%Eb3}17&{V{CDxZ%;$8S_&&-`WVE$M(4WAH;>dx?K>gxd;;L%x5~Y?|2gZ;A`Q^Po3-Fx zdw3|#YrYs)3}1TABw=Zt0Int`k=5v0LuDq5^jPPd$xauDK%6C@!mF+M$vum3Qp0SDwvYiZgj@%Z3n2*EpR61CoQT-Rix^uEq7!zGFX zs#<{{sxTn#v#K!Ua<>hH?-W(XVS$f}EH;ra&iTg z7H*Tsbc5-C8o=)|SbrsWFOjqtpv zLAAJmZrQVN@jQQ~3P$gG6SNF?+L8u1rFWO(qRax7k8QP~{5J@|Q>99pN7jNp6rQgg z(^YsJ;Nm4YwmF8bWd|^_fFiJ5goGcwSo~9Ax;_2tV5r=GH$tr*Xnof=` z^w(~UYc5f@l6~R0y3ZGfIs!t}_+>i%^s#roXatC2F-9qj1hifJm;+TawZBIxpOSsD@<(8Qz`vQ~Wur?m zUx-K)XKp7i+5)KO%GSBsN_H3w$hj^RPzVPWm;+4miyGI)?9)?n)PVq`ZAo{KLFL#u znC5JH()icT`aq#72LwH29`|)UXONNvgSMR)iH>&w;zY@wlZB4oUbb+{v`G!%MEt7B zR$csPPZ9Lok3J&umWP;mp!_qj?3-05dlc+_httT_pg{HWx6th&vj6xdXheM;j^PIZ z(4y1T){5|Z2*Kk7@KN>$c00MtE82+G z;^Cns27mkB^ts*ZL8aF>9h6`CUTyc`;fqmkd^+g;jXAZJ+P-HDgJP>6=ER5KklF&o zg9@LNm64NNY-Nk3ajhU<=$V;7h59g=#Wk?aUC!1A_^aD-KK$Ac4HnF%l!Xf8tE`l} zT-E_;z<8*G3@R>C-bUbb&lbbcbmESQCAn_d-`DP0rPw$SME60A3X@b4P&YseE_u}w zPSkUzH?OboNs^QBpv{4QCxh%UFT1Ji1%>b%OW^Nl zw=}n!Z(-%gcN9D@^u79Zr%#5|2J9P1c{l_=e-QU3na0CahMr9K0SN_z_|k9ob>?jK zh1}t?a?4`3@`i|AC59v^j@g3ORlkOO&&@?|N&g=MaTLJ?M?pInP=T9mxo!Rp{yq?6 zm0!UEbJpEC6db0gi%yU@gElbe&Gm(r9Kck67=MB4wwPfy3R>=QS)m$&+n7G`$RgPZ zICY?qa_)LDr2Q7;-!y!MG(ZDQzF=YjIEHSu@0xRQRE1>e-n@4s0+b@nRm)64WL>4& zB_VeWGX)>1XQphkerQ#Sk)YBqq>Z_I@;c#_VrO70-8@r{OOVdwRRrmNO25#)i6F2x1q^ zlCrDS>p(|C_Q<4TYyIgc3WHeMjYEevp!+$jvVX3!_dJ~|h!;>b29!wA``z4YvT~zt zQIhRKuox!#+NB4vGrk>D2>$HCa}^(AK1toEh;M1^B_RfG8np1dL1tLaX|bu$HQB{$ zvJSb&G@pepU7gQjjjc7(RVOQ&KCmG-m#f@)zRl45Mh4?K+)kKwDvBV`LlNG?wmWcO z-`O-SW=;X$cQ-+Q?gHt~?;y+)(2<0>58`aSoh55fwV?q^E$39Y*{vr6?$()Gyij9q z@ybJ++twZS7D6=mF?%bDlCmnOQR)61gIEZ~7Vv{Ttn#3FmmaM$nI zD%{K#DcE8Wv`uaDOTU_ZV`gbruq1tG;;6Q0EQ^}dTs^g!8|?f zjo3>Q*Q%2p9iyXQ!9gh>(go;Wgk^&!j2EwfT`JmhJfEHcAT`Ewmcf_blJP!#w+3^y zZZ3pklt6znJiEKl>-tfw4Y{&1)L>cf>__+Swwzf|1dbb5s_63y$Q+2ViSL&uwo3PGyj zA~l_b`~BXm*N4&$_Rt@@I|s>!_%{gK{&a-MF~;ex>usEyq_irbgXjrhF1OvX z^WH1jDzicvvX*U5tAY7F3VT)FrL1~cc<6j2zuM0$A1D2b;Rw^8Ck>Pz&Y#JD!csr) z4Yq!6a>Y%I*0eF3w_wn2kg+&CyTskSh<8B(o-O231NXaDqh-{gSw>4Fx*XhIzO&~N z=aTET%PBiIL-2(G-?BHHP*ByZ+^T0*xCOpH`A$d#TtEESGazi>)Rs3?cYZ^`$t`~T z_j&Xv(~gIm?o!=3E~-0k^-Y5E5TNtw&QQxd@aoP#<^Z1)o=c^>d@QoOGi7@R11`ucntN)AmxX;c}ZFG{_ zl!G-b8)SvjTa+?YumZswxYo1y}BZU#uY+dl1AKrNF-#*?}Nk1yIvc z0I7FWwh0S!$<>5Pz3KI#ga%j%GZ^IN7_^Krla)ZGuTXjShyY^)ni8?)obLu=i5pfS zT@y_2)fmCj>ULgCN(w+k{tH-t9(J|3h_TQX+@L0DYYr3FhEp8^OvqdC@gfQiLQw-B z8Kq~z^9}XOR`&5{@$Tx#u8afMM0N_Szhsd`88ik#_~KlYFUO78XU@Bd`M-7%v;muq zKZI||XPKwd!EL<((z;+%Ye#3pbTn8vVK(Y!0N!Tm%eYBYlVVJmWnJkfK6G_*oa)^O)4`^K#7shUuf zxTdmhWVaxHtJ*fvZb3}}DMeu*Sd>}g?KZ$2>mRCX_^~WYt%mr{4YRCw31)?D@x%B> zzOOpVdQ&-nY^!g=$rWbkpaN7snhjyuR$rsQOgN(~8%oAAKs=WctBff)Z_^<2dSABt z{AMW=H@-D4GD)4PGS@(%SE3}3^QyBFqC;agA6^p@o?a~xZ$hO95=%GJ`6oIWqaq!U ziloC(2<#x*gpSJPW#don@R?sR{QRLYy8=B_WUmm6rx*b)zDm-Kl_gho^jg^voB0bW z(@RPOVfSrb3eSQimbzWgjsyA8AfZ*pW-ipqgF5=xAy%S}_fDi-Ird~Yy+TUPh;0l| z0K`nkvE#4A0>+I$gE0eo%S-6h(4$w6&5->rq=#)qSTWP5WlkLdhF!}3h z+S>wa5|U&{NPG>4$RX*{>lD6w zRhLuvKI=uQIyNA6{-0HaR*xt8#?INUOM0xV+~BFrL1Wfm`u?gKdE7UL$$L>4^`XSagJeSSt7w10iYX|$j4NiMe$&F8iSc+ae*k52MC-zz zq7yS?of8l-RX1bBt@-xJ6`0Sxt8vj5 zDgG5Np!m)9R{BsweOKh(-%QiQ9{MD}?b?jp6zvky8mG&PDI6m_r+RoJ2RRZ>r{I zBH@m14RmwefJvbt5n`U5r0FJa>$|W<&ZhW}3yMA_ahqi|d>AK+!XA9*=UcoEq8@Mu z#8kl|S*aw+SrJ*a>VRXZR4Y}Kf&14VIKM}tnIO2jORwA4mUs{zfZ#%cKyPu=n~4}@ z(Ee-DA8Mu#I2Tzlj&FTDsn`C*Q>bGq9|TNgJZ$TmUKc^?BxnG>K};trBJ82rF5=HN z(iWWzyM3^`sF?RE;hbz4jwj+mv=0v*Si&*?8PZ9412#v_uyo&HiDF|-Z$S;s@|RWh znk~L3#x^$7FJ`{IxH?@l`xoF~0pik=FJV|c2;dXRM58xb(nixoNXDuJ^F4x{#rcBg z*U@9Xe3zMMCir_H8o7d4cExh<>KM3dEbD@DvPRwxyz;e=Ab+XCmBQZ%nxuC+E7t}r zS>s6D;cGU~MJf?$cRiu@x&g0g`tUE#dZR_^vVh0b7nFNwwc!`n)8XK6nqatwSf@B~ zv0Tn5X}MT%1~Qxu&aLVl3aVrC5J6|ncq;|xRmaxqvOeT2J#u;3vmY?(vFg0+%-wCR zddt^8bqaQThYN5SC3t=5;tEq7C)4B<^@eou9f0K%Ocw>0iFVc5hyo|=Mn%ZVvw)Ry zbRrRh8{}n3>XwS)JORY7UxG`uZ>m2Q7gqD=K!(b6`adA78aGTFN5PHyC%{nFCU2?9{M0k_RrsmPrI)P-sJRhINEc?a|>`Jfvxt_&uRaPO|vKQYYp;(xU4kg zATTe4mF`D{%_k(eAsVW8)~*FdzLxY^1I4<`c~hBlS2;DCA$d!yVW7ewerq2 z!;5hjy_fG#{*`2&yJIEOckQ&T=wzn8T7y;pbt!r!wUDDh>1qzJDF;U{-T|iDbaP*Daw+- zG$UrCj!8x$4ZK8EY2?jz8fUNCU5ui0?4m9=0|uWYZj=%Ft-60K=D5i_X#bz1!4BO# zUGmrv(&Hr19(9$<5HB>|IyA#Y5LxtHlccUAoNp^=yPo^fEI7Xm%401`au(aH__zgy zF5R)@XIc@yYkakAM{Ljt#Q8y3!w9|6RfW9U)C9Axq-Gcfk{3dJ4Bqkx?-k6EkEDWm zIgY0vkPnL(pv-x3j}A@y$pW6u^ESm;IkUHt z4Y|$TEoRpRnME5=P~MS1Xs5~al)#(dkg^e*cvh7oyWy<0>|?&TVBS7t6K#_t zuwHSe@Gyym)iY?X!IQ0H`ADn(M?NQq1_fumP3QB$1eAJoVdWd3`8+FQd0UX7a3 zsB?&#ucni;;rRz?&WhTv74KJUY=QTj&ywlc8Grz_4|NFF9l)@64{H`7R}SGuhwTw- zl#AY33US4WFpE9<_QpAcnqDhu!_c@(wCR|w8eQS;COB51-=nS@C zWaGO^UJGi3P(@qmFtT<@P#*aoJIe=}!5CNT)$fOgj^mXv+mz^4IT9dh?_l!b0seS+ z(D!hW{WM4@bwL83xQ@ErcDZGPO#lt5ln3jK@G_2?fEdI@BbfTALE57%CAN+#=7Go% zwwEd|DBFc{ag9lO!>PI8NC5Z@$~k`(T4{k98<>!X=Yreg(??cLRXfW*sNQ4BTX=y(YxNK zwAAseEzTR7120cOOMu;&_Qs^?xoLD_Uo3N3Q%S05rL6BF+jT)1uS1e_?@(}`#W;o&<&DtOURdRTx65mLU?77Z*x0t2v zkZ!3S6}FFo3qth>c9fil6)P6E3t*h6bWktWcSXBOk*i8&F2u~BeahIX_8h7brqG75<46|Eh0^- z19-BN9B8~q6kfjnBXp1FyC)1UV%6??Fz{{TUURIB?4E?kf7D75NsM4kMAV=OWt4L; zwE*nFprmu!a!^$I^C4$+$HUGBO))MW!1)Pu$-onNB@>ASF?9KomhRd6Y!pg(=>{|V+ z*NcjA+K^=w*ELGf6h%HLSt<+gt~ID{oGzy~PTfOR@;1nLj<#a4i2x{QaP zQ6(9L<*rU=b?7(+= zd>ADH_LK%fe}?4Nl2JQnxZ{e?z%xu}RU+xuVlgs=a1YJy6Z{*|^AMFrpdfn#c2^8Z zm}qLQP_O`{T3vvj<*9X2`!g7(227!{+(Ti-S)UQ-G0L-aw?JfImKS*JgiS5a){!>2 z`olxZ(Mz`jFX{aVm~r*kJ8z&EidQED(A|T$r*Fst-0dxYXn#sP2O^KF+=1}Ui@1+7i5}B8$yXESqc%i0#idV|@Q`?f;n8S*MIZq|LQ)$ZWkR$PtUp8~3 zD3r4Q(GyY4-AazZJfWtdz(a~IWF-QVXKzZWD4dgdXL(@H5q>3Mj8;jXYa&=RCUd7a zCiD6|qKXZ-D zIlJixuGk*pb>mbSEikatbHj&c#0 z)pq?^;Kr1(2C{)ns|z=ifdzwHB(o4woZfgon(X2X1y?_cch>E0$+baS2xrddbv9Wba8z_JKn?xM;wPSy4w2*nb3 zf}oCCdQlccC-(}}aq;VB(t;r32<<6ppZ&}Z5l!@Sv<9F+!2IJ6`mH&P4;XR*F6AC@ z53C~eLF0sInDFGY9OzNv6yU=6F)Jg9klvFGI~d+3CtLmSE{Oy6AziM6;a7Gr{8ZFk z-|Cxie)?6umjv^)UwYm3pL4qFfDqzsFNk)Ug{Y{dxQvS$xOaY*Xs^cZRuF( zjKQy$RrQPr>4p;=Zf&!!Y#&$?2NI{H@ zc!?ULgnnI;xWV)a$_)GtD^iE+{%PibPUy?6{lD_e0mz2~>ha}rLwQyrZGPH@U+eou zv49b%V{upP8f-w2ond|1Zzcu1v#XJ^jGPRSuJuxsacfGqt`D?WUlZPerVg@_H$SP2 z$QpLl@nGr=WOqk<+MY;FFUFR@wwuENyszNbhKvR=WL2G5z)RSVIef@!mIQ<)vd;L* zVEH&IQ*-kjp%`v*{066o9)A!&1%GyTXnWWp4Dh{&atvF`-96x(7m5jHAV!4*B8Owg z5Xr_=7j*keLIPscc-Uvl5-ExhU?g1Gd#|e{3|!sZqXMY^6<5}7uS*_Q|p|2(VaZETXgb4y}k9@BRaV!H@{IY^da z8~0ljg}L{s3YV#q`z$usy0)Er(m-p&JYatXLgFs6 z!XVPK*qrpX6xtB2jwdr+SqnCxSs*d5fFr)YtMVo%|C5{`1Uf;Pp-ExLGn{{*HF=ZXVmJY-H?$>)qmVPY!EZzS z$v}I}6+#5V&8%8bKV^xeH|yr---BNM_9^Z?EP#OIZ2;cGK1c3|6oQV41A19AK^&BA zwlXCb=?LNs>Ud6vgW(*3y>{l9PoY|j^EcwUU>6@=DAD(yvG)~d=&krQbGj&#rK$6o z4=tGea5CBMCds&gM+qIC>+D=?q~{tEs4UylQ~9d8=#W+mw#3uJ_^J;BYJ?s*cX+kh z4gcF9#-?>X3AG2GKcm_t18k?L9%5ZV+__0`oZ~*MMo-$Vdyy1?F@_8w=*$xEn%39O z8AhE#W^1@2On#)(6|_|5Z=z(}0}71!iIucTH+_>;4H*Sq)lKVjAu=$6XH=C3iE;gA zM*rAivAetl!h|>;cMdp{5s%T$PU1WH;0W*|svv2nP6hX4L=~iwKK@PhWvni;RrmqI zV3lZ3eaykSyhT*o`+1-I)5@%3BRFci8piZYm6cX& zkw<63vKX=97e9VVRk4S)YSBV|bCj7C1w?65s_IYdQw-HbS%*VxhnfT+kg?;%2ezRy z>)Yu;q%&E}pwcRplc?Mb19`x@aio6tUdI9#S}(O@H1d7cG^leY#Kf z^BDABppoV(JD@+}3Ubxqji~WETx+OVaNV^~xj^PiZ4w^z$d`t*R_g!i5d&~zHaoy7 z6~|E}_cgy|ztBo1@4{9>iQljUixS0cQ87fk#Vw{@^PWTtY+xqoV6T8{_Z3>9C5jWK z115BRUd?u3u@98aIly|r@MkIF)jrM|Q$UQ{tXel{t_Qa-yG6MUuO@6ug*$66ZYO=c z`(dd&>YM-sIm3Uf5C8)z{dhsl;>_7<<5{|ux~~BQQ4cI zt|s`1=`Y-k>FhzxXxy7q@b9b#rsQpdAoN8;AP`tLoi}GuRBv)Cq0I`;2HYca((aor zLFRzvvF6?t%Lm6N(}L;}0`%!+UTS;=o{pGqF~!5l>*(F9PYqm5NO1 zy*==1MTLp;I1Dyb(2(C=%+fx(&J?iM0Tu8#MXL0;q_Ot_DWDaas03Nz)9nVYL2W6} zS+;3Ua}siC=0h3cGSvA3zszFXw&xeh@= zS7fG;%%{qh&HdW!R*S+yRv6WAv_>3LuOB{`lfFUX9c19%)(Yt|+q?KnO?rP7Eqt;{#osn1?wZ_xI<<)4| z!=%*StvWbI#ZrEw%pOO3{u~kw1I{29d$B1V5w#Wgvzb=)DSQPmmL_uU%4TmqakXHj={=?RP-bK6KNNhU0sT~D5aQTYnIcq0ST8K zSF(DMy@w)zU0H47c!PYnK#>jh*{jhIp&O`Oh3-1;wBIQ_-?(FZlsG;g2i*aRI(*59 z9a(-=q03;5woqRJTa_(h2$3xC>H?_%Baa&}(v+kdq3euX5K+PtQ zJb)2s7nBYoW8u>4?jp#1j}xrw5W*^uXk{BJTn-?xIEmyq2>^|sYQDx57s^$2)sdj^_>gt%@Pr&|&2Ron7yC3`_>O?}P31Hph`?|4 zQ|QI}C4pSfAHBg_Zoyx2y?8QlXv^=QvHOKkCQ|7Z0M%^75HR&Pm>UX*d?4HrZ5HtU zgRc6~3kUc&lf1y zp+)7=yr1Z{yELUZA|q%RkM?fnSa^y2O4(V$0gG}PDceLf|IR7O+MJ8XD^7+^ZXomccA-85#`b3`P_56$>t2}Q4vsDyJ)Rd&HVNe$ z5TdSX1us`T|10GGeoKPD9!dk#*#gzcF11=&Z6*d9#~+z6;Rl1jb`q!sayxv`xK4@fl5fSo(U}{NRtfLakRcVfP(D;JxJ>y3sQ4 zhR$r6r7`6$O8O`2q=db%S~~dg>I}t+eT7?`BS55 zQCU%rF6r1`A8|8B%S}*``1#$?xIIVCkD?4i96vVX4`eqTU4>kuZ zJ?O)|S-Oy(l8~sfYjp>NE<#pm`Csr^!Yeq%C~L=R=L>7D28|X#xo}9a>3rT2#1}PL zM@eqd^b^3nXcxo0G0+6R011HV-Gn@(U0d8|_g8F7f(1*sgXJC@er2fMBi2be8z{So z=pa_m$s3pk@TGB%I9r}MPn;u^aJxr6a1?%p_ksK+{#V|McQbli=vuIP17H3X4o{8- z_k-7wG7ry1TAT|m)?SIEb&Mi82lE76ok%AFsa`MnOZgO%G% zeg}0QjDE2VHM9!cnaa-c>?v6gw`C8v-ul-p{t}lF7ruV`d?p&b_T^kRZsyc&!89Cv z-$#^+)wb(WTSc(*io@tBX3qiJAsmw%$!>xi4~)G;r{4eY-8R=EP;t%xbQG@3Hv9$= zGQ((413v5ESUhpR(KPMmbC-*EAg#|wX#dBoRO9ITcX%qq?xkC0o*wNon4_n|3_ zp%U`E26dWI4nj}R2- z9`k0Tn~zVvw7>4U^TCdKxWNxvR)Ef~1Y#CQ2YVL#bzkBpl$>R=V*he#jr{#R2%I(Z zY+lySvyqpo=h@(Jch9qd4~1Q+z%g+doXzMkESeI9 z@C&H3v6sWwC^S_HYN8CdM~IQ3$e|kU2qf>J#3F=RYN?W}k1vxtU};i{L9qiXP97ds zzp*eiC4*J#Ke_mR_xUq7LFM_gPS%KrcB8>{4=l(RNpe29VoSo|VX2ZTsU7;n?uZT1 z9uOW`Jz${*y|i*1s-hM6BtDZT)H2xa4St$W$KjA|&>EBDK7bFPnd9mFws8d?*lL2J zos-?0U)!F%Pig>PE#}DOovS1+qaEzUA@C^_&RO7vdv&9~0MST`lRHRd73zs&RbWxA z{wLkU6UVBGV-@X+xOIZwixKf6G;IiySy*7i4FN&!VyIUoC&ibhYJ%BijO==VaUVrT z%(O*GyBj&-4O$HY^iNcK5T@Gb%_BINqiV2#5>Gr=Ly)||6p|PLsRC~d>kIm=`<|wY zF?`tCgWgIglFUc?qCrhhB|VPv&(b&VD9{e(OrRAn8@{lB)g_gG zpf5;1x$*;GXK+Bz8+ar!=1 zQj|AS@TqNL-12~rsXkD+68Yz2@?xGI3WhJcbx>MB%FD6;{vD0)h(P;;NdKo<<8sDv zhYEcnZuzIL8X4eHM2=8%X3J?X+WhOIM-}rb#&liPs1n95vgjkV50LcyxWm91XpQYe zbO18LzlpGBHjYRZwi*8_eG+$>g^2(|qVAz6Du+aV@O?Hp@heRy{KeeBP=+TZq1ux>WMYL(eEeL|#2tPEkPT)$0|v4=I2A6)%z z#`hGp3;epZ%xs-Pt`qZR6@-5Ztq`wV(@~h61<9{@hY~7TNgP+T01+Lg;k!vswcR#B zRKJ||RY##1;-H-hF2XC<-Xp&0_$UI{h5s0b^&9ri? z%wVz>jSuhz6uFTeCL=&*R{@8~lDFJ4#dYR$#7EEMRed_2d*7!7deE~S z&P2JCMnCQBZ((N}e$hAqENnp_`ZFN!4ezARx&@VsD4LBRWTo;VO5w1V4>+e1l=+MC zKFN6_GSOH%0?fwif?sId?n%5ER`ErOyZZ(B0d*4gbPm!RQ`Q<>>Et8r*{vA$!Tj2 zgG~gxGca$!yp`OH)N`4!P=$_<5ON=9i|j@8Lt}w5(BNu>FXba*IWo_FzaFMH-Kude zh6s5gLz13Ooz|{C?TtI*o5{-&R&;U!dGfjJ0HGDX>&^6lLU_wFI4pd=jQ!9F-gl|( zWCae+o>*tp5ygKn#I(2Q4~HO2$e<6qEe}cAus++r7HLBeIGN#dnqpNd1P8hXxC_F# z#Nts(BE`klNu085t4=&6n};Rav|<6s#Ffpyj13hr)(i7COho5eLF))b=DAL+aU46L zf}aBJEf<8tfr{vfDi{gi^oqkUx3DbX;j8#_1=z@Eb$)80-PCQqo6v3XnsSKHOWHDz z2K-Z&3qw)pU!VrHEGJ%Yhl%>46S3yZybKb}!6=v^u z-lm3;g;3k_|H64dNX(j1G+4L}u0l|=%ji=C=Z=+OcQTyua3@Be?$okNabhFAc3~K5g5G^Jfw5RTy;u)6MVFwhGHN zM&YpokU%an$iWEANh$|Ws{!McTG`3-JwY+VuRVMp{NDf7Q$nlW#ZM>35B&kv8q8;7 z7rT>&T}`^r1+rRBw`g~(K$uu%W{9FrMxS#1*$2v1r#2&ql$R203*)DdHnuY{-&t>Y zVGXN>ft8xxE-@Z>jQ+;-^9(A{V8%<$ua7ZF!?Pjz8c=RtUJwJlEpC@@n(7Liljf&} zY|Ktx?2U@`=;nzC>XAkEfn_k@YrVQLqxA&$R~svd^7(^G!BjeGvj=~tiNF{a$);f7 z=F~9YK#aAW2q|i^>M`b0m9=x>ZyPHR5a9U*&u8Bdz-@c073@(CijQ6Hy1reRe8@`b zm2`_wUQjGRngi+vp2t(Qzsx&fVq}OT8Lc_ugQ%T|pGC8ljrBow zRp&(I3jgM|o~p}-hZzneMgO4o{=|(RRNNjdU_MosdvkXWHtm3TOx}pEEePa>}1vQ!pr(_zbrojIr=Uz+zB+O zkUMb-o>Q2&n&~m(Mh`hvPE144gqsPayy|~pVpHl?dHXK2W>1<1e;cJt*D7Mi_*NB z(kK$*c;azuZvh1$C~QwBRKEf+GXdwJKbC>3Op;ejQ@E1 z9`av}*WHujw=e(aWBkWQ7!KJoI9rGhW?3?_-l&!PXLEExfg&52>aFDN-axjF0TTbpb_ie3=5P@s&btOS`cg{GoJU zp7+Ty7x>2nQjWOw_{dFvIB^f5v!?zM#k?q{$aQ&F+s>DRC}lj=07Ic=-J+$bjDkDb z0ba>#U_Bm(2zEv+$q}=4LO5_P>S!KLg*%5etp<>U(nKc}V#-@Z0_M1Y59TyuN2KhiSa%U`H-guFDTmgN^9 z)wS|NP`SX%b(@{#^iw*Kak(6D3TNfx*7V=%^!c3hgE=}O)G}qnr5|}Y;J{5cIP&;f zSsw=(1yGx)09)ax2*XV~AP^{lw%(t>@A-YW!x41MFnCSl_BUPznn8-hB8}OwkBW8g zG{t~VP%b|$*?^G15s!iKE-+&jV?gCl9*+EK3Qb~#*PO`!a}2diy9q%$*nK@j_i;Ko z2jqSH=Jl{Ko+ThNPL_!GJOnd~qQgVHz`hqX7o|$7;*H@j4A=qm2q*)^apBRbH$6Pm z1)urja}%Uv1W za)YCNiEgx%=785OwxLtGZC@;xv&uT{p4&5cH2Yvz2NgQ}2EPafKJVLt>p~43JV&*q z8vW_UB;kbnH`sI)ElcGG6ttu<8KDg&#~C5+J4DJbGD|01F3*Z_jQ<7=$(i3*g*P}# zLkH;EkXt=LL8G@4`7&EyIr-0fKQUs(%w!znYd>3fh{QL?7Pt(w^Rb!>qhfGdp}}p$ zozr+17H*!Bn_PaQ0|_b;ny8r2VC@u$lng@Z&+4>{i)N@Fqtmkl z+g0_U*z|~8U|8rgfl|RneI`B!0kMT%KoquTE~T!|7jmlwH}phM7q$==1O4P|H0{A= z?gxIrhnHsC3x!F=$1G6{&Z14JM)s9x(~OR|y@&B!&5u>clDI|_H;0Ycv)(xg3;>?w zfjg2^A^F-D%ofYD;BRi@X3GT%#E>um>^!B81?ti9huhntV?j-}k-S3P@gwe>=XqKci#U@+K6&sJNpWv`9_gl=(*(QRWocNNK)q&IpmsU&Twu7*X>g!I}f`pRS0 ze{&(5oA~ohGohJhIt#xlI)dw~3Xnji)}3I-;Gj#w znZY=`Vgz&(@1?WF5S#~#f_xkYi?#)E%R-fHF_&_dE*s^;DYi7Eo77qYoT&*p*`b6R zfiuTMhl~02hIhq(lO4Ei!S`hK{{oiQD7eEb*)LXq3; zV!AgRCb7kj|32Ttlu?keuDYBb7bk6~zApAoZ<7fZ=@wt3AJI$mT~UydIPml1e#*^{ z6B5>z7sM?`1e^V?61}170hk)U)9sq=n|oDY%&A%7%a`<`9sU)51x;4fLrp?h$TCY! z{UtbQR%iTTLPXjE8x0kV0iX)1B&{mL)D6g^I;K%SGtonQJ6@>do69Q?t#t(dRzR3@ z!^Tka<&SL+t_hSJ@N_v8MgI`++VvG{7fXCh;3Wq`&SIjvq|n*oliw2lApIRqW+=;M zA976haybmRk;^*mRtE}6e@AR!w$6?fxY*Da8h8=@ZYxeF&c3NY76z3QO38 znjs%g`u_h3`u^?34ZF%6PD_jS2P|4xEI0u4=KDXY;K~T)1i0BC4*$~C1GAcz(?z?- z9EgdGF4hZ+kG;^khoP;Uv8&N6Ih#gHxliEJNl;c6s4l2V5W`VAyL%0~1k-_1I56sW zaly${aO(irUZ-b0Of1sZ2J)g7Np=xeUdX%;3u!`fYfw4>83jgsp~(K)TwOlGim7s+ zT~7^av5SkVN0eZdc|!a1!OmBUFLm_m1aqb+=WgXa`$*P z)JKFS8jl|o5cB0Tmpq^$DA&xN2X-uq3CmhUh4En@<8#0lBcgy30Q;e`q&VL~emja$ zXwbAa9`SNitv`{7fhUaL74LN#cLu|AXuG(iD2uZ0ot_o3y^VGBG3EQ?_if zQ9aHeV>xsLesZr+a}s`%#I?)w2JW&cdjN>@Wa*@h$)H!dWhC< zP~k`XvV?#f>J^PS_z?r>Ul5kUGE%yd%um2@&E5iw7|Ti?O?KzMNY|{kG1z>lS~60h zOdJ@1fMr$M>RiVz{!?TLzlaM06s4v38e%w3;2t__7`n$Guy@8eq^u=gDX-qe?ef^y z55l8e+6qojbhWmXWj0+Zoh5tN7Q6C4h_P83;-ot~M#!Q2bjUXAHv8qyJhQ5Huu>!f zsHgOuiV@_v!P1Y5KDz`pWHt=mp+UC%w`ABnla915S|{Qnfs#IorW26CQ0Y#3r_;I9 zVsb^WgP&7OMeZ%H7S4^} z-XQ98vw1Pwkb8nmXi0}e7U_2mmJ~jhZ%%R+zu+X6@oWYz@ztac>*IuubEWXjRf=6~ z%hc-QENU<%<`9Av-!GIscq4D~vYKYzE}cm7zmiUS5A@kq+sbSf$TNN{n-zE->)%aT zV0A|UiWPZWA!VoR56lS^g9|SR_9idXb(O>*4?0ik-F$|S|2-bN+ z2s`rH<3hjYa3LJy7kuIv`dwmVWQsKY6Tt|5Y$g>Q?#PG+LxjOtqVpcY6YA81xu6ry z*)1l$V|i&?V}&Tv9?Yu<0D30yfcz-|2IxL(@qVNzSI}0`zDiCR2`KleD3yzNxSkV* zS=(sl%r;ckRu=7w1m3^U<@pb)0i^bziq)JtNhnE85m2H<0U--8!V}9UH5=&; z&2rB4-`(Rr<`8e==Rlnh78J&rVW-c>;Qbr;(gh8b9)+*JOu8^(SaBwDC)er&WxG6j zpj?sqob~#%kr=zq>Z%$j4U20TFukpp&(Xh?6 zuT$bG=p6q95ila)LySfHUz&Q)Zjw4s2uK-7CEKAqikzYmZm&q^O!(3AtOz+s?N{(v z*gpDy&bSxTi7`vDLGN+2V@&jd8yfipFbLzD%T84a=9U4L)dFTkFGj5 zg^gkKp%>$wQGqw-*B@hAT?fcx%ak6t2ERgGB1dl_`0wkwwq?EpXH;KEuPPH7fJuiD zfR_OuD}2uG`Ii?*!FoK@Iw>61Etnz~tXyHtr^m=N)Y7eO<*43(o4MiWX9-2PC@O%F z*F*e1hl~JQMt8jk_kk%Fj%|-}ak8z?&`fP(Jl2X(M4-VhJyLP;$5c;o1;tHzfl&<$ zRKRH~x%7SLGQEa;f*T%Bha>tO>^|v|!kVSd+qP4nN>NSVKr004ka=zVcn9%CGua!Q zAqW6po9X-$O7}=W ztkcXbxZ*N|0OQ(AkdCZg4iT0>R$B!Kco|PW#Fx&f=KB&>m8aoP-576n=wNWivL|06 zPOk^qSka~>E(lKG^lE{*_84fN8t1sUFcvYzv6S^0tc<*fPH!~300D^L7r+=w=HlDb zj`l_`f@uZfNW`TWu^qgI_XL5>8wa*aQxNxVjKw6cSJ|-|tw{;TS*U;{yq4Cu5g}i2 zI>X=en;h>qm+Xv@QT5JKcMVnV2+s%s-Qaf1)2ctVDhlb1@x5Dmm2|w{dD)Z7GneLu z>bHFTQ~w_4@A@ws%fu0b-D7=PC7=s|3dH3tx9Vw?(3;>@6yvim1CWwt6tc`dfKgE! zs=+m7u2jIu)?zW*gRxm6Es1u~8JsN9s4&2EvEe9{bZ7G-7`0*QdIQS7FzZHv>js}@ z*;{;452WwS9J)Er4$1J0D$#r?-7SPY!y;JKunUBW zxgd}}94PGcJi!g>FC&cD#P7AvyPQfxB|fc;24r-^#(aT@p;&>brDG4*hJ z+0_}Ho0dj(I3(3-_jx;vHlOQIJPo8iR{>|JQHN+nR@**y^WCIx)6a;zZpZ{HHa7vB zn|@-eqcEmNBNTKoUm;jWRT+ELbOmc7yl!v+0}g;h532<8(VFqEL;L~wh=&5(FPfEZ z!FHicfJfr0f4{z-0hxrARCd7!06=me={x9tz^8LlpVhyvLZt}$l(a5cC5?}GE@f&1 z&PKoch;VHVu55ypkAWdjO)*|R2@5I2$h!-W2S8sy(lGzU{fTc->%ZDR3!xC zfV#kw)ZLn|?mHOlse2F>3GRY{yAh9bO!NQ1?mLxURJ{YVZZb>giakTzB2LyD1_c2~X9Kk-WnBXf zC+QlGkF*V(Tm(*WGUM2yWT`LFhN17<>-I3#0cqq{*c|yN+?2i!y{_d@UOgeJ1j|A; zIO%G&*NFYUX$*$(t~;-NLmR%upmd8KVN!AOP^vnRD1m=tmWt8c)5Y+!yt0cYfQAQI zwg;k61Fnfeq0)qf_hUg<%}#D6EwIN%2tqgu*&i@*&0DxlXG3<8;*Zyh7>x!)!F7W; z2rB~ykuKM%f8Z|NqlSp`Cfva5a-qtZH-B(E<#J()#VF%1xY8*jjUug3#W$?%Qs8yk z*>ei9pX-A)B5@Fzkh>g!a_gHKDyk_}v040f+}cGx4<&@Y!zTWJ_P%YojU(9>pQ5LU znF*g0Y=HMrI6|UY9$1t}lN!(L{n7yfpa=@L0YFkb5B~PER%T^Y^$i3-(vrvW*7jJq zHma-gvU26hxMZ?w5eQowu0jhyTq(I7@jgm+aF*Y9zDQmBoYRPJ8-_xw${v6>hqrC6 zTxr={y{r$wtiwb5Vh_|tw6efj5Wzg<#n>C{3M*g#eXixB@85>?h5FmQ;RimS$fdiW zP+5?Qi-0Ct7kTmu%=eq};IL8SRIohq{eFD?Iiub#6SQ{}vsE9HT7`uw9#?iiS2Hof z8po=G*prj9iJ0Vu$C%Jyx(~oYe1W>Qm|7o2OLU#g8G#-UzBS|~5L-5h$3kqep~@TJ z36&jkeVrE5PS8NZZelRc5LRky94Yb>b8`hpd_5;mm~BV1!^7w=`c7GRm&q0QoY4RU z1dkm6-kYogDQdv)1SDtF-vjN=!6`YKnh%^RXlmNc5o@HKfv>V8QI0azQpy~KbqX9v zP$wxf$~_~@wXNylc2^y?#R|f+f%sHPOOiFx5~?Vbeh6|Ekz#71{+Y4zME@snDYKYu z*~)dqiHr2$vcKqFWk|}ZwSma(sRbe97?I)`M2!Gt;3Djx>Al74fD11AZ?J#b2HQN` zxwGx@FjFClAES*0c(UwaEYTXI z6aP|@PAuA90-|{2KpH1>C=jpJGlwm?*yLw3^(&nBd^hsiEffWLsPA(&m(2^sX@=7w zxf^FVYhgp|KHu;(sLeuSn06R#QzQ&0*}goaV~{YBZB1RLb4U&g;lA7=A1OGYVV7XX zD+=GJtuClFZnAGFnW@k&T%aC|OESb8D8Yl92E1ZEr!h2Vs0xLq4+GxSknFYY=j!KC z;3Z0B3^;JUX19ZG|M1W~!IO(j)X@{*J2TE41--sAwGXUJGHtbWO7_y1{_i7DIpmXTVb%GhNwMk*BK4K! z17D!C0-j*G3BKY@9eWcUARq5cH(aBqTXZ#yZe1(@%sg+%U7t~hp8A&i1tb^g**-ktewR2- z?gvIMsZek!zo>%uRDO}?cR9q(asM5UUg*b;tIoi`8eZDd^UqHh&xfRNw<}6rNRztM z2}}iL)(J)yCdfKtF7Dvz)tsHuh7dP`JYogH z*rb%WftjIIv5Yw8yJ^e^kpB@7rE!*vQehUSiaP z92t4^C6L%3Uf^GjO#WOA<*3sZ<|_&KlvBOFpxo?51MKjuyZC}&$fcB}21!6N=UPRk z!oODAy;`i>i^4QF8f8I=7U~FVDbXU22Old1x9U6t)2{m&U@TbUP%cYHSau^HxdAcB zmK4-gxSpwh_G=TwVih@s$3!_7kM7$ENiIG$+6sbY>7dS}*UFj!Z$D~+qs2wZzrC6CbFR$&T1G%B9 zmu{=rOE)6d6Qks1thEm{?ci?|$zl_+0a5ziWrUG#B{K}}(60bE7q~C$fXtMa0=7a$ zeQ2eqW~qW=}@tlgsqYTo|#=8I%XpZDIFgHo3pU z$*O~%lc=bX>_vZZvVr_n2`!)=Mav4V&(WgT+!Cfz`k$M@l;&bHL4YKQwF;EwivCZ! zexgl_K1Q#jCWieo${;Rv(Raed0jXG)`2V*dLe9gi(T@1&iIb8)@&YyOoIuS_CUSq*{slmPvAzRo?1jr&{IGmuI35#Aw5p+_juZsd%8jJ;vyewQ42-w@*h8I;hKe@UU{g&R>jYhR~B%&vboz|5o*avu4{ z21CU8RA-{<+bcWJwsRe5OoR;ZTAL%gPMkpP4YF#Ja^cyp96|``C&YCFsIfcl3jRtu zf2iQERUR@2oa1(p!;{FR<3m9pa2pNNl;-gLQ7XBt1!Y@Q!CgY>dinKQGa!h#ks=t` zy1^a>R|VLVR@v5P^a2I67X0?xTh#61tsrxn_FU_Io59)#$cq;OG`89!scvNm>3GlZ zfBsfxza0K?HfJP1Z#%;lO_U%B1hs8te+$sfbxYg~rk36-_{~QGK$C%xFzn zizGqXeZ;95)Y6rGqc>8u7`H9=b1lzJQkJX$X$L15v`9&^oc~vLg9zrp4;2jwA9CWO z@nc)fvW{l-6)QHJSn6g9tCg2q!;oHC`8KD6$zElOYKOFeI{Rtoc?UO$Ye^l+rjliki#kL(Ht_N~_fiKjs@mbfRbl zw|}V_TLn2#+a@bPhf0l3Yg?Adi1eL6e#q`CLI{vA#25w?&L|T55%Hk7(|0B$k5z>H z-bZ8;VTew++^-V#t~0$99#;>%M1=lWTiT(#ObaHjv72zh)b?0tpW; z&DoYxp9^dN)x6eG7d-vKeeCpZbc+yqsB*}&8MFf!9&ouoq87~WmD6k@ZGV)( z3J8ba13$$0PE^o)7?(xTW@Rt7X$^RaDe*>YDtm@vhiBkPWT9BulDOxYdSv8#Z($$N zgr+UZ=h#-=1t?YWl#eCy&!U*cDaFD|12F~zpa+hh80^6MF))@Sa@n~x-zjht0rEhS zcq@<*^?_{+219=X*%A~#LcWGq?Yaj^@E2|ZMa10;ianl|92IpsElo^MyS7oHdD=T& zDC(29ot~Z?AD_K>{p!OTIWqN>6$amR`V@7PZ&>hH{aM8rG$bfw+p7)-4EAIfGvgQc zP&U&4l`8Q@%?iiYTO#B^iYX6UQ$J9wH%f^WyIr<+y)vIeu1#9m38(@J!hFtquk9(j z)%rUEch%ly~*)@suUcUtB3hVe6X{5mf5ZQUSulL#K}7xv<4A zXC~z`9+6x8JIvExt1fi>nL?9zEgak5P^>S%#_4|k@FKxy2o=MW;9RhEkpALDSGQS_ z00^1OvRu5y3LbC}ZM2bWC_jf9qORMRk=kQal%JXM!S(pyiJbb)(m4f%b_cEq&hR%i zq3r=#_wx^#H^~*b=zwCSf)5rmq34N$PLNmx8yWJh4TlBW2$#wk!HoZWinoFDYqk*6 zS5fa1NIc0CtP7`Ck@G|1dTJ%z;)3o&kDVb<3p?mhT!AN&-BX3WEso65*%9du2n5@+ z$;7E{iAT-xU518Skdaw2mMBB6DsEbTdzB1JYug}bZIlor!%4otyXTZxkRXv!M&;lH z*aS2`0Tq?OJk&Gp-s0w?U8Gg7k!*& zP5mID(8N#9D1@Tt1DMs`TZ|rH8z*9-#*w>vjLZ<#DSqqArf`&$sKh0~Vj@arC?&rT zS_$cZ;I=l7IE4NIq{9<;>z5a^iNPM^%ti7~k&a6kcYt&=8+PuYB6U1Sm>x1}pc3@@ z6##}HYd2|e$5XEa7sPuEZ3&j>{H4lYTM&kB>-dP~xRYY!?`D4x)FDp#GpXDp)IbXZ zs2TJ7o%Wdd8b+7=&6NZ4ks^2XM&UT}81?$Ar1nkdfuL4IA`<_p$Y?0eYR+m8!kS~! z@gzFtN%N93R)#dKI**Y!85KJduqABmwRTUO`VQv2a(oG*V?D_sB{LGFKT&vhxjqqf z_u_epYox!I#LFRc#6}ZH2HrNVP)LSFgNeEk&bTn|ir*L|fo15Yvets(##*CZ)>4niS>v6_1vgqdbG1#xtw;nf1fnodc%m}!lxNDV#pX3OxiMl5 zmxfZ(;O`xk<3M0*3b%pQKu3eXV20s0niLy^B9_DN0AJLoQbz!MmJ9Y}>Gd=~SkB{? zRW;B;g<>85j&jtyYgdiR>&Jo)lWHpkj$&Dy)BmWAmfmgRrK>N)iT&!6Pmh00!U8GF zXs}q#X(gOf#=(h2XTHg$``?Y22=tUCDT7c*B=3kSeAq<(Cpp#(_Z7>4AO(GSP-{dN zu6gupLdchS@PNvA25)X55t}msf(Xo)F0|)mAf#B<_Tm@xbi$sFuMz-fq0l3L{lwJ5*-bINZ!1$aX_4j?z( zo7n)x8D=6Qsl~#OcCTiW@#MiSCmcD*ihBBJ$OV6wNhUB7Cv=M|B}>gELRt?rs4ysd z2cWy?u?n7kLoUJ|5?_Zi{uV*0`env^>MAq_B&WecmMuV6L~c0Zqzm*28dB$72#`A9 zc)t2zBQ`Kt7w!nvj{n?1#uYbx^^aKfqr+EORZL5C#2;!dQ!Bmm#o=LivtDQTyM35+ zux5*O-twg1&w35MzO)q{BUr0hIS$V|R}grB+2o1YS{7QFpc&PKxxDNnlO{5AwvpK> zNFe-~vx>qD(^F6x=TitxHzdqc!q6iFK}3Nq^24JTFxh1g*z4S*Gfyc}3FB&u9M^Rx~7-1sZx3mXOV1+|bw8dADfgJBOLD1+zD5QbRbY!;nJ5}S4wV8HK5 zmrK%uQa@`^u2~%qbIo|g6~)o3y4omSp7`=B*(%KE0T*WM5zNcpxadZBYYhGub5p}9 zL_K^3s`^iAH6jR0RYS@FeX>4miB5TeCl$1m<*t$-2bQ^wW?TS=`a&7eNd8lj?cHG^ zm@};Ams@UC)~kKzFvNPhh1{r)2lE)fv1@CMjA9Inpn6UMQO7wWKFJoGha{7eZ;{B%1Pq~BwLzba?r;9PiP0>)9K#PWGnxyu$$~{L}CX8e%wEfNRI#aCqO5ypoZ1oz9qq>-p>_sSt z>tR2A$1e5~qe#rLVFFrH33Q{7k_Ii*D%_w387Q=-JD>3ARJf|`-BH6lEe+fvom`I1 z!2OP~9VAIo#+i&$yPSKLhu#HnwuVI`XX0oxYvk*IRLW`O_^ma7n8KP>52fu<~7 z*co2WJ5doFZlQZ+3b-1$uok1Y9sJ-#RrAa)?r-pr(Oh)f_*ee|tRb{Z>I6-Z&;X$S zs@H4701bdV2wGe7y-hcNM#$6$?4T8cr9p*;vhh+qdMiI!T>x_mN$RS?yQ2HKA`?Lm zi%DgjFPb4xm0E)LPi;C^HyaPP{3|q7c0``a*Pp^Ju3~(Ye|`<)Bf`Dzrby-%mkl2c zGhc!-AvDKE$hZVL;zKPo!@6{#7_i=_m9kRdS|7cQVo7y|bGfsJ@{%u2xJnW$PSSEE zb{udVhRb7W??YDnT>|>=t0_QQW#wrIm~0~`J(;r01Tm>FWN@hCWAeKou1&dVeK+tx z=!#dkb2^X^E3jk}L%iq&zr7iE$+oXRORc6Ch}IDe2+ZbssbRB2s|Ql_U3Wgcm|@hW zS-)@DQS6|B@Ds*oOIQ2ZtG4^;VgLR<_m?az5|=)Z!tTr58qzp-yQE@R&1Qn0#vEa$ zzzPIXVB#-_P~~n2H6Fpb91zb)dMV~l>qb&2ivIp|SeA#z3&S`RIB-HCur)>aV}N2+=Zo9SR#{$~zVuv5Ec;X+ zVm-zZh^KJ%SZLgWRTUKSw*@O$!8AG2&fwS>G~Hkdosyb$y5u@QEtE)Yb1-c!x>F$` z?|an@I7Uv}yr7&SZm9m^C`5&F4ZWP3F^G}0(iZ1Xo=4Ba#+~ps2aK;7i36$VXb!q)} zoeLXIb(=PKKJ~{kU7Ae~Z204sF4)eUN-}c^VHhvNiYma7)FKe9IwqJ>PIC>FB3ihn z0L=v={5Iv77z%BJ1BU-5{mbm*(w~e`Lgly$9{^;uIKV1w5ABWf3aJux_n5hPAQ4UZinsVAnxu6eTd!{Vx~T*|&&I{%^VY0LHoi z;a_{uR}XlIeo?nE9VMsiv=&FTAj1UP^5JImo+=JPUeRw$9$}^PxV_*#K>8s&o61DB z_GP?TDH6K!d6QB1IBmSiEu1;1ph=Msw~s-HFTf+|Scw9ILz?$qRb5`++mNIV`{!yy z+MP3lhY}BbUvf|}^Hw0YTF4!5s)pR_dI9Bo%9QMA+7ndd&WVvEuOt7c%yi?GUP9&F z&vn-3!X)a~CJPGfM|ChXD_Q6#99%Mt6@`R~&ToktSXJl9?M(b=$vo{1tkv6z@j`o! zyQk3q0kLH~_@p}1_<3ony`U=zO z>d2T)Flfi)sQFdQEO5_M3@VV%+6$sUR#zkqiEF8v*79%kcb;&3z)ID_{bHfKqHyWg z;W{2ZnKI`AtW_vo#=Bl_ZY0KYjqK%2K#Nz%bRdE1qQQXGL}>7e1)oPny}*a_Fn`Vq zHRDzgIhkas&mK3poqJ+wzW{JOR;(ObThKh*{Pi1OtK@1SR{P)|cxL@RZ$_DLY94`O z)Xwm)G~0&i+5%2S2p#1^QKf?Bm-EN9^C7-ZAr{w>dpt2H$m`vixzp4@dVc z-k-EU!C%ZK_u6Pc)(vJ(DnYW=q5Qkfq2P&{9v?hYb}0C5zv3GBPl$&F5@hSC3fovJ zxr3|303k1lf+))!l~rt^)IePlI0OG=by2$$*1Y;(K;@V>yeH9?cC95iO^(f>KjXF6 zV|ggmA0FD5JLK;>nhC#WOXtC5rOf4f2%ZJa^pLOJ77SsNzzproLN25?q7)6I5Ge1jCBl!{B3EuzFqsHt^?`g7`j0IQ2G7+Fr? z{NaT!QX(>OoKB5kZ?adC6QcxbznAbkY(m5P)`uN$g#146l2~=muJ2 zD8*~7o|r&743_ul(`;1}S~36CDL12IWCn6kR;;MI4W?iBc#@ZXoH9Qbo%!dW0+2NVIGittz|RID7BD#S z8V`7CB8m^9yEO&7!hvz6a>#uOar8Tc67^vDa~ zAlCanmHVW6+%+RP3Cybl<@XGfe<}vbHJ^3<(BlYV+nEHh55BbW1sVkZPE)xf&g@ev zLm<^&Z)(8nho%PL1qD?BDyqoTfFn0G0OZJ@G;C)Ual)h?Y&6qbji@YnB!ERoIXhl?jU@!z&umcXebbSNwApt3qe!neR zG?cr%;%&KYz}o;2vO(<7#Qub8&{Gx!iOsO~0Ud3`10H12z>gtcg9sW%LQJ6Q1rAMP zG|dJW9I`-vqewL>W31DzWUfnaz?8M2xu zkA_{O`gEZz`jJFelNbm(`Y-y$+qOQ8C44uLpwButUtl|y5W;|3xnukUltwwgYw{eV z=%cL03h4Ge1?>PDSHO%uk0*EIOtwheCz9+I39qR|x(zq?CBx}pX9ASNF=I|n#do8n zBWu2nFY!%t{Cc3*!gG^EG2+{vpdxHoG;SKa1h~{<)RxRwEjU!4n?4R_C04JXcyRzL zjG?!M?dYT$U2+vYxi!Wgc*T5d;v5zpqNFM|M4a-G#*E;@=maRklH`yi`3dMcF&OzL zQr*t5ul6~^fZ;)d)3=H?ikYS#9UneB$)FGhcM(N4ZctKQKLi1el5~4B2(uAO5Mj8$ z#pXluPJZztOWReTc{B~PW;EuTW=xODLHm~NeAl^TxVt2ac~_amY%Z#q%O_D|eeL1? zf8^kP!u6X9ck-;nna-XH>2J01f%vs9aG@&2dkm{Xajt)`c?qf~5Wu@x0mD#}*5St&l?+za2qL2(2r_bFHelPc3lM7U^iDw{v+!weE=W{0IvR zC5p_rH*=HjhTTl1xi~M6pJiwp7qK%z-;n|c7f%o0(+2QFSYYUJ6bb>P7x}5#6z&+3B7fH*n7%MbX_0xb%%nAh5RR(j~`_) zD1|)wAl)bpI%|wYYe2d0IzmHFwBEmnNwd@r85Bcob$hTwjlV>{t5Xbzyg(fls)c}g$l{WE=+$;vJoo& z@Ol*U>ccQ1*BMccSyaI!ClOVB2#o;eeAjon+of_&Ki%t3=Yt^xq=jWsq}5rnkjx=} zVI+7ax})0^auZ$HJ2dyY`9lv!7z{61_j7c6L?xR4ndpS=2lCo-X`d=J`Yt^f)ktun zAaH^Y(GG%0thJ!WDdLE4Dv&1fCup0*Z{?3=VnS*V|mhGXGm#O`Ag$iIjeNbJa9KetEG zH6^_8WJ{R^p)Jd9K4dn~ojjX2w~S2!YTXPylR^oUupO`x=j=H!8pcy5L@wLxB^zkl zzhJX023`f@L2C&#g*9s--XgpBHw9OiI!3KF>PFF>-)PT|*ng#_z7l3ymDk9g`B}MP zSc!NDWtIcPRsKTR`(oU805x$8+z5qpVR(80l%+5AqdD|5 z#3V0$LwWkj(wC2KhC|@xpD3;5I=4Jmsq)%`NVoFYq}H%HyDW$&7EZOg`IVYqjmA}9 zju29VD_RuFBH!et+Mr_&ALPb&C!ID(8hV;d_BLTN`3JKKcvCUOkzb*xV9MFF=|1M6%73xR>eT`{!3C-`nYH)%h~}H=5ZNPp&gp%a0+lXIkriiRD@6iMeo;e7YJ9+r8qfXzqY))C=ntQmFCG7P56yxvuN z(I0W}(=n^A=aN#X!;sDP{LXn9hJrnI;N;<;)rpSX#bnW8$#x-AsobTtD|It_>tZ2ke2B zuc)Sv`s!FWXMUJVWK{P``GpLrJ8(F@trovZlrDkO<2ZM626!Zk#-OV>` zSWejL-GDV=aSra#Ca1T-O1Iloa{c<15d>p%Zre;drDH9h9bZg(_w9=87$*%Wvc99h zbP|*xX-+%$oOFr&`~UT^7E84j@ic~Bjyj)_1%pt8-ZHl~RYbx+r%+y$l4B;&WID%a zAeej13Z!x<{Y9GIeq<}i*+)o{7c@m1aOlQ;eo4XXA5`u%(sWUFj16Wn2UH?&I{@3p z<+?%$BC@YPomT)Ru+#Bd=L=(*)0)k1x@4ti7c+=N&s9PtI`x}~WgLvfLE%#)&5R3Z z@MbnqIvN)(y1S&x%@-}`WvLSgIb7aGf;gZ^-g<$c_#(E>L95_(g|-9QoTR_{wR%Z* zJsDCvPMO$LMaj22XzL(DJAOd2o3R@4_qBU^#JBlTT!F_RB0sT z(mT_#X*c{jPF2et?hEsFK5+^y;*U3qqY+M+ixFt#U<&n10Y}=e_A(5^Kx^Ng>-Um; z1g*r7fazk75aESt4F0)2d-pM;!EhBVoSm_jiL%bcWQIJ+34Iqg?ep_n5SIXBs*``< z7_z0CDh`m3CPQ>~yuSa?v2hytAdy{Vu{045?PCi#;9(g z7f|niLHDEyd+Xcq`~Y?Qq8*=^Q`|XQ;31Ui(3OHok3)b&TMYpxC|eT`S9cZs!^0{9 zRmqA!F%`1Vg7d=**A^tBv^`7G>Z0G=6{-xpJEm13$9FalZN;C9&9mEOcAmwRo2C8L zo5bfu6W)ReZ(l!PxoLgST~2KjdF2{|7tGjuFBoS%iC|z(@J&$6e0fE)2Fp)&0X0k~ zvkp{oKmx?*(vC=yqGHek$PQ~X>?^-kkCl9p5qDIBOHjHxF|wA6uT;)fZ$g}WStc1r zDCI1yCPHa8OAxpK?Nzc((DnuOh`2(j6d)}f4VkgM7|w6Ow9aYWHM3uZ^PzfO8*LAK zEz1P>WcN|jM}l9Jgc)d%WztMHZGfjCz^CpG zfT46)gNcV0Med47F18Aw9_*u89~-I^9Y=viIRvdtvmYUeiqG4iz`%?T_sb6$V`s#iJ%wYGE--FvPRNc(Mpcy1Oe#lPTheY)#Xw^+pht+^u#PI@0!oFJ z4kj7_Y6Osf=uTT~oqvw|ZQV1d7d$lY!k<4okSJ)`1_d)8W6k2`Ykrdv*6IV_H-;oAtf zvLtO;&P3ydKh6k>p%iUJY%Co?vIZ;DD||}e|D3w2WxZu2$Cd6y=A-J0I_CYJ8+yY1 zfCE429vo!zJAjj8(O-)^t(~Jph24cSXRP(8++Cr`ldHz3gt&2_!t@jh1KUXp2u5V1 z4ZV0k0B(&w2Z+`@G$20vlu+@026%-FW_2Q8GqFtq2NIn`S3L^~Pru@gOtvDC^Y(nn zP%2&Ud|U*@ejr2L%-LNFp>nZHu9UGU-AUsuSdcl4BhqvLrC`h9EJD*&(VbHPwRBcKw!UUIH>Gh)~q(QNS>W^Ppx(rd%3LdhNC9WaF#|tyNZ-5bmPwS#{ztNb ziB5Xggi{H+iJbZ%E!I9ib!P<%B$pW!azBA5LHmV4lHlXJWq;D9AWa-wOE?%S zn$vJ#RsA_2I|K}@2Pz#>Mnf#u58WiV2jk14PqxitAf>pQ43q9BUEkgh0}gV0M60y` ztwyBzZJlr?o~RMd#4}~#Obfwym8ga?HIzPH2G(*(D7xY3iqTHb&occ3i%sP87Z+d64nRP zOXDNdGxBj|T#*Y;TCLGwh#arq1%-S*_!5UE1Zyt`pCpe~oj#YvRGkB_1Ewr<8Ir)# zkC(T#oxI5z{RxR=oE!02r~`D+WAZTrJEN*q~p+(|3t##VVy&o9^ye%*5M`kz=oEdjgoOniTxi%?$r77m0)Z|vSk2-W(_$!EUtFeiF=A1XmfAWxxeVUF5|2-CXMkE zhTUh#1=bb^e6EVAh zA~y279117DXVs|#E!dlyZoVyovZzs^3Z>b{3gAqhuDtKqKxhNyf%?$F#S|Rj+Q0aQrXw*RLgoFK()2U$#Q%FGQav$6PqraQkYpm>Pva z8?W1l8QCf9nip_nb_;Eu8rlXNdEa#K+QQkpkfcYU{DU~@e>|AE=OpQMl`8Pe>sWFD z6`XwUr&wLLLLuhK$?sw6!Vb#Lh;4`&4lrlZ*du{4tMIMIPv$I@G~E$WReI5w!OzZ+URfxj z`~<(onkMF-lzzKd>RZ8gM_;HAhUPfz|AoW;r*tj5*YS4cWR$IK($g*@vSih_9p^t* zdMdS1v2ptGA7|;SH`)7teoX(JouxV=@lPjb=dQ>^pEth2giRC+ zWnkO9J;F3h8D))AMTSV@Hwj`cXOoc|yTen7H}3k2waO6;k=4wrGKfEC&jdPdy!$*u z_HHiJb}-AtTEN1xCpU^xL{X6={fHAcRH!!dUH?|Rp?O8pZrix zEaXphbr1Fx@z++w3lAJ_!?3KU{ooZ^z;n~Qo)qe|9rDM*^l?{E9$`IH92+e%*_Gun z!fr+jq=!D`$D_C%Tns%h#KXlNKE*7ef|b2QCD|m&h}eJRaB@?8Qv<)gz(L)7P-AWR zhDE@J8o5&=C zJc1A?ay)%;{!`_7N3M}2DxUXeQiNb<6}Lhh*%as^u;8I|sZ|DnNf=fz7AFyEeS!aL z6HktrCtA(W$Rbw>`c63cFC(1fx@w{ghWxrh{ZOTfP~1fo6Xy+?k~rsBLFIbcXl%G& zW=n)7FdzVp=xCQF86AWR?S3E+Jmv_fb@bc*p{o*<3MHmBu<0NG``w+T7)}T)3LIGh zk_wP&n1b@JGG*1!%#O@P+5=mj&7H8Z>zgFK>E8PKMvfK4KZ;$T>g|le^l8Sf_(5lk z`H<4-a)XjPu=UI}}zmQYk4kkBqghgfD7J^rjsuJ&eB4R=J_Cj~M ztI2hS4Ad4^BfIy_1%w+j(!u((w@4!f4HCA71B~uewo1N+RO7_@a1#Y3rX{(4EM|uv zP5jcYv`*&9#dq#G+`g(2Y;eu+F`y``hHr$)R~0az7PEfb>`j7OL`>gRc1E)2SQUO* z=fcxi%C<-kQ1w#2gx>}T0RlZk3G-a(iaY@ddLdD$njautff1})LH#bbmy>B}#5X6J z&FaH0T)&SUT)_Nr!G41UR$_p5=s(vT1hh@lH^sod?$u-ffh^t3VXZdV8)4@H%8w}k zOvrPnJ)nHQ#tyY7x-apSqc8~UR5=9-uPk0Ii+T!A`S#D!cSiSBDX+Y%+w;55RD?Ak zwhukC8A^Oohwh9Wz>0@|c#${<8rG)(*>M}WE5P9^_Y?3(+NN^HoLyb(Fo5|H*5jPN z4xhY+YgmVBLBJ&%4WxTJE^7v9#HiqxG^HpGw4QwyNfW)2N0{D?dC*{7RLywQa`;jE z!`n~{2TB9x&7eosBb3F*izDG#XEDd0_ZsoXx-&?UJ>m6U4rYPa3qgNLJfh`$SXI_; zEBbW`z6j>lyN;Z)6JA)Nwn z`?Z-sI18k@duXk2@H!sd?>Shd4erj4AS`W2HwXDpIM3(WF$<c$8k#eW5&`pvd63SiIfu24&U7@-y2)KK z6`FJkoeYiAbhv{RU9MZO@OZQDc1h~mUQ3Y!R`yMT1?0i+UcCiP8%w$^G)|$M)8|_j9HOgIcH2-4QB1&< zsd5E(7sT!x$PB9ojDM>NNS3E6n}_P5e|e_dQe=*76FbVaT-GatM2UYa<6kW6mLDcr&!hAJ^Ju$0#ZDrqK~hMu?< zEpWqvz{abzSC!mW>I0;uEZ{h2D^gLL>rqi-q9rS}Lbjy&lv=ussOyh!F?M2h3-kc9 zl`M!t!G02^`yk-POdaQqH4=6PG?Yo3{7+LEVuKRK=VCE*O|Y)x@nXt;17K>OFfR?` zkbYxXMrV_}^YaRJ*lx)}>&c~#*cdb}X_rNBhX=e+eyW+W=YXt);t`xkkgUkK` z`sZk3OWzEtV!ZhJtaTxV56TcmgrCsT@H7!YA$p({eS(t3Xcez$sPr#p8A7KJdtq@i z8yA@W#+0pM;Zve8NBXa?;Z%qk^4(>!zdWk^efJ){Mim|nC@){ zuvqL``w9!?+8rd!_mX3B$8+`0djilnMo=@6C6j2&PJ*XIS6x5yeY?+YYNSlx8migY zPk+&1WbX}TWMGbIdb*#rPqMe|V}}M-P=3HiBp26UpArgjl_o9i@uY3>-DOdJf%AqL zMa_gs3+=qfuh6w}@&Sy~bk_Ma>d?GE_)^CHkeZn^&-%m|Uf)v;6KGiLuQe<;x*97~Q0|G-{@Q~pcyd!i$S z`#nO*d}J#XqWHZdxEY-@eL9;^E7*d0eDn!%3--3sR_L|}lh{TmAkZ>t_bO(oomHw< zG+RE}!t??3;o}%P3~6tDCK7@aNyv;Z!;WqtjggQ|-XRa{ z%imQbk`1ELyvC10uiFL60oQ6Ei5>yYfbLkc^%Lci-$KTxO7+&oh~DHF%D&&fm}DYI z1#Cx~JM||#K@%@f_}r{khUc*uWYok*K?7e5{z zI&b9s*ux^sr;d3QjNN7i!fG;4+%j|$cMQe1?%TOaGzQ9!kjkiEMkxfz!G$X5Y%#eM z{BC-$fBUTeGi->igTB^6Zb;eR1vCCK27crDo5^#Px=%dLh0Pw|{Pyu^D}5T3y`Pah zr%2TyviD|cGJ8u}AiQ@ilz0ttTcr!D*tAGlI+=-v+7tg15ank*H2xOGS>lT3LEOWB zT~`)CUw#K1k<^~9pRNxl22QN9Rn#~387_jRe2GhGE!zNR9TPK(x1FUA73clC4lr_H z!s)Pf&{lp6i>=D1Yj7lm)Pf6BkvR|ROC`ffkpR8)Dl~0x*103-+$OmR4MXK_>_0lj zmS$+R^cWkvE)`<~vS$CBLNYB)p;!El@|N%V_lz1(7qcUsMlRb^moNvKwYD_M#&`qN z#i>H?GeDpWY+tgW5Hyk$^iT)I`v_ zhtrZL!n6_Wfcni#3^3^!GAZviqmZQ+oMKa1Sd+PA*hVQRAb`G!f@_AFmn5?1NILh4 zmCZQ5c>l!V;MoHm_f{}i; zbdnwy>}O~0aHLN`;pHibZ%kw}?5>n0-j){WKp% zKji0@khS||67eB{g%!vd{-h+&j#apj99X_5*tHx*flJ2XS5ChJd%X}*Ch5+Y2<{G~(|NZOVUglaJ3iOOT>Zb@a zyCagsuum;6SsuHT+hfu51Fc7E%r>D3#@ypCgVD{XHjR_*z7}W9_f>){hM)q2wTAZ~ zUC>xmM7norvqe|{90^WNLgnKV@Jn!Rprd$pcZb7Mq6HDrst68-I7q%Fy~t$^#c7XBrLug_!ihDFirexBVzz z>(N^5l-;2NSmvQ1RaJeb%u8g|8)kT3){UZ^+4$g?Z^?G zkTgnHbPh#Kg!7d(%qS_2m_AkyRXjR@%*gV5I(q91E3{ULaTE~5H9pj=bJ3eW8Xt-Y zs7|a;`pT1pn8TTJ$@*PWvOboxy&;Nx@DY9oJ1Q0>VG9%pV4Ov{` ztTD!e6AsRNS3oJ0=^~WDe7d02$RA=3?Ma%?lO#`62Ki6EqB3VL=O2WIN=!w`K6*1y zI++EChYVS?BnE_(!0YpVi(0*?&ho2W^0AB@v{4*YKX+TQ;N@etm4D>NZp#Y6Vw_CL zS3$>#R9qhLGHY+U)*9PR_7fCsk>*RPkEr{^w`m9T@PQb}cIY*#N(km~m-w4ni0H6o zHMk9+pB*Ph9u07zCEn4~5uiXK@wn^?Y(HK&SRDjb@z>qCk*>vB=5pJo_K)IPSkQ=?DHVcy^uA9{vu-W31+ft8yExuu)*#ovth5(gDhvfhgNLe z05O?FC;X0Puv!8Jc?`47io^-SL|pgMO2mUfCyz!DRl*?)Hjon$?9{}wg>o#xE!2aD zT8|L8rP)HXPIGD#STSWqD1He-0eOOWKzfye69Jg;%R*2OSpC(=K5t|~H0!`s@yS<~ zbNzdvMi9}D;HvAf1O#tk^r@q%NPP2jOtl{hdT#WBo`kjG4vHHzNo9@q5v}amt9By) zmhqZcfpyg~Y!+2)4I?zHjLBC<15tRJ8yy^Gg+0+wjy*CdkTsVr)kXn>S#VhHrvqN{ z&;b*2Z%?BVaNZi0T8ey=Rb+LQi3COmT64Ms3f)R+zcCtJu}u$S-#KQD3^)kkH3Ac0 zc^og#e&tiG>4VYU_k6cab~ocN^I3r4Ho46#V=Qx`u*m#lM05#)(zJ!G=C;{VXaT21 zgRfO;&N&3idcqV%6y!!(f3V`7YA(#+gp5DSTEQGnO=Pjy2et=EHd=1b8Kc4Hr^ya{ zH|Co9R;5^H-xzGoc%-=ucmP#yQYwL8c{jb$H0Dz5z@8Vk^j88Y5(iRTJteLV2K8L< z?F^MB_MAfN;a;4wOCu^YU#pmzNmF`_WrmQ<4^qY{m;FyasfnheVnjgX2sYm2vb-Ls zXmi>a)k$!q$k38BQm6TH*D_7U8!7-yrE)bc!%*$-Hx6Ul$UTD;%^bv)@h1pHsAQsf z#}d{6E_G$uQtpP;A-DXK>22874S z><&Mw4-D|Vl)fsmTg9NP-+u0&)1P4VR0)w44gxrmTgct~ATsleP1Use*Na*wV;bADaT1SU_ zJ_;j?{F~^hziCE&c=&SI8C~=`e`Tx*Y7GAUw;zq#;)@<08-S&JVg+nt%oLRk5Yiq| zto!x72+0DIVH%0NDCtbkqtEsUecUcg?}HwdhWf$_%)>jT-826X!7X*IohPowr-s{PXe`kt5sN99xhz+| z5w%DK@S=Teh0z_%R|D#fJvGN{IS!D-+mc6v)3|TB0Y+6k?L-IEx*D8a^=1d7eAslG z;TQG$JS6;MSP*kmcVO0>k$ap$yC%02~Rlc*m z+;3k3mAmUUSo~LyU75PA2wO{qYI;Kx)vK>zlca@6RbJE(K{Ayw;%s|f!&<;yq-}lh zul~h33OD^(b8B=D8#$lL@nW^-0=*|D12l5ROpIb{J|9AfPU|{y9+F-E&gsnz^~1hP zdMj7Ph)^XMu9Dum4#yZD@c28TxF4=u1kL1Ti|VB%z1}k?IbDeBbTm{iD+%~N5WXzf z-G{TXzV8fHk=YD4FAzz5kRLulfFE&icY$(SIUR~pr4Kkx0RP`l5M&B>k8u@%8hqL- zXt!i^zn0ujysIs=56C@3oc96t$gB9@<0ck5|%u7A?C7o33! zfF-O3;MiWk`ipbO*6JMGc7;|{r8HDk#B&YKG~)zhyUAYk7ac4ToVyhXDm?p>39v_me0kb|mpX_JGf#)ti}3Poil z4xMM1SPwJp?DGJ+(W;-qn(bR4%K{`4iZqVZFbPrRop zifILua%EvaITuF^l7Os2!*Oy)nkB}Y0E93M!#@C{M#+4L#ZV=Oc-aL1x)zIQFXnv( z9}2Aq$bq03Ak6?5ogN(W;;KSc_1jiIq1qMW6qjcp;zA$le{O^?F^8jB!ui74I&+*a z&44SjQJu2KHO(gTokhPGWcdQ$|6EiET5MNF$2;*(tKRhEU(A`4JJx<*^qJ zaHLSraBY}{EfnR{`kD1SD`TWDc?ET?tXng%SysA2$CZx=6xvZ#67b?HpXQ|?{tzd8k1>UU9f+K z>s+%?11yTk2wz@-MeRDqBf$Gl(DItyC)=3u#Gx$i3AAMj%n4>1;XxQikt+sP3yO@8 zC0mFT($lx6;5{?^MEhnb>wt7(7meQ+3O(8gDMBxn_{wTl-Bgw~DxND>3!%vJ9Cv#AC_#X`_ezODZ~63G6a{;_bJH7G~T{ zF-csznpDbon;$_s*e_pU`&1Q!(ql!+^Qvg+76RlP^`M3AdiwzsvRt=rI+DCxK4Oc) z!-BTY(;euXSXYRM@1u}~pVk)zZ7F0GSgcRjLzCkkS7hr>*_nCLkqmiIFc{J1dK%(+ zL^nrfM%_Uu%I*{<3C?YAFh`&P)&Dc>10*HDRpd27W5z@Dtn=xcl#}Rv7GOZWD@q=D6=-L z>j%^&4BCXvAZhku(z|bW$>$|*CNF4eFsfRb+Gm`m5Gs0`;X}4k^iY{TgitOV)(9ST zHdLJj&qua=$-AP%!^nNS4BD1!Rt2Oog0G`u4!d!{PlctD>bv2c6_D)cc(jkEZj&pF zZgokaX`33){LPuMeYc!mB`Y5x$Py7jfrA7i5|o9|QNZ16ga}b(7kxpeKM2NNUVjii zI?^8uU-%F_y@v^OtR7>|v>d7`XmYJ4Nn(BV8V1Dy41n7WSTepHDPw8l=v>-R^`d-b z0DR1V&Qk|b=hU>12iRtu4uoZqS3JQ{@$=ec)8bA!C?bgJ8z&nVj(({t9Q{&XIFhHT3rG04hYLr^E+rM>sq40Je?tRP!F9)CGKBc39B#RL~UYH8*;!JliHaS(!jVS%>VoIVq7%L36vg}~P+2jK2 zj(tre6+&{5jp%`G3^#E!(%SSY3|7Mq zAXE~yj6yBu2qY*B3;CrfTu8kDjAA?wv#}ZPoyK6viUHs9aZtv}jz}Xj#z*d0!BobeH4dLI4zBmF%bkJ6@J(ORfdvNL5+vH3$#I!BN zma&4aRdB;!jZ)0nHd4)Y%ES^th3nerNBdK@;q=gWP_EvXsGVdZwjuHz&S_!+7(yW8 z5X!@8*)fivk4!5>G*N>b?EwQrief{pLk7m9tA?b`3|W%fs<%=zY1b8yp`VZ;iK?0F zgY#1n!UxgD>Rq}WN-+q*JBF*^ckFa#v(CLIItq8dK_aAfG8GGVI8Dk$KmFRqK)bmO(mqu=)ZxkjxZGdQ`XgCUEP@4a=xKf!wDUw-^`^*KM!$U!+sB1dudew#B ztDIE*?b4Rx4&rXHfC{GTDr4`M;}x2X3x`r&ZNT9ksT2PjOj_!PcqSpOv8zcfj{_vAb zet1Fb#_zuMrC=l4iMDvTNhWdKrgG#;s0{pVN3TM5VSJ5U*VR30DqZo*}~Z$fCfMiUZZdlGx8d(M3sNwraMF ztS%;7;TyrG!R)k}(|Dim%t0ONr}sr4scVLB%Z*mTxOFnNN3GrNVFE=^L+z!~O%s!{;uT|~D!`7g3mRU?Yyblqw*Nz`M2Q_xy( zC}hZRn4n@yikyi?oSFnVjjUt~eYQ7KnTM91c5&4GA7|RWgRz1VeL#bF4U7Ld!B`CH zne`E=UpdJW?y3LD8mE)#4f^@|a^H>tu*%KCeO}G+nR7w{2|1O)q)lO9{QuS#*!?ZQ zUpPttbEa)J1xBYT6o3ki7fBQ7DzY@R_6$TrL_YRq^23WnyWG`=Rafvxr9g2%pS-#X z9@M-d(iX-iU^8Qeg1(~w!%I1Tf82-J?4x$Z&>8_0Xa&e^yFis6IE9;zNWir$k{v4y zSv$tZ4AZrcEMNA~^oYJ8RcV*TJuWCh?ht4_`H1FgdLNWgJod>rg1boc?D$-DKT2Iy zL5fTGJj@UM0%x3c#=#^By&8+dZ3R(8m6SSya17eH+f(G*7?K@c7xmj znJ3Dft)>opk2?br)`ZTboXd`H>w}VnNq1AsC~58k8AWvgGcp`c%TY@tUX&)%zK+oF z6b=?bF>7~Q>JIcWk#@e`Er{juwu{99XqF6p}=G{@G%llOKpdEe-pWHcFcIwY~)^kSI>$p9e6 zA?@>DMHD1t=f7ablAy!T63JGx{F^wzN}fzqN~Cl1iNkg*Fb8A+f&?*}l)>PLq>s~V zhx4ky3K_PVd%Ju1bj%<|uQ!+u7BIR>>5`(RPOB~!HB~J$ifR8u)VzIX$w3G^eTPAf z(`+C$`$dSorW_%IC&)h0B^G-14E;Y6*L22Qup(I(ID14FBUDyc`yg%_Zw+~ETG!Caw@T8q+LAXr zi?tRPgR*W}0c)hEC&$NUZ$6xyy>A5itcBafQc$j!jy^N?*Rj(|_A!ZTa=~Uh3iOIj z4QwOGkoGsKVxY(j;fK$V#~BaiV7W)G+Svxwoa2sTL$YJm%Nmp8WV(&^xj8YyZ#eW< zV^>c5k_0}7F*J1^0;&;GYcTz_bSy#5<2F51wE;13FGCQfoM1J_8ZA&#xwT zZzgx+SCWG3)`d+dqT3yy+Z|Dv%8lUBjaL<&$Hw`Ah9}tTp=*(Q^i&&u7OcGfpZn0- zIkl37mK(61?RpSFTp(#j+(C~vGxM~5wG!e53}9TPF(~gSavcvMsEU-IL}vqZPH6f8 z;=I%^jroFNLZSKOrx;q#Ua~vCx>@wF-F%;4gbEew?rb9>^W+gKYG9WZRn#IpeJ|^} z(^IkIuTsndz3am9V_9Nn=&J?-mTH`W!Q9(h2fGk$AZbX?Wd3}!e6)Dg z>wTPP6KBv?ytr=Uct1$DvMx|&(4cNxS$8<_XoRG( z*U^CZ%q2bdh!V?kIQVk1p)2beWbW3W1TYZV-HVHHjlnn|umh8Ovlw$1Ug;r%lAVt|?`8fi)LD05EDCBt5=BJe)e4Nf=gmbbn$>EimUBH%6)%sE)s zfA*3(&K}{-y#faqBx&b-Sj`f}f%Pw|>f80RwUw^p$fm-&Lir`vl4{T1{e0Cs*5}oz^hOP(I+ZF9)22YTUNOd zobhp3DHDkTRWMJ|fi^kkAvhHe9Z*M;_>Klp{u#1)9$*tEKu=~;?Q9^AL`sX9vhbEy zsEzfQ0Q55Hy@_g%&jbIi+rN2KL_%Ek7q*{8-47UrRs>i0xb$4TaXnXW6&~cHIpi6! zatHinf3V|}B0sE2&dbn+!`fo_tOk%V%7g`8A|Q0;$p4iwda{G!IGhfrQ)k1;oXsr8yOK0ywI&)Axo;bk zAL)Xu4xT)A;E;X9 z81WuP#ef^LxWTYVGj!2yF7()=(6c5(2k$5_bR=sg?*fxXrRg3dbOU$e3@#6JZkmpO z`Gz#{62r)=(<^T7!mc6bw)S>qleHC*CA(Q+a1dzWetm*9k`a?g2WZTt&9y%^%|^k^UlfRIbEQ z+0uQY#5E$fZJgEi_A>_<`6-THnM^T}0s zWHFML0u!q&zfbwTib{W8PcUa9k)!&fe9xjjB{L+wSebi&l0jf#gFg(EmvHajS90$+ z`X&k>q@V&oY6bth(MRBYh#X>Gz418CKVsGXHw%S)ewh{Wk^0pa@{!3^=6I;ZsrafW zehJEs=n?JVxM#cBfjmn6Ow{{bxDz9>+gFRlboWX|(ux)?tV%l8qXI4RU`(2SJ?IS zIiolWF6-Kp5ThZgM}>xp_#D%X@I~df@n6$>tv)hErD7Yv3Z-S)hF*q$Y?LV#y7G5y zv?UqGFOx0F@hjrGxsU65y>yNro$H6zxGnMS$o7 zlG=ijHE#+rA^@wD3a^n!=PU4e)t(*Ov*leE>)+8N094uWp-`! zR;!imI$TV&kO_(u+5wwT1eJ!sLXNQQbA(?z!<&A(Xiy}K2G_0HE_ycDRiW+b)8Rb; z!hD|v77AIgwqbvSkj?o$ByvV%__#A8olkw>b(S>pqYKGg()w3_*qw~})R*hKB`REN zeE}b{(|+@6igK59i3&+xEhP!G`R?b^ZIPiQ0BW@Yi9IOR19-sWs-FAIo91qyVccn1 zrf|v8jw&FKtQ5mlX|x;&aJ`D8yG{=j4Jep8rmamTnB*>dF?2C?E||V;ycx_%OVR0K zZ0BlR4ksvIIs$AkB#(*sC5V5h#E4v>CI>#lM$~N^I5W@R_e)hiLFTZMY()X|W{f_m ze&T9%9Jbq&J09!QGH}xJ4{VHPMuI{G>$H)_f!Sq6QW~x8x1i2SOSh`GRl7Eo2c7!F^){fq=JNLtY)V8QlAFSzih@dbIS1tv@NV zZhnknt*8pmaN($HBUfa>8=Xpn9v-XjgKA)W=nO{aF6+!b;s5&`y};m|&;h9bupQ8p zLVV;G6=B2Zla~&a%@3?np zQxfU^DXb6&u|ArESoPVCk!Yx)^8#Bz*)VB|E~3pV#Rf%rxsKg3M4ayr||XIbW%IooT0ST<+j^_&`daAd$Or>)nS9ueJqD4z0AUkDgaC;8HV7Y)Po&J zqR)jE0MZ9G3rWNErPc4{Ciolpw*PWcmG6bkzH{t!1Q1u`&(5){lN^in7-Czr`lLpr z*6&q!fg!WhQQ)GM;*RA=IE?*Bb{uGD6>K=AQ%BF$_kGK0L!2+#@WK|=lTj)jgduJnP7Tas?22VoIA&8#CLwY zfy#+y(#NAmPZYv-mZ3Nj+&xeqy4vK<16o?6q{I$39r=f85x0ZM&0JQuk=Lshqu+ww z%#WO6{1L$^faNjnsheFUKPKGa0!X#t$?TkCrGRK|nvyMy<&kjkMASJ#>Hg*D91hu;t0_tb6xRsaUwl;$Xv-HoU#jZ4M7pZ3 zesD494Q8^F{E_^lFvlw;C18O>27$P36VCI!z*o8gR>*8>*OgA)#xWnbIeNwZ59BkN z+JQ!%<K9Qcs9Yo@_g<6x4!5HEogC3Dc2+F%QM7@mW(XR<%D*{=V;M`MP5^+|L zm?jX|M}gXnI(7OB?Zxo|8fJ?Y=K;l29CkUuwAX=x^(6uksB7Yf`!-Zd=`q=00+SNLV4P@ zjm!6(mhOTD8abmlR_n_N`GAf<%2UN>AW5)L7*((SATucAb~6+bj`T~ z1hnTU@1m`zj|udYL6%#mTk7f9;|{fc&Y^)G9>f+!&Mnbyh%n@@zuVVW!605N(OsLt z!B4%^{<|s2_-r2@;XCgqJrGjHpHNM~Hv&&}KQMa98#LfU3rk7BR3S%Eev#jy z4ykn=lCIm3?UFul+!@_0`d{?yWUHn?!G2n8#^&wWwU_}m>#j>ITPw1@#dlXkv$>?) zV%s6+6HeD}dZus(Krm^5)+FEp{u37a-9Xm^#s2&9)3ZG#mx$Vf4mYb0{*<_o}?i< zKMlP^2#o0=_+Kcbo`D%XR5cd}nC-b4ATc>Bc@_o@wvhlx2s$9UC*g)(&pRhvfXHjt zn|P}QW7E2wSw-VtD)Q}k(^1d*83GU?>FJGi35?xUU$M^jt>e0tm!@xusyJ4=*D3(# z9iUL}<}{PY?WDV+>dk?bBvIFS?kQY|b3m2CnB;f9K~*nu^hWx=av z0=B)b8OyRchn5mPCKW z6bGbO7Smt?NsravuX5|5{cxiLicb_O*EUugUDO#bE)6?rwZy(8QR%xc5! zi4f7HJVcPE`6C}96yg)H2!&Y_*M@GO=`Tf2SU@*We5B+ZEaFTq35MS4u*9~!JLNiZ zKWul&Nio`H1cbRvC|IX8N|G^zYRtWt3TnC1%jOcW&h0alwva>W&+c(=enPe9d3x$3 zy?f%1Cdj7AUE~tYMyX3&<_UMZ6h0sNyJ@M*Ho{yo3bC2w)9@QV-QY zWon9~nxp|(G8@URLZh)ix`2w+v&aA6H(F;_PP^o&aGUY}e$b%UxE;b~J3Lf80>&Mg z!-g%AfBAlQF)C9n&;xeqxy;Cm9}f>N2ebJ??z1}#-w)!38(Q}B^WCcN2l4Q~)}O}* z(PiGMHIsuI&`%fC!`!T53*5nX;Pf(!)Lgp9&q}tGi_M8J$&T_-{fL;<(gF9aUyDSE z_~iz(t)S*KxsfrexbTWzygN=nyyLYpG_~KpQRuQRK>e!7YISl%egr&TQ0V9-@lF8h z*G}02Z2@d|=0BR6oFM#mnb1o&rr5axfu^b^7rW;Jn;?pks$TDrMX^lo6=k0voN@Js z2o!d{5HWsCUde8m2TS5%&>C3_;hwB8io75b*UF48*~G>8at)HuWV`2}$R1D}NZg4& z2o@1fp)uaglv((yhUJ#FxBW&zGE=UKMO+LfJN#L^ahd|#gqfw8FhAiaYjAYUzBO`H z9ACSf_!RT4(N4xU^AsCpb?LVv!9AhI=&SP&N%m9vAw7F_-2UJ6&Bylp6jiT=1mdI@ zh?;|M3aaUj3u=Q!Lfa<85vTBi8eQL%+=Y9KE#m>(rs|j2cH)r z!U7oO`bH(a2OAfE8m{h4;Uo$SGV^wFW&%;_N;{@au#fImnvKA%mFbQxvI%_t=d&O* z@kdAvyA06F5M}BR@ki6B$@(`zH6}R8&k|{;V6+UxU}Mh%K!#n~H4qwZX6RaCD?kMY zyFf4l?V1H%IlqCd46DxmX%h+7|9d<{My z4kS)GRFNP|Hoz2uvv+kzGVDGPY1T2^x0BCo(t58A{jl5+1!lX^9COaX4~r00yJ2Ns zJka0HXDU!Jv2@gUarY6yE>FJ-yEK^jHEJ-}*3fFLYl$avha0w(no9qar@6E{DXS{| zFKER`8%3=_=U;z#;r|1PmnsZ8^V*|C9D8<>JgGrpot8E7IAqIByXnc%QRsa>?RN)2 z_ym22ZryLOaYL&VS2P$mj1!zpF`X^r>v0b`IRjmH3gho&ZTz<=E9;|jHzJp{$)N(Y zY33xhm#cJKKWsGjY#iMg>?p6+OY~wK9DU)*jGghSb57rrCEHHKmnIZ_PSE6HTcW;# zY(P=a%~iu9o{eoliAF+LCQ*c0Mg`d!RunZGt<$=+hvq37FngHp&hhPiH!;w_vdxiE zi~QAvuk@hdt6D>{P$?c|S|4T=MKzUkr1Ds>TQP5uI2g04aRszmHDk5=1f8@kk;=*hXirITa zgYPmbdtJLJ*EaN|UZc-cwF$&*xmBEM8{5fg_OJ7EkxV@6L)hc}{1dHCf!?zZRsGbO zLQPXYuC*k4=`YCX?Ip0fBd1AOG7X5(x127i&V|PLA)cO?dGOl}z&m}3d5o&(Z$y^F zHkIugm+)AT5SRR=-g zFN4v|2;muQwhynB4$ag_zYSeJ6vhW{h@zrkUAo_BeBvT+I1bgn`#)rWH z6-Hu+QgRWZzkoA*3LFOJ=O>8|Lh*BxScVROm0@e?CGQl4U^d&|wtq$jpCA;J6kN&6 zl)J)NeRB9@dky%6bJ6(^Z=YSAD%8-7I88mhi#B88ZV4*zZUrw0E)>Y0 zY=#f+OmPE!fczvbnb43!E>lQS;!mn!j}m|_ID{usH0|4KGuXDtLZ{I9n1E}Xb5w?R zuRuC*`d6^IEg0SbWHU4K16z)V#ilp4Io_o_(FmF(QSHtPz;pmNm=>DV%h}`%BWYbG z$P00Ze%coyQ%; zOa=dKA(Oq!f~pwiccv>3^sn+pAt@+Pw;V$ib#Qq61R{whh?O>7l-rl91uVOjdT21h zDA%T@EO9odnUq@5rbgV-E+Fu%(8Gt-SG6B0c-gd>(a2nh$sTh5;R7nBDGZl03fqC@dWdR`>$Ah*+ zDRdxV`K$Zd=uM)_J7N?{aQSarRUhn>hkkg`!eB*Zc? zBCmVZK%WLNMclq#CVPUvq|u(gfu7RvTy(}Tkp~IO0^z=3h|umrENNRIKpcmMl0I8X z>K8lYT$*RfQ5Y%!NYbt}&;#lU9W2Cn5|BxSRQ^+B0!lYenPk2AW&@cATNF`R9M9dh`FD zpXjKjYX7ZQN3ENI@-;?|h5k@xM^sAHs|c-l8RbPWAtQP?-z(%^wBp*>HWP!ufsgt;1&X&@ekB%%*u2kT z2!gGv$QTVub)bdqEng}y+KBxW;~FFy2TGeAB0;79Yb3ZE*UuS|?#KfY`sEY{lnEVp zH5>c_2@#gXlfXLo$C7VFhmhC)yVE;{bAVzvT3o;q#gRdB`;$zhh)VVtI#>RK7h)FO zJ{c06dw@GXn*by4>o^Y!3vygjb9d&2LV}(PI4j>Xz5zPgx%ouV0LRiO9Z5%P{`-#& zoy3q$pm)l1v-1(P*4dtuX0}aQ1-mz?Io%t4LXgWzJ$$b?0Rtyl!{~oPy;K8XZ5aD< z{AGO5Y_?i^&8^n%{?7K^-p>BketP_+{OBGA)k&t65!nZCqq9&=RDM^C;1=*MCMxDz z*H8yqNhlh=yH+R~sq}ANbs?-rFhM*%eQxUmYKlT)s56#_uqiPLYTrKy)&NEm_Bt#( zqmO97Hu&c+e9FAxec(i z+Qi1hLS_2GRF4MGx)t!XlIg2zQou#yH^7xsQ(Y-GOB_VE=pWFb9SX8JvZ!fOUk)*K zLr0RhmTvK=h|`zMy8{f$t0-_g>IbUKg~Fk0S=HE{>=cGj!}Anoz$GEFdzKvmT(|%Y zyAg0}j+}d+K{afHKXh2KEBvrB_K0fn0ITt?a^&1b--L_PukyqH{=vM*vYdb9vK$#p zx6#)i^TC0N$I^v4bi9Iehmjk#>A;X^cqL6xZ^_XH`4Z?GTFhO9;xnablAteknsM$y z^@9RnNdhtx9;eXc7iGTx4==WWKIZB%q z;2yH55+2Jzz4%AFnMkMk6>=mK%p8Fu5#!x3oqA+{6oa^zj2q_q37k0)l{j)OMYTAF z7OU{3Wa;?U8Z3n$ZiJ{-us_}ZR+ni50a5)F7QZUgcJPr9Q-p8{*#nwA2x~*%$Tz9C zs6l)p1cgQ)AM=$_ph{)PH{L$}oU@NGo#f>*G{hM2q*#!kdyIypl76QP0y;9A=#Dgdu;IyuueD|1sD zd6+l_hQ(G2Sh-@TJ#j@c4=Ci!Uolw`<{Q#+0?z4O2#oT5bVbn}x5UbR16*>6c(>T3 zCCmg8@o!UfQ}!=9L+Z1j!UR&d`?;SeSm)essfP3ywM@j6VnZY2sd&Pbmf_of=%W`Ul?beshx~V z)BgEtaXq?6fR(AyclQ2Sq~3BvS*iARir{VpQFj!9Ts+RN&xzlHbej{ubrGBNF~BOU zT^9^#xr;3sdZXGH2bTB_FCdE7F9Thn9#Abp`>Gyvm8YsfSE>~RJli^k$m0qjZR%v3 z6gZa}nBn1cLV-Il?c8HVY46D?>l?sB-Dw~@);4tQivyE(RLAE+LkPgih~!o*Z-~m`o+AX}RPBOL9ntIX2ua}}*SdIp ze}I;#1xN;Mi4!h_vxxnSi-Jf(dU|qvoE;sXr2kHjV5*U=yMd(H;4&NIZ?G2#VjhZX z*~~3K4LV}SDe6w8CUcvp7E;eOH1TMCIWL%3jZvN&G8f%LA{Zn#eWh*n-cGd0n0IKn>4DPNaxc`VfyN>H$L)EcK=>-Q9E5kzR z-GtjK`|HsV#HCb6S;EJWaL72h&f;n#KcEpBh88e!nFCcue}smrq4O%tQjPj8-K=ka zWg%Q`Zg&T>?#&SDg@pS`&HU9POVJf5eAmt;ySD{u*Ny_ILjcxBW)3-x!MKY)Xq**8 z5ud%8IFG9+;ZSdeq<#RwWz@M3!jfG?J$IAYXTqR^Ip^{p*go#{zbHDU#E&w`2fqo#4@JfDOdi;JJoD6*G7Py|S}IfJ5nC|n928_9 zv?lvUsX8;X^tDl__!&l9p1eIfKg~`*W^bk^VA8wQD_ZfU*d*%#B1=Xk9}1!$p5%ZJ z2Z&<%nS<|%vZ7g^q$9!ntHA#KAO8}z*!Ln-Wr>P6RP6YlrU;enqAg-@d5;07!yzZR zf#gLPy7thbitQICcbeBMZv!~~w>v=%Y8jAvr$@x&@z7TCR2)SenR?J9=1_Js(;M1C zMLlujg-`BaPiVZ04&7X*`92&G?sj8yA5|RKNTCooj%{ZMmy9SA^hb12Ob773{LKbT zHn(nMO!TV-Vv!3BK*;_DQJVC$iyWLGW)H3c0XDQBbfs#-37bQUtA5(-_B)#ivy}(k zd)Nq&qaIGIsiiaPvlGG;!%7|a7LWUI9cQoHa4ev<14JWjx^kNE@J5Yz%tdrGsf;Iq zA(0p4#^#mdNfvp8H!r1#qI+wV5`&G7shx3PER5xRw=zo|5x63XfIhf!&@pS1YpD3) zwTJPr?T|20qlW)zQXkJL51G@3G^OSY z$}NOjHHVZ?ppvB|zl&DBo)w2}3e;lJ=Vs-215G(rfxnRhL+wBZ8{{;L^uUJn0IT%? zn16uVq4Hl25yBNWfy_Qd@ol*<;;vG6EMs%+IH5(_PHCa-5atiJ)@yeyRldYd<$YM0 zh4n>PSTK=#F}`o3a0y&;)6YekX}SzCm!Dr-10!6|5zXj?_hc{ni_S|lC;%4`Wv7o~ zwi1e(4L)5hMiaDVI0s{L9dma|H2q*eurjUMp~Bu{xg_v+2+FQQNJbCGvlwmze&H=k zGJg79t`Ed#ng(j*p2a+zmzpY#J3qYmBmbAMh#V0KzvJ*wC$(<*pNBfrmX_p}TB%K( z@k&e}!{nE0!qgj28?8uN5W&iOSpy(aZ$SC8&d_E|2(F}MwOYC4zZla^s&7Cvfpv(S zDk^Lj(6Q>zb(RM0W1$SU9ifH-RJG8I!4agaG9zX}NrWT$m_%VB4E!D@Qm!8wsHglM zuWQrr*XcfR#jBpLO=g(k{^NYL-nIa$S? zuHzI7%MV;ZMwb!!52jAqB&F&uURQrzZZqv*@qvh=$S_|j3Wwz)Bg`2ogw;T2cHM(q zRH@j*w%Q8_QLy$CfJJ8~Ba}VIJOeEbVD3Vt53SYyrA`2J+HB%T)fnR^exGo|FXK(knU zfFk%RSwJ|a5)xzNLlctV%ueCaw<%5bN~Fvo4SNOxs`DR};JgPH)=fk3TU;K3&1ujW zzV1~#SFhv~o4X)8&t;oZtx!VM!!Say&l!r2_;&5)^J57gC%D|Ms(1W@UBt4FsB`EX`1#zS$N5 z0^QYBd0Dw~WqR!8lhCN=GD7vR7D>2xyQIJnwaknNAPIx9m3VHyEn^*@OsH{XZD5mQ zFDE|N)X|L5ARt-hhFuoN2|Q7azc4bZ0Pc^aT^6;ziH(N@QgGY&zqLLBzlW_Jc2MkU z=--Bf+R@qnCTT+jzwE<+xPXAyP1_J@4~Yz)6gya2TH?HWlva@4xPz3B0(XKaFvS@t zvrh7p?76?5Pk4I-8=mDy5SbaNyltLmXeZ0;)S9ys#Nc=WS)R{5h@+jwV0@48dY-t! z4!Wd4^y0qB+a=hUwL#AyX<&B&$yUbkIOcvSLNe5il&Ktex`MX{oPVGpr;5>lIC~!biGYYDI z{fi0oLrB(d)6C=1>0|rFS~JoXpE{1-BTiyTFgrWxf-X#lfM#E+tTl*S{|lHw!IXUs zi5RprF91(s+=o(pD5$`KO#@10UUa9zukFgZJG>>@M$h@{u0*m&81H0l=VDYdj2k>c zH2vuAB8kX9J$EHS|Mq2?)I>(f$!(!%qtXM)_jlzjHfc#cK_(DG(&aOL3;K$7qxrlX z?{DCaL7aw+7mBQ;UugnR@hd@guwv2`Hj{$Ioa3_!FlM9-d%_SMeZ#OWj+Qzl7L4`b zWaMyS%YQJ?mqr@uTf4E0X<@wTQ z3FvLES!Le1>&`EEEE4dZi^VTvd%f>63fomff;f;L%J%Fy!wbl z$pQb#9AN8U|A1hYnlKc-Z1jLN@ks}$LNih>nF{y1$0xGRm?}S*wiex~WDEP&7LjtT zP5z)TJAQZ3#TXd{Xtg74go6xgO1B~XpsZ1pALHOmrnNK6?|=9%gB^I0S)kxU*M1Us zz5?CZrc|K1qlvqAGr_WD1ZBGMHhZpV_(mq$8WhzZvuSrkNm45`#`i)Xq?*sxjw4ct zwBlSXwx7_^Jw%VrM*Pet!&GE13Fy;cyFJky3_9Qn;x#Moad${+H@+`hK!r@Ugj^+c zs512id{7`pi)1j0`eVu^k+0ot|Iz8pXH*AhZNb3KIgt?UDCzo*;Srq9K2*1zB?H8Y z-$CfoKKrNtA&Cv|@#%9s{*LOzq6{dr$ardg=O$1vtbKtEO>@pka556lif&qjX@^J5 zo@*oorvyC6C6mQw(%i?)ySvVe3`>;6=Y!9}fpS$wIKM5ZlT86GqgPr&Rr(Z(tF)>f zj`M6rCpr}F!&%J1A=;lb=T`d&t#+czZg3E|oP=@i*u!I_%$!khSKK5vXqA>BplMlk zn19fcK4Y0cPY0iqYrx}#8#;pHi<+Gt4!xl;>FtX|CgD%SlP4!;HWRpI`#n>Gd^!p}NsC1JdSabgbFTdX4Rd z@o?fMv-ROjjA@LaQ0i@iL1)`@XpX57;vMB`UHkg&>LXH^!*=n%m35*4Th|IzS+WPM zliHfi?9=Af3ARaUQE0+z0~0~sC!G-s93B>skjg-$=?p%dj*SjQf5?M?O^}ChFyo^r zaEKkb8HZ!r%?xnn`T{!mR+RTjfiBg9;P-4swFp)w24i+&B6@e_?OaL^s%Dxf#ar~7+ zbQJwnjbjL6HZc=o4gkSYK3_~mv)LQWEnJz8Ix}SHmzYf2KKsC#wn2bl_sobx${5ie z{}ac79TPaO9gJ0$cnTfcc((pEUzd}3d(5;w7m%D}OR{|8tSNz>TF9cj48oIU zQ7^>d&4|Mi=b<;7fVNN}GK6<>5JXl6=e8ti2b7AW9pfU&KJ-*_$nswbMU3Xa`FF3n zDpIWYQtb6qvNbz*7TBM27x|dsAcnXP01g;$v)PX^@Y2`>)6^5)AR2U?tS7?-J|Nz7 zgY{euVGl6IISzyFc~>Odx|?;TdJ!~kO*Cm;Arue2am-a1fiGn6Bu3$NF7%$q+!EtIk7sOZz}(+jIO6 zJiIt`T^ggImX*aT=3-HA+3KU}U6@NrQo2#w9ViGj7DoW3{62Fyl&>EQe^N#ghiiBbo>GIEpuxh~a`#j-or} z;m!;?p^Im>bdHe7aZ69T&C5cpP-*Pp3b-S(KK=kZ<#;MQd}&VigD#wigQm`FCf5SC zo9>;mGc06B13l%u?>!tR$^Dm|`9wMpm9pFjAe#iF$0V&fBZGJ5q!#90Ft-TbY?G!l z256C~+5V8wa40cQtw~-Hp-qA5urZf3Xx?RsB-5M%<#WDchJ*lQuu;&LE}}jz8={Vw z1D)i@#q39v#z3FN8=;wxrg1#-Q-Vhr?@n-Oh@NXqkntFk1pyx0{5O~gWfZY=H?5Hv zne)PQXn5-MOu*De8%*icIi@*Zj}qSzj6&3;-XyVN)`P@nvO_s#cBB_v$5#6j1BaY( z)P=)9UYc2K=e&smO9@ZaIpCau6aM_59m87XdgX_%yFb^FRl-`p9ITO?h|vB%`o_bv zTCRMVkHj!TEx#HZt5%?{v;*qO$1Jy94Vqfs6fkv|NN}`*d1yKa?MKir*LvHJ8*t*w zo`@uw`Ai}=v7z@nh`O*9sPg~&j__(gqzyJ!{Y`%JUO?v;XjUKm%^ZLX1r9VaU}gTr zNE-Sc0i0c{{N-Z$l2CA;Vlr-=*B9vRHa}bEi(yLqsEu)Ehh*pW8Eng>!#DxC3%doWa5yTRA&^R`enxPaAD zm>pDxErK|-NiH8NNS!NWEmYAA)9N6CiKq!ItT8a=P(mAPA0H_WfC~l+!&@1&5?HBp z7*B<{Jgb?);2``pGsV-X=Zt6C{ej#meW{E3d>Hzd)o1i%=tLGK4qtwDDXsutyqk~Z zh+^eO#^9Vf1_>sF*}m4LR0Ct>=;BNgy-x)sfee#&i}U}V%yFasGin5m_#B?(9EQEe zrd-#NuN45->Vc7UDZGvG#@F20hR^TjOBm4>6e`etC_M*KhdU@KTC>`Ky#Pc6Ov%pd zYAM`U0EDx?bUeWZdYmL-kpf012>XL{uUcz)GvJ2cSa~H~xqq?*dI?YYm9BlT`x#sz z;6EZ+ha3qWs|NU#?e}})kJcK!0X$O2DCyXXc0Nq52IK$57EpGT*~y%E1pF&aWmU6Y zstSnqxFIJjgTT+(*_u2)r`(L|^~kn5Jf;uCx^56uyyjf2H#!^({}rQ;)Qyl%15JYk z1AmGc;80|E^zJmsvU>lbigZRK zQ&IaakOO;UCZl%?B7-PryTsc2t%KwvNXxuPWYLdorTj)(^u!HHPprwEJ!y2V@dav) zSWmyqA@2j}$JaJc00q5CY-bt;5d(Uim*r$V#XzXWQ|?zr}MiDIwpwmcbj?)$T& zrBtW`JTup;W{X^gJQk}|t!k;}Ru$0;FO-U4NDI$X>#GCqUU|UYQ6j`2Fsg+p>L@c( z`49Zb%&9mzJB9p=8fX`FSv#TF<1|1HL2V=X$GPP0SofTvpi z3-J@|U)mV9gPuy1WfXam25tB;{wPsG7T%9uyWPwy;34A2&7_49i$gf9KE?*5oo1=b z&gJ0Bt#gEi1Q2jjQ@~+0+xo_=Ltp#oMT#7GdXW1=j8RK_E>e3Aezcqi4a*b-LE}p z&7ptIJ;~I9)_NYe8+yN@LWfzd?>Ox~9?d%;&L^O6nrnjYPQc=y8CB zCX9`=PebJz_*@4j@LP*cb$YYT>Hyr8xpx0=;@1dOF`dq|aGW}Gc|C3yGq#3oHn5ln zk09SKb_1FhQ$lYzY*Z=Jacb^3Gpg9CNqhR!?WpR0Cr}H>6ua?wSBO3!LzG2#CdymU zeCI+W&B5Adg$GD4p_7S4w^8Q~n3>BpWH%#Y45J+vbNLb-&!^UQRiPI?>k3^F`gZXD z%Q^_{6bG5zmsCB$*P1L_``s;MOLYx`V)vfQjM_|ck6@pyLZwhXnDV{*b{BFN!p@gB zG~vPELoWyYVQ=mRAjOzt6DRl(D7SzQ(8dwPAJ@cUFtV!-R{`)(DVc`~4UY7pY?w0? z-C%`b>xwOLGWrE^9f$?pMUVzeYQk78tGX0o-uk&rwR$V(dBge2HNcE_hkVxk}oJ)B7_FKuVT{q1Jm*+rAU9! zsu|FzoeRjTe{$cz-loklqRdl*UDO%6PL>0O4tM0-%&2E zr^S9l3^?o~QSp`{H$}D?|H>J%xH(Y)Q;(n`Gn#L;nhZV?^yG<5?Szw#MK=hl7K8Dp ze(wY5us$VsW=Cg-+ok4rtn)~$d5{om$^tULSa$@e)@Yl#5gBghTr5# zN#DchSq-&KvNuZ40`sGgJa>k7o%>K5Cbi_^b~D=XJ8YkE((&B5vK*UxL{16Z)ok8` zv!(GX@7aPzG!t`JC#u?DB&Rg4=@d?FqC`$tpz9)==Z`(vSmb&H$~M6z#>4*ETUcs$ zxUCk+8qb2g@a6k#0>(1#D4!P%wMhFBry*`6d+R&GKzeY<@u8pX< zX&X^|5t}&JJF)!od@^C)MSO7CUv#gN&y!hsnL;DKC~UGWi6IKT!*92|zsRrDB8Gm+ z9l#2{;{>Qaw{C!tmM}iEwH+9d60WCE`W^RG(~Hu;-i%4u(W#NHssY=0HY~o3XUi%h z23z$pUD}ku5JkJv(Y44FQ$vy(@CcJ9mzYDbCK8wd6xRd=0fHs z*IH-rh^j#B1;^Gx=QA14o1J~&2!v3i7LYDnov1Z+0X19GBs%$-;Wkc!ABL-!RyfK~ zU}>VO6S`1SFu*8x`MTFTK`PVly+vaiTP#J#t266pOL6gB4dQJw7GbZlI#P{dx}=R< z`94-F0Djr2h|rB;*R<7<)K~*_!ZvkH_?@iN)cmXmFNg|j*9{1G=rsGMnRWG6l#yxG z*mIODIU~g%^Th6i%B`EJq!8Dk7*40+aYEs!LV#9nThUeTQsTn7YKrq+$Ae0@dpp0I zVmmBEjhAfQT=!fDo2jTDm(QmGwP07FFQGF;gY{r>J(?r1eL%l2Ztlz`oGPMXqZn@E zTP$+|-Y#Ovrq+i%$Ujche*{C6yTqYmUtz$Q1Yy@GO20PvLHS9ewR0P{GK*ON{ zsQ+e69A5{sL=#KX02?mn;Vz?qy+a(an7GAa84-u60$vl<;%AL@#jdWk5Kqg zoD-cC>`%fU<{y|(fQMCwzw;Z4Q^mF)eA;i-`t0&jxj;KU?%}zm;u?Vwz&D-*P*jK; zR%#mV&##w@-sH}~h*@sKE+)=TZXqijGF?E!XT-OdeCo4uHG*>WoU8*epV&hAU~wpz z9@%FUS|~;nSr_7~Eo(ElInWeO4N`jV@t})y34r?mMr`9X-*4Y^f_ng!mBa#NoI4Z? zSZOGY=kOV6JQo${6s@d+Zmn}}O<%_@Rx6%&s6T2oSWuWIXnnID6iH?1Rd+dqeji5N zO0X6+1zL8>;L$$Nb8TFHgWo}D*he^UUB*R8Z!&GSYi@T3v+i;T-3cL4<^5af6j~`f zQoa-GNiTTd`BxO&4vV41)hv@F-ch-^)At3rfRZfvfvvMaf(#thjpIC_b@U-{ngesi zwqa3|ZA1{TC4-0*0yLB?`!0=K*QSyrDCY@}l(Gm8w8Iw|wj5M?^7gFL?)n-Wle;zT zIE>>zhIl~RspfE;C}4tUQ8Zr#81o5mwy6L7!H0ai>h!*n_%$fQ07%s zQOFl@g_>{l>NTp^IY$~di<%G!dc8OY9(IM(dvnnk_wVLX1ag9ru3D=doL>5#cvx6t z!M}rQ?J2A#H!!w#X=!E!wdpHwokZN7N1PSbmi{TBZUA{V)KN->vQ8{0xIf_ltrHL& z--~HgvmBnpE%k&naCy=Tj41SXq^GQqL}z@e&!_Nw)+d3Z`1~Z!Md9F}ZU2}=N(H3ywb8Lc~INHYL)lMhaC(@ah~ z%f&Tv^#Pzll44--(aMXgl>P14?w=Q8b@e*a4(e((CMz3<$W&Qe&L(SdN>Gpog7o_A z2uhA`&SUa@PTNO6Cr5ANcOWt~x~QT6>N<5`AZGiSy;egg1>v+9nX4p!T@?mlKU;#oU0HH zeKo(H%oZ}O&~20lyVr5tA$xQBKFG3G+KxKW9rzaB*iaae0-hZ`rzKd(A|AD4o6E69 z(Z=LziC_DBYVvdq zU~=pwl6t28FZlu^1_>1TU;0h12|~0m4i71Yad_wmLa$V=s_Y%E`l|pb2}3m-fU7@S zxkkFu-f3z7ABiuJxhnfzyYkDD(CP4BOkVXABo~h=`Ft^X>doc@ zUUu?4+lMFVjTPigEZ|Px^41A>iV8|kWKfE4O3xr5Dss6q@{s%o)>Z9!YAAxRgUD*(*5 z13ae8)F4c>L-0A8=1BAFX3dEHp)H_3alMcz3ML;!d%-`P#U?;O%a@l}} z0~h)N;)5UlHNYIYFur`CGk(B$@roIK3aDuA9j%)BPU z3LPlI6hH@@PFD*Sp@hQWjl0L_JCz&lmuTG}QIxq*+Xu}(XY{)Scz}{(N%2xq0jkSgk50f6)YE7`8dJ#| zLf*Iasv#!jXgiBLmh=TvF&NHq42MK0*oq+vmZaBWB3Gmo`D%!@#InM3>1I>~$q zc`Dn3{4!>K)vSBihHq!ge6Zogj+ce|BQ%cYYCct6F%MLxY**eqa1?d~i3|f!aduHj zOQGw9!{ITuW)Nc9aoN+t;?;0+!GRYjXDM}MJMB5e6v0Kx1}pgtOp!;OLMJAo0}mFK z$Ta!l_rgS*fr`d}0^ZG!RYcF;!MuCj?=6S;RaJBZ^a~@v1Ag$&{>7P$k%1<KDvR zE!X3O-?f)>iR7Wwdz1xF zP4~#B@g5$MpXw5bRn>|*Mk7MM!7jr86y#;x*wpMeS<4z0~ zNWH&!!mAo!KC%hq=X?W9{V)55%xQ~5TqGoR+rBKTd~x0zZM&Diq@(Gda9S?uzX7>xkUxPFcyq2NvE%b zJ^Xbx4_bcGgs`AezLtQG-dI_OEI5U(LU4eVD)I*>m(+wxQr1~^3^_Ov_;YfeoFcAi znDn>i-Bt<$DxkC3{|)9Beb22XQDs=%1|BpTVL(guYgyNtE_1Z)*IC2+>|%%N`J0I6 zQzpIG4iSpy;~iz<`OUm|ejHtbc=>Z8Ht{lad7Q(zF^va3xi+-X>_Dw20UJvh8lftgG)F4F9>SjK*{{hs6-Wp_Yy>_{y1f zk0#oE7+{X7wwWE#u|aULH|)R4a2OPaC=4-l$1Y%3I9fo(`wgfS%0xQg%>;Suw^B)- zUSbtS_f@uqbz1K_zX(I%A>owmAXkua+X1$`&aMAtx5C?T%v4$IOqanP16xeVnxLRG zyoa^qEN$p_E&(5*!e~+=dI~aij%Iv3YD|}l9BIxy&)iUXK>SZIAu)vTc}s5xbJm+s zbC!Kr0^93@(P#J66rpN0bhq`W&a}qlN8N6YB_q^{hKL{g&OQ^dD^-<@b|4HT96HRd zXB{QJ({Lk=wM%#x58!Yc5blKp57jK4$H`K)|Kfmik4yV}IU6-7t>q+}9A!ita`<3> z_>Ow75)Fs_SvdgDp>W||j-mk2gl&d!<36~MXt>}k&rq08IV+T|L8Wr4^T?1@xQL5L zA7WN>`72hln6OX{!6htOVIwgTUWwI6&Kj2pG0X~>HxWElGO+Cy*|0g=vt|+Np||mN z$`Q=n?h=FaMtnkgT+1qQH@9i_b$T{9z2WEC2JL+%DvP6ms|-rq?f0X!(f$?6LxLmB z@37U_Zz?P1K=C>e+Uodttwb+R>O_OK?3=8UE3v2HLCrX{uSkbWsD_Z>qn4WoDzJF7W%-UXC@Lh1 zNL$PCbFb7l81Le>p0KtmsBc9oZ>fM2r=0uW@vbH||ct~7ohlksQAo=%e_mEi`p z#6c&xv(1%T{8Pr|;QowsyPg_f6}pC66f>kUbd5i+K-ZWo1RWOi6J&k1iJ0-V@+Q=( z9#mP|9pH7>5#YMd+gn>g!a+AiGQi}3#Q>aL|5P1xvr76fe|7TtobA>{QIOWu1&6`{HpSWxE zQz{K9fp zYW?6k9uOnVMl9hv$=_@il@%Nf!W+wDvTs-w?O;An8tfPh>H73s%%1(Qr~5V+4yW4YcO>8{h50=p>VZHQEy|`f2&fx@oMVSo#uPP)UFp~=*!c9YLJvhKUn;v2&a0%GbH!)F0Fo zwaE+7BT=ANW?cXH(TH#>-Yba4T5j$L^9Q$``Brf@QlK3*#*6Dj)bC$4-Ej*rmM71D z%aeTV=8%5Pr|xBN_w;lrxBI~78Z1Nr$t_Z5fS9n`SS`Fa3!Zs-jz$xR0`z;OuX2_f z8`lpH$DMC{yjs=zyiB!w&PQ9ZDPN}AtqatB0ju#$h~>*vyRSfX>I+S^`xbqJaq!b5 zL4uZ-AO@zXxHhaTAj^4nb0meMfAwdRuOY})R+38`%4mo$&29e<&23F>Zj)xuq?m6% zI_nGB8wHxwChU?upl@uCBE0inX@yTaH(o190Q7OsDK47D5OW!#VTwo%kAS*XfQqlH zP(}Vub1NxVw!_D*sciRwJwG8?-gN?7_d*l4-Ph?&wO9{!^Ud!OYtpDdQ3)UF-4y0> zsP>%LSsz`7?3tzFjCTCB7atZ;{9)1J2$kd@Et7(@(L-bPJtyqRtUZFWPAX+3&Qhkd zM)U`_#mRFaMAJmyT@KF##?NlI_B;mGiPC65LkuhEXi~)GhU5DxL6K6?6+*T-fU_tG z?GUT|k1Xh8bS$-h6vBM~`7)rvI$L&CnmLA!)tl?Vu*Zsf0g?$|VR~-p`)wixLUE*F z|8g;!s7`f37FWK{Ez+8LY1^%3#ffJ!NVfA zBaYF=?$A-xIlMr`Bg-}la}hxNw(Ou&%5nx(=qXw%Frc3|#Cc7+dr%y>pJHO}Vm7^) zVMK0Lh~iOXY4r4RR4QYrr7}CHd!Xp$Tq9%BPY<7Uh znPaI#XdSy-;y{z5gV)yvW?}DS_}v#;KVh7KeS$~XI6g@@hSux0o)XfXFFww%^i=CU z!6*HtGb-S{a8DOm*CoCFAackKQXiF0LZAQ|Y zB}PX;^o4Lt&ys$D9KagFDX5IW`@pHHRSG|{w7LLgmX~&|F-BGyd|R^2 zFtGpNUP$#dpeU?sVzaGf-r%mIu>h-!%Cq=WjFic66U`PmKdJh$`0#9>RN#&NI zAIYpw-=90cLEVWlI?GV8iatt|@V9)q5dlQ6|5+PS)Slbf&%aPHt{E<%*m#L8jI?S~ zwHGx$xx_Xr*8w5mhqQpxF-*Ua+Xz|nL{)zmlcYZxHr@}qpMJhZ?xJ2fWfeRs*3PMq z=OqSQM`8uhZq3Ae18%nOlN2S1(O}+H^)gXmav7dD;HW9#y7sWnL$x#nDtS$x`=X$0 zS?e1l+uWQhw1>nZ>6R6-nd%4?85pNeq3~71cps@m|1%Wu1w>iJjBx>Y*5(=@5w!Dq zx^iipkn1fLDkHt&OR(?~EFj5ALm|ZbS1?r+M3J-8Szb{#+(D8c+P0Y3`Er)!k4tv2FH1odRZMOvYI5X3~dgGMH+VI9zlYX-|+mO z6JRkz>A+FnsH+#aiL7*1UD_YM9HQi0lJdS8EK-msIp}RIK_2DxodNj)f`}Y!gY~8f z^~eSrxk=uW!C-FqrpV#yj?Q%cKFW8NYX<1u=OeE@#T?0QAJ2y!oSw2?6(sfD_ypqG zZX({A+b!!s9uu`A#RlOX&lo$4?H@pFL~Vi3B6zG3$HW3jbvLDRd?l7_4yLU|cPb%~ zN2#2=VHm-y6EBKM>78Q_%o~5~Y#k(T;?wiw^*{cgHvanOuO_v09YMjL`-}D^24(2+ z%ATWxzbDKS0}PhxwO zFI;C;pK}M8Wf6UgcH`epFxqa?oeWiJDvExGxC!x_H>Yo4qOJ!w4fKj6UjGX~uQ2#0 zGoyPVSnJ3ny*?@fDiH3On|LjRgI#7-f)Z|c@vbu=3}*qp&n7cps2KMby$fG&+;h6* zKkbMJrJ~Md8oP@siCrm4rrnt?Zm)GpENj2cAduIxTVtoaByj|KbOz(giN6bswxD9E zG!r@DI%w_^brSW8GlELIsQmHc<|3N5tQ3 zalRNwT;XZ}&0R?^Av71_)3<|S4&b{=vX0d|pY&910;{K*Rc=$1M*2Is>=y<%!ti(F zQFAa9dvvspS;r)l)MvA4Y?RE&rk|2OI=v6vNxvjJ((%j-iW!x~b#I84Uet`R_$?H3_O!!gt1VX$ zdrdv~9WipfHh~Xr=fEF3!_jP^u8I0EKxTExh!bbGgRWyEIM>Xn~O*Fp7X z)*hn%2EFA|H;U&^RwnLc2#$0qXz8DdPV0b>w)=080Q1QdLeWe3z<9 z9vAJ-(2MK@57Bh59*(A>rjEwe{)tQ_;o-S~4;95RKbl+?ln7Bu#SPGb4%LD9{;+J% z7oMi6A_Yo4Il!2u>o;U=zOvgJtgL3R zaXRwM#+`-WVjZvLUEY%hvL18Lst-E}tmS#mb6Ld^G5lWS`mhtp72Vb-~KhSSz;k~E} zd0M8~rUC)~>0_*SFP7a;lm`%v#~Hitd#;oi=zm(undx-26t!4qT>17AsJ*<=(mp?} zUkbh+oI{D9zEi7GDGil&%;gg14SHRzP2&M8x5DgvZI^1c&H)dZ2V1Y1@QbeVy34s~ z&)gThiCibyimes8xra8pI9nRld}jJYnBmjCJ$fmm9mGi9hA7M-QG3&jPjsfw zb4hu^gqoPA@{AznNreX_OUdk7EA~N@wm}}U>n2RYho|gKkvPMn4l?E%EWB+FXiB>x z^uw|=@!bVFc_y>Y?A}Ra8#H3k$J67c21*2KFy>UA*@_@ z%WBa!$AkAU)R=D?knI?r`HYa!_)p~0&*|v z3vLr;W-s2w)U zDr|w$iD4e`Np2S)YjmJVnV`(IWJ%*}aR6{_j`1qVEe2#Njl!Qa+&S(5Tt_??7B)m( zE9k4bi4Sm|U&}x|V|!T9vTQ>Tmt$fjMaggi@-b!fSB)wCv!t%mSdP1$`63G?W(Ems z#JX~nFXVa^$o`wOX_SE1ck$#D0;5B;<~3Y3&*6;OZ(a#v;?wZ~pBg9c-@xmg#&6mu zC^fu0c2#K~8ThI=*_5|~{#{v-KRXt5m51~=l5>cmJ9I(G%HB{uENck}0h5&z>W}?_ z+2OuHJx5RTx*b_DMpf-#SXD+K>H$bMMQLKS)sC_3zWHHLdvngF&L~f1!r3o#fgzRf z`lh*OE;y4dSeoVwTmchx|dD`GL;`fqHPQ z447LhrQ7_5T!Q=ZP9RZZ2}J?ye}Uze!2vG^+@v~Q_RWdh+>IU8!x2%4UPUc?P3n&;N63do;UHbxxS~q3G!+_< zJ67KlAThI6MoOO->_*+mhmX_ppov>f0lq2dAh+J>hp5raAL&kpJjRB>4)JPVg16}^&NFOgu8=M;>X6;uH zv$S=8S6cP~_KwG(JYq;Kj|P?v>a*wqDHPL`O!W>p3PA&F39ZFO8~M@~}VK!=5iM;^N?=J9MGo3k>qcY0vkv zS#`MHeE|_ddut0BHO7bI|C$@;F zR|)e+z@EnaE9q*7k24tXfBt7>V*8cn!C$Z>R&M6k4<5hZrG~D=+0-r4ij={Pow7>U ziLJ&qkXps4c0~hrCL|ZsIEKY#xt>Knlc!`&@*TB!mymhUp|W@+`99mB4(pKGC%C@6Eqe?`5e14! zJBfrjUd+YLtQ_(fM@{s3P?kelZ|VKEFvl(+n{tpVpnCA<4?O0}62m_=89~&@kCI zf;2r5E&;M#{cdvCzi^TEt-ANdpUJTZhM@*M?c8HvMUO;W4oJgLKtI#xNkrf`Wg#8p zMZw$tmPr^0Xs zfce=6{zl3HI!#a<5wRQQL+N%6{Am6yApfcA1;S!wotb)!O z2!AcxLSdjxh3*9FCU^0E8zEM*jdO^_8f=r9Ft8+ae}DtbL%-d|@%LL;0!Hnk(kQVR zA#$KOrA!iM0sVA;#9S6phcnbP7EPE{ZdnIXkm3d;>aTC6+y ztdjXc9*CN5)DEAWRbif`TCm4BDx5SOmD51D*h5?EO@$=zG=#lvce1WxASA38WRX`b=!Y*I`bKR zYA$KVs+hfgw{zd`@i(QbW=9$i#tXi?owRoGKIcJ=Uqfq%DKh-AvwZE~Hxa6DGpC3V zohCTxOR`h66YL3wUwm73Z>Kv%CD`pSpU?_IWu1kx`*goJ0o16e6HB$NPR#`i>QuVC z>@0^=g2OOQv|av7GL{P5#>(suB##>?g+SvEqmpM#kUPuIu>31j0ii^b8uv6LwB)>H znTrw_k{*V1b{5_1Htf44jdd(ExBaSf26N57c5GBH=Y-v0J0Rf_UYS(7S3Spm$JqD`uFuE4)xD zdm*nBm4NKI^U~ian0h2;ji!wO8A5PuJ$DFV#DDr1aIt_ko({$HrmD-Xg_}<_OkHrM z;8U+}uEQ;A;j%466pSHlQ-pJL8}HBIl4Q=Ff*zE;{5=)s$i)BtbAlOJSd^O3T>3@C za&QC34x@YAanaqm`pAg7sy{Mpk;jlGr&Iu#iD5uUi729U3<=4L*b%vIJRQ>z)S;?+ z-kz4E&%hMl3b%;E^R38YqnAjPKEXe|9ZZ&hdJt^IBjjF?)pnx}ef%Z%wJb+MI4I4k z6@J0mD`3AXBSD3y83)Qvd!0&k zXxEj%j)Ly>XfODO`dsdCF*sSkWh-g!0=WQFQ{M(ZjAnalns1*cF7QDBCd%e4f+T3c z2bH*ia60hX;%geng9g(?C9|Q4Fa(y`yH8-_ga{hsFd3#@H{Nx^6sO(O_uU5O1tRmF z;%AVe?KMtZEb2~GuKRxAC3MX%nc3S1NI)~%l9k-QxE9q0A_cB{RWQ!d+-%Pg!mwn( z2=)Vvz}ibsZfQW{_=CJ-n-@$?SOuTsE&0X09#)HbNgr-NWpZ!bB4#0BHFVMeq+(v; zMQ7N7h^z)x#r-dL2ouvoy$Fr8w|sAwb{06j!$YGUWKGgaH1*$&hV%t7&2JEX{q9GQ zR%HG-a`Gz1<7bdk3z`;p{m!TO^mDOp@+YGR30bZu6Q|>?Mde(F^Tr+c^s=axAjoJ_ zotQ3g8$}fSH|r;wO8i7)uJWKIB_GIhpy~nI0LZIldVKDXrh$`3BP`x&hFB*8r%1l| z7D;mdRvemICXXn9M5}nAk7N)mP%5j^JB2ufzqKHTu z^4ay^a`9{XEIlOwylLmeAx$DXs=9tr(`k@Otua1DjsgT#u%k6$sTKT=BrQ|8Nyz5p zvJ-t#Dky6aI6QPOF#Y)9yZ;KCw?7cz)+sChjlrUqo#DLi{ttRvK$h1@;^}IXO6^o6 zo>6t;30cMR#Is6oQ7ZV=iD#X&{ZQ^s4cZ4}Xff1)>d1uBz&vo^z@rE;=-ifv;HyS| zVU~&sKjZ#gs&Q$%4viH+u&&0*cn;d=zKwjy*S7{JM1^E9%X+@p9UZuw?0lNugT|g2 zgBB8|taTu7zG$*C&GSsKq6n>f7YUyJ;L?fSheoP8o5nAu!j0fs>BJ3GNPH>aC8c&D z52sB1;~Z`vFhMz#a1V-qeYW~QzDcvYv<#WLt?((=5gJ*eqQXrB#dHAYVMTe>Y%IFT zZG(n;$i>j5CAC?0qhYH;-pL6^!Z$Ub8w5aJ9ZmOoi9`&IUMNn3o+SOF>E^3r4(UjV z5Ft-im<+DeSIRSKECS6vFp{9NeTj7WhEK+7FvU!ND;2VP@w1aCVPf)59)1VSk)oSh1#)LCDs^rxaKcilPqxkMH-L^t=)tY>vYF6-u6n7hCy z82#emshpNZl@jV30`Rb87R!?)*WqJCE}keY1WfV4(iEn9l+lgVJ+`!EjZCb#fr`Ll zHnG{0ejmvi3{SA!yo8rRsoQCG+CEsgf_$w_Y|H$zrw?(Fn0z{ZH(nEz{3P!~eBnuTD zc~zZb%i#%pvV#1;7ZNoQQK6WTT+JrS={Ef6$Vby6jj}0AN7T&2 zP!cm!86Cvd#5?Rwv(LBDY-ykpZs+1k1D=lTlth zBEl?BfrgyGZIx(*+u%}VA3~?Hfn|pw z3zwp$D->j;MZC1asaWWsCkKsf2GSoheJci6bVVx;MF0si6|G_y>QN!&k;QA%g!3dS zjM^qB8$tRb0bSaLROGM@d}RqQ(82vDOz6=q602lIdH{A8sGIiZLJnt2#<&LizJ{oQ z#LFTbAgs4|pQe2Qut89wj9f^a&&Rbfm_iX}t>S?^DBmB$tmpMY^QeCE&*QVV7V;Wex&kWnDOk1%v(F&N)N zf)ym&KB5mKj~Jf=^9o%F;Uy*4fOfEZ&*u2NY^u1zLXsCrG-&m7zrt0s{>N38I1pT zwT8yaRtVwl+;|xo=5lyge21=UC6Xbw;46}WO}E2S__G~JI|E1N++Ce;#gwaZ<&{jc z10oszH4?TmN)^9UK50nVbhJl;!ji_;3ewUbYB!;f13h36rxJS6h9>tKMy^kKi72>X zb~Mmp%%;KbfS~!-QSjxpp1i1vocKJX7oXw&fOy{`c$bN4>C_`5nkB#B^TbpKE@iF# z<&ZW-3T5U;W_PFG$akbp$bcdl4)I_5P0lxDq~BaW_$wBOyot%YBRn<;etP%v{hMD_ z*|+OwYwMXVOyPdM`|#{3%#DnUOcR|iya_RpqPlSt$|7hSbKqr8QSHrVXge*sH zU?Pp(|1xIuIC&W}3XqWFC0wQWp)sR-S{5dQK}^p)k>u1Wt{F5a2lF(Wv*mcqQpNzl z_cbtX#r9BbLOBd0otZj#?NF~sU^-t}a#1QsYVl_F^(?}a;T-)avbj_x{aXW=rT79K zBdu|DhR$xKwTy2#{;h3NUo_~dHRx6ps-DUHx*hoPP^uq_2C@&x0)FM$>dR@x^Q|5( zu<7#XmUua>o!O=<=idL^Dt~|b@p}Z~3YIYl$<4m{{`((6q*|b$^a-G=$A#09MV+)V zP2ko-!SiJZ$X0~uCgE8sS)@vJfP||w4l=a$hoQ?9D&|FmOgUqIJ7EKEL zJ%LX1xAB&l>pp}!!1|KXCm*#S)OZDIKFC04eW$A@eS-S+=oMw7>Jd|DmB(XS2x-Zs zPtM@#KREebeYwLvw8h{o?Tr$c8*5^J;NKANhPQjJOZ{tYk=wpv<)#V-$FwRDN*ZNusk5cX*HbB^AYb$!Lj47@;@X*6=SX z%((aCN7wz{a)|PTtHHBLQ7CxjJUgviJl(p5Ohi7!0A|siqyYBe{^Rt=6o(h9fsTs7 z9CxugJ#Ow8&B4Y=$(ENa86S);mjg&!sL~XADIAkAJk3Gv)C&7gCzg;_o3X+L-M1&2 zm9rxh66A7@iPpVU?Buyz0KR8FMFqd#z?8-04F})o!OKfmeUA4p`_OYh0j@+ikR9=0 z0gR?3FP&jtZh!W^fA7dk`I8(xr8O(u>uJ}7JE44zLw@%L{CylvvAe?H--g`{D}rSN zMZA$e_FhoZa7kiM!8*aZLI@ukViOW2W|ka#GV0-B&{UgFdA)?ft}DBy`T#)EIJ9W< z5*|+@tuOdV6c+&91|tD_AcvxV?8*@jQl^A2l4@PwYMVDKkYaWCVeCRgXBjY6_Wn~h z1O{P+W{6Tfap{F3FM6JJ{4TaSv?#%<0+7Y#3&W31tL3fU;5PpJxpCKllv{5p^e6x^Py}ln#G|EClAKE? z4}Y_){<`@!IDT)~gLtIY-Ji|}Xo$_9(=1m9?B6tI zBilEBn6T4)NJgHD+kDc`*_%N+h}K-rOrGnD--Nyg9iW__l{`7v$3CPx@ADTM7!@=9 z>&y|}KrCnFlp6caRfot(62;){hS3|=tH{)$If@(S@7^Xy$45VXIDYfKF-MOTSfIe~ zjygxwtwTHk(VeR$S)^XD$|w=5rze?yN4lfx94FYplq6DM{7}itZwzh{-lzrD2qk5v zv8~NYbMkWnJBA5VCN-q>_3x&gJ~7T z2V^Ut`A1UMwa7XX_*S}XQ4KR|@1tm%YTz3u&_Y4)+oP6%ry{*fbdp#1-UMJ8DT|$y41{u}g-0w5sM5R>=ZA5p9ye z|01uHvW0ZGAhMy4(G-MFF7@$Ip~I-U<0P1zn7Z41JbcHmzH2x^zH~H~MS3}m7$jWi zwNht~LcZ23Vg3BBj48ve-cqMG+gLY8w1vFK)C;VuZcUPK)M7(B$i*|P4|?;^gg$EE z8<*XMivc$!Z^Na(W;Hyy9$?s_oddUDu4bgW4mxAWh_HY{r{qtQdrIf=pE!N|x8^$h z!M^;vhmcPFdVaO9y~+(-O&865o?LcD17HU1vYaD7 znat2yuwuc`Q^YP2Z5k&aw&sorlrr?8mKL+~**ff8lpKr~*9jyV`ZI{8Mrp)Y#|xIR z8kJQbr-{d0#6gtmE4Twip18ea{xdBcCa>WK2Oe0Ee?q+ds;xt2o8wM=_A#mV7u!s~ z&)4G&A=~wM)=VBWIhX|bh==44?bo(XQk>kw^ zibc7z-q+xl5u8CgQN*%Z2(0$dOR|{Ew%T$9@xc^v{3VW`ljaCRk&*~J7Y(o7Rl$5; zjv$OxNl=WCh`YW-?B*^;I|T{Dclfn}*m-!hYfyGAMn!D?g@NA`KErKBTRM{R1Mt#l z>m9Z>dQ8sRZ_!Y|XTLyR;wRAz2cyA4rHM;N?u084Jg;IhSK4TRR+g*57;%DvQ_RCf zs#&fhi0bPcb^|IzJ=KzzIv>QW)l=Mk6kU>cP-rGDn7TbQK|#5p8|~6ryMF-LpD(h0 zuC^pO^vGKg%toL_uHOak2W#z|A}-BD;T#^6Y91<@B;aFu^_*Eh+XoOS_6|Gmwa?3Iighky>N+vC~ zLWDs>9g~ueGMZbOC_$kSL9ks$8tm}>QrUyP=|We?b@#YyC?y z)DwV?e~`Ln156F)nw<9e@DvYomDwt;bIq!FFH zr3_s8=gSVjkSBXY|K?0(KSwu#-Zq;V!SL~d5zd0Bvs_HH70xzHe*OM8s9-e^M>R@Mm!nLHrBrw-g)x9&+XhdCLj z5E=r_UsXvN7NKZ-Kn3IP$dbDD9t(` z6=}6X&Kmcb<4)D-fZcZv3|txjE2G9grag4m{}ULgZm*i$JkBX4M#tRe(j zA`=nh%nqj{j;ClPQ~AzSAi9G{zQofH^uAQAVg}DG4(1ip*vHU(?xs44__SkIcDuY<})ljr{G$2%&{9rcW~N! zIBcppsyE8Q#O^JDml2j04QqkMK`J1fsl_xCE&AZyPfpgr6h))Jg3XLcI$^yhC^P+< zK*({^zxptI*~j87V05hWoE`ZZ&~c^)L^3@6n5*AJvIR3`FpLq{^jgCA^TDHBKLBjC z!v{ekdpp@-NP+z^Y&G|G5AX)gZ4zp;gc5kt*?qX8;+@t&WE|+5Uc1Ax0C9zea>#wF@I5CZBZ6vsh*f)6opP=!eN3xUQi*)lhU8w!lA9kEF7z5QOg?mtRRRlw@}j9GyXRm zvD#ZMlvrRGBxDnvB_q^Eg_)x}B6f+L%bG4G+tb*qZ?U!;v^GP`5Hgi;HJDou2%Y+Y zkZ;9gp`(8+lG0sL!I{Mc)2cw1YlBG@;XKITWMud|P^FSc;Grow}MbN z6tmr!2|h#J8;`0k?(V`Chvmf`mNH#=Q8+ld{-*EMfeziE+wQoqR>j0%s<+aD9ajD^ z5hWXjHVj#Q%4cl(@y2*VEWBPRB_MKi zCNGuXhj=vjxQA<@j$ZFR=!l_x*1@$QnUK~_&w$JT^OBwe$@Bt9OMdU^YdkN_&=6&D zKHnwR5QlM6EsO{F|L9TMZ+WmQzyeEspRP|yEbH5(Li&+yhz!Fl+SXkK{mynePYJ}D zU1t<7s^?%`fZdbj*dS{VH)O6LV`$e<4^NZ3z?5zSdJ(dr;|w50$$?%mzBg{N^^>H_ z&J}0eLrEjV_==%I=GqL24qF^HZ~_3tA;hTn?v)xrm70rM`|;(Q>%p+cBTNS{Xnz|ZeMjB2vN*i)xNViq~tPLIwC_K5|k2QBG zCX?OpVc-w07aI|Y8`kCZ*-@07w?D=d-EN<_%D^cnlJO_iha*4*)+-WV7vve|ka~iL z725ejMT5;7Ggdx4Ir6UB=A5bHCtIXZ$E)5LTPC+1-ng;#<%f?_xDS{ZyQmvQS}KRm z<;61JtU~sRRGm_0nLGJ9f+A`B8P3ZVld}3)on`Ef$Js0d%^`StfBOV;WuytTy%v*8 z`I;ayx9H;Gu^17B{gVsE`;-;CZL}R#g#n(m`WDn|vdL>nB}(14^Rl`ihlg2B;lE|F z*|as?Na*%kR`JDm`X+Vfxw2zs*7-%}*+jW(_^@&Sj!3Yo7P#l&G4w_6JDGVlHqmr` z7W3#PXrT3%^NHE8F$E3lDNRO{iXvS02+jL<*BuNRK<%JLoD4j^ zq0)4n-Vk8%Y&L)fub4%_qHAIPw)wTxYlr(c~W<+Z^HHIS9x^rXF{q*>i)R^ z=+(U;lDe4)MNME3pq>$2m{Bn7Cv@=sf{CT)_frW(SR^R{=(_lEE~AZiiE=Jx{xDk2 zwa%X4^?{%pb5_Tw@%Pbq@%Oj=+rM887T3#*zxV&I*&i*DX*HXFN0WN|amoMl_xWTw z>-PWtVFF6BcAQtATPm7~T|w_ntyESvL|zP82*ht>N{<%rtP{)!cL#vbHOwP)5pAV* zZD-2hPdD0u4T<2761CZQKl&JUGq240Q~Vf6b_-n~=*1!~ml{gvGT}9#L&!~x=ett%g))YwsFW+a*!IH zR$8I8>Z}y`9GZ-zXX~wuf_n=buwAzsd)q}klKxRrM2Of*GtJ=S=!5#WD9@6OEsKLhPsC% zj4`@I!3OTT>TM+zx{Mlrb{w`Gj=1B4Rep}^7j`5C@<>g3i^ z2y@?-MBv|w@tp53ltNOu5|7@4W)rFp1}+oA$w;@* zs-RuO-}thZFS>R%2bTE7YG~0e^%kzU6uOruHmd4Re^3YX^fklpUwi`kULW#nvPcp< zJj>HPJQT42Gd$1rMc<;4vNcJM+{=dj>o=?v(h=VxOjSy@HC@Nup-2-dW^Y04uIeRt z@rjb~HDDn5jDG|0GujnNogo^X1Ya{%9gV@Ck~gEk2O712MWuxAEHgW3)Thir(Dr=< zIJttl^17P3A}^IwSMYHUQ&&VNuh(~H2&zX*YCFyqsR?R9m_K@tPO%Xw2+;mp$V6Gi zrt!Nenkz)P^~-UfWC4>1zb!KwlECUgI^B_?H3GKo$oxqBXO>Uwv3W~=YJq|e6+OV_ z&9R8V(s=7j0l#KS0g!%amjA^sN%h?j7VPbhf*=K8=bC(q2%Ie-dUu`sxvQQNXhU_& zmFa9gZ)vsYpk!v5K-VcdTk}L|A``Z`K9bJzN>mJ$s)|hy9{NuD)+eby0assHfP#uP z{u1EHh&qn(1L;4L z=Zu#o@~vl#(IO%{_+{jrFTJ3=&kh=@3@S@;44 z_kGua#KEyD5O)GxH`^+S#v)zv2lyCHY?p;G{!ys=oI`E}V}BL>@SWgN_CIp-E6i)u zf{a+=)x={ki7|Ofmp@f3S0b^w(v8vNO%(CDTaF+mT%vw&yNsJknoRb(vkcd`(EBrw zcTmfi&gJ@XdWFh+V%3N$jmtk6%%8UC6T5{*=`LT%j+ zl;heaB?6tWM`Gf?wi7 z>&*9xh>qG|nR50Wnsyk_#dnTb;&#vb?gfvXD^?gOB-I~{npgcX#iEjH6w!gTvSz#0 z8D43}?mXVo0<0CEuM&TFUQkL)P~bh>IZm(&FRyogSvtk@+S1a|j&n%j7OtRx5xN zPQ=>*1}UTE4z6Sjjtcr5FW zQPMjeHa?2)Zv2XHz2G^*wL*AQ#6I<~rwAmkqs7@=22>9zh4L@i{?tIKN)b z=B^#6UT6H;wO?ld^=jY|!7o*Z$P6xR34afMOS(2`v9ykDj@dhpQ&n|Ro(YzG>kBV` z<5_c5NFIE)5Ov3knWRMAqmLH{!y<-xB7BGP!4?x?;>c#PZWz2(mK>H4UtUr+N7Lg7 zHWEZ%ERFFUhv{gO{w)e)jii0d_9kd@@|H?Z+uM13phG4=_lQLZH+#VTOqCGz6CR)# z)O!4$R8XQX*{QY%oldDeV71<@WSy(A0A^*zB2UQ23NB#uwY>+-uZ4ISOPZr+wjQKs zMq8w2qLXPcyT>-M41_cntbpVNgU_(N`rLEv({(tL_*CAgy60VVJ1A`@^9c?=$Ur6A&;jc*$PGscAg`OWlt{L|^DC$gu3OhtsrX^JU z&)vQ-MBFzSQ<$@YkVM^ximSSN?L~v10TBv#XLD!w$HrZEKA*Y)5YcV|;uSn28h3|_ z!E|_!ExaQ?L1S-3edR6tg9H#RBJ&fKUv5EfB%4ZP=}Y9JZ`u?q z2lkRlCu(B=MbZxD{xs&-pim%RZQM?r6@rYHGwIGS3!`aD01u zp2#X;Bm`^%QVnoyyVvaxpzcEuB5vFPijid%HEvG0lf>1$l|b#FGsyq7A7&UTFJyrA zX;9BxE`A{c9B#l#d*X`yvr|332pPZ=e(d1;`rEhp3KMi2LiOr9p>u4?US!A;Wy&+r zUVDBfeJa0qhRePnq;*opn?^v&1nM`LnuS%SudKh1> z^(2&@9KkQRqAW=J-{_MlzNdGIN)x+NT1c_n%hHf; zCFP=d`lmA}^&3Gp^;Yiv)g0xTANX9{Lo%kn2+5IEF?pU7eW3VX=up*qtc(a8W|%{J z_w-Zgp~iDmdM2}WNlPxkrfz)Cv9iElAw~dhNMkzacl&o}6(z?CpMsfh{2D@`)>jx* zsf?IVT?EQku>fwdM6WPcp{smVD}AKPhVY?ZbGUJ?gR(VUF50j6lCD%zOP35dIfmU( zqY*Mu01lnhZ>BpKII?^|X|pd%!RN`j7sxtDDC&}2Bfm$2izoJ!o2XyYOix#=c3wH7)t#NmZ&cox1XduPLN4g`q_+3Jn7YT>?e^ zmK7RcylIpY$5;(`swlAjQ%cPca5w^j#iR?8hNb{Xg0c{-6J(YM zL6ZJuP@Q@KaMUt%2NYnZQ=M^DQv4h_wJ5WHn~n`NwQv@Tw-Z%yG06mFW{C zzkYP??GcV&6a|TDJffs^k?^wv-AQY)7e8CR!liO#zW(nE#=xOb1%zD)_}*HdH(r?2 zRiJHlf`KyR4AWHm;S66+SCyHpQCOQD6k#}yTokK0~+Sm3VL znqRjMNhH~Y@nRtkb^CTOLF@v;4XVVhI9RgDZbAwGVv`)jrb{WcX8G$&qp70Q*W5GgDd%oG!1c zx~T29iYM@_Q&a`xowO>|%XB65pTORjfgy(aF{^PP$c{hBnv}SlW=N-GWQln~;K8M# zPn{TpEWcdbn~4!Q6TL7NU3S)n(_Y^vn9N$^FQZ2JK3`2t>qc-pZMG4_d10Vh--L_P zuky_Y?c3@^DHPGADYSFd`LC5l2nHdK(vr?>&py);89s?3z~=$h*fo~K@6 zc^EB}Lmq(=D>d*|s@IDL1uKOWN~RE(yBagtg(cQeL0GWqz35UpH8>7_((zCBzfwLh ztqH=Xh5V;6g=elrTROE$ zCIs)DOr}dX+WtQ$vtf@vLVJT6@t`Fl7!W~{GTLmi4C~qhBESJ05p7>J_-jozY+j_! zy8J44kGb!4e4uSJ(<_Cm1)1oll063lEB!6t;B&vx-WFy^adH+Oc4hwNp66D2=B$6p z>GW6@_i*A*=_9b>alO!QJ;9YR8SjT`w+)%cz^~ND9y0sVMvo{$DsJfjn?Xo=@ZBMh zPXQ5`(ORsM9x@MB57l;Z?8Jx^Q7AW`w!oXiAT<<;{0LR$FQMmvLM_S`MKMSHlUO}L z9Ni16x_#YSOo@e`pp_P@`~5%vW7e-e3~LuqjjQ524-d0*W}PLL2^ebh<;GTgh6iJw z`AWlzA&7MZDydm9=nZB7ps<(n18~3QTlxNn?_@q2eEFAJzn3SS7Ddwf*|Trj;7L0f zB^5(5X7t)pYR}dD2BQY&Qo_8)lpkOzKLN-_7~c?wPqV*HzKBpoAY$0bWb|1Aj?L2F zo$k!f;&M4+-NE1`so%B~2H;<)42Q`X`12;>%|c=dcNn&lMbXdD*uOlQEYMemU0Pd1EgUxL zK59;foo@enGQ@Q9IXVHhgx4KDf#w#T!0XwpbALAYSO53UY(U1JK8h0fz+xSrM{5J7 z2mE~2Yazt5zzLYqj=z>n=p-{7B}=k@$t2nDgzk92R_tNt9#F@J0m^K@XsKvJ{Goz$ zi*X*S|MCp1z#l#mfA~3%KRooIRbL5A0TC^+fvR_`{<+qse!=hKnv-x~+Zm~&|7(NE zwm8Pb%@M;p!4Gd1c_|OEa(&7LvbsgE{2j+!mqSxX#O`9Ml3YBDN^jA`;3mUR(0EHQT2L4 z*AVT7outgKqFweMuz&4dbm?bviUvdpS`Gb-$EpO1Q}BF)#GV_(8|~7!*;L*xNORjF zA&>1qv1YaO4`#X#NC=n#t(D|*l!iU|wjmPPT5^R4N-NH^=~80wlVNtd*R#oZvfo>3 zdf6W4n~QKjWQu|w+r0OQ;)WRrbsKXq>12J&^Y5YbBWZaI^3N>TzeT0x9L{upy<8-i z96fF*IvG5r13Us1?v7gWpZ;=XSGMe*rk~vDbNk$vSFQ{-~ajZL292cuQlb7kEB{_f(=!SA@Fp;KF zkVq@-1ws!1jJR(3kY=)m=`!FCgc6jzLZ}2EX+guo0Esdqaz!(sHb*EQlt@hoo|BF> zmnZ~&R7Tdy)_w2xetH-K%FMo@UXS zrKpagBYGCsbUD7Tlp_Uc4T-IHaP?U7a=jpDTU(v+Jus`1I6uR^sTbz~yUdeyWDDTJ zObEhxoLqUSIL*VA-)oQX0LDRCmb5=IUpmQ@+l3`21r`Vuvm?=aNC#xrSq}p0VI^7W zmn829N@qt5OemdRAnFzeOd#F-s$g4`i8Yit%jakop1OQ$=bVxb(KZr{cnEoyevdy0 zQ;Uq>j!=@L&te3C%`UOkg9%ds%{>OH3KLY0=Ayp|NIjW!^wS-6)a66l*I29#=R${5 z0>}x1UWri91{m`?V|=n$&c?1zcF~9&XO;%*cj#fhN|vJl+}b=Im;p zY7+0pnBt>vP!=#>rAt@ecbb3km6s*g(wp0+`Do;_q!`6m)fu2%b?ym=h^3?cB!CDB zit7iJzRZ^o5?gss2yzU%-7SW|{FHguS4G86ydI(?=N9P+^ffju8sZwT4kZYm)7IFd zg$~Sg7+D3;$R-6am3D!p6AzvXm*7C*Gw3qtN)gXvs@~x0dNG=yoOs)SHU8N7>=7TX zTZM~-r34>(P3E#Vqj1oBM!3fv&i0u!ft@zgg{vaWT>2f-G z##_x(1MgzYTi4-4DPb0dYXTVJ^zM!h3T;~x;7Ni!Ak#*>9fqi(EIa5111|zI7!u^n z>+JZk3fc^YbSQ!nZ2&1pDf^|4i6~32!Y&Kq-37>75akUK?2ifL2W$MLr&)nuzb{3w zYkd>t5#_6VEdipr)!;r-*jisxM7j5FrPTtkHm@KSK|du|G`DikUkF^X2`W@^i6=^< zbU_stuM|~rafg5KJMo7*5i#6aSQ{iWU7D0X7Yie~eTD+dB7Y3{9YL~~Q(N+owZ{)0 zfGV?`R6#j};h@>c=>2T40?7fP(6zNocTs_)kbeU?Meb0y&8y67?xplQGp{Ne1rLFj zooVm^|0MCWH`N~O9?Gni0_{s7{K=E}I+w=gTylWfexEW|qoQn~W&C8xx5 zHcCHVp<{lo#C^fjT#HfTz1YjBq6h>ZX@G>oB(awS;!T%FQ`EwCmxHlB&1utb^*f|K z!DREVRCAU*G1wubwkQ5*qwMnk{w>g!PIZb<4P|TXG?$`Xv`Tx(`cy=H zI?5>CNiGVI9w8B5> zmQlVGaG`Ngs(AlEP?FY9tHkcBnCH77KaK|7_UAY{0L{P+QeXyznsuLbH}IMUn4E{F zZmul=95P$Et`E))L%rIKM2h!>SJ{4>_tjY*u}edCSJ1@oyTYgQhUJqRa;>MSeJLE>7Qp z9Y7_WS`94kx*bpi>7l;bgM2Pu-_@%CetC1!g`d1>h@TX0&7Vj8#hb}^tYmBbwA9?@ zQh=JRHc(vt2>e3IO{z1+C>o6jP+QL|oR~KLIIXSooq}Sz8-?7e2|@$1X~qNbmPBmW zkA^)7#AZqTBEG+*3u_clKVV>de=LNbfGMPPFe#^+@yrEzS$e5U5&aG1hHYDKX1HYA zC7mKDsTxSl=1M}gtg=ddG0^0a#mcV=-dM4Qr;hXLi!yEt5ck*WuImCeZv|M1jYzJ< zf-Voj+XZ=k>>B`DmymJoOQR>A)KkjeVy$rd-8@D`TKlJUIR zf%bq=Qi3Zp%(IF1$HC`haZTpj!R2z?6|-Q!fjyUpE_UO_8F=Nsc`d!=kdq~Nvw_FB zUyR*w-vl5!YGqy)9I)AxpOGmzTuKwVa=v7(T6^U$x|QhdZW|RGix~+w5J`!B5+={2 zn%L-$-;IYjeWMRTpbiFRc}Q#xDpZdCw6QQuykbNZ_?_Ey7qTU3pC8{N38ab7mh0Mn zcMAmz-3sOXh7lgfK?Ri-ybE@v%NugcS?yLwZulC48@HgN* zsX>7uF(>E0oW`%;CLjNC9{-WNe*605y#0He9G%Fah5;+OFSD{=;wk2imK%_>@8lU! zBw^77b4>23w!l3s(_lWTH1`Z~BC-p)lf}+xi4l8IYYa*mP&kz=f)&Up@UH6rHK5YE z1#~`{dDm6HiBOH#P>mIP!xBJsMuMvX&0CmjSOf`in(;yfXpUEkpt(KGdi56I@HtLd zR%6 z_ZX6vL>bE~PmC`Gtb;md=n1w?DEgsM2D=xt8%ST}WR&p)?ub+@+3CuD`$;C@?0=Ya zdS{;o)A+jKlPZOJGm&b5{-(LrE1yba(hi@Dqz$14>?fCqO z^i-+pcd#{7;OgX&4WgUFn2)}_&A;-A;Z1h|&xf3Pc-i*HL#?nu<>3YNMis$MKzek& z{-x}N=X-QPL#U@Ort>A;c;ToeG3sf54tNvuW^b-fx;Hn@EbhY|g1h}GLGp_93y-!J z1hO9Mib^NLQQ{v7+YCZHlO;L!f|Y}EG3T^lrqIMx)UW5On%7-do{Dx)+e*LjOizd$K&ZJQjLVt0Vq=b!zts5<>GAm@Kf=@r6*9=@@-y<}I((F@;a zCEeV|O8FI)5qmlH2iaI>WjmKmszwY8ZWm>~5(ILNt^%n_py>cZ+yU@|l=aCQnXHN~ z3Hn~b=2V1%z?T+|GhKWV^LSrpZlTBz{qZc<#WvaFQv{+F>qhBBFH9^BS5!`mf)8wl-RX}YuA=EK5Tw#0fzJsK#SXf|=5tJiWm;JZY39a9;=RZm}(=Xx(L<8L9LL6%nS`ZaFi~iv)F!q5MY8f1dBNxb`*N52arUZT`k!v#ZVl1D>6clu&j*i>!b_70(~k#SkMXf zF`?bJ7{RQ19u?W=3ug&bpO;3tKl8H^d+2p{>Ys!z{H_(!{ z?|+D-6}k?+&P-p(_LHJIu)`#_e}$ro9%21DSgEIk<2Od=e#-kkrwHZ|I=rC@p?l*H zI$$3^6K_+RBn}wNW;lZpGb>Q4iqKMkpJxY<2EoG7*>70Rm?ET2fh#JiPg?+C z)Chwmfqe4{GT{HQ1OIX16>VY|(X5Bh-!Q5R>6i8|CT@Zwq$B`l@=J2xVY|GJueq_? zkh?)7nINTrf7T{HTy);S9LUv%L;SKWGtK!NdXL!f1faIZ@IkgscRCdQ@SbDcfa$}i zwS!?|&CUW96r?&370A;&JxF8T@sU6c%uLuE+1xrSQ!QbcMMtz9;Vug@c2*1Y?pLa9 za$5a%=448ObhPRchB6u@evSR~kxxPbCR@O&s-uU?)+VWlVkJY-^_A>W`SG$+EqbFd z>G7R|_N8>=JW3NdFj89~v@8O@sme)NlEMw9cG_Y%(MH?bu$1%#ft0QvkZSJcV&j2A zZr+asIB?}pYe!ut?YC9f48oO?c%0@S912U@Co1I8x)C)qEev=ba|DBB4JO+s*7Qi&mXOQqFJ@`M@pVbrE4u0V8% zk<4MI;jfuwM>+|k=ryHffqx%MUamDz{lKza|I`z1HC-^(7%ou3K8iSHL#M623gE$&Mk4AU5Lo5JJlB@BOC^x&{^JIBKehRTz0qW^) zh7AHlv&>K!lu*av(!xu7Ue}AFVL|e7H?}MAm4Fi|Ue_JTvYw$_jq61s>mk*tl?_J! z!4g?%9|r7l$<72@rG+RNT1EqeCsZYFwCB!3x3wCsEh9PO`aD(+!BaCONNJ7B%!U{_ zNUt@d-4HQ|zMMkMc8qzYzBh>HlU@o|^CMdGHDe$~H%9Z1OwZFyq&&n7@!%i63;$IB zfawrirsy-kvZ)IwqK48hX%4BvP>!61fkKOGA6_{_k;l%0sv@$8jj5Y)9+=APmFluR zc2W9_v@T&`UFz<|J>y=9WUs*i$clLgqruB-L3(iCK@(KYPWe90k>KHOfj2nCc!>py zp3w0Rss-Ty~F@eiVK$V8^*@^-f$LD~(Sj-**M9}b;jSKB~rkPnISseg} zSe{_#g4Y1daj+QRivPt#0818}oo5O@GA9qTlI8f*nEGAWtyNNT$=CwMg85<#U=0cn zypiz-AVbBT!5+Jq-E6_Sctcas@f8L$QB*T6V$ z8-ETaw8V(_rOM$&?AA;*T#*GL~ zW=nHqoy$hAjiVZGr&_lBavQ*))ca~P_b^c>N93y^ZI14t3p?yjo62rV+WSw31mP*J z24f-o%BG@v{z6>INte(jklbu)(>|vzT5oXaT3%I)Pcuy+Qpfa7keTP++7p5 z8(?yw>t%+Kw7x~P-{!o794OZ==m=u6;oW_l82`(zjSvNq3cEV|o8=MjV6Y;tJt_De zxd0R4HX?qOG45GvLUG%;r0-B-yJZJ0I}UKgH@e>mn5?VcZ#=rkU7;_*ebMy^=HA$9 zn2-rdrp(qwE!ize%4baphtK~E*`Nx5J>_c|>iv>a=xDdUCU%sTLqvt-;*75#OMkKK zenP4K1e{-x4t9hxAmw5S@mMbiTFQn zX!rLpjTtR85q_I2<v2kzH@M?ZN znJrRC0(|KJkq9I|Y6+0+F2@9EsLF-7sHi~VEc8?K#WA|2UzOyfz;Ou;Jwpd@qNI8> z0&ar{Q(2+Bnh*{NP`-_|$MYq`73dMz3V-=J2<<9m_&QiU1f-&z^z?>m%5=3H$Pu7F zh+3QJc)Mxm9s^4;z5?0RSAhjd^%=E~#RyWROX-W}kxa7sxqE~$)fJx1CWI=j{2`Tn zR|mIfYTYO#AZhqWz@vsZqM^qRe{P;V{F#yOlQM@LzOnR|>2Jk%3T-lcyS-56W5f4V ze5`aT;N$AEtTGeUZpG{E;>NLx9tY0`6*sbcEDSUu!wPTyCQR8l_ktw zY%LBfSDH7s>_1UDm_bJj0tWMW6aktsl|by7)!0QXU?8W08o{SPL%}msxVY!ZfxifL zykhndmJ6#8}Kl?6;!}9DvaloM5Ub)WosYwW|TH+pj9`ra)m;CqzHHdU=HSEb_=6G_teBt zdQ{4GxWn&V*gzAT8Dr93u)nY>p%_x5M;0|aIzbe>Chv}iDggvD z7%fLtN;$#3^A;wJN;Ve+r&RarH=0&9QAsOv|D6p%v3n$LXgMbXwDj?SJau!*-9{ zyesb{)|4oUT0C_=Si{D|6wlWmNqW)6&u_K6Oe-rcG7r?A?1E`9;fzx|$*Sy^4ZfdEL#(wG{J zED>9yt16e1Cr_f_7+BY=2`VZmMhyqfQy@->K_~6W#T^9eHZpXQ4ZUw56k|^jzw?5ltGf9P9(d7Xx|cvR$$uf8Zze8m zMd|*d1mZJm{DwtCMhLvjZzp73n77&g&zwDQzfijWeR25gTAw81IQ%GY|9;}w0-MYG zQ*JIVC3V*LHu4!X+iXwUV!u5GX+u39Wx?sE#wA9GqW0ZO$qt27`2%)?h#++Dj06_6 z$L{Zwtd~wo6$e2mo{z2W^+eX!lyRsDnO{b}=ikeqiTu=~EYJBJGT7#sZ;uLaf{_PF zikYHA&w1T{lgkaNS4*gHw`2Bh{|FuFMJNJ4tq418FojMWtlgf#Oa7jxrg{DF|94 zl2|83kidqtDsoZ9H%G{S{lsTO3W}LSHx_jT;a|Jw2!4?ZGnN3nimWAQ=wOKs1Da@O9vhHnIqzi_!R z&>h{>_yw}PH2E35b^zYGB&XxQ9%&%R_1Osnizn&eVim5HC@p9i?0dB zUo@nIekOh!RDeN)_{F2|RxsWzkZ(I+_E*r#Vlia?35z;gK-|b0TdWV#O$>VBPjd&wLp0SF z|9sz0WVM2^A!&vPGMjjChY@83XJ)>q4v4rJ|q( zogC&u`7Rm}rPZb|Y(NqBF?hJr_*9#i6^W(L3RuUzNG`aLQ~F~97|9q~9ET&C&l z3cY2qs8eGLkH&C%~Pu<`k9C;Qsq3`mn zLfuh8pWoI6m<6uS?*U|3+jiq#N9cJk>~sAin8GfBHC9%FC=QpqNNCbugko+TAk-3<&; zfr>O`0-4_2=`uCBu*|)B$0kLIaN{+!#N8?MiR6Y~GQQ!HZTjWH6>e>#DS-U|Sy?_; zig1KgGf(VcUAk6~jeK@T!OLK(w53n3o^YHi^)#0~$o$qr9{oFNN*uG~i=$I)pbG#7 zfdi^&C64<01B66_r!!O-0axr^<1fj@1ickg2x&?Jr>TbZ9n=dy7y8Mf74)oFmiUN0 zgO-zkl$x(zAo(Lo8ZeG4S%RMf&B)KRSK)kAG1%d*S;xO*I}AW?#sRBPd6etLHRPK7 zfD3xp<0>?S3ZqSU-P|M&RICsM7?)eYaXj;cKle$sMDLP+2w>bp%jp#+vLGkvb`2Xu zt_jOf9UeKxOZqwO&M}_>g0Mn~;bdH)!f@xiec(T{{YCm7n+!vw@~ufebhTWQ5sRR> zhYKiz$o@`o{&7aab{M+@IfT@r;Hn9z3tv#q2p2y6bq-_}sDHy4u3)R#H+h9#p--p% z=%sWlzsfHqBWcsE_}>OPw$Fq{elqAh9H4%HFe;s#qOO)sXYAk>x9IZH^@|l&m8eJY zrK3UoYe@kA&-`?;}EWv(G z^jPzy1^O2w!m$?WTXuTby&xgunAD)at*R6w^dV;G$DGbEK1Yhj;%aj$;P0kPA>K_J zfC-J&cH05@qa+B(ecoy&GmWP(kM!MqczfEp{~2?IFduA`-e)wDg)ZXN1wHv}{1wWG zuP$iKRsJF_NCoSs^NX^fSga-|#o-r*O#cD^t@KHJv_TkK0PI2yY|!}LArzR5(Jtnv zUq^58aTRTy=3YfEWWBPFB|}IuO`rg7 zZY#;8>Dm4nwN&~ZM11~p(@l6VNTyQCanaXTL27)^bLxJ4i@ocM+9%&uw@6Yv1Y7ZAMf<@tQi{(T&E zAyl-uz9;YH{$QoJRUMPR@X>^lkwFhKFv~fCET2ZbC^IE{t%1MxlhPc4r6u!;n zlwC-5poc@0$uB3V(;IThZdP_-+f=E+CuGL2V--n)GD3xtV=!9guECfXGjKa2p|b!m zK^snFS*qskP;&xVfGNC9wXJ%5E;3FS^*Q0l0vk_@UF<^IF~*Idg?^%?z5RXRI%6u# zm;PL&^Iz;cA`8In=t^(X7rCju{#{?bUbAY6)?PyCpsrcRY+VgWSE+QCC7y)C>QtHs@%I)Kd{ffHoX1JC-)NsP!?O9gLwFCqtkkriA}WFC;5G zftH5`t4LUitV=4QZ_WCn2NXu3VwZvjMJfVDf%UfZn`&irNu{J>dq*A92OXrC2cUAh zGXG0I>k-lpSOF~Ev1t^G@by=*L0jm4!mSVEwOp+;h;gdQ*b<;XDXlPI5k_j@0;eYn zFz~&wsqKHfuff4CFlGIrHZ^3Bn0H+OibXj&R9E{ZnBp6Ltw7`!#`Xt*$d1}Pxf5z? zH9kEwu%XBnyI3TZa{-=9p)8K!7;m6`d-a|Ck-so!_X}qq7|Jt{+^av6mL!jwODV~n z1MPXmAQ@Dql{2@Is9tq(*GId;pHjomPSDOIZ9|t}6&@Bi36V9D;^kl|FhU)Jnxpn| zw~scQ8RD3>^j4}89lCf?kN`UwGz|n!odl+XnMr3rNml(H#U*3CVW_qUkz*b`La*B_ zeTO&VP!Q!REhC?8N%ReBAdFDaz`OkPo#prlAfeXt5f0e2^jY?A{lQt#!&b}IZPjs@ z@jxX3TV}798w%|Pt>cJ=7QK*V__%VVQl+3z0+-ajgc6o1TQs1lDgm;a@wAW_Rn$#& zeaIl2c?k=vy_LJ^TO!tKfxRSP8<~84lz&+0h0UhC1pa0`m{Y&QxQR@OT07MbT0Ve! z8^In&d)h8MnbbtD0UIC9wxMiKv3!n}yiL0hboU3OsvV_T&WnTZI(XgNvyV~o>-oWX z`$G&ljfMrTmZ0Q0&lrq1;P~L~CkuY^k2`wIV&06*KxeDRKQgBrNFWq@7V>f{5xWT? zjUWZlitwaY9*ZI2m)M7Oc62VZ^tobP6S)n+E%)2N%#gib$+;9NzZ$^I#07mpE9-_K zRzffEgd9i;gf3(SQSJmb33n-PVV}Yr+>-ud53oB-L!}m-#6>gGf2t(1pHwG2J{|jH zj>}1OPwj=71`PsQ6qBvb0v}vI#!>RQfit#wd_nd_Rc*@mUiT>(B@g&tTRd8v~fBDWMBHZcS%%SAS zE)_H7l?1@QFjp``=?YPP06JV zq~JAMHNoaiIs&l~{V_;3QEzv{)q%<(j7O^#h8KGZckC2bvN1WCpoj&Sd8tm9mxUKn zu<%{XiWjdqx<2f`SFIX6N>A99xRups% zWT4r=DnX_mZ&#_&<~QOEtPc43=Y|8q%TVfEGIN~@sopr+vf+nQl$APHY5Qon(~OUX zyMc^^RQ=01{WRWFO6X@4q^2e09h0dU1h6vht`rKEz`J1lky6k4D90x=bmRej>Lz1& zbfE6-e!ej_LsGs-V^*<{g+8l$0`y?g2kmD}GC{KESjh)`1AaMoGZ%rT>)=)d+HFDE zNLXy>9fN4{*6h{fKRP(&@ft7y4q9rc&KdyzYu# zUb@t^SAC^FAF+mq^zuIyz5Mzgmh88T z$~D}gIKc%)P!A`%#M}i5{Qe=%Zq$>fU8C zyfVzJyVWWcUuD%dNXZ}(|1h1ULkZ3S>_hi`Oo7>JF4#};$N2Oulm&nN@%$8$=O{9G zs~5oj4&Aj125u56_X6$iA)OxLZ>WqzT!4DJ7t@O zKd7-l$KAjesUJ-Bo@R)P6jM2I$4O}V#65WPvh>6oj-v+fQXOvv?=Ho%R7|S+KY2v}iXEVE9-L|LS4?dL9<$ zk%FbpuuP>3A0;lw&KqA!@i#xCEwCSf(NSyTTvK3w&2~mwqaMytUCqR4Rx6VuC-`iK zpt<46M*-V|ykkieSQ=fPgaH-9!Ylzar*bod$|bM;F`RYKOPwLM>0(nEFILqb6WAMd zrshB|2ha>reH#u!g3_KfF+p-?7FExS#C+MzIcMTc(T>}M^h1;W?0Psw7)&TE*qr7v zOpFQ2%~Z-_i3w=sW5c^DO+WTa!RCXqW4li`6FEFk(}74xdN~^+-q5|PhhxuQ?=B)X zs8xd8O;p%T2xxeB7X(;^A&?>KOAg*|sOJhjoWWV~9sZoQgxoc{mI9K+Fi;rzJQ8!R zX_Qkni}v6n&p8%Ua#Yp(W_WdTp=IeexL4nQhd+L3w5K0aWZj4TJPdKeF(J{GfQJ8u zOpcpZ=WIjqsBiG7BV1rI(n7BQbZb;~KQD}GxZV>~4 ztT)~a7dYh&m~j+uVjoZ8GK+FMjHxn~$DT~~!jy|f(mqb^aW*71;b%Pct5mc*1nPC< z7UN1{FkEF;f^PeY`G6j2!RYcTq{RS;r9$ytnE{xNG0;OJ@w$77anRXKpQ43uWfLLU-N&Jq}UbuZ0kOO%S9MjS{Od<1Uw-_6I)c_WD zbw$$1i|EgWjhjU7WlMmsFdtpbI}<86X<$!=-4r}^%ky3f&##5&9%{$QLY_rdct*~? zT;&-DG-7u{U%`mB;k98=(&FzLapAwhwD}7yBf@Z1_@IXn?Jtj{#fvmvYjs8sS>93^ z5|+gaD?>tf4&j<-2DycSVZg6zdqNBS_914^+V+(#Qig+4Mk)%+B8aT(m@Z> zeTi$&TQ^WS?=X6=#P?svpZph$KZ!OGg&_-J5ebTvfD;Z;lK3)g5KMOYmrL4tw8CJ_ zrqX6{-U?Eq(yix*#gVwuTk!EfOIFg{2_-44vh>prw+N9bCkD(Y+xzj$6U z4c}c>K&jn}_%i@H{D-lX#D%FhmJgJY&`RJ@Vbrj(Ip`gdJc{?L1EaEfXhz$LMb_Rp z%PV$5Dxps9t{OWUHM6tqf@}s$T9!M;&r$#B^{L*>imIv)KkuQ*bWnxlf@XGc8M+mP&tEKF-Nc)0 z1?5`i@0Ct+l>*KL?u5w5J>Y}c^|*&_o21vj!oAB5V2%K{jSQ68n{>yrFe5%(DmXxY zKI?b3PEEH>a&PcFL%|`+L+I?X!#QA`Th4+~N?mBBg7dvnD51oMitU6dUo^qAwVuo0 z*u06so$gC(Tb{)T;M4-9V27W6e-SVZpo zmZzlw=|v@V7KRD1R%CZ%o|SXe#Ia+%?X!|f&yYM3rjowEje$0uvK>axM+ReT$=ny% zpRAu}bwBBSH$<~nhBFRo#_0g5M7I~d0iMuQpQ{z*w){M%;`}=|PUn515_i= za5v#}uG<`!%*W7iGVUoxiHS^8$T(l|=cu^6a~4bn9`@ux9nP}_^L9;iNb1Ll)RbuV z8|aJg_b~8agwZ9FhsNb(JS5OL7u?sGoa=Ss4qUVpdaiT$-L-yiD<>RC@0bJJgIr1w z84kY|-^6mk6LP6Vg@zTTKHPl->YQ&~hJHN8U%LqFCu=L;hGr6n1|;$=K-L1bwk1Dl zQ*7V2W6D$od^P?wo3p^DAizWqbPUv)_at>|QS`@Cwxx<+-HrdLq!3Wn)7jai+lJOe zd?SuTi0p3iF%HO%gY|#h60k$@!qNh4-j4Cf;7}+bf^>0IY|68$BQ)z_0v{+d_=9@~ z-O#M0W0gxXQdKSW-(uSzU-7|ZUspx9WVT}+#m_DC%N-Te7Tn%7=+@-jdTAGviBZXX zwc*Nitu5EAes*8Y#e%`muQvRNp8DmiMQFoo%!WmAK+^^gj8q4o7n|@;L2~WR&Z`$# zVpRIet!vmFANbzSScAknnl-kyY<4OCif)L2GzWlHZ!>j1dh~%py#|0)F_LD{-v4ZoVV* z%j@y`!dN}GZo&RR=}2lYH@<9R6lB&xS+BvbkiUi_Hg&jPR$yKSG0SjmU~*yiFTt$b z7)1g}*7fN$%D6G9ZzOGgF-9{2gR}-S9iND2G_Fy>Lg5ONoYLDdMBXlY0rZ#gsG}CU za0i&z+6M^Ui)SI78U=15m1xyPnRkeaD{FzMx7L&#WjjG;Te@IfRa;r84!DCv7$>*- z4qr!Sr|^ejp5+hg8`B6P({Ue<=c}B0h1p^R)xagW=2>?(8j?rgVa^(aYlp}(&jb!q zDs^`H&C*J~ljWlFPq7JF3jBwiO@x4|ulh)B?zNGK$4$Es43EqSa{M>8>P~OLa7pp4 z(T-a~Hv9sTFhLJ5s$XXmW{xm99RIhfp0h9L9bBbDsHSy*Egn*iSYD=e2TiDyn8(!} zBn|Wt#{+>~PF#;MuX8OQkh8ugcet)-BkR_Ib=`IJnt<=Vj>WGb(?nW-6p3+`ANe~l z_q0QZifM;v?Mz5FO}B~M5Ow7@ftAJUX!dVg zv+k{!)m>9@6~-*~)MkP|+~|0Q9Y3RNEkNd4oVGBgVL_QU6=gW=$&3^bgf<9kc18Oa z@q!fI@p>(&cySv_@Oncg|GBRBYLZi+hoLOufX z#!5Vp_bhv)21%BP%$%b^MI4Eavw?&al`>8ga3l0C(kpgZ>K1%X=c7sdx8-`ayK*}~ zZw0)Q{>Mdm+r=<}jo;qS8l9b5UKvUOi{fbIIN7haRd4HEqsMWm2&Opm2Yb}mFbK*_ zXyz(~1|_hG>+$&0n+n;NK(eJ$@6;C1#BNwU? zG!Y|203_&Y>wZdSjn1Td4e=aE!_DjrcDq?5D`&PfoerX=GbVCm9m%`zb(ku-MSpcJ zQnsM$^H>btttX}kcO!dpc$4g&5c&*$UwU8O`w?wjO-Rj`~K80GyF7gsgD-#2D!6ycoPTwwy z5|RQjW@LXCqx|ipk@otM2&SFc9PY9BH3Ix{$!-QrI%bmutlRiOh<=rbFcSCTM}5_I zEP%9yZ45fQb;2B1XY0JrU`ltcSY6?Ftw65PaMZoSA1WPzGvtP)8LCnOx}Byal@{(x z*fYf;Ktj;_x+Sw8+P|FSS*;fPJsL2(mua( zKVNx6ue>2YfuY_zx&yT;!}sNi=jKC|Z8-ShZ+~-_NKcC4jPowXA}1HX!PDvA=wa{R zY5orf!>Fi+7W$BFb*x=YQLGnb+OLAcTH9O&&SmgN%St~P#$o*?ERl@nC}no$V&o0o zgFk>6RHW4DFS6FYyt~qqeQepK~E9&y%eVnV6o^t z*FoKU;)Vn*S9T~x6E#KOCWk;xQrG?^sfHk^=yE9G+NdL7kWnSB&H8bOYx~4x*JL;W zM0^d+J=zDfktJT3n|2qYq72}`>cz6d*I5nJ&8>sAAc)Dk#{Lnk7Z1CF(?QkeK;bH_7G?LHX%jj-U|izzxyGJVJfd&r1pK7 zlY7daf9Zb@(B@SYI3eXKf?k+-4rw8&lIuaLkgxJgX3}MhZMQlaY)CVP)wpWwBz1a4 z7W4abZ+|}!dv;3X{~)X&PC2`ZFEVW>bL$i#dx1y>*gFWmAZ$^w%1U%O#mHj0f+yB0 z6wen`Fj8e@kTT35l*=TNU-ovGC{3Pk_dJ3%?uMu( zQ^3O*L}iKkjF%whub^4pGR<<>q-Zr2T4sQ2qRF4$f#4*7W3aUpGhY{7Iw@&p4 zF_e&B34wV|CaxsU*DOF{M|_vft`}*O`rBXh2B29C6Rtq>LfnSJtjX``Do` zaM&zd_^Rv5BF28{)SJ|VW!;8wA&=abTQ`j&fN(d_90hH~u5pS5b2p76!!(dkfYppr z{TZHo-+7oO3f$i{yeqxGzeizXc8$VApB;G(F)L1AO*}Ap1r24dyAl9K1aDvoPZcf3Bgz_utt&x#tz=D*n1yuUPt;v=&x!yfy77OAoDB zqw{1FrJ$bfa@{X^#v@{#W5hbV(j_+qf(I#DJT__r`81PLHO?`rIWgGnXBc`izU^GG z6@h~J7(?XUO%-MR@u>|(COYF}OZf9&RU1+CyM|fHlXQZc9QDHSUecA}_YS{Qi9aL( zx#cGjnmWM0z{Jx3<#2K6dL~$0nsw#d4 z^yCCRJ0$%!egrk|9s>`KZyOYBO?zi^aS@lOXa*Qd7V03VVpyNTVyrTwXXAE`#s@T{ zU^GsJLL6$GFh;w{Nsx}_ff~m{-^!O3pdM@O3QAKh3}xo&)(0wUw8CsIlhMCC7=)nZ z7YTO|2?g`Ge@SX#A_PM};}s`R2L301pB|oLiq_$I#6QqYB!kFl8Zymxj8sC264W>L z1=+AmXa_!tV8(;UwC3TNC&c>HnZABqdmo@fJ)E(#X)|~11aw&bBWQGkJ!{u zMlJsXA(5HCnsq*A_w@U_J4e=LT#lY(8P6-XGUXGqkE2`!r|vtz@0Qn6n=E`|V4 z!XQ~`AIP^+(;(KMU5dEu1G#hG$h4EDs@C?Do9ya;5~@DGf-y$po&w*<_LT&%#=(uo z7dHSjK^q$rJp!GqrHnaXr)&&~@Wg3%(!V7@%#1iiA}kT9hS+WghE=WC?o1i8KzMcXl-He)=%IYQS#wC8_7` z3)7ZRRbA5JqrWMfmkBogLp&p`C*x zIjw`zCxTg#{^+LI(IYh&hc-SkMlsGjlN~gwr5MJ6AI|UsW6>8_D9!kne&6v)`p)XZ zp|4s@qvB1MH$7%CjW;>kIv@id{gY=n)+k79&qr*J*V9U?p4Y7z##>C((s+o+N-{Dp z;a^^+5sy%k<0yvCBh(E7a9aRt1LySTG}lLwqQ|0w4>OGxhr&Vms)57OnRE>)Hr+8i z@dcEMr`~#xI4KjmFR06y_JLAO0Z?&ivBHqqn1D=alJE-$f8gnQGxw6WqX$HAv(6sF zSOFOHI1r6rfS9ZkwK^zyF(U(W8#ubbF=;U?fxm(>m>#<BG$xn&YC#t<-}Jq201(H$V2 zkP}~#Io!mzNAZpBVBD48V9gPMNj8-$3G!sHNgZb~9I1_lx5#S{6o*4i<82*vBqxNY zpn6a?Dtc1Pu(9YR<5ZFSmfd+!^yQWEf{EYaOM$rGzq$?vT36hMYjaRwTofG?d@Mpz zcx5{sQ$!`0tMGQ<<{y`CTy8wH-8w)Kq<(4cRdETO`iWc}^ls*_%AC17!#VzVhPL5InB zFlT&XB_dj`)=u?OP7jHWrz^r0%2l&-U#)BSG_)7$H z+{RHW7q~@Rr-08yF%p~VvM;NHS=R_{ds$;Y-$ERv0QBu&#K&n{soJ9MJxuppeeo-`nrh%vBt`K|E~7SUP~4k^I@@S8zdT zm~vV+TfpCi^36N>Wr&vOEy8be>(Wsa!_FJg^5-1U^1ZZR?Ts)J8;BjYt9^O`ry*cW{XUEBUzZrJO=lT1V%UM5=y!um7QE2@SJ8LJR z_OtHA(q6ObfNM@#NWQl3X!wV1uym>Tv!fmJ8*RKlNGpAm;_*x<1vaf}lYKj3U=6iBw{3ovkXyzDnVh z2pEgBa@rrmu6sMfuyWS|peQ2tBk^h`$3}R(?L4r0>-t4;NXz@#;@~CH*}{OUb=0Zh^DnT8ewNre zi-K_UO0<-dXy>4)11Usz;iW*%3wqrc5ekW>g~`lwFQ_Ytr1GH z7l^NrW(6xtMu6nirHovHrDNpTy2Rf0h`=esOA!kStsat2EOu?;?xKAr?u)gVU9L_` zxF8>2ee+}cv`;KPh=+Y&t^vJTT-dm76c;A2LS^}thp?W*u zxasU#U9Z2quoWsrACT9`98jeX5E&ui1HzfwTqSOloy=M{8X{ECo2OWmi!Fmtz9q-U z%qxsFDBKsLt>aL6}UlokE$1g8^mU@ ziKCQkU2BwrJ4X_pOr!Rv)`A!GOcz9bw}2PCWCxFAsA59?p?sylGMBEl6cL#R@l3I0 zbo@AV5FB3`ApXjT*Dm+YOk9u(l@SaAkj`l0K{z;4=FpCzBrVxw@`i^y5MgGq64NG; zInGl2Q!Dy$|D+FWLZtq@f40CPM|Ck{v#O++#kg7D`NFsV(*5kKTR&IFUtgtb^^bgZ z3odUcLDVz9wT7GS8~RiJ@-FFhQ66pn)uoYaKqBSL#^iBZGiWgS%ZE{nwUQx7p2PXD zHbZpb07?wEildvB%Oh~Qmrl(tIWUWc5G`YOSAdC%6&=jCx1iN~UCDnOxxQZ9Z|Ep@ejhl^iir4P6E7?KnG=i2VqCs@q5g*;@jpZM}QiHIgR8rEdWEf+PDU;{L z;E5mx0@uZzPrHg_1Dp^MV%>$xXQw(uVUmvR;2X-;;Ph)iA%TjN0{Pa5_&NUAbz7pzK{n>*YkKYtY%FU&@uA5790G%h?M_y}iLke-&I@p9ej&=`iPc?OK zxq{!OX4AnZKQn1@<@1ZV_m$@tg*&dwM-a@4d{HgjVjH}3En<)n7km=ysv8OGgN#;% z$3li?;Wpz$6ahfnBo?ld9k)XYCK{-?J&Y+(+8I-H)Ifpp0UXU~a*<-Fb{aQMems2l zzIpQY^ewEU)B*gFUOlN^Wgvx)9B=B#@os0-O$Th^6+A<;0POzo6x#`oSlpQ;|Cy(A zrO-Eb-F$HHV^IU1_CF_5|It*8$qWe4x1iI`CJ*lRXK|4WTPg!>53OUddg*7$5Eq8d zuaWlaT=3UOn?+&Ds?q*mT`kF}Zk9$@fQR}K?O=Jd_1Mr>uAHlgZQP@uA^qGvRlHMDi=UruVv~Y>-`3C! z+`HqmBiIK^o#Qh&|7`Rx1FVrdYg08re)% zN+WqnnB-e`9By<)dD<2#05ev|?+erPoD9M;AS;Snhc~Xa#a)%b11JnONMI_iNPy;^ z?8X0dWY|&(KOpR?2Nb$)x!i@(<(UjX=uAklm0EKQD69VITLX|u|8n(7j z#soufAdReE^fYrr1#*H}rk5182!EAZA9Xi}WB$4d8_-LxgX*qUci_;cJii8Xg) z5tO9ozt>tZ42r}OcRFZ|Sn6{MvVM!PqzYF#4+nOIg_I$i$gQ-4c@4hHkNill4-5;gOdZ_m3M==F+&yvpyvcH3gwYcY-OFy5dDf6#mV{4r}5i&$%kLh`&Y`Y4Lzrm=T8-19*#lnKs z-So;Btm_qcmrz0pjqNkYt0Ysfs`I9abXVZpDH;WE-Sa6f`KOLcX>b zLk^e=`D?=c$TwX!g$~~9vEx!@A5{T?-Iv$a15+%4bmM3f{iSZCFj!l?80#!emmCO; zwH}YymipetA5E|4Gm`fcH$+leGfSvZB}yb^vkXqwV!oXQLKXoY7_zoZ4J!N@kTE4u zx?c5Ss~E;;H!es%_RsbqKcxci|J%R#KrZ zvTx>KzdI|t$aN=LNDe9IWc{lMa~q!=z~h8g0HGXw55gmK>W;AcIf8f;->MInLSK88N3H1T;a%{BCEsTH#Kaf>ex(3Z?sxu zQ8-H+bi~wzoy&yd*@en6ptxQ8@a)Hm-c}U6VItdjOxB-6+H{ZQ*vV>A3Pwlxc}`od z#<)1xeTiolSJFA}CMfrz@*!r^UV>4+3l8+;5|US%l}oMp=}MMNNmfw^iCTPiV%!W} zL0(IRgRaUwqHQ*QqUEMAthoYKv?5@Efp#;68CLVWzaRWVZMAwgBv)^5Z!usZQ&N16P`PcE;J0vY9r|~&*$oIc~JUDM3e@sfa zV_+Bz@hI7cN{DL_J`9CVvbp2QWITC~0R7{5_8y(A#yBh{CpYNFCqL^y^FC!fJ0F9v zDd;YEtWlVSYjDiUvJAw>`B2suAWtke4^+1Zqp=^Q2$$Fw?`P2PzS4p8 z^(a=B$7a&L85n7!*F)P~2(xIv*AN5Qg$ofOHX5KZg(_$&s=()l03ji7a5r3d8}1s% zP#>POUC$3)-EF_&ty!nnp)giWq!2WPX)sqmFu}+Y3!XvQIZ3Dqd<8WnFIiW~ZmOs% z+0|A)|CQYIN^T0k4B2oxkGy_suHI4juHwd>Z6* z!B3EB9izyNPoV4}2aY!8pgPRD4V5-zizj|Q?0j~@C(#D83m07$g5k|9Q|KW}GXi}c zz>GFcfkpb;rtm4mKA=o98*469nYXN5gxhqw>nt}-sHH6Uih~LDgP!|lI=p3J3JHdc zemFa<9X)>;0jGl|WUTbIuR`IEW5`a|72VJn**GJ(|E@oU_2Mx5b5e;31s~66$Ct+h zK;L$OdVY{CAg$|AD2E@{FR_h=$zi|S2CinSy@2vp5qMZEQ;2e`hPh0NUtiljWN}w> zVJo&(Rw#wLDB6J3EcE(-e->;L1-DrvE|Y4-slt$$Z$wQf+MSJOICt0|lkqgg2J7*# zb3#=clr-Hy8p?tQ75P&yxr{WTu$W>^yJ~Ce18Xx$B|2%LbGIc*U)WzTa%~xL+Q%=M zUW2eWH>%e!0Xw!?~U%Dlqj_|bLng#2bS*q{#TW}mE0lF%4F*S+#k zQOE!cq$Xp}!tg0r88*YrMdSj~6fAiZ#N&8;4b_dk3yvPC z`cYoW3X?kPwWSajt@SYq?S&#snW~Zie&!LYgQce*v4XJ89>5l1c0F~a0Q!qqYGZ08x1XQR821~@{^@rgLhZnk`VMZ z5t4zQ2Mw=yOEC%c1*Hnq=NJ*%X9FEp75s%)e=CEPuKS2$fZ?2UkI7y9BOFwOy895d z;^6eYh(7+Gfx+eRD}+d zPHgbmQSj!$P*0c5=xk4?ssi}se_T!)yeO~4@%N3YH+B+z7#@K4knfKFmfz|x^W{0A zWv22T)ggLcgxPdPnrG@K>BZQU2HxN{3%|mrpN5?&Q5rHle3=APFAr46l~}Ryb?Pv4 zX&T=@WneO=4uAih$RGK?u!_hg6w#lbYMcAp0dVWV`0AAUW>ld^6cd8aL4CkdvOXhBcK9j^!w|N2ATM)k2R;8PDX$c;vNlWlZ zeOj{4H`b^w$GnJg!6h1F_}lgZIW!u(`mPoMG-X*VU9?yv+SKtYo|6v#C2DD*u9usa zvw1z6-BvOJ16eN==pK>v!ZXU1IeuhTz7tt5kdyqCjnS0Co(jNNkhtywuq^N&^5~LP zI>S=hW(D^Mk+HvNEP}$2F<6d(&-ht(_+%qgm%>*NsEUP6@2IuMN}@t!m8c#fyR^jI z?aoS2Ie+BiUH675uA`Y?u!Yqr%aC;;8z~>@CkFUV3K2J{TwXMrmAiV@WeKFaOZG#I z5VyBgn2tI36OH6{qGWPl5VTH#{36yi7;%Bd6s$G*Qqso4=Yj$=?rE20!Uy2Y`5xeQ zze_JWBwb1eW=|e;kDSo2VL|zPQ{U%%?MNUdL=USFu;(G0%H+p6;qF!R&6ucNf)vC^)s25Hj;<+I&g~;slO*gY=Cf zdoEygBHJ7wFs@9R!YCogmI@3~c?VTfpq+O9G6UwfrI-UWO%zw48;m7|-N{r~TCE`n zfPDbsp)`wA{J}n_pgzqa!_H*S&qUbdN5~HmK;&l$NTgV2`Q zIyasKRsO!^7-N5BsdueZ0xHDWL)S3|*rIfdvIvhtkc1U_I@gJ;TXm5&9;k_|@yK&W z)|~OTD6)m&Ia|78JAaCa7&%oU6ztNCgrb+|WeB(K#E_ZGzo*ZD`+ zi_ijL;S}>cIRN+8*ORLl3Y-9U`+%aalZk>ALla^hIrrO0gC1Y3IM~EnNix2jLfX8I zdKv1%kO*Ud9>;g6sQ;Fnx?L~PZlMsD9>)(2gn$YH@N9(H?lU4381?F=_iT3|zCIf7 z+RU8x0ENp5DfQ9_?`OSiY)}o^|Sm2n3TqgZnZ0*A_R2&(Xhe?o( zm-6-t<#r~yG%Cd;7j=_#csqMIzXqMRC*d+P4wIVMQlOcmAO&D~a@K}7Hz;{IP0>0{ zLI98`sKcL^H3|81z}_Ep58**gRb9M4|Qocn8E6 z8m@ErtPOU#1;1g}qF(MMknbTg;wmt=s?$g#*qRi!Jn*oxW2GQTAgM+^0kH#d6MGDs zd!1X}5>ot|Pe|d=LB`TpxCEXz90TkRga!; z%N;mK6-;_4U66Fgh``-|Md9T-XfPlKyIeslnKyO}W0S>N>$cc?>MgLXel50D@5TY` z874K#9yhw(THgTYk)|2iGha&pbhId{;r zrwOJ+k&HGuEeMKp495LDZbE4-w3a2OS6K0=k?-SwjB-Tj*E{%ifLe^*l2>!v1zcSSM}d z=VW||HlH7Ov%xOSv2g1->!yEl*N-;YFsYm@9ukI(S{xi-P`Hyf#-h(fu~rrYcjh=y zBTl!cbdSHUSv*zUDY=VNnvBpwimTRAZxDAO1<>FnICLh2u!GC(L1AKyR*S)dBT4iU zlr~4RZ8k=BAYVq(YqRE=Vn0Ax1nva%oVF!y+(UdPJs-1)G`s_=H~-hQzZkn5+{bLc zi!I27bW4;2czvtWS56LTgH(L~JA^`=U zgvgYVcmxH+__W{03vyCWSir_-5b|d;tcts(rLy$^Y-b35;Qekv_=G6`M%)<7{6bLy zvMgxTLu1q8{8kj3jgOjZSyYuX%&gnVv@ThTKe2)G37e`!nt(C`*u{+F7!BaJWQAq1 zL9_$q#l%0rh(si=oR6r8w`(D4Ao;$Wqh&C%fe?M+ftq>(9(nG1!XxDbu*dM*_8$5Z z@QW$tFY>oI-t&?X@#)FgM@zOAL5JHL9Q2L%G0gW7s4;xLV`h{rL<(YynsgEz*|A^u z%nQ)XZl`@P$Mq>eaJQ%mvQFBV+oQN`py7e4Q7DN}=~J%+W-PD^U-eWU0bcDE2N&&Z zV|X5yHfES|pVRlXF-3vEZ_$$Nk&Yu}ZJe51NqY<4VVG)?2c|oQmRtQ>%i5CTL{*jX zX0lW{oaX?ZL9)nAr(&nYm1#_?>n_z7%cBkc6>L{D*hD)FY2>d`&I11a<5u_c=P0sU z`UJM2MXQTY9Do`8ZKqmMXK+i2Ug5Y4I2<3JC&#X$RAPr{1m>&uPvG8Fi(@$5i#&y2 zhH2(lcVa|{U)@3AkhK8ZKL;TrSi?Q;bSY7vNjxZm-M>XYL-*FBiuzNX1T~%jMGP4n z-j|WT6Y@B659EC1^Yz;M6lcj$@XK72bIHXH?7aO71?)n5uskA`6MRCmyN0|z3f5%A zK#L$AexY>v9Z<_NHvrYI%U5zWDoGi*vZ{)%vkVgC8V{#1sA)yH;!K^97%!p%>D(H& zQ3AFY8c}CQnWYl(l@l2$&H>@Cd&GhI3g#GN6w&Tqy+{n_LoE6|G46N#gK7ztYHZh3 zW4i{V#e7&2CZEbElgy{@HK`2xPj4ang}xC$6;k5JJX|GHL;E+i?CQ_{IuR7T`a(yB zp4mcMuS0fMY#}{LFm+>%-wXp5Aj!Y5h4g*0Ih|ijcP<{15vZy08Xu!C(U0;iq(5;M z#P6Nn5tbdY0$Z2;$#ljG5`yBIAEVJdMpMVA6+xe320>+t7Mx--43LsKy3KRtGWbsa za+!tXK^p_T3!P7g7oC`|JP&mf_mjOM6$_q(Wy1F^t> zK7^lS97B(HJ{ZSEz~&{vTQQf$M|f>*`_o(77K%dWdSBMDgT)dmD;H01tPbr)tP>=;OG_UYjKbSI6 zDlPCT9SoC|x=YQ2a(JSEP1KeyutG1NvMOHFj#xTwiXG0i8^8*L%eWFW*`OFi`Wt^0 zG?Lze=|{S&YgKb3Pou_Opj2;6vgL@d5BLcqx-E(obuN$LcbJ;Ts`bhtmg&a5Fkzj9fn*!_zMGLcP zAK(07Jc2+yr`agb8Bw}T1Cr9GsW<)z4Tuk#=Y4Bt8j-Bq}{WzO#kp`c8G3R{*d6@bxUDnIv| zFq2`E{h`AA3dseVrGOXgp!STG%urycg71qP)`c1mb(L|WNbpM_E;OA_>Ppi07}U;w zie4(?DDYh1{@wYM;)DMS=>_cr4KQPE|D1$_m-!el#a-|;ss`~u}NlukXbBD3= z1HN*>|4dEqg_p$_Wb193n7sPZ{1?1bt)KHxzk33K*QfmK2h4}0eVoC&gVG;F#Z^%2 zYniT(X>$njzUeZZ41;_?t|SGsGCk0h)HKY?Vs(o*yctJ`zjQBdNX_sl+m5S_McW+A zWu>w%X0>UMhm?LwIA3_dpyhy=?F5P5Os^pU@SSI`0IvJ~y9lbB+uQy$cqSDscQ0Y~ zil#i~vp=Bi_yPn>+ToVXbzEbciTF(noy z@DTHR3?WAQ2*r|gxR$}(flU=~uh4odAWEr|TUF|+1_7h(p)?5i-&WHQ8W&owG0`mI zxW{BV#r%4g=;BE1;zWefgE{!o!-u>if1{#&ft$AqLoawgF(p?5IB1Y)dx8MT0eK85QS%l-*S`#av?;3>A*Rb zqSFfisxmLR^164V?McPGH0S=ZRdc6sX6c6I?+1Vm75+P!jJZGm@ecrqz!aN zgs6ZEDnROZr#rE8$pJ;PhTuCGu1Fw2_j<9C{5gj7&u2d7{Y_9yDasp&z?+Yl)}?h* zdnt&u+@3{URip+Oy5JdAlB&(Jq$(v9eo{#VSfB`Sgqu)=lwRoF)2WTMlFiV98T4QY zQlSyeB71A)9_{qT@()}?0BkgqBhFgC9b@G0L)>&4nCLzR22xXa?rQT2$}_pHs2|oT z0Vt3a76R~1mqD*X$$;QIL~M%yM(qMW&>BF6m$1&A6bX-Zjqjep!z;jqK2&E7BZsQi zXV?y2sVIaL&`3VRp{BknTPXa?{l9BSp{+BTxu$T^JBqVBr*NWVs$1`1-1tM452d@a zF{09~J37Ue4$Eq}IB=XvwWM!?oa!KgzLoxhUquh3+fWOATIqEVX!PUGSCxiRX=~U1vaNl5{R9cE-6jD8>Qhq?2OD{#|F%$I4F|dR?>eJ-YtUCwzs+0IK$9GMxGm zXZ=XscZHHB?kfh|*2%A+lv#IgujAcb{|anUHnqY`SKR)acw3+1o}hK>C4pSVr zv9Nx4pd1qegy^3kDy_ZJ*$5Df(UHMX6r6_FS$$9!{4W~2fcz(2%zQtv*eA(>Lg0 zy()la%lt?Zg~Q6y;oeW_?D$rSKjNpnQHQp8ABMG;ezxH1>u+8DF~Lo@OcZ~WwZLs| z%ip%CP%xsfj3$|yALmN4H;-4=BM$A$*~ULQ17HK_9B|oGBUhqvAC@H}>l_fD%wcDt z@jnX{bG38sC~{xv?*ox};$S{M#~aAbqfBg;4c;~WIH$OuyV$B?W{oVLqf$&EL*Hvm zujey%Qa~o#O@!0+-odZNipk*a$sr zzKpMpYabDLL`^^_33CeqIC*FUx{Y!_O-9dg*~1Vc@X7R%IQGO-iVIMq=CCDw%E)(w zK>~||t%Z4zt@sz62YJlcV1;>*${IOKE(RdDELh8`OjERWL5LcP(GQXfyQLdW6$Q6S zZgm7^3|C!d+V;DtOnT$Li7AVbJ~3%Bv!xz{D!^m}pqd>Re%=VH_WwUpqFptGV1jHC zFmE_}=zGmowA)Y#4?clWnZdyif6F}yO+V1Mn9jQ#T9sT(I^A>%oqiaNsPz%bC_W*G zS}>jm(3?r8?+0jCLYOp>mOGK4NiI7;X>_Deka`xFvssc}Hn?T^V{~iQ^<%Z_v?cIB zjkW|HDHo5-m#t%_q#jjZ?%Rmdq;TKkzFwOV#0VH{OFz2h6aS zeF(k2%XxJ~s!P6BO`*WJalub1z3gBT0C|9U@d<8^oLzX_c)6B;91@y~wGA0FjgrAZ zVDS-*p$g;1kfX_J-XwJu6eeQ{CX37xy#~BlGgFhyhrtrE9OHVMVavRX@&?wAg)Glq z;Ri|@6NaAkgL=pM*U)$ke)7D0ub=o(9*#3&qOyop(D`1CC3Ro^BUWVkiNXr3$U%~mwOiIdMTx)7idB#j$xA+INDBTu4JH@ zeCX(!(R?Im4c0%zarR`sGKO4|C)!Ic9%f)p1hIXIh>2+4&uLz?!3*N-UB++fEipE) zaZ~Na-lspJ8&OFu?cSYo6W%pgFBo4;lgsfSfSfXZP}HI< zzDGN8%AUj$da?$R_#>#P-67u@-a-;T9jMR;EKS8Gv@A7*H<*($V9UZ#reT|aNDLjT z&szMe3ZNO@A2^;A+J4B=U>kBX6xFsy>&2NpVRATOm-7T+LPq(y3W9?vY~;M#-9l}8eRR6?3^5X*^3 z-6;+&)WI6V;WfwKi7tLNEf;wbUE zU^yS8+>X-w{PSY;hn0j6L0TncHn%euBM1`mq-a-q3_qytO#*D&L&-lSbFe#~@T5q^ zzl>$HHk9f@Py$cJOaPE2ah`!vi+d6Ly4iC0wb5eiDFHnO{uQ2W8(wbM@3t@4O)gLd zOhT!izQLwGP5BsCEvlyDZ68hZb1Az}B{D?~&U+SB4QpFXu>E>-1cG-`;*TnwqIZUs zkK(YrfgMWr_eY)Yzx$T|CiVT|aSJGbONNb>l}V|}nUrSZ1Ih~>M>N4C!}B@28w4df z8g5wKh7?i^yMl7wA?HxAQZD&5$QRd0FwFL3HNoKOEL1N+x)JMBf8yYWW|^=j)KxHdRglzgXgw*3?|Z1lh=sA}ZM{XFjh z_xH1ZTRW~uyyhap!e?atRN0HU8dYz0*%?qfIO`|R)#kqFq`P|}bdr>yS0 za{z0cqa&NmgNdwuGZ@51uYc)698WoR;n36D=2a<__C83|^}3PNc;3pI0GVaUTW*#+ zB$8mmJu@b(x0gYz0N^72RWsH7ZT(nxd8m4>JKpZ`!R`oFymTm(9Szbfp>h4 zX8gN8rhafiHW@%{vZhFK7|2~F)FGWU>1TjxPM5ZFj~HR#`PiCGBR0CCXHf6NmjUl% zQOy&$i0Ko3dmD|xh(mKYs~%Loi}{s1G*wPxb$vO8x-mp7s6Pb03_Zan32<+vvnXg` zAaVm9F?BS^pptz)m2AT2*o*S(B2!MQr(6+-+qc(`qq2gxmi%^uk{=K-voRGP^!$6Z z(9=n>l#DRKiK#5x3*ncKxxKAr{Ge(fcKJJpewcEJlYMvwp%e|c*%-tWi1!K+K#^Mi z5Qn+&XMXglk-+`&a6X@gA3e>xB2DDNSn-X6+{a{Y718i zy7uUt-CZ<}m~(8%ROC!zqxvjW|AwW5)%;v{9m`J^lZkNoc^AQb-PY6xCXZ7tGz4nH zvUcO68$@8_OOq`aUdBSvynLTmFi%aO1;zzgttPXlh4Rjlrp|++F6;nWwLBd<6dDk8 z7L7;HQtwXs7v!ibMO4>6P|FFJJ{wXgS(WJnIr9S3$7J-5;m4d!LwF{&R8%NH)7*dn zgA19}rrK7_lTW1uP6IFP#Rw`kCBB49GuFHBIIM>^!al(11vaqJ)8Xte@c;l~=;qLk zxpP}(qh9C4GLR9y0Z(=Z7u50M;8p$$=frMxeZ@E19Mw~!VRKM0Kh)r!+h{;42YwoJ z91kf>;{0ugJ)w~wSOk*3elJ1K2L$**+D8T<4g6~G!kBNpv+_h6wagU0Mdv)Am`N|3 zNA?TfMhfb=in_L>SJYekY}Ce(PuZl0eqxCn;qX?o}8P5R7= zr3vO}b_DQmRxg#cus9KhPjqYZoQRtBq7)Zi@8Y3yHT{ZLKKfr(oAa9}cYln+ znuS}ncf#Qq>+eAnjUHKfEaUz@(+=!yo#FJ2mqe>?I$8)WU^wJvyjl%vP`I7pj5@|h zI%Nv?^Ov+53er8YF;_RAe|ueBRIhX$|FqTJ-xtAO+g>lVjlM-AW%ndIa<3cq&)=~5 zK!$t@WfpCCj*x=>isL_LGN9a+Zz~mWsguP0o6454l0+NyKq7#OlT;_RlClNr3t8Ji zbFOKNUN{IdyDM;yrGO-&;fcPqDWRl)`4u+Lf z$j%YjP&A!G4j}2x(NO|UkknH)R78mn6do4Dtl5;8poxDxm}@(r@CM{UTRYVcCQ`6T zd)u4ZHLe<`+l41n@zcvH#YeMk2*Uy1Y|X|q&^_IhQv-%dg{N77*M}*7NjgCodrb00 zd;?`2s5(I62R%(tcNa1Qlm?Yw0vV$Ag)C_Sj-VLX4xt554x3wOgh;y3W=0DabsDAb zfa~{wO1i9HQ%gt}bJAi+jjUB=SI8sV8Bt5ZXGp>+`;2e5u6i3b(j)3^ct(|aTW&Nr zfSkBOwpKJ;@5r1yMjxdf03%dBxbZyT$YscMUb$tDAeJQ1OX7d9A0F7?EDS7Oz}%>v zn?muKL3%l>9YE7KIsf@Ie)}%@@auW}d-5)R|Mu6TbNxP5W%RJshHWV!;G#1+oYkl< zyjVB0sq}B=%eBFm{POMNLKvR(udZi9)MNDEITK=+i|+=RQXUo+W7t+P1Usaan3+|V zmSMH5pwlJ1urC;0JaYt3Pt0c01lkgJ{qa0E&UsbwnvCwZpu-;{y+H|6$>M}fNQPDw!B2)W6Vc=^Qc^G*j8R^HXRO#XVeTQz`G8Rl%aC?8)`Qv5uoNtokYBVS2xWAu3F#s4!(gs08 z^mT+M0Wp;ftv8ql0+!H#K|mj)XxV@b?e)K7Xt z@7=M}dnkhKkV!<*6BxUEVX&TP#Ya))A%%@OUsAL+GFKp)} zsBT9av+<{NBr#wFRhW?BijV*mJgJf0#UBrU9o)qjWL7q`#w*Xf;|l8kMMly>=9Ss; z=Ogct6Cn4tvM2&}Ug7MP%IdY{HczE|}4Kuhbzs$|v=Vw()57Vs?= zg(R*v!w(8BS~+Qs5UN~)y*eEVzO+&5BhMX54zLRvrQVRXrJXBI6cLnnabueFg^!mY zl;E|J5|Z<7ZS=wT0tFcb_z3hNT|0enq;Woe_y=8H77#95PK2FfM*;@UqBo}7NZx^D zNp?gUDO7tsgG(sCt-whUDcQpN4c&AS zv_~LD!A%x;Ln&i|jzrtJM}W-W$IYaL`!@jWlXl5_>!adNOh#YFpzpGO#k-7I=Y7Ew z&>xhCZCtHBOSbGrX1jF-u|moEbSy9LzGwqH0*qj*7HPm2LIdun-L0*?AUVi@4F#&i z;XM)4s0POrVtN$KAgc+`#Riy7CW-OCPz6j7^y*^Z$UrWIfh@=?9aGNqNPzdKCwI8p z^n210nu7r_wO9HwtkW=OCX16vwhC=};dGMeE!a8O$Z~p_AbKX329aA3BAiYU6_N>D zLAtIa=a7}bc(U7-fKs_WE+_C&Z1l|!5^A=X9?2k*e>+RS83k|UUBBZX+k z>K$72%A}-W5{&aN!=#L;c?TXG7-9mte}UmmKPP9uyOiWCGMe(%j7`aHry&dA6=)6+ z)>2BKK$XSnR z5F9|#3KZ11>84cazEv&0b%;@Kr;=gAIUH zY+OS|k0K$Q=?(w~OqjAj%vFk?W3&pN`{sYjk7r}){=Vh&JntTJAdgLQ;`DbU*wT)U z>Ua4)dzUj_*7x64d|tj`;Z}c5&bGW2xn9n%t$mV0Sq5KOmqNmnYow9qZbk1npV-A@Dw3-4q2U}5m zLmx6CG;1owPH;~xbR+E`N=rLK-ucmVK(VS?x!0R`53_wVgtC)*dMNotp|(*d#Rw%H zm{;;!Gb%g;fVttCkkqVtWW-KhU~MH0v)%AQO?tpp?$e6|xXdl`8e+_B2%U}-U;w>A z$}ONbGQ^`7>$+^-qdWXjRTqf_A}1`%?YYO7NdbC6+4DmpsI+V*-JW$ub6FhEfVj0L zH8^=rHBklHb$5~4S(gEJPOvw-)4P^(BHOf%F3cY!oUHAf)Z~}~W;f%MqfMGn#;Yb8 z9q)I5lR9v)m5q7(_jPzznCAA37adjFTR;SPMykFqekU@pT<0>XPg z0mC8lQcG6zRm5dxZfII2Pc6^O>}}ZG;yOx7g$#zFxjCS07JicQ0^T4~Uhv?GCEnK$p7`YRnQ#~{P3Cu4)zH(Utl)PX^=o&N5 zO_Pmebf@cBa~>q)2=@Gx5=jJ1qEeD_;o@_DpNWFFpHwE9!_w1ADk?v-`pVA?a^?9L zg(;F=zm@?3O0O*AD`_b!lEyp<^USJAu^dY)c`Wf|{rG3FK~xbE!cslG3*S^7VCKDC zKX)@SgR6u}r{Sj1{UYKVmm8dji|GWj3cFLXc<#H6BMSGrsiX~D7YQ5SQ9yT^X@YjrB7o!oY2 z>&X0&PkQvOU@@F{Tkk>+Nm!&Rz<9yv7Jrc&=t8|{Yg(; zsmz^w6z-HYYWJj29?yM~=f1?3Y|eSgJt*BKT_M91E2E>YY!Mj~Il4hu1TOSpFUe`i zLJbw%f-zfO`9c~>={n?q_=wZIh(C$m8e1w4 zi2}eo(PRtqJ9~#Br?6HNH$U9%+H(q3bkUa^D+R0T#fsJmD60Gx03|@Q(tk|ZJpQ(t zt9luKZa`G7Auk+;Y%NdlEA!X$u$s zoc%!f-mA@~`LS5yc2mCmY#KbFAoasEH6{*u;AYc6r<~gbzqoUb*Vg*{XPwsPKkKzV z<)Lb=PrTjZTA$n9i}VV^!=*@-Rdt#mG>@RA!eR#5J|g5H0-xnC(1mtr;OyBefKRAV;%SGx_o1V+T|(l)G<4 zBR-rX(`=q$vg_r?@hqm^-t;$)vWIdupREB<6}I(w3lXU4zI{mDyPNFoYTB2mc|n+j z-DN5s7jO0eD`-f`lU}G|Od;?z#iQ^06Lfft#+Xm{c?N!yjxl|1%K%(}=Jng^^+Yd+ z7uwWP46__ISwi$h&#ENx+W09nGB}XN33)`$zOFh8DFUYeb9JSK9Pd3##+TeUV&I+s z2LP4}5lfXwuHD)rMpw@Z(COD3&#}W*rl=y~0m#^ng${~+_=4iiL>U<~22FqjsR?rm z0?EhFUohtMG-^BX0TBE68Z7m@^sadBcX1X;2DFNj?Gs3+4GZU|vkgB>5hdL}LEg?v1K z#}=hma0JH=5EZ-mmm9ihVWov7#e}_=Bli9=zPKT!=N2M?KB}~yP)zo@yD(Uz=z=<4 zzpNr(mCkTibC83i{@H+P4CFu}($^Vcs1@itR3@dKm6&xL2|4@G@a17V$pd+?j zu5)+hnp|$s9}&!9nmyP$KuGvWS$2pyGP#?;XwlzT%n^wx8nLL2wR+XWxvjS87T0JI z-ZHnnYar;pLUhr)P9SW}?^1u(A@z9s;AZ1~0Zy)M$90zPQE&@7-S}-bK}M^~N*}6C zz&c?`&j$(E4zJ<{acS zlECPwJ-2_rche(Nb%K=R_RQF-r%>jMsTIluqI#jSO!C}yo|h7aBK+)c(<^}t5SW%a z7tU`g6}Mb<)!)e1{np#x&yKmfJz7vJ-8?z;FImTQJnGj)sb3Ub8vRov4Y+hG)|$mf z2Q^*4w5*x0$X7WsUJ%1fPg7yYK<5Er2$4=g>{T&S4A02iby$6gcC@kXYA}Xsg0=sW zc$2j}QdS8q4jUqGg(;9J-b@8Aris&F5cau#x#&64V9-$vEg)X?$b5QJzAiMt1MLzc>k zODf$P@5jX_z1*Uxq0PoeR-UHU(<+$9lj5;wPbgk^W0ZE}YjE^+NS>0{*Z2dYWJl0( z1TNK|HO@Gqv3KI;9m0c)wg}M%nSQe2NhL1k*qFlpK;!{=r_IBVD2FW-M!VSwKS7-6 zjw6Ihpmpq2Ob{z%h+e??ILS0jmZRXyl;HU-#-%40(4gocYZqY5LO)ipSWD2K`_EJ| zD!a9egYV$HjH2BP`y<<`t+0{xM8+J(gb=P;ih&R9xMs(=PM5*`oL3=DnA$Ujx*B** zI=S@p;vBWLAMu2r(WKc%OIsE0cVAC^zAq7OGU~G%{f!1xj<4$s?mG`tcBOrSSU{Re zD2vgNz@3d-G3XD;t>E=WO%#(dbQ1v_N~wBv{vNll5neK`Yagx!_?0#Qs;L&eD+q`m z#7!z_O;Sg?<#H#%08G4KfgB)Se~jJI3~8>oYEY^P5e6bvx{LU6?zRZtv@pjnQ(RYT_>6Y-CRy9 zV=x^>_mF%|A>M|K0ai|@D{3#v2(f)5ixky|g(orytgAZl9sHb=%F?UwW=1Rjm^m>^ zu@6yvR0$BVM^RpTR&Uw*D;DO2C1qXi3EPx}qC;?*%lISrOcV{q_ufLCo`G806U7yj zzSGawo%s~nIgJR4I;wCuq&$$8mkoSg18%M>s0LF@nYHO6=hD^P{y1?}uJm)-o$K65 zsPNjq0JtL|l9bhoW(B&%#iFLs3luu?G(cJsqrOhtw|3kHEzbO5n75yK#H54%rf^8C#xSvMm%4}3k8W~Aj1%X?N16Gr?AeFt}yRT>jj(pFV`Dq~Y+K|>I5+ueSo(R2lp zwM68h&itv=^=fqHu|+c5_C!OE(}*aP=0Cf4FSN-($AE$ZMAe5_Ej{3?EA=?@A(3Wo z&|Q~OH)jaRp#voHw-#CTdku8kbwSeSO$)zUJ`O}#JKLEnNX3T}Y7xvqSB3ez?iOvL|TS!EFlyt;# zZqzW`G$F3axRZp?}TRCV2%FjEGfNffP$<; z6WE1Q8IGBZPQ`Iv`25#;-fKNCKyF_t5q}mXLaXlq?@^&m2ww^mzQ6CS>vcK+>Zyw+ z#~nzi0&)H+BFFh!rTz1R_wT>eob3ceK61ss>a(7i6kpSl=- z%M!9@AS0-D$m+^pn9L&5!f5A0=$f@A7AFJNsAHD~M_lIm%NS0UNi-x;I~3fdQjppRmty+8o+6&= zlQ>6V0ROi6|Kdys1}oa$s&xjdz78I!oarTx)VJC4n$ZSJ z1A^XH`-Q*r21V!tq>ew^_2aYi1iv}&5+2}8mRaw!FgVl&d6o>2MvU^oxJ;UO%-4R% zPQdznm|SCoS-%SEi*)a202>r+)FWx1ZHehd;5f6toR(2?k2j*u%2W-sns z(HsQ((CxsTN5;IOPK$TMob$3~v;T(8UB&9>*}K zg9(jAb3>$p-VZug_)AVG=#S*>0D&l|n{d-|eL(~F@)h)tQGf)9WEEXBOx7i)TkHS5lz z=}hAD%81QOu5j*9$BhrMJ&jZ6M7rT^=XKoh-1 zi3udZ;P7T~e^KwBg|8<_hF5di>uBtGuKNvjp(JaJnwl5(#B5{1E@|m!$tC&*=M(tw z56oR}@K+HZnz3HNZDC zL=#c8PKcSkb#f+!VRXpL!UyF~$nGRX9UA^Kwo&fSs;lTCdr&LIOV~)+yy)1j_9@K9 zB4~)Opdw^=t%%1}tB}2|lOf}4(7s)*6bdc6$CW~}JSWp%+fIIl0GgN?74#svm-$sr z+Rn0uMay6CxLxl3E;b{)=xTzl?`i~HG3}Yl{t>RSJ&^2~~u0JJb?%3%; zLuSF+xW#}#UAmuAP_Bha>QZt))HrGZEJ1QPS7!X}5wl7K4_t(p4RNBmD;}_%tkYVz z49zx{LrQ;5+;c;c!;s|5bP@&fhuW{flU)bW5xZ-7X0h;HNIwp2Q5DjF_I}c1XzQ{B zb)Eb6*jf-!C}~9tA4V?$ZG0CG*!?iHp1ULeY@r_5)=t3Y2$QIflJ$P$IAH&xLC)hO zsE##6Pw~UXm9SBgMua zYkeoY4xj?HlbbDs*D_q21G{Kg#O6HRhXCS#6i z{Rirn^5Jd-Ncjn+ifZ{OsJ3YjUJyw89x!vUmLn~vzUAiMo+91G95Oa!jVF4csC%bZ z6MQKtw{|cLAuESR=q16gANKbpIulq5(-|PnJ{}DoAVLDYH0@6zMD3JKf9nk9X#)`; z`h>EE0R1XeI8oHfA-ZA>y-9gWSn#N=eoK4h-Js%wK-|oN{hYI4?|}(N$=+yooeVk; zP(MEEH)9B~u4CMf6A1XO@tdY}fpPyp(YPw& zK&;D2M2|b7P3a)L()x_j=^P^+P=3BZZy(YhWa;PQS!dw5k*+{g@`S!Jfe46mEp(PN z!=WgWf8Z>q1Mpjsj^Pp{SPV7@+({hl)9g@T1}8#@;r*#-gNM5S&s} zXLq{>TwJpF2ZWKmoQg2&z0_%ZAwnAM!F;tZtCiGuKd>$ML+2Kw0aZtI)$xGN_(H_} z79-Q8S3$`wkcMDd+4`8y4#uOAlEij!Ehm`H8VkPeKGG+$-dvTdEzr~~^|e{GMaoXk zNYc$DQB#3hbS}AY6Rfm6x`L?`2s{u(e*Q#eC$r$p-B6$lfQb<7kS<~K+~Q@P0M#w( zF0R8(ct|pFiklXeOHxPKl~qm1v_5KhqNWb9*88c%c-W-V3qUL>a6Boq^Qi%RU!lKR zfy@<$F1C{9t{X57{|$=Cw2zi}_NA)oH4A9?xLXKM%q9=8k+|titU*GRyv`M~TI3sH zAEGQrm{syXXA*CA%whOX?n=N6Q4q^h-HykP;=ouCCFF=6xs|vxkTy$>t1Sv&>F#H- z-r0$_U5j+h6EHn$3wPjl3RVuZHC&Kv3t-zye=JY%{Q+IXhpSZjw_b&`Q6g@s$cs$L7n24pYBP8oJN7m=gN z6IUlS`aPWEcz@q``dwz2(^;qcsrvDJmCiR|<=@0TN!S2f3&hfhO<30Bl7VRB4qN-Q zhLuDF7tRy)^5R%D8OF6KaSS(YsTtylXKZtdXd*PG(UwgxHZZc~f55MXUAFo(PcHw( zG5fD2!ioUF-69p4;}!ub;kzD%E89$tsFb5RC~*^(V2hwz53?51OT6yZ)jvaf^YYB0 zU>GPC%9q9jo{%rWFQ-A-p2q+=Hzk$LZ}@RR%VWsc&yD18V5y*}Yu#rbydu2Q&i?J&P&&8$yCXPMUiY$cKh^~S|Cs)L1AhDoce=*``tSt zBP(kF2!Nmln?62mi2#ABtjrj8?AY;bAjcQI5Hr8>URUV+g2X(J8e<8@k?(p|OsjN*f)j;E(VoR2UyAcr-w+gu z)m*Sj>}r_*S0X<%I$NKS<%bHX{v(%^Blw*rXYy~1F5TM}>_%*JM!Z39E-)17%W*Uy zj`2p=Jgkbe5foF*zp7q>pChOs5dbtL7dp&R$yt+m%;*8l&b_+&S>L!wV!xlt$ z+zuqZDB&WN6@nc%6&5C)a1_cIghsew7KTL7zB)+nlK&zi=?!Ix9sCnXX5^5P26>{= zI>zXQz1B$C5ph_O&xb!txmKJiM{b~ zI)nq#+5o)S^yK}O%QY1b4rG4~Wz*cS;9rr4;(=C-Uce+op268RD%$ci2en<|jdE4A zhzQ>k3M-EpnyMsH>w_$d=wY)R2i=_&zO#yZ9g!Sj6HPJAR7lt* zX;1_5V4B7^lkRQ0zwh2d59uK8*SEuN2X*`ljMYt9iFY1H>HOn#ieh*Tg5DhhYr&WZz&7_6tdg9C zL37_;5gd73xANMv&lQ|6!2cx?R*J=Jz(3Q#V)-N;PamY9v(%(9>(QU{Em|iKgkoh9 z&S$(-UatNhPGOQ<4sM7O?e8bh5{3w3(gghmDv%TOQ{YcO)N*N*J>TGn_t4X&TSZPb zk2KCJAz}lvw*rpp%22jTVjex>WWdBW9f-PvT_MF_b>$Mrlen(8b7 zaTMl0H+<>8jxFYU9vL!hy7oz|GdX(#oWdQln}iQdW0fog+ma`Yh!Py4>;X$24o2fv z;lanyT+&&}!cMYh>S}VV*1ND`(YpN-y~4OAq$OZVym!!oSMe>Y_C^tOru(jl>OiIJ zo}*HmmAmR(nUoMfs2;cN>Ln7#t&*SRJ{>FNDJN1oH4qDI6c*K4h&Xm7I*s+}2fx)D zPMJQvZ==V8(ga$TX-*HP&?BBUB7!Da!6Vu(>VL;U*Rmn>u6deIPp_$(=&*@KN%nW( zAO~xSSOYT^#ELs{1VmUoCpi8&=3et;)&9-+oY0xYyhXf5s?m)0p1>`5avM;eeL%M{ z#-9P}(8da@-n(La74{akRY40aw-2agYZA{m*K-=@uw4*xvcNjQfYfRGs`fU;RivHB z{!m}wp!%j_4T2UHp~kU1#(|ts|0mJ?vBWei9L``KEewgSpeR=yS#S{SwHX;Tx_q|8 z0Ag?*+J=RLlhlgE;@9wpfBRcN6X0nCnj)sj2;AD2Q9+=e2{qMcK}~pJMngVc`OHw$ z<7$&aJ~U=6Ou3)YlKrd%^j#I@W5Vw9kl})42{w%FAk5;Gj}V=jF*&w_BU` zle2VC3sfW`w=IO6Wrt>{-(b+%^IfUBiL=CubutMZ*+e zOTP`FX!GoUfSni@$tN@GhWdN{_xM_>|c0~`{Vy)f5dP5ALEz#n_#2? z)*irYQTKc!~Ab*lmM z7-=g2c5v2)kc|+t(5(_nG-V?F-JrCZr)J=HEqn)*Q|{OVm(RWiuS>K-dLLF5jq2UY zt6)NaMV62E*B26-Xo>C3F!=~sABG^cF!U7i4~$;(Q54$trO7~|l%J9s+iyv-q8Y(y z9ZjaaJstIfV7h)AVKPw*of*Bf=OSyk5NR;HLqnH`Le!Sob123vN%MNW1=ap&N{*G} z)P~+`L{|}1dfGC0GZxRg{#yTZwPoM-85D`FKjrV=*?B;u;soK1$@kx2@(?EM zXl7@pL)W4dEugxmiaI$(fn2ot`2MuC?DOkCz}|g8WQiqGBxP26CgAKDjDy;A=u#0! zI+#;5@JM+EuSwltR!E70=VuNn=Xc_R@Q<=Twg?;B3o-^a%S-o3y`CIP?hL?V)OzR* z(OV?|l%!v4bk)m)G%%)o=pmK+7^pw5+ZFFfVAPRV1)W5U)1Htz&Rj&h<3ox4s%*Sc zIMJ&)!NPMr#AA+n3ZYbNpL&?NFuK5~>;XEpyeC-&D0#Gy4f1EHnR=D_()$F5XN7+m zL(Hi!HCbPZi%nrGeG@LOP+xk-ZfC16z038>k$kkm-N<+FVYBfJv9kac=G>MeY|avx zk2WM-mN*`te|(atwh$XJVb0U>DOzEnoB3fP@>+y3^P@PM9oTX}x7^<3nm)L$0(F26 zz{(t68Nec#f@&(Q>3lQ=$S^`;f_?ZdOuN|i1V=F~?W>!?CloMgVt6jYQ&xZ#b8z?C zlozhGgC$Q~0oL1kNU(%*NjVpnkMUe!wsCw z-dHY*nq$TZ?K7Jz@DC$g;H_tn9HRCUn}LLtk8KQC(2v#jslhsKH*aqZ+ za8TxaH6a8E5DdCak^Ke(3(-O`S6FE7K*Nd)y%!-tUStBDC^b{zRwsU~qz1o^-Xbgs zYVM#}gobIpsSptiPr0>Hwo520YnLx`D2Btybbo*K(yJqZv%||4oPV~8Y*uTPpL(T* z*_GNAKai>Bx*7Fafc^Y5cei6-9!h6_zqEn$q1gX^|BCf&SImAyPqlyxpx6UrUC=_^ zM((x+?ZP{#KoC_#43tItVt^6YJLU8H=`Opj7g22<|lyRI2JU!wi9;q%7Xav1dd)ncb0zEKx!Nz!ijGpnQIP1|y zXjtLG2uW;Z&K5K10az6tcw;i@+05x^tKa&RIyv#=yvqqvl6aKoej{MdkiEbamT^2{ zcMcg+x5yYTuQn-n8II+O+*mFx!mGHQzR85fkn$|wis(Hy*O2%k*74hFk`rL@FsZRZ z(M#`jut{fl;}jkp>DC4k2U_elG6C}K)?rr+Y%zN8KQ~?M|D)DXIox>?#vH&Lv?pBk zteg8FsgX8tm!_Xs1*zW37Z5%eqiGa#-Kn|jv&Ks1Jwxt1^S&F$LAR^n#f4-ANa`bJ zGmH3G??X=lLe8pQ!NmePB+$Iuyp(>l2gk?(vA=&fOk$PC#OS;C?d;rUl!OL&!Ag)R zlN`lzH$f|jHfYpX(1Ob8>3bA`P|Uz;>Qr^D5b;db+8A6h5Y<{|>^#Hj>l=Si7n>#M zk5;&m9eKB+Xh|c7U^fAgsE%K_kYBjy8o~E`?R|8h5ECKGYvpSn-JTzbr{{4?gjB4e z4i{m9vJOuSQ@o-q1#-7#WGQI6ft?ilX+rgFYyDDkFC4~3Qtyr$)AmUAhu7O;O-bjd zsP2>5z7i-jiNmobXJEhF`|6GdD~HHSUH8`}8E`nsn42wD56hSx-TVQR2C(P?eQaCF zBO|D@ic99smQXw`#0-LHtp{~eXZ?nZxjmQtJG@K>kofGFWC=b$v!?jFkdFdVxh|(F z{aG_q1f>Sd$VrgWae%uEQ$T@ArC5(J?PrS)ELY>a6G!v5;V01NNynhYSY^v)RgcD} zL6jQ?w=N=O!*l+}heH&RvgxJneG)K-Z!Bk$S(|bAKGPQ|d@N#G{@*|BW#m>p`H8Qw zEv5mN7l1=ipp&}PgyEp*oT_7XkjE;~mo<^hZ5orn%;H7HX!z%#4tBs+CU=YthL{1> zCY7Lr5*>)>U=G*%)Q-^WMA+8h(S;#dNxdgOjALh<-I3exkA+@lI3{Ffr1hA3n6&-@i=p zbJ#V$_x0b)uV-pH-);C_h2Zx+`Q5CZa^S1p+POe2yLI{ftBSAjvf#q};8#_=Nf?EC zQ(z)~7r}f*4xSKOzlvQzIoaZble_@8%g_8Sw4BX54p+tTMTWbHlqBJk?NRxen(BPo-h3qK`4FEe^FFaERZSMwW`NcO{+hNTWQKg9PLbuY zjnF^fLCA0~LXh%@swmey>4rH!j0|XFewP-cAFG3|@@!EoFcTuom~3QJ=Kb*&WI__2 z?c_(P0u-mSM&+F1kf*3k_egw}AqYFDq>y#&qqTyzPLFk*UJZi>*b_eOkCM(6e8z~m zGo*)rfAu?CTqN5g^W4%(w_eb#E5`c8p3XiZ#nvSPlTL6|8UaPSj))IcR}Ka2b=wd1 zsCA2Z59H1zcVj$I$WBkwi7tnWMhmyJjA(y5mN4GdN-rv~ei2gKw0$E@0rIB%sm1;C zt4|B00;?$0(Q~mB#lZpCDX7;&p+TU8rD9O09gRDpR%7rWg227;)#?mJ+cbY#`^MS9 zl#*=0f>@PJJvAlf{$B24H^2TH5z=@3&5{;Z79XcAt-N4^? zIT?$7Dw)#0jUFk}WP~ANXge35Rs988hzfQ>^(Yt4v>{aDOd@Pr;c};OdTiRml^LOt zQ#@j`B%h(qIVp5Sj!$}pY{gBeAZZZ0YfgU|3NokgEnEnVHy-UoMB4~>$Utuv#*`fy080-KlNgs!! zBe;uiA*+4F*)N+W`-Is4LDx9*N7x~Hp2Zxjl`nYRSJuO@b=b?ssc+_)LO9Xu}e>?++koJa2CAnaISMX{|+l#?RvxwKR!gv zn6ZE-Pm^= z$?(*YlUx5=<TGAS68R&p>fMn;ekop56UaKd3UWmN*xv&#O2`3D$+)kg1) zcdHIsaOyi3Jv24Bkq+2H7`mo64T$wU?x6T=p2h}Xu8!haw|~>03E0-ymUaShdghe+ z_13g|uL)V;tyWo-T*~R6U)gu6K0?%BJmVcEtgcK`;)NN26!s>#bQx@_m$76w)D8cZ z$C5!G6jRC81u*0WJ-{*?L_C=`#v}Ryk)fGm3M=SILx{|90pnu_9rWfd&^K&mcP)); zG8Du^B*6UeHfPfQ9w1qni;Wy^~BGji>8C&&U_dOE8Hu>=D$N2Z;-5YGvqZ6)2P^2&~ z91YK}*{U)^i4VRAz;z=<6{UyAg073xs&U##h2UPqJ*E&EvP@m^#+L(Bx9HmpkQb5E9D~G37WKBj<`X4r?Nlh4j>e|JE zuE3NNyDrk5sCKPE=ImLWro;^n)&)9_cM}JqWlOM1X!o31z+8(I+ zAc9fbpc^CWz~fZ7xiZl!J>OD0u9_!oX4%G71~Ei)XqJMM3VT|5C8CgEB2w)9G$up( z@$S>Jy;)2lTZoGT9@0T+`QT)YEa#H{3Kc5IhS1lPX+x=0TtaF{cW0+y0w*7ORl zZxi#z7C(MRIC@b;ZBNs_(dZ>(GwycLu{T94thOE~eImYkj!NL(#SqRp7uRPxzVueRx z#7k~*e5!GVmuv*=kuQ)tv1R5SjWu}o;L;#|SnM2> zL!}w#0G3YGZf5CmxSdku;XR;w4&?j+^F$}fd5RfisXV%nYqfKLR>s_LV_HSVftW-l zv%&hX>(!7C9qigCH!TV0{7 zMDXE3)i6isw4zQWFSAVEbe9%4V0op`rOr0@}xVvZ!WeZB#3p373BMPlO~VH zwIpq@9y`2r8D(!q83naJ#uG=#tczt7{ygT~;y130f{7)B`%y(%ytE2}HGaV$G#qzd zSc2mh5KZC>-)}$_IVCZYXbQ7-CWo@Z8bN;2S))!TzN%VkJQ=gfc^l!(_?FR-^ES)Y z;qk^8jZT409>k{~o9~(jZ$8E+$A`^>W_*S_swD?4Gy?-B1m~63K@O*-M!BNuaxEtF zv54@tumoGJ@woNi=jc?76R4bOeb{eoHCUK&RBhT7EON!KzwP4Z#mpEJYFVLB{#Sx| z1Sjv`c2zJ^-NUmD)0BvRQ|zHSLXu-;z$Ut5z%~alJ1~-uW#Fcj!(VeMvl204YiU2= zuIZjNdsu7X_%#oEkbmw)8z5eIZpF=rF*v^&tA%nGx-=O>cBFu_txAu|Hk^urcEM^R zIw4FZTS*zN`cZ)!{OaY?Zbl$MCaysNt5EDu0ECUAWK}97*(QT1(tt>vq59A3x~t~H zh-ZHin;b+SE;*EjNL_41#F)__Q@fFoaJD%@cxbdBzafWd`)0uZAW$_OzW?)o%*j17 zO%H+xxqe6IfWAtWUgn#vNfeI1+v$#hBm#aVDLtg8f6ce@x9_!4T=voDA!NQh>FL-L zm;6?G=HvD1hu}$vZ8;0l5pA5S+VEmZ*#nENb#M7?sq+hxX_yyqiG#tZIbK6xmNVR* zqLb5v5ruivM<1@b358)09EnuHZ} z>>q*_^fW?K7X}-t9KL)+eq!C{HB1w^a{!G%%XNWhf%<$M*CU5;lVM4zU$jr9txxgkHLfS0 z&3fKb$<|GcPz)aMZMW^4w!+&p48?;gK++b5zg-{F9}r`5wMzoDrHI&7pH}(`C^`h; zsxo3RN@xsDY0|oJBYYpxoVpGW6Fj64d#22k`Vr5e7As$ws3OnAyH7Afy!MAj%d>uy|uQa zz^GP?wsWsQLIFQU3y9VPn20nQ{caENE7Fndhe=i&xaeN;Tt-_+RP^KFB;YFuX?QYI z@v@;uRaWt_!_S43l87#mF*&bEUN`Y@h}7q9yJqU@;mHf1NQUSw_)sV>ujUl=PSjA? zz&fLU7|AbJeKwvNqCqfUKU-@si$7W`Tm>G+tuDtfj*HQaV;G}BmBy3e!3GH#!7{WrCK8fi|Mf z*#i<5o5z@a0@WS57e3J$DBc~;aSp`0vyZ_AVDk!A>mDUWfZmJ;*3n@jI^0_v9H6f* z4qYtUxZvJzM_oFI{w^WE1?su7gKHmut&%q`F3i0n7o*bz2<>F~%bNY!bBj5WoL2p= zUAZesRK4ZPU;1|lhJDpU1Qy)Fh5S9+Fj9B23Rln$yZl-|yM}~(!OI@=^+=b(ev#v$ zdrY1PAYARtvn}OqkPjkW?9Xz=-1e1wb^_!x*qlWC)_{kdVqcQ$_q6zj7D(l_W4 zr5Rx;if&l^a5FhYwDVX-qj}`^^adKUlP&p=q=&g~1{*19J8hLzhB;?Jq$W~d5AZLQ zX~L3#xk0hgv&ZS9Hjw1!+(43d=wH}KbSNbQ>KG|Y`Cr%A)?{PbJN^&egn4fAFE^M2 ztaJD31RWcyEM?S7Ff60lO&egDfBjRA9vz%1vP|Jx2rtl0-FYty?wZ^Pm!JyIC*Y~? z^4A9H(@hoGst;N*=JT!gFOvkRefwqQ{D+%C7t(9^qK_Pd^Gk6I&gx&ruiCavj%hlv z-*xYEyH3U#uJkp?d4xRjmMR$;b!<*5VnkBo=mar6g z>HCkgOgjlo3{-3D=$I0rukJ-M>7#D|)JJF!HEFtl)NG2etD(^JhD?|<3>^HeHSY2^ zGJ_^;#R}Cf5c~zgZUBrh4Am{27SaqR5&_l3aV@Q?x`RAPDL!Mzz6JlO z&3IA)m@f(rg_JIR8UYDvTYWf$9yj1k;H2vsa2WC_z3<9^zUfUd+YYLr`&3>t#N9Z^Sns!lRd9%^~*EIil91!3$197K}iI~X=fuZgoLF7(Sh=z*=g#?O2UIm#%P5;Z-Gh0JIRrk&?qrIrUWlSN^sC+B&AOX z^uA|#daH%A(M7SCR>mKbV7o9M$W8ZNx-OG;Kec;-omeUxq1OY*tzIJ6c>dx zgk3r=#-wR2od|DoVeuYmsH+JZK-)if1uis=K+n2J!E*r@o`9FTq(LqK+Bag=-q8F26c zxkL7R8?C{^wr_V`Uh2|SHC6n`qU0P8Wr01QsU-)s(z?%IlkzU-&aUcmrATHdWKvl( zb~F%`DQ9esntZQO-`U;<^GOehUX=+X-Fbl|cR5N)ap3@9D7Xg}2Zxj$wql%$PQ@evBwkM#fYKc5V}68`m08}U z`uFLIaob~lM+Dl;o6!h5<09#-DLT*rU=r>C_@}ta7&fr>C}D?9gXY!@qK4cv%s!45 z8kcJ%M#X4LqDESj$qfQ`U23biaD9a@d-@oGT=zKu)DOvM9OuJ6X;v5F( zW?uCtc+PCPFL&x;+?F)NLG_=Cp!!PR zgp1R!@}N36WwT2;I2$W{4So;iMbt2{_zkdTcXWcwMpLeUz3rkU4_ihdvAj1AsDlgA zn3fQrk`tsFCF+-Rex%_0P+59p9-4gIW>WiA8t(-9{qaHmbC?b;r&s2kpDPBySCf0i z%;9DvXH;BA5Kh+X2ojnXN5d%`YpXXgR2sOf1wB09`Vbz`$$+W`Zupj*xro0Ipt_|l z_EO`W9pU8@;>{%zVi`pP)4!1o&q6%+lQAQ+n6YA)`O1cpUl(S+BLzToa52?1$$y1- z9sf++4hY!2QG=0IVy&IBvOEec9QUk)`3rJZXQ|>MMxZuPl`5`k7FDX^x~V>v2KpRe zLG`8##uann{K4{@DOzGP2?G}iKSbhiGVna1&bW zaCdonA~UFmJe$+QDq#&uxQH!u)+qpq?gg2%(<`?Me&07Mj@BXw%o0|7({-~j&F5o5 z=#PL#U>9No2I}WD;k1k#luH8`cb#Z;b&MP^D)bW8`IT#i#a-n7oQV4~t^MC*C0;;z zq*5kz@1a7?X33y^=6cHhzRctCqysO;fr%72t~4d|aLYDdW=Q}Q-eIa3V*>wXcpv0W zc-vPbvo20gVJkpif~?S(M~$O&df-*Y>pO_iT!ZS_&AG~8qQCtRb5fn=u_TD(ola@x zp16K+;LG-}{8;2pIQQbEUGRoN6&H{h{y{8o;QCkM1c4ZTCXeozk1iMIs0V+4gAB(KirseAT;%Nx9#u)E2&fCU z^&M!p{xxOU!6X#BeeWt1L4>iE+(7sPrK3!m1D)R@tRvSfuEaaVC&wF#sKBtV=UL(k_7;_tKA=kH&=^}}`Yb(wrExZFJu6#G`%I21398eMeE`-+kZ9>bIf zPn(Ad8#Ir12${Esa_-J zSN(uQePC1%t@yY}nR~v2+G<*lVm}BAS&AT)pq*kQEr?AfZ&(b)XVaLeeTqn;?w<(y zoQ1RzkT4)LGEMoYf!nOFN4!W$%I}%wlRus{(bpR!?MX6%09Xq6T0&EXLIwxsNhPM| zEL^Z{9}8HBFF}<3rh_5bs#TECpnP+ybK3&1#DJw-Pj5!BGM1+WPMPG_W0CxVg$}#V zmHM?gM*tN7O^o244#YdT;AKOZ#4L?7REBWz)fg_ux>7j^qRnUE=*8}OS4sfv9BHs` zw{#`v3H9i_B8u|4=M@^<16(B)F=^fP+l8zo07K#}4gza8E>?4@xm|RPZI0B0$Rk?h z|C^47upsKV$2Wrk&Il8c+g(?pZw>AV1(Yw8|Pt~$1~=Ei__<5)4N-2TnS4NVg|3ocJ`rIK&6PH$EG}!Yg2+L6PzoMskqI|XQxc~v z-*NLAVH+gc(vD~M69sXeJ;ct-`8;#@oIVS0NS@Za;rOmK?lh-0EgzVnD)7F&ADCXP zKjEqdzH@GT-CwF%7`KUT)xSi$ByV>CgP6yPGC~qOT%kI;Tdt1ge;EZG<^C>G#xaAw znvW_<|1=kDlwEa-1g92nBP$7pNJL{LB?DsYb^qe6YQc~>!djur3L;Z+GP_8VSMbrU{bl&uCbPqTH`4Prs*fGa5xA@Q_ z-@j0(tY4FJd@bS%!uXU3S5k(GV|-3kC83vw-guQ`=#vpV;Ps8a4Pp)%6LZ#tf<0nG zNsB7MV5;crX33(9yjjnDLUzp@s$I-|XS4@wveyB5gtDoyAA;|B3Q3)%mat#MMWJQ> z^hO&*>;UYKDhbuJz&#;jX&(YM*OvSu?SLq;LX)<3*9L{4*TF=vF6gVAMx^NUI?ME> zz%RFE6|9L>2}!gL_xSihz-m}WU zRvIHuc&G3NPKzW0{ch~Y+zQ+o0$?Gw;XD@5;O|5SU{qjX%Z&_SVgejxxlrkmVGAM@ zsVO`5rgfEQ8?0M+*|=320)Io3oGX5m2~2YHoHkY`^4p1qPk(71LooC=G!TqgR zcQnB`k`@J$DKsoZY*;|Yg03)N)$m?bQ%!C}#8525nKx<3N=!yt>c~AT@|)OfkFJ}~ zI%WkP24i|LP@WSvqavCX zIW&zuae}0kJ@ZpMCiyRq4|DhX^-D~K#Hwu5{3O#H{_O6WzE5ciWla%E&w6cXu^U;O zQQpRGw%@so?*DfXeXhV|Utv@DnOGXCssY zR0kNCmzf}))-{n3+a7e z_sFtFg=w1_N#kZU+h3|2uz?k0Z!TAlLZJ}4Vw#i9_9=B&y$#As>JXj)Vgr}(J^)A6xLz9=wmU8RqspN?2!Df1?$7eioY2%Cn!D8Mb7@B-QGyJ}JB zUq1__MpA`1Ij&9%NxH!<-$CY_0*39edW9Q78=ea2)KugN~lphColg+F3a@71)56dc41Ffstqj^t2#j;+Fes>UPPHA`; z@b%U(GkHZdFy?R^W~!}cPNyN-4tSuF$#U+u1a#{Xj=whTc4vXYRhg9d2&@z9QQgOhcc-(KDjz+U!P1x9&tQrfql`vwWg#|MMz zgava1Vaq9kt2gqqy_BP^sJosk7w>2PK61XD#F)1pFOqa9N=uew1)^wLI6G5dBW4@CCO#@Hr?AgJh~YqOEB|1 zD<9{Tvv({CT#OO$roG8+lu(J$XL3Mj&yv+0j$aSTlL3lbsC$3I#sV za_ORKC+edlg?3~Qp|c?)ga*GH&V#RSQq;HLwVpBXa>w|a2r4?}oi7S4PXkH8ee@F6Su-XcXi__MTOa;35v%XG&CWLSnQRkUkcu-oRTjcQo<2NoY;6B-~ zsB^!aOq*!-sG*tz=@ebWQupBfOd%Vqj#U&bW>h91^FE_8fmh0v3A^!CfIlL(1w=7l zo%oTeXO@!Tk4RA^VCEbVWUC_PB#%HtoB)7B*TO)?XIu@3pPcmFM$p7fL3>aKwnZ~f z%N65RcRGP8q4vRM?cwsqvTUB~EoW{ZdV4CT2dW=nJZkJAFm|I1+(X;a)*ZMPB?cjz z?2?8sZ6v?dqfPfJ;wyBVJ-8Ulm7cKu{OQli+B9`~CKS_waLT8O-TOrSD31Vo_6zPnz?5!X^{kAe7P&W!KhXK_!@RME^F1F>FJ= zk+dg3bP(Al@?Qaodkg4*^S`-G7d39+aWpdm^1o7L2WQ?o6PxdDVeIq@O%DL7CYaU&m>2Ox-0%p$$%7>|7!%#nPol08P--4QUZ3LKK-i2;JAE#K^sU39QK zWvY8+d@~R zBu4ob|G~eWWOe*8FPd;&*r0ZTu34WpfrLStb|%RkE0Z$L0(ACP?Fx6NNrcw_(96kN#Z4j|$Qg!)M*!7pc(5Mgck>si2_7HsI& z5^APO!iv#3B&Kv5*Cdb>hrV?wDu-8oK@G~iPSPS?2Vme{#Sz9!h}&@sTS80V%mh?DBZUM7Pn3EDpd0#Uj%e%*Hwb6rd*A z*ZhN=Ik%a7I>o>V#PmVLx$4!v*t<6|f@5n*mOtMy(1>z_glPL;5N-Mvb zPDfV#naf!EGYpuQ;g&ki?uQyDj3TV-5GER7dZYwil41Pz=FlY=x$JL&7~;$2WYz&_ zWaDUKM$GEWU%lbz9-celpigNSW1vNQW_TdlSI#giZN#G2jeIFGr^0N6S)B zc%5R&5(STx_0(ePdrO;y6@UsZ&mIIX(fUrI&65r&L)=M37h>}co`b{}%*&YZJ|PX@ zp@9X9-y)?za0^^a5!$#4}E){9^o*j8MaR9x|)_wi0Tnshbc=qwMdGr&H;&@0FgWx4_ z2IyskN_W;K93<0yGO(qS@3VL+hMNUqJ z{wRzX)HYYDce_d^uE}=K^}I04SHSTLnN?Y-UW6-WuDp9er&=Pme~NL*tOM6taVsAKr3mg01{?nmywo0-$IeRyzVnDeQUkW!CC z3!ayYFy32IA*o+}6S&(&asBd}em0J)wG3z`5r(|hKR|O+4pts`?rv$igT9kFZ++0$ zmhIRUY9Wr z=f3@OGZ~fA&wMf0l4=>_Lexa-C7NwVq+Q5u>6NY9XS~#m^@d79C>)RsM-x~yR8NGt zFJQwDH76l?JgRY^QtM`V#YvlJ%Ed;eCI;E;RG3xosQCupoFRRwVh^Kc7~Wq{r=VvO9k3BR(CM%P9K7bi0kBkEo~~m zguj0kZOA{?1XE1Hrl|5mvLJ15ZSBb_RHe6lq_=&|r$SMtLVtc(iJAdwHXa?bh`FYz zSvCyqZFM?hMDa+!w1w9Q6CUoybW)5gl_$;o`3X}%s)<|+&i|lxFNS?=07B06u%qqx zpC?EahwWic3wqIZbCUlF62U7ScAUkk^D^M9;Tos2BE|P3UnlUWc|FST#Oi*}PTZQR zW+LT)Tf%qy&(6?N{n9Ei_bMPA-rrhi7h9UBF}Xrl*(bDUAQfi~Q?LkrtJ^jz*zVXL z6F?J~m&sjI?~WSN_6W%;c8~#3FeQ5fHHtKo=Y%q-)9APnQN(z`1vkw*Ja*7~@tTre z1>p6rgO?B>EpvJTkt=%q(o)4EPV|mh^ya#*p}kyqCbb!?Orqx z-8VpqC<{H10mwa;dIY*;Bx{R+n%8{ya6Qu6g9?!P%Cb#xjk_l*3Q^&?(S0ySU;1tL zZMWpn`4ki+&iU_?TMLT`num2r{9RGvEJ$&#QOQ*K`62CF)Fg^@%Go1G%rhF0{}jkg zY2O<2Rjt(|9*yuAN=<#TOFXknYbl?P-}N@rK#>%ZTWI#763D~6Se_mvY`APVi51!ufydENVFL^F35=7K|nJeVx&0FJgykGW9#X6cQd@nYQ$ufy&xueiiQyv zuUM@05m57v|Dh*uz|jHo$BHxhTqm+m448-L+rsu84Bp4=cLe@s4v`TK zqnqBet8H_cTwwu9n)}`XRM2{@hjh%M?6af74l7$75pvfU1oSFSR)T=|4xDWF^VtB> ziXZrVeUZL2o9`j-DBPqYefH83=b1!u0r zllS1`n+lh?zdu148EEH2!kY3ozU$=RAQ)( z)a-XEn8LxgR3`HvFXAow|E$N~g5LYTb<`zlkG&qr3)0y3QuVW0BVqGPPxX;(KtP$B!6#j@Nq%=U94YnXw#E^KnEuUy0}tTeyZg(x>*v@NECsU{v7*zS5+(RJ z44)R^z;;X5tDtV6g|RJB07Uz zsXsdx!?7o=JtmZyt~{D(NWJ?7C)shunl|^g!Hq+e6H}8(nqb zK+vVomocSWF^O_?d<>c>2toM2RCH7{!AX%{&rvCyh5=OFCMeRHV&31s`@8eE?sbI` z6Edm1&n&<6oqqrR{_9?=f8J^Ro!6vS_8ojW9HLB|W5WittWq|S$(~quC)sC!!PbmZ z`V1t4l%BnOv-Vr?HxV@Oy>uo{)n|01gVF8GV1d@xnJCW_T_*VnnJf?WoM4ACL@Rvz zeY|&(PTN<>Jw_l@IH}|=ZDE{iaWiE%Yg2y{Ots##(Y2(~GVVk5O!^X{?c6Jv?~ET~{4qNql}6iWpm!Y6LW;?d z{)g@(*bjEmy@aa=bq_r~%DXkS4CSaFL7O0I$oKjeoZDI_o9~?35)ii%qz0VqtdLC2%0`nmx5aS@H_ zvdzi}mFu`X^eJr0)Mz(47qPMiSh-$l4Fsfor4tTH178qv&QB7(Ug0YIU1(-mDp!z; z;Inp8?C+Ae&Y_99yi*#Soq)*gqpbk0T$IJnRXr~ChJhAYXS1~pA|t5OtH))wsS!^p z_7kKgk~o9 zcSEz0Z^okud1f`y_U(Dj)%eEEUC80kq&397JfUxMp7#8L-J8&vhbStKRVeVkg=Ws& zl&Q6{%D+WF1b6^;rYy#P+l$8TB5+YhflZzopy1>Ce*RbLVX@ zIfm%=kOKvMM!a@G*2WS?k?oB52lu&|zqBUYpSl#xmyV%(`hoMaL??>JChQIg%(?3C zdUGBQKxJOYMh$enS2WeCMVoERKPHfC{z!D|80`*dzm(R&r+=D!5)Rlu?9(k_(yBPbFNlIRi)2nK~dCbCm4f7 zrDXIGeUh^FXe2T)$L z(H)8+2BHH%&zSv&F74UlBLx)BK(eA#+7k1Co+J&2unetVsT<$K{3{0_pGIwT0@3gEYS`mkSXws3 z%wBi99?9MwJ#cx|Y^uI><+s>!EFhd&uo#<5*m@0Y#n`j55yY|-Qf#8vmiJl-_C^~l zYtcfsKPghZ2{knit;Ow80ur%5g4J#JLqdPz;2_#Q9gfsQ$-2)Hh&PSL#l$w}>%`j; zgniCb-xq;z?J-=#SYjyvTys-2pazc75ga)iTWtgN6s(g8Fc&R^S}c+L!D20+OX0gL z%8;+!qat+ss@v``DCo8Pj5qQvHCNRZ4NL=#5~YZNFsa2OUsH~+RFq+C>w0Q)8|a*sRl zFQ;W&P#v}XSTPW7*QB}R^ipj!rza}emmcgw8Y%6+a=0rqQF-MvPfmla#=lO^TEs$e zR&h|@%YMgG<;0t;k34*upCup2?@U}Mk&Y(z-}08g6~dx<76!YP|Jr(ph&dGMn%vH4 zJkAe1x!~{(J*yESp{fqDubh9P&>tix=OWE62W_%#|&e zye$h6(WY~|7|gdNP#b(4LJa0>t66i~00d)}g)$FjK$&nze9%XDz(puiHvsCcZyl8R zE>YLcw*yGXOe$g}H$@6nbiP)NRL%k_WoO~@KAkR`yE2FxWN#%v;-ds?M@ z_KQ8;&q8DM36(_;TVVP^zv}#llpSSul|lLq9qmkXt8zg$W7M@NZ0wN(QSU`*{QEg2KY4;H3)9 z#;R~TP?eaYjZDcC+GQ9LvIx3miQu)+wBz_32xAjpEX%gZ;V5r%4C(`cwmv(4A(E~! zN3THPS#Z5A(W~8srn}C~xrIKr{3&VOf?C2n%-Ogi)0tGlD zg;&0R^}ibRo$cN4U-3UXBF{L_$OJmzd2PMbO4PIMPWB-Bz=PsrF@Me^RX~K%Rsj?g zJaEtVoi`7=5fOtYpjm(z<|0{d5}W}4RJ1lN(Dp{wR*kCyj_WT~{@MU3zj?F?XgjG7 zT5+Y2>LNeHLx&~!D~YWF0H@mO#TaEAmSt6Mm6h$yZttqB`aqyG}+GLeM^1@Kr z!|onb^yPeML0mVNQ|xpe=>pXVC;CWu5xk{{gM1TDfmUba_GMvvY`1R!7WFv+UQ5DN z+<`YcN&7ZNZwTj8z=jM@@sszihXu?96l=R<#qS{8?5xznHTh*mgv0g8P#!F#+U|TQ zq2M(>!xSw%#ScktiXCxd&A`B!6)@{S=-fS2r@$`-~lfdB2aOmIhtKDW~`EDaXt}MCicH;(=(gzZc!o(&*3HqSDMVP zFh!40@Gb<>e>H+^#A-)!0n8dv2juwsSM2ngA~c5Rq?DY=HTb#>~~pY zRNN!P?f8G^-9D2ew~6LB7xBYVR;5MX(S z5<=x_&G$4;Vza|QX_ZjMUn=6HTv$_zLx>v$MSq4?%$TAmjQ4Cj(Tq!$v9NgUO9aVo zxn!?gpSYad?nobtg>DWovA}JvWke~vO!2#B?VYh;DfY$NN$?|W3YkuFhk0YD^*>Bg z8$(}Y0^h~ko$e)IN}~lP?Oqq7ZEBSuCh{bk*Rbs~9@C4LD{}dCflPTFpX0hRMDY|i z(6525DHP%w=$;G!aNXU$CT}$o@i5rTU3M1B7Cv|xHdeO$2wd?Gw&daieCs5cG0}58 z&5WYSF1-2P4GD=P9*;~>FrY3VpogHbmZ+sB*F$km+aj?A)){(#qsphz8K`w8r+ z>!B1dptr7x3=6d*xYtclVaF= zaD5dXp=y$u&I%@#dIG@<=rVw+!c)djQE|y?kYY!niI96)0lj0j+oYD$Q=tD$`IuZt z{ZK!M-LTRfGPNd?9?lPN7_yH*oX^?Jw}u zgN1a9xHkdIwR6);$BpsLU^cP+j%p$(J>}w;yx}wl1;cDUio;ez6OO;rXh$20XH&z$ z9@tuK&P%!v57O(lAAr@Oz4q~bSe!T zJ&{i7W#O$K$p+Q#=EMF*{L4umjkkraqrb}!abn`L*-0;2P&CET^wKGMRT^~r;Vdq9 zduFCHBVDfT(MV_M8e)}PDtEk`1WIR_M7YBthw2_(AiRbBX&r=$}15Ow6@vV7u~ei0V`zM+28j$lyPrx zN>o!y4hEu!tg}BjB)nuckCuokUm5fl0hqpnIXP#mDRLkb;4R7VosQvZJ=c`Me_%Dw z5dXHNu0O+~EI6o5;mjYm+V}Sn3&@o{vsq;%pSgN7hdro;XFSKn>rA_!Qg+~3AST7H zn}G=~U{d{xwU;I9EqjmETkwWif+TMoi~oQpQI4OH_Q|&?YL5`(u!TZlNRH_?7a{$R zkFVDojDC}>A1T*4kreg+eW-bKF9&@fIm$<={gh76rmgYxBZ(yNtK2*bvaAANGB?Fi zYH{Y|BC%`GCk1;ZRL2CmRWEhsX!%;QjhV^y^0n<1lmcSkUFnkHs_N7WlY|HLuN6r9 zEnEWaoM+Fq@>o02dA9>@24hDF6ogef@BNbq+VwfV zuaXY{&11ZHlruxmzG=c*1)huRo5X#jKh#8S0aYoX!@ro6FFumcibvb+D^MkeyCwwL z?43=w#$*aL4|@>d>qQ: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

q=s%l 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

+o-CO*H7lQjY~W9NJN^QOIMQ1 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%0DFN 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

n_~&Z;#-ve|rV5GE?n5 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%0D0CCI2#T$-f!=S1_F)?N<4psQkeYFZn-# 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 QpE9jil_-Jo*_+h=KjIo@{r~^~ literal 0 HcmV?d00001 diff --git a/source/LoginServer/client.cpp b/source/LoginServer/client.cpp new file mode 100644 index 0000000..d41728f --- /dev/null +++ b/source/LoginServer/client.cpp @@ -0,0 +1,813 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#include +#include +#include +#include + +#include "net.h" +#include "client.h" +#include "../common/EQStream.h" +#include "../common/packet_dump.h" +#include "../common/packet_functions.h" +#include "../common/emu_opcodes.h" +#include "../common/MiscFunctions.h" +#include "LWorld.h" +#include "LoginDatabase.h" +#include "../common/ConfigReader.h" +#include "../common/Log.h" + +extern NetConnection net; +extern LWorldList world_list; +extern ClientList client_list; +extern LoginDatabase database; +extern mapEQOpcodeManager; +extern ConfigReader configReader; +using namespace std; +Client::Client(EQStream* ieqnc) { + eqnc = ieqnc; + ip = eqnc->GetrIP(); + port = ntohs(eqnc->GetrPort()); + account_id = 0; + lsadmin = 0; + worldadmin = 0; + lsstatus = 0; + version = 0; + kicked = false; + verified = false; + memset(bannedreason, 0, sizeof(bannedreason)); + //worldresponse_timer = new Timer(10000); + //worldresponse_timer->Disable(); + memset(key,0,10); + LoginMode = None; + num_updates = 0; + updatetimer = new Timer(500); + updatelisttimer = new Timer(10000); + //keepalive = new Timer(5000); + //logintimer = new Timer(500); // Give time for the servers to send updates + //keepalive->Start(); + //updatetimer->Start(); + //logintimer->Disable(); + disconnectTimer = 0; + memset(ClientSession,0,25); + request_num = 0; + login_account = 0; + createRequest = 0; + playWaitTimer = NULL; + start = false; + update_position = 0; + update_packets = 0; + needs_world_list = true; + sent_character_list = false; +} + +Client::~Client() { + //safe_delete(worldresponse_timer); + //safe_delete(logintimer); + safe_delete(login_account); + eqnc->Close(); + safe_delete(playWaitTimer); + safe_delete(createRequest); + safe_delete(disconnectTimer); + safe_delete(updatetimer); +} + +bool Client::Process() { + if(!start && !eqnc->CheckActive()){ + if(!playWaitTimer) + playWaitTimer = new Timer(5000); + else if(playWaitTimer->Check()){ + safe_delete(playWaitTimer); + return false; + } + return true; + } + else if(!start){ + safe_delete(playWaitTimer); + start = true; + } + + if (disconnectTimer && disconnectTimer->Check()) + { + safe_delete(disconnectTimer); + getConnection()->SendDisconnect(); + } + + if (!kicked) { + /************ Get all packets from packet manager out queue and process them ************/ + EQApplicationPacket *app = 0; + /*if(logintimer && logintimer->Check()) + { + database.LoadCharacters(GetLoginAccount()); + SendLoginAccepted(); + logintimer->Disable(); + }*/ + /*if(worldresponse_timer && worldresponse_timer->Check()) + { + FatalError(WorldDownErrorMessage); + worldresponse_timer->Disable(); + }*/ + + if(playWaitTimer != NULL && playWaitTimer->Check ( ) ) + { + SendPlayFailed(PLAY_ERROR_SERVER_TIMEOUT); + safe_delete(playWaitTimer); + } + if(!needs_world_list && updatetimer && updatetimer->Check()){ + if(updatelisttimer && updatelisttimer->Check()){ + if(num_updates >= 180){ //30 minutes + getConnection()->SendDisconnect(); + } + else{ + vector::iterator itr; + if(update_packets){ + for(itr = update_packets->begin(); itr != update_packets->end(); itr++){ + safe_delete(*itr); + } + } + safe_delete(update_packets); + update_packets = world_list.GetServerListUpdate(version); + } + num_updates++; + } + else{ + if(!update_packets){ + update_packets = world_list.GetServerListUpdate(version); + } + else{ + if(update_position < update_packets->size()){ + QueuePacket(update_packets->at(update_position)->serialize()); + update_position++; + } + else + update_position = 0; + } + } + } + + while(app = eqnc->PopPacket()) + { + switch(app->GetOpcode()) + { + case OP_LoginRequestMsg:{ + DumpPacket(app); + PacketStruct* packet = configReader.getStruct("LS_LoginRequest", 1); + if(packet && packet->LoadPacketData(app->pBuffer,app->size)){ + version = packet->getType_int16_ByName("version"); + LogWrite(LOGIN__DEBUG, 0, "Login", "Classic Client Version Provided: %i", version); + + if (version == 0 || EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) + { + safe_delete(packet); + packet = configReader.getStruct("LS_LoginRequest", 1208); + if (packet && packet->LoadPacketData(app->pBuffer, app->size)) { + version = packet->getType_int16_ByName("version"); + } + else + break; + } + //[7:19 PM] Kirmmin: Well, I very quickly learned that unknown3 in LS_LoginRequest packet is the same value as cl_eqversion in the eq2_defaults.ini file. + + LogWrite(LOGIN__DEBUG, 0, "Login", "New Client Version Provided: %i", version); + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) { + LogWrite(LOGIN__ERROR, 0, "Login", "Incompatible client version provided: %i", version); + SendLoginDenied(); + return false; + } + + if(EQOpcodeManager.count(GetOpcodeVersion(version)) > 0 && getConnection()){ + getConnection()->SetClientVersion(GetVersion()); + EQ2_16BitString username = packet->getType_EQ2_16BitString_ByName("username"); + EQ2_16BitString password = packet->getType_EQ2_16BitString_ByName("password"); + LoginAccount* acct = database.LoadAccount(username.data.c_str(),password.data.c_str(), net.IsAllowingAccountCreation()); + if(acct){ + Client* otherclient = client_list.FindByLSID(acct->getLoginAccountID()); + if(otherclient) + otherclient->getConnection()->SendDisconnect(); // This person is already logged in, we don't want them logged in twice, kick the previous client as it might be a ghost + } + if(acct){ + SetAccountName(username.data.c_str()); + database.UpdateAccountIPAddress(acct->getLoginAccountID(), getConnection()->GetrIP()); + database.UpdateAccountClientDataVersion(acct->getLoginAccountID(), version); + LogWrite(LOGIN__INFO, 0, "Login", "%s successfully logged in.", (char*)username.data.c_str()); + } + else + { + if (username.size > 0) + LogWrite(LOGIN__ERROR, 0, "Login", "%s login failed!", (char*)username.data.c_str()); + else + LogWrite(LOGIN__ERROR, 0, "Login", "[UNKNOWN USER] login failed!"); + } + + if(!acct) + SendLoginDenied(); + else{ + needs_world_list = true; + SetLoginAccount(acct); + SendLoginAccepted(); + } + } + else{ + cout << "Error bad version: " << version << endl; + SendLoginDeniedBadVersion(); + } + } + else{ + cout << "Error loading LS_LoginRequest packet: \n"; + //DumpPacket(app); + } + safe_delete(packet); + break; + } + case OP_KeymapLoadMsg:{ + // cout << "Received OP_KeymapNoneMsg\n"; + //dunno what this is for + break; + } + case OP_AllWSDescRequestMsg:{ + SendWorldList(); + needs_world_list = false; + if(!sent_character_list) { + database.LoadCharacters(GetLoginAccount(), GetVersion()); + sent_character_list = true; + } + SendCharList(); + break; + } + case OP_LsClientCrashlogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Crash Log", GetVersion()); + break; + } + case OP_LsClientVerifylogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Verify Log", GetVersion()); + break; + } + case OP_LsClientAlertlogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Alert Log", GetVersion()); + break; + } + case OP_LsClientBaselogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Base Log", GetVersion()); + break; + } + case OP_AllCharactersDescRequestMsg:{ + break; + } + case OP_CreateCharacterRequestMsg:{ + PacketStruct* packet = configReader.getStruct("CreateCharacter", GetVersion()); + + DumpPacket(app); + playWaitTimer = new Timer ( 15000 ); + playWaitTimer->Start ( ); + + LogWrite(WORLD__INFO, 1, "World", "Character creation request from account %s", GetAccountName()); + if(packet->LoadPacketData(app->pBuffer,app->size, GetVersion() <= 561 ? false : true)){ + DumpPacket(app->pBuffer, app->size); + packet->setDataByName("account_id",GetAccountID()); + LWorld* world_server = world_list.FindByID(packet->getType_int32_ByName("server_id")); + if(!world_server) + { + DumpPacket(app->pBuffer, app->size); + cout << GetAccountName() << " attempted creation of character with an invalid server id of: " << packet->getType_int32_ByName("server_id") << "\n"; + break; + } + else + { + createRequest = packet; + ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, app->size+sizeof(int16)); + int16 out_version = GetVersion(); + memcpy(outpack->pBuffer, &out_version, sizeof(int16)); + memcpy(outpack->pBuffer + sizeof(int16), app->pBuffer, app->size); + uchar* tmp = outpack->pBuffer; + + if(out_version<=283) + tmp+=2; + else if(out_version == 373) { + tmp += 6; + } + else + tmp += 7; + + int32 account_id = GetAccountID(); + memcpy(tmp, &account_id, sizeof(int32)); + world_server->SendPacket(outpack); + safe_delete(outpack); + } + } + else{ + LogWrite(WORLD__ERROR, 1, "World", "Error in character creation request from account %s!", GetAccountName()); + safe_delete(packet); + } + // world_list.SendWorldChanged(create.profile.server_id, false, this); + break; + } + case OP_PlayCharacterRequestMsg:{ + int32 char_id = 0; + int32 server_id = 0; + PacketStruct* request = configReader.getStruct("LS_PlayRequest",GetVersion()); + if(request && request->LoadPacketData(app->pBuffer,app->size)){ + char_id = request->getType_int32_ByName("char_id"); + if (GetVersion() <= 283) { + server_id = database.GetServer(GetAccountID(), char_id, request->getType_EQ2_16BitString_ByName("name").data); + } + else { + server_id = request->getType_int32_ByName("server_id"); + } + LWorld* world = world_list.FindByID(server_id); + string name = database.GetCharacterName(char_id,server_id,GetAccountID()); + if(world && name.length() > 0){ + pending_play_char_id = char_id; + ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct)); + UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer; + req->char_id = char_id; + req->lsaccountid = GetAccountID(); + req->worldid = server_id; + + struct in_addr in; + in.s_addr = GetIP(); + strcpy(req->ip_address, inet_ntoa(in)); + world->SendPacket(outpack); + delete outpack; + + safe_delete(playWaitTimer); + + playWaitTimer = new Timer ( 5000 ); + playWaitTimer->Start ( ); + } + else{ + cout << GetAccountName() << " sent invalid Play Request: \n"; + SendPlayFailed(PLAY_ERROR_PROBLEM); + DumpPacket(app); + } + } + safe_delete(request); + break; + } + case OP_DeleteCharacterRequestMsg:{ + PacketStruct* request = configReader.getStruct("LS_DeleteCharacterRequest", GetVersion()); + PacketStruct* response = configReader.getStruct("LS_DeleteCharacterResponse", GetVersion()); + if(request && response && request->LoadPacketData(app->pBuffer,app->size)){ + EQ2_16BitString name = request->getType_EQ2_16BitString_ByName("name"); + int32 acct_id = GetAccountID(); + int32 char_id = request->getType_int32_ByName("char_id"); + int32 server_id = request->getType_int32_ByName("server_id"); + if(database.VerifyDelete(acct_id, char_id, name.data.c_str())){ + response->setDataByName("response", 1); + GetLoginAccount()->removeCharacter((char*)name.data.c_str(), GetVersion()); + LWorld* world_server = world_list.FindByID(server_id); + if(world_server != NULL) + world_server->SendDeleteCharacter ( char_id , acct_id ); + } + else + response->setDataByName("response", 0); + response->setDataByName("server_id", server_id); + response->setDataByName("char_id", char_id); + response->setDataByName("account_id", account_id); + response->setMediumStringByName("name", (char*)name.data.c_str()); + response->setDataByName("max_characters", 10); + + EQ2Packet* outapp = response->serialize(); + QueuePacket(outapp); + + this->SendCharList(); + } + safe_delete(request); + safe_delete(response); + break; + } + default: { + const char* name = app->GetOpcodeName(); + if (name) + LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", name, app->GetRawOpcode(), app->GetRawOpcode()); + else + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", app->GetRawOpcode(), app->GetRawOpcode()); + } + } + delete app; + } + } + + if (!eqnc->CheckActive()) { + return false; + } + + return true; +} + +void Client::SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version){ + int32 size = 0; + z_stream zstream; + if (version >= 546) { + memcpy(&size, app->pBuffer + sizeof(int32), sizeof(int32)); + zstream.next_in = app->pBuffer + 8; + zstream.avail_in = app->size - 8; + } + else { //box set + size = 0xFFFF; + zstream.next_in = app->pBuffer + 2; + zstream.avail_in = app->size - 2; + } + size++; + char* message = new char[size]; + memset(message, 0, size); + + int zerror = 0; + + zstream.next_out = (BYTE*)message; + zstream.avail_out = size; + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + + zerror = inflateInit( &zstream); + if(zerror != Z_OK) { + safe_delete_array(message); + return; + } + zerror = inflate( &zstream, 0 ); + if(message && strlen(message) > 0) + database.SaveClientLog(type, message, GetLoginAccount()->getLoginName(), GetVersion()); + safe_delete_array(message); +} + +void Client::CharacterApproved(int32 server_id,int32 char_id) +{ + if(createRequest && server_id == createRequest->getType_int32_ByName("server_id")){ + LWorld* world_server = world_list.FindByID(server_id); + if(!world_server) + return; + + PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion()); + if(packet){ + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("unknown", 0xFFFFFFFF); + packet->setDataByName("response", CREATESUCCESS_REPLY); + packet->setMediumStringByName("name", (char*)createRequest->getType_EQ2_16BitString_ByName("name").data.c_str()); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + database.SaveCharacter(createRequest, GetLoginAccount(),char_id, GetVersion()); + + // refresh characters for this account + database.LoadCharacters(GetLoginAccount(), GetVersion()); + + SendCharList(); + + if (GetVersion() <= 561) + { + pending_play_char_id = char_id; + ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct)); + UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer; + req->char_id = char_id; + req->lsaccountid = GetAccountID(); + req->worldid = server_id; + + struct in_addr in; + in.s_addr = GetIP(); + strcpy(req->ip_address, inet_ntoa(in)); + world_server->SendPacket(outpack); + delete outpack; + } + } + } + else{ + cout << GetAccountName() << " received invalid CharacterApproval from server: " << server_id << endl; + } + safe_delete(createRequest); +} + +void Client::CharacterRejected(int8 reason_number) +{ + PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion()); + if(createRequest && packet){ + packet->setDataByName("account_id", GetAccountID()); + int8 clientReasonNum = reason_number; + // reason numbers change and instead of updating the world server + // the login server will hold the up to date #'s +/* + switch(reason_number) + { + // these error codes seem to be removed now, they shutdown the client rather immediately + // for now we are just going to play a joke on them and say they can't create a new character. + case INVALIDRACE_REPLY: + case INVALIDGENDER_REPLY: + clientReasonNum = 8; + break; + case BADNAMELENGTH_REPLY: + clientReasonNum = 9; + break; + case NAMEINVALID_REPLY: + clientReasonNum = 10; + break; + case NAMEFILTER_REPLY: + clientReasonNum = 11; + break; + case NAMETAKEN_REPLY: + clientReasonNum = 12; + break; + case OVERLOADEDSERVER_REPLY: + clientReasonNum = 13; + break; + } +*/ + packet->setDataByName("response", clientReasonNum); + packet->setMediumStringByName("name", ""); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + /*LS_CreateCharacterReply reply(GetAccountID(), reason_number, create.profile.name.data); + EQ2Packet* outapp = reply.serialize(); + QueuePacket(outapp); + create.Clear();*/ +} + +void Client::SendCharList(){ + /*PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply"); + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("response", reason_number); + packet->setDataByName("name", &create.profile.name); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet);*/ + LogWrite(LOGIN__INFO, 0, "Login", "[%s] sending character list.", GetAccountName()); + LS_CharSelectList list; + list.loadData(GetAccountID(), GetLoginAccount()->charlist, GetVersion()); + EQ2Packet* outapp = list.serialize(GetVersion()); + DumpPacket(outapp->pBuffer, outapp->size); + QueuePacket(outapp); + +} +void Client::SendLoginDeniedBadVersion(){ + EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse)); + LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer; + ls_response->reply_code = 6; + ls_response->unknown03 = 0xFFFFFFFF; + ls_response->unknown04 = 0xFFFFFFFF; + QueuePacket(app); + StartDisconnectTimer(); +} +void Client::SendLoginDenied(){ + EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse)); + LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer; + ls_response->reply_code = 1; + // reply_codes for AoM: + /* 1 = Login rejected: Invalid username or password. Please try again. + 2 = Login rejected: Server thinks your account is currently playing; you may have to wait " + "a few minutes for it to clear, then try again + 6 = Login rejected: The client's version does not match the server's. Please re-run the patcher. + 7 = Login rejected: You have no scheduled playtimes. + 8 = Your account does not have the features required to play on this server. + 11 = The client's build does not match the server's. Please re-run the patcher. + 12 = You must update your password in order to log in. Pressing OK will op" + "en your web browser to the SOE password management page + Other Value > 1 = Login rejected for an unknown reason. + */ + ls_response->unknown03 = 0xFFFFFFFF; + ls_response->unknown04 = 0xFFFFFFFF; + QueuePacket(app); + StartDisconnectTimer(); +} + +void Client::SendLoginAccepted(int32 account_id, int8 login_response) { + PacketStruct* packet = configReader.getStruct("LS_LoginReplyMsg", GetVersion()); + int i = 0; + if (packet) + { + packet->setDataByName("account_id", account_id); + + packet->setDataByName("login_response", login_response); + + packet->setDataByName("do_not_force_soga", 1); + + // sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership + // sub_level > 0 = class alignments still required, but portraits are viewable and race selectable + // sub_level = 2 membership, you can 'create characters on time locked servers' vs standard + // sub_level = 0 forces popup on close to web browser + packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel()); + packet->setDataByName("race_flag", 0x1FFFFF); + packet->setDataByName("class_flag", 0x7FFFFFE); + packet->setMediumStringByName("username", GetAccountName()); + packet->setMediumStringByName("password", GetAccountName()); + + // unknown5 + // full support = 0x7CFF + // 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city + // 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city + packet->setDataByName("unknown5", net.GetExpansionFlag()); + packet->setDataByName("unknown6", 0xFF); + packet->setDataByName("unknown6", 0xFF, 1); + packet->setDataByName("unknown6", 0xFF, 2); + + // controls class access / playable characters + packet->setDataByName("unknown10", 0xFF); + + // packet->setDataByName("unknown7a", 0x0101); + // packet->setDataByName("race_unknown", 0x01); + packet->setDataByName("unknown7", net.GetEnabledRaces()); // 0x01-0xFF disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits + packet->setDataByName("unknown7a", 0xEE); + packet->setDataByName("unknown8", net.GetCitiesFlag(), 1); // dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas) + + /* + 1 = city of qeynos + 2 = city of freeport + 4 = city of kelethin + 8 = city of neriak + 16 = gorowyn + 32 = new halas + 64 = queens colony + 128 = outpost overlord + */ + + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } +} + +void Client::SendWorldList(){ + EQ2Packet* pack = world_list.MakeServerListPacket(lsadmin, version); + EQ2Packet* dupe = pack->Copy(); + DumpPacket(dupe->pBuffer,dupe->size); + QueuePacket(dupe); + + SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags + return; +} + +void Client::QueuePacket(EQ2Packet* app){ + eqnc->EQ2QueuePacket(app); +} + +void Client::WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key) +{ + LWorld* world = world_list.FindByID(worldid); + if(world == 0) { + FatalError(0); + return; + } + if(response != 1){ + if(response == PLAY_ERROR_CHAR_NOT_LOADED){ + string pending_play_char_name = database.GetCharacterName(pending_play_char_id, worldid, GetAccountID()); + if(database.VerifyDelete(GetAccountID(), pending_play_char_id, pending_play_char_name.c_str())){ + GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str(), GetVersion()); + } + } + FatalError(response); + return; + } + + PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion()); + if(response_packet){ + safe_delete(playWaitTimer); + response_packet->setDataByName("response", 1); + response_packet->setSmallStringByName("server", ip_address); + response_packet->setDataByName("port", port); + response_packet->setDataByName("account_id", GetAccountID()); + response_packet->setDataByName("access_code", access_key); + EQ2Packet* outapp = response_packet->serialize(); + QueuePacket(outapp); + safe_delete(response_packet); + } + return; +} +void Client::FatalError(int8 response) { + safe_delete(playWaitTimer); + SendPlayFailed(response); +} + +void Client::SendPlayFailed(int8 response){ + PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion()); + if(response_packet){ + response_packet->setDataByName("response", response); + response_packet->setSmallStringByName("server", ""); + response_packet->setDataByName("port", 0); + response_packet->setDataByName("account_id", GetAccountID()); + response_packet->setDataByName("access_code", 0); + EQ2Packet* outapp = response_packet->serialize(); + QueuePacket(outapp); + safe_delete(response_packet); + } +} + +void ClientList::Add(Client* client) { + MClientList.writelock(); + client_list[client] = true; + MClientList.releasewritelock(); +} + +Client* ClientList::Get(int32 ip, int16 port) { + Client* ret = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->GetIP() == ip && itr->first->GetPort() == port){ + ret = itr->first; + break; + } + } + MClientList.releasereadlock(); + return ret; +} + +void ClientList::FindByCreateRequest(){ + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->AwaitingCharCreationRequest()){ + if(!client) + client = itr->first; + else{ + client = 0;//more than 1 character waiting, dont want to send rejection to wrong one + break; + } + } + } + MClientList.releasereadlock(); + if(client) + client->CharacterRejected(UNKNOWNERROR_REPLY); +} + +Client* ClientList::FindByLSID(int32 lsaccountid) { + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->GetAccountID() == lsaccountid){ + client = itr->first; + break; + } + } + MClientList.releasereadlock(); + return client; +} +void ClientList::SendPacketToAllClients(EQ2Packet* app){ + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + if(client_list.size() > 0){ + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + itr->first->QueuePacket(app->Copy()); + } + } + safe_delete(app); + MClientList.releasereadlock(); +} +void ClientList::Process() { + Client* client = 0; + vector erase_list; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + client = itr->first; + if(!client->Process()) + erase_list.push_back(client); + } + MClientList.releasereadlock(); + if(erase_list.size() > 0){ + vector::iterator erase_itr; + MClientList.writelock(); + for(erase_itr = erase_list.begin(); erase_itr != erase_list.end(); erase_itr++){ + client = *erase_itr; + struct in_addr in; + in.s_addr = client->getConnection()->GetRemoteIP(); + net.numclients--; + LogWrite(LOGIN__INFO, 0, "Login", "Removing client from ip: %s on port %i, Account Name: %s", inet_ntoa(in), ntohs(client->getConnection()->GetRemotePort()), client->GetAccountName()); + client->getConnection()->Close(); + net.UpdateWindowTitle(); + client_list.erase(client); + } + MClientList.releasewritelock(); + } +} + + +void Client::StartDisconnectTimer() { + if (!disconnectTimer) + { + disconnectTimer = new Timer(1000); + disconnectTimer->Start(); + } +} diff --git a/source/LoginServer/client.h b/source/LoginServer/client.h new file mode 100644 index 0000000..4d3936e --- /dev/null +++ b/source/LoginServer/client.h @@ -0,0 +1,131 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef CLIENT_H +#define CLIENT_H + +#include "../common/linked_list.h" +#include "../common/timer.h" +#include "../common/TCPConnection.h" +#include "login_structs.h" +#include "LoginAccount.h" +#include "../common/PacketStruct.h" +#include +#include + +enum eLoginMode { None, Normal, Registration }; +class DelayQue; +class Client +{ +public: + Client(EQStream* ieqnc); + ~Client(); + void SendLoginDenied(); + void SendLoginDeniedBadVersion(); + void SendLoginAccepted(int32 account_id = 1, int8 login_response = 0); + void SendWorldList(); + void SendCharList(); + int16 AddWorldToList2(uchar* buffer, char* name, int32 id, int16* flags); + void GenerateChecksum(EQApplicationPacket* outapp); + int8 LoginKey[10]; + int8 ClientSession[25]; + bool Process(); + void SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version); + void CharacterApproved(int32 server_id,int32 char_id); + void CharacterRejected(int8 reason_number); + EQStream* getConnection() { return eqnc; } + LoginAccount* GetLoginAccount() { return login_account; } + void SetLoginAccount(LoginAccount* in_account) { + login_account = in_account; + if(in_account) + account_id = in_account->getLoginAccountID(); + } + int16 GetVersion(){ return version; } + char* GetKey() { return key; } + void SetKey(char* in_key) { strcpy(key,in_key); } + int32 GetIP() { return ip; } + int16 GetPort() { return port; } + int32 GetAccountID() { return account_id; } + const char* GetAccountName(){ return (char*)account_name.c_str(); } + void SetAccountName(const char* name){ account_name = string(name); } + void ProcessLogin(char* name, char* pass,int seq=0); + void QueuePacket(EQ2Packet* app); + void FatalError(int8 response); + void WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key); + bool AwaitingCharCreationRequest(){ + if(createRequest) + return true; + else + return false; + } + Timer* updatetimer; + Timer* updatelisttimer; + Timer* disconnectTimer; + //Timer* keepalive; + //Timer* logintimer; + int16 packettotal; + int32 requested_server_id; + int32 request_num; + LinkedList delay_que; + void SendPlayFailed(int8 response); + + void StartDisconnectTimer(); +private: + string pending_play_char_name; + int32 pending_play_char_id; + int8 update_position; + int16 num_updates; + vector* update_packets; + LoginAccount* login_account; + EQStream* eqnc; + + int32 ip; + int16 port; + + int32 account_id; + string account_name; + char key[10]; + int8 lsadmin; + sint16 worldadmin; + int lsstatus; + bool kicked; + bool verified; + bool start; + bool needs_world_list; + int16 version; + char bannedreason[30]; + bool sent_character_list; + eLoginMode LoginMode; + PacketStruct* createRequest; + Timer* playWaitTimer; +}; + +class ClientList +{ +public: + ClientList() {} + ~ClientList() {} + + void Add(Client* client); + Client* Get(int32 ip, int16 port); + Client* FindByLSID(int32 lsaccountid); + void FindByCreateRequest(); + void SendPacketToAllClients(EQ2Packet* app); + void Process(); +private: + Mutex MClientList; + map client_list; +}; +class DelayQue { +public: + DelayQue(Timer* in_timer, EQApplicationPacket* in_packet){ + timer = in_timer; + packet = in_packet; + }; + Timer* timer; + EQApplicationPacket* packet; +}; +#endif diff --git a/source/LoginServer/login_opcodes.h b/source/LoginServer/login_opcodes.h new file mode 100644 index 0000000..734ae2f --- /dev/null +++ b/source/LoginServer/login_opcodes.h @@ -0,0 +1,52 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ + +#ifndef LOGIN_OPCODES_H +#define LOGIN_OPCODES_H +#define OP_Login2 0x0200 +#define OP_GetLoginInfo 0x0300 +#define OP_SendServersFragment 0x0D00 +#define OP_LoginInfo 0x0100 +#define OP_SessionId 0x0900 +#define OP_Disconnect 0x0500 +//#define OP_Reg_SendPricing 0x0400 +#define OP_AllFinish 0x0500 +#define OP_Ack5 0x1500 +#define OP_Chat_ChannelList 0x0600 +#define OP_Chat_JoinChannel 0x0700 +#define OP_Chat_PartChannel 0x0800 +#define OP_Chat_ChannelMessage 0x0930 +#define OP_Chat_Tell 0x0a00 +#define OP_Chat_SysMsg 0x0b00 +#define OP_Chat_CreateChannel 0x0c00 +#define OP_Chat_ChangeChannel 0x0d00 +#define OP_Chat_DeleteChannel 0x0e00 +#define OP_Chat_UserList 0x1000 +#define OP_Reg_GetPricing 0x1a00 // for new account signup +#define OP_Reg_SendPricing 0x1b00 +#define OP_RegisterAccount 0x2300 +#define OP_Chat_ChannelWelcome 0x2400 +#define OP_Chat_PopupMakeWindow 0x3000 +#define OP_BillingInfoAccepted 0x3300 // i THINK =p +#define OP_CheckGameCardValid 0x3400 +#define OP_GameCardTimeLeft 0x3600 +#define OP_AccountExpired 0x4200 +#define OP_Reg_GetPricing2 0x4400 // for re-registering +#define OP_ChangePassword 0x4500 +#define OP_ServerList 0x4600 +#define OP_SessionKey 0x4700 +#define OP_RequestServerStatus 0x4800 +#define OP_SendServerStatus 0x4A00 +#define OP_Reg_ChangeAcctLogin 0x5100 +#define OP_LoginBanner 0x5200 +#define OP_Chat_GuildsList 0x5500 +#define OP_Chat_GuildEdit 0x5700 +#define OP_Version 0x5900 +#define OP_RenewAccountBillingInfo 0x7a00 + +#endif /* LOGIN_OPCODES_H */ + diff --git a/source/LoginServer/login_structs.h b/source/LoginServer/login_structs.h new file mode 100644 index 0000000..bd8e9b9 --- /dev/null +++ b/source/LoginServer/login_structs.h @@ -0,0 +1,61 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef LOGIN_STRUCTS_H +#define LOGIN_STRUCTS_H + +#include "../common/types.h" +#include "PacketHeaders.h" + +#pragma pack(1) +struct LS_LoginRequest{ + EQ2_16BitString AccessCode; + EQ2_16BitString unknown1; + EQ2_16BitString username; + EQ2_16BitString password; + EQ2_16BitString unknown2[4]; + int16 unknown3; + int32 unknown4[2]; +}; +struct LS_WorldStatusChanged{ + int32 server_id; + int8 up; + int8 locked; + int8 hidden; +}; +struct LS_PlayCharacterRequest{ + int32 character_id; + int32 server_id; + int16 unknown1; +}; +struct LS_OLDPlayCharacterRequest{ + int32 character_id; + EQ2_16BitString name; +}; + +struct LS_CharListAccountInfoEarlyClient { + int32 account_id; + int32 unknown1; + int16 unknown2; + int32 maxchars; + int8 unknown4; // 15 bytes total + // int8 unknown7; // adds 'free' option.. +}; + +struct LS_CharListAccountInfo{ + int32 account_id; + int32 unknown1; + int16 unknown2; + int32 maxchars; + // DoF does not have the following data + int8 unknown4; + int32 unknown5[4]; + int8 vet_adv_bonus; // sets Veteran Bonus under 'Select Character' yellow (vs greyed out), adventure/tradeskill bonus 200% + int8 vet_trade_bonus; // when 1 (count?) provides free upgrade option for character to lvl 90 (heroic character) -- its a green 'Free' up arrow next to the character that is selected in char select +}; // 33 bytes +#pragma pack() + +#endif diff --git a/source/LoginServer/makefile b/source/LoginServer/makefile new file mode 100644 index 0000000..a4ce509 --- /dev/null +++ b/source/LoginServer/makefile @@ -0,0 +1,32 @@ +APP=login +SF= ../common/Log.o ../common/timer.o ../common/packet_dump.o ../common/unix.o \ + ../common/Mutex.o ../common/MiscFunctions.o LoginDatabase.o LoginAccount.o \ + ../common/TCPConnection.o ../common/emu_opcodes.o \ + client.o net.o PacketHeaders.o LWorld.o ../common/md5.o ../common/dbcore.o \ + Web/LoginWeb.o \ + ../common/EQEMuError.o ../common/misc.o ../common/Crypto.o ../common/RC4.o \ + .obj/debug.o .obj/database.o .obj/EQStream.o ../common/xmlParser.o \ + .obj/EQStreamFactory.o .obj/EQPacket.o ../common/CRC16.o ../common/packet_functions.o \ + ../common/Condition.o ../common/opcodemgr.o ../common/PacketStruct.o ../common/ConfigReader.o \ + ../common/DatabaseNew.o ../common/DatabaseResult.o ../common/Web/WebServer.o ../common/JsonParser.o + +CC=g++ +LINKER=gcc +DFLAGS=-DEQ2 -DLOGIN +WFLAGS=-Wall -Wuninitialized -Wwrite-strings -Wcast-qual -Wcomment -Wcast-align -Wno-deprecated +COPTS=$(WFLAGS) -ggdb -march=native -pthread -pipe -DFX -D_GNU_SOURCE -DINVERSEXY $(DFLAGS) -I/usr/include/mariadb -I/usr/local/include/boost -std=c++17 +LINKOPTS=-rdynamic -L. -lstdc++ -lm -lz -L/usr/lib/x86_64-linux-gnu -lmariadb -lboost_system -lboost_thread -lboost_filesystem -lssl -lcrypto -lpthread -ldl +all: $(APP) + +$(APP): $(SF) + $(LINKER) $(COPTS) $(OBJS) $^ $(LINKOPTS) -o $@ + +clean: + rm -f $(SF) $(APP) + +%.o: %.cpp + $(CC) -c $(COPTS) $< -o $@ + +.obj/%.o: ../common/%.cpp ../common/%.h + mkdir -p .obj + $(CC) $(COPTS) -c $< -o $@ diff --git a/source/LoginServer/net.cpp b/source/LoginServer/net.cpp new file mode 100644 index 0000000..fe6b8cd --- /dev/null +++ b/source/LoginServer/net.cpp @@ -0,0 +1,363 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "../common/queue.h" +#include "../common/timer.h" + +#include "../common/seperator.h" + +#include "net.h" +#include "client.h" + +#include "LoginDatabase.h" +#include "LWorld.h" +#include "../common/packet_functions.h" +#include "../common/EQStreamFactory.h" +#include "../common/MiscFunctions.h" +#include "../common/version.h" + +#include "../common/PacketStruct.h" +#include "../common/DataBuffer.h" +#include "../common/ConfigReader.h" +#include "../common/Log.h" +#include "../common/JsonParser.h" +#include "../common/Common_Defines.h" + +#ifdef WIN32 + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include + #include "../common/unix.h" +#endif +EQStreamFactory eqsf(LoginStream); +mapEQOpcodeManager; +//TCPServer eqns(5999); +NetConnection net; +ClientList client_list; +LWorldList world_list; +LoginDatabase database; +ConfigReader configReader; +map EQOpcodeVersions; +Timer statTimer(60000); + +volatile bool RunLoops = true; +bool ReadLoginConfig(); + +#ifdef PUBLICLOGIN +char version[200], consoletitle[200]; +#endif +#include "../common/timer.h" + +#include "../common/CRC16.h" +#include + +int main(int argc, char** argv){ +#ifdef _DEBUG + _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + if (signal(SIGINT, CatchSignal) == SIG_ERR) { + cerr << "Could not set signal handler" << endl; + } + + LogStart(); + + LogParseConfigs(); + net.WelcomeHeader(); + + srand(time(NULL)); + + if(!net.ReadLoginConfig()) + return 1; + + net.InitWebServer(net.GetWebLoginAddress(), net.GetWebLoginPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword()); + + const char* structList[] = { "CommonStructs.xml", "LoginStructs.xml" }; + + for (int s = 0; s < sizeof(structList) / sizeof(const char*); s++) + { + LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structList[s]); + if (configReader.processXML_Elements(structList[s])) + LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structList[s]); + else + { + LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structList[s]); + return 1; + } + } + + + LogWrite(INIT__INFO, 0, "Init", "Initialize World List.."); + world_list.Init(); + + if(eqsf.listen_ip_address) + LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", eqsf.listen_ip_address, net.GetPort()); + else + LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort()); + /*} + else { + cout << "EQNetworkServer.Open() error" << endl; + return 1; + }*/ + if (!eqsf.Open(net.GetPort())) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort()); + return 1; + } + net.login_running = true; + net.login_uptime = getCurrentTimestamp(); + + net.UpdateWindowTitle(); + EQStream* eqs; + Timer* TimeoutTimer = new Timer(5000); + TimeoutTimer->Start(); + while(RunLoops) { + Timer::SetCurrentTime(); + while ((eqs = eqsf.Pop())) { + struct in_addr in; + in.s_addr = eqs->GetRemoteIP(); + + LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", inet_ntoa(in), ntohs(eqs->GetRemotePort())); + Client* client = new Client(eqs); + eqs->SetClientVersion(0); + client_list.Add(client); + net.numclients++; + net.UpdateWindowTitle(); + } + if(TimeoutTimer->Check()){ + eqsf.CheckTimeout(); + } + if(statTimer.Check()){ + world_list.UpdateWorldStats(); + database.RemoveOldWorldServerStats(); + database.FixBugReport(); + } + client_list.Process(); + world_list.Process(); +#ifdef WIN32 + if(kbhit()) + { + int hitkey = getch(); + net.HitKey(hitkey); + } +#endif + Sleep(1); + } + //close + //eqns.Close(); + eqsf.Close(); + world_list.Shutdown(); + return 0; +} +#ifdef WIN32 +void NetConnection::HitKey(int keyhit) +{ + switch(keyhit) + { + case 'l': + case 'L': { + world_list.ListWorldsToConsole(); + break; + } + case 'v': + case 'V': + { + printf("========Version Info=========\n"); + printf("%s %s\n", EQ2EMU_MODULE, CURRENT_VERSION); + printf("Last Compiled on %s %s\n", COMPILE_DATE, COMPILE_TIME); + printf("=============================\n\n"); + break; + } + case 'H': + case 'h': { + printf("===========Help=============\n"); + printf("Available Commands:\n"); + printf("l = Listing of World Servers\n"); + printf("v = Login Version\n"); +// printf("0 = Kick all connected world servers\n"); + printf("============================\n\n"); + break; + } + default: + printf("Invalid Command.\n"); + break; + } +} +#endif + +void CatchSignal(int sig_num) { + cout << "Got signal " << sig_num << endl; + RunLoops = false; +} + +bool NetConnection::ReadLoginConfig() { + JsonParser parser(MAIN_CONFIG_FILE); + if(!parser.IsLoaded()) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE); + return false; + } + std::string serverport = parser.getValue("loginconfig.serverport"); + std::string serverip = parser.getValue("loginconfig.serverip"); + + if (!parser.convertStringToUnsignedShort(serverport, port)) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport.."); + return false; + } + + if(serverip.size() > 0) { + eqsf.listen_ip_address = new char[serverip.size() + 1]; + strcpy(eqsf.listen_ip_address, serverip.c_str()); + } + else { + safe_delete(eqsf.listen_ip_address); + eqsf.listen_ip_address = nullptr; + } + + std::string acctcreate_str = parser.getValue("loginconfig.accountcreation"); + int16 allow_acct = 0; + parser.convertStringToUnsignedShort(acctcreate_str, allow_acct); + allowAccountCreation = allow_acct > 0 ? true : false; + + std::string expflag_str = parser.getValue("loginconfig.expansionflag"); + parser.convertStringToUnsignedInt(expflag_str, expansionFlag); + + std::string citiesflag_str = parser.getValue("loginconfig.citiesflag"); + parser.convertStringToUnsignedChar(citiesflag_str, citiesFlag); + + std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel"); + parser.convertStringToUnsignedInt(defaultsublevel_str, defaultSubscriptionLevel); + + std::string enableraces_str = parser.getValue("loginconfig.enabledraces"); + parser.convertStringToUnsignedInt(enableraces_str, enabledRaces); + + web_loginaddress = parser.getValue("loginconfig.webloginaddress"); + web_certfile = parser.getValue("loginconfig.webcertfile"); + web_keyfile = parser.getValue("loginconfig.webkeyfile"); + web_keypassword = parser.getValue("loginconfig.webkeypassword"); + web_hardcodeuser = parser.getValue("loginconfig.webhardcodeuser"); + web_hardcodepassword = parser.getValue("loginconfig.webhardcodepassword"); + + std::string webloginport_str = parser.getValue("loginconfig.webloginport"); + parser.convertStringToUnsignedShort(webloginport_str, web_loginport); + + LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE); + + + LogWrite(INIT__INFO, 0, "Init", "Database init begin.."); + //remove this when all database calls are using the new database class + if (!database.Init()) { + LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!"); + LogStop(); + return false; + } + + LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0.."); + EQOpcodeVersions = database.GetVersions(); + map::iterator version_itr2; + int16 version1 = 0; + for (version_itr2 = EQOpcodeVersions.begin(); version_itr2 != EQOpcodeVersions.end(); version_itr2++) { + version1 = version_itr2->first; + EQOpcodeManager[version1] = new RegularOpcodeManager(); + map eq = database.GetOpcodes(version1); + if(!EQOpcodeManager[version1]->LoadOpcodes(&eq)) { + LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!"); + return false; + } + } + + return true; +} + +void NetConnection::UpdateWindowTitle(char* iNewTitle) { +#ifdef WIN32 + char tmp[500]; + if (iNewTitle) { + snprintf(tmp, sizeof(tmp), "Login: %s", iNewTitle); + } + else { + snprintf(tmp, sizeof(tmp), "%s, Version: %s: %i Server(s), %i Client(s) Connected", EQ2EMU_MODULE, CURRENT_VERSION, net.numservers, net.numclients); + } + SetConsoleTitle(tmp); +#endif +} + +void NetConnection::WelcomeHeader() +{ +#ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); +#endif + printf("\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n"); + printf("EQ2Emulator is free software: you can redistribute it and/or modify\n"); + printf("it under the terms of the GNU General Public License as published by\n"); + printf("the Free Software Foundation, either version 3 of the License, or\n"); + printf("(at your option) any later version.\n\n"); + printf("EQ2Emulator is distributed in the hope that it will be useful,\n"); + printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); + printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); + printf("GNU General Public License for more details.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_GREEN_BOLD); +#endif + printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n"); + printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n"); + printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n"); + printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n"); + printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n"); + printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n"); + printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n"); + printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n"); + printf(" \\__/ \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_MAGENTA_BOLD); +#endif + printf(" Website : https://eq2emu.com \n"); + printf(" Wiki : https://wiki.eq2emu.com \n"); + printf(" Git : https://git.eq2emu.com \n"); + printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE); +#endif + + fflush(stdout); +} + +void NetConnection::InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password) { + if(web_ipaddr.size() > 0 && web_port > 0) { + try { + login_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password); + + login_webserver->register_route("/status", NetConnection::Web_loginhandle_status); + login_webserver->register_route("/worlds", NetConnection::Web_loginhandle_worlds); + login_webserver->run(); + LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port); + } + catch (const std::exception& e) { + LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what()); + } + } +} \ No newline at end of file diff --git a/source/LoginServer/net.h b/source/LoginServer/net.h new file mode 100644 index 0000000..0f7b628 --- /dev/null +++ b/source/LoginServer/net.h @@ -0,0 +1,147 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN + #include + #include +#else + #include + #include + #include + #include +#endif + +//#include +#include +#include +#include + +#include "../common/types.h" +#include "../common/Web/WebServer.h" +#include "../common/MiscFunctions.h" + +void CatchSignal(int sig_num); +enum eServerMode { Standalone, Master, Slave, Mesh }; + +class NetConnection +{ +public: + NetConnection() { + port = 5999; + listening_socket = 0; + memset(masteraddress, 0, sizeof(masteraddress)); + uplinkport = 0; + memset(uplinkaccount, 0, sizeof(uplinkaccount)); + memset(uplinkpassword, 0, sizeof(uplinkpassword)); + LoginMode = Standalone; + Uplink_WrongVersion = false; + numclients = 0; + numservers = 0; + allowAccountCreation = true; + + // full support = 0x7CFF + // 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city + // 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city + expansionFlag = 0x7CFF; // 0x4CF5 + + /* dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas) + 1 = city of qeynos + 2 = city of freeport + 4 = city of kelethin + 8 = city of neriak + 16 = gorowyn + 32 = new halas + 64 = queens colony + 128 = outpost overlord + */ + citiesFlag = 0xFF; + + // sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership + // sub_level > 0 = class alignments still required, but portraits are viewable and race selectable + // sub_level = 2 membership, you can 'create characters on time locked servers' vs standard + // sub_level = 0 forces popup on close to web browser + defaultSubscriptionLevel = 0xFFFFFFFF; + + // disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits + enabledRaces = 0xFFFF; // 0xCFFF + + web_loginport = 0; + + login_webserver = nullptr; + + login_running = false; + login_uptime = getCurrentTimestamp(); + } + + ~NetConnection() { + safe_delete(login_webserver); + } + + void UpdateWindowTitle(char* iNewTitle = 0); + bool Init(); + void ListenNewClients(); + void HitKey(int keyhit); + char address[1024]; + int32 numclients; + int32 numservers; + + int16 GetPort() { return port; } + void SetPort(int16 in_port) { port = in_port; } + eServerMode GetLoginMode() { return LoginMode; } + + bool ReadLoginConfig(); + char* GetMasterAddress() { return masteraddress; } + int16 GetUplinkPort() { if (uplinkport != 0) return uplinkport; else return port; } + char* GetUplinkAccount() { return uplinkaccount; } + char* GetUplinkPassword() { return uplinkpassword; } + + bool IsAllowingAccountCreation() { return allowAccountCreation; } + int32 GetExpansionFlag() { return expansionFlag; } + int8 GetCitiesFlag() { return citiesFlag; } + int32 GetDefaultSubscriptionLevel() { return defaultSubscriptionLevel; } + int32 GetEnabledRaces() { return enabledRaces; } + std::string GetWebLoginAddress() { return web_loginaddress; } + inline int16 GetWebLoginPort() { return web_loginport; } + std::string GetWebCertFile() { return web_certfile; } + std::string GetWebKeyFile() { return web_keyfile; } + std::string GetWebKeyPassword() { return web_keypassword; } + std::string GetWebHardcodeUser() { return web_hardcodeuser; } + std::string GetWebHardcodePassword() { return web_hardcodepassword; } + void WelcomeHeader(); + + void InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password); + + static void Web_loginhandle_status(const http::request& req, http::response& res); + static void Web_loginhandle_worlds(const http::request& req, http::response& res); + + bool login_running; + std::atomic login_uptime; +protected: + friend class LWorld; + bool Uplink_WrongVersion; +private: + int16 port; + int listening_socket; + char masteraddress[300]; + int16 uplinkport; + char uplinkaccount[300]; + char uplinkpassword[300]; + eServerMode LoginMode; + bool allowAccountCreation; + int32 expansionFlag; + int8 citiesFlag; + int32 defaultSubscriptionLevel; + int32 enabledRaces; + std::string web_loginaddress; + std::string web_certfile; + std::string web_keyfile; + std::string web_keypassword; + std::string web_hardcodeuser; + std::string web_hardcodepassword; + int16 web_loginport; + WebServer* login_webserver; +}; diff --git a/source/WorldServer/Achievements/Achievements.cpp b/source/WorldServer/Achievements/Achievements.cpp new file mode 100644 index 0000000..0d65164 --- /dev/null +++ b/source/WorldServer/Achievements/Achievements.cpp @@ -0,0 +1,332 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#include "Achievements.h" + +#include "../../common/Log.h" +#include "../../common/ConfigReader.h" +#include + +extern ConfigReader configReader; +extern MasterAchievementList master_achievement_list; + +Achievement::Achievement() { + id = 0; + memset(title, 0, sizeof(title)); + memset(uncompleted_text, 0, sizeof(uncompleted_text)); + memset(completed_text, 0, sizeof(completed_text)); + memset(category, 0, sizeof(category)); + memset(expansion, 0, sizeof(expansion)); + icon = 0; + point_value = 0; + qty_req = 0; + hide = false; + unknown3a = 0; + unknown3b = 0; +} + +Achievement::Achievement(Achievement *in) { + vector *requirements_in; + vector *rewards_in; + vector::iterator itr; + vector::iterator itr2; + struct AchievementRequirements *achievement_requirement; + struct AchievementRewards *achievement_reward; + + assert(in); + + id = in->GetID(); + strncpy(title, in->GetTitle(), sizeof(title)); + strncpy(uncompleted_text, in->GetUncompletedText(), sizeof(uncompleted_text)); + strncpy(completed_text, in->GetCompletedText(), sizeof(completed_text)); + strncpy(category, in->GetCategory(), sizeof(category)); + strncpy(expansion, in->GetExpansion(), sizeof(expansion)); + icon = in->GetIcon(); + point_value = in->GetPointValue(); + qty_req = in->GetQtyReq(); + hide = in->GetHide(); + unknown3a = in->GetUnknown3a(); + unknown3b = in->GetUnknown3b(); + + requirements_in = in->GetRequirements(); + for (itr = requirements_in->begin(); itr != requirements_in->end(); itr++) { + achievement_requirement = new struct AchievementRequirements; + achievement_requirement->achievement_id = (*itr)->achievement_id; + achievement_requirement->name = (*itr)->name; + achievement_requirement->qty_req = (*itr)->qty_req; + requirements.push_back(achievement_requirement); + } + + rewards_in = in->GetRewards(); + for (itr2 = rewards_in->begin(); itr2 != rewards_in->end(); itr2++) { + achievement_reward = new struct AchievementRewards; + achievement_reward->achievement_id = (*itr2)->achievement_id; + achievement_reward->reward = (*itr2)->reward; + rewards.push_back(achievement_reward); + } +} + +Achievement::~Achievement() { + vector::iterator itr; + vector::iterator itr2; + + for (itr = requirements.begin(); itr != requirements.end(); itr++) + safe_delete(*itr); + for (itr2 = rewards.begin(); itr2 != rewards.end(); itr2++) + safe_delete(*itr2); +} + +void Achievement::AddAchievementRequirement(struct AchievementRequirements *requirement) { + assert(requirement); + + requirements.push_back(requirement); +} + +void Achievement::AddAchievementReward(struct AchievementRewards *reward) { + assert(reward); + + rewards.push_back(reward); +} + +void AchievementUpdate::AddAchievementUpdateItems(struct AchievementUpdateItems *update_item) { + assert(update_item); + + update_items.push_back(update_item); +} + +MasterAchievementList::MasterAchievementList() { + m_packetsCreated = false; + masterPacket = 0; + mutex_achievements.SetName("MasterAchievementList::achievements"); +} + +MasterAchievementList::~MasterAchievementList() { + ClearAchievements(); +} + +bool MasterAchievementList::AddAchievement(Achievement *achievement) { + bool ret = false; + + assert(achievement); + + mutex_achievements.writelock(__FUNCTION__, __LINE__); + if (achievements.count(achievement->GetID()) == 0) { + achievements[achievement->GetID()] = achievement; + ret = true; + } + mutex_achievements.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +Achievement * MasterAchievementList::GetAchievement(int32 achievement_id) { + Achievement *achievement = 0; + + mutex_achievements.readlock(__FUNCTION__, __LINE__); + if (achievements.count(achievement_id) > 0) + achievement = achievements[achievement_id]; + mutex_achievements.releasereadlock(__FUNCTION__, __LINE__); + + return achievement; +} + +void MasterAchievementList::ClearAchievements() { + map::iterator itr; + + mutex_achievements.writelock(__FUNCTION__, __LINE__); + for (itr = achievements.begin(); itr != achievements.end(); itr++) + safe_delete(itr->second); + achievements.clear(); + mutex_achievements.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 MasterAchievementList::Size() { + int32 size; + + mutex_achievements.readlock(__FUNCTION__, __LINE__); + size = achievements.size(); + mutex_achievements.releasereadlock(__FUNCTION__, __LINE__); + + return size; +} + +PlayerAchievementList::PlayerAchievementList() { +} + +PlayerAchievementList::~PlayerAchievementList() { + ClearAchievements(); +} + +bool PlayerAchievementList::AddAchievement(Achievement *achievement) { + assert(achievement); + + if (achievements.count(achievement->GetID()) == 0) { + achievements[achievement->GetID()] = achievement; + return true; + } + + return false; +} + +Achievement * PlayerAchievementList::GetAchievement(int32 achievement_id) { + if (achievements.count(achievement_id) > 0) + return achievements[achievement_id]; + + return 0; +} + +void PlayerAchievementList::ClearAchievements() { + map::iterator itr; + + for (itr = achievements.begin(); itr != achievements.end(); itr++) + safe_delete(itr->second); + achievements.clear(); +} + +int32 PlayerAchievementList::Size() { + return achievements.size(); +} + +AchievementUpdate::AchievementUpdate() { + id = 0; + completed_date = 0; + +} + +AchievementUpdate::AchievementUpdate(AchievementUpdate *in) { + vector *items_in; + vector::iterator itr; + struct AchievementUpdateItems *items; + + assert(in); + + id = in->GetID(); + completed_date = in->GetCompletedDate(); + + items_in = in->GetUpdateItems(); + for (itr = items_in->begin(); itr != items_in->end(); itr++) { + items = new struct AchievementUpdateItems; + items->achievement_id = (*itr)->achievement_id; + items->item_update = (*itr)->item_update; + update_items.push_back(items); + } +} + +AchievementUpdate::~AchievementUpdate() { + vector::iterator itr; + + for (itr = update_items.begin(); itr != update_items.end(); itr++) + safe_delete(*itr); +} + + +PlayerAchievementUpdateList::PlayerAchievementUpdateList() { + +} + +PlayerAchievementUpdateList::~PlayerAchievementUpdateList() { + ClearAchievementUpdates(); +} + +bool PlayerAchievementUpdateList::AddAchievementUpdate(AchievementUpdate *update) { + assert(update); + + if (achievement_updates.count(update->GetID()) == 0) { + achievement_updates[update->GetID()] = update; + return true; + } + return false; +} + +void PlayerAchievementUpdateList::ClearAchievementUpdates() { + map::iterator itr; + + for (itr = achievement_updates.begin(); itr != achievement_updates.end(); itr++) + safe_delete(itr->second); + achievement_updates.clear(); +} + +int32 PlayerAchievementUpdateList::Size() { + return achievement_updates.size(); +} + +void MasterAchievementList::CreateMasterAchievementListPacket() { + map::iterator itr; + Achievement *achievement; + vector *requirements = 0; + vector::iterator itr2; + AchievementRequirements *requirement; + vector *rewards = 0; + vector::iterator itr3; + AchievementRewards *reward; + PacketStruct *packet; + int16 i = 0; + int16 j = 0; + int16 k = 0; + int16 version = 1096; + + if (!(packet = configReader.getStruct("WS_CharacterAchievements", version))) { + return; + } + + packet->setArrayLengthByName("num_achievements" , achievements.size()); + for (itr = achievements.begin(); itr != achievements.end(); itr++) { + achievement = itr->second; + packet->setArrayDataByName("achievement_id", achievement->GetID(), i); + packet->setArrayDataByName("title", achievement->GetTitle(), i); + packet->setArrayDataByName("uncompleted_text", achievement->GetUncompletedText(), i); + packet->setArrayDataByName("completed_text", achievement->GetCompletedText(), i); + packet->setArrayDataByName("category", achievement->GetCategory(), i); + packet->setArrayDataByName("expansion", achievement->GetExpansion(), i); + packet->setArrayDataByName("icon", achievement->GetIcon(), i); + packet->setArrayDataByName("point_value", achievement->GetPointValue(), i); + packet->setArrayDataByName("qty_req", achievement->GetQtyReq(), i); + packet->setArrayDataByName("hide_achievement", achievement->GetHide(), i); + packet->setArrayDataByName("unknown3", achievement->GetUnknown3a(), i); + packet->setArrayDataByName("unknown3", achievement->GetUnknown3b(), i); + requirements = achievement->GetRequirements(); + rewards = achievement->GetRewards(); + j = 0; + k = 0; + packet->setSubArrayLengthByName("num_items", requirements->size(), i, j); + for (itr2 = requirements->begin(); itr2 != requirements->end(); itr2++) { + requirement = *itr2; + packet->setSubArrayDataByName("item_name", requirement->name.c_str(), i, j); + packet->setSubArrayDataByName("item_qty_req", requirement->qty_req, i, j); + j++; + } + packet->setSubArrayLengthByName("num_rewards", achievement->GetRewards()->size(), i, k); + for (itr3 = rewards->begin(); itr3 != rewards->end(); itr3++) { + reward = *itr3; + packet->setSubArrayDataByName("reward_item", reward->reward.c_str(), i, k); + k++; + } + i++; + } + + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + masterPacket = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + safe_delete(packet); + safe_delete(data); + //DumpPacket(app); + + m_packetsCreated = true; +} diff --git a/source/WorldServer/Achievements/Achievements.h b/source/WorldServer/Achievements/Achievements.h new file mode 100644 index 0000000..7b1895c --- /dev/null +++ b/source/WorldServer/Achievements/Achievements.h @@ -0,0 +1,176 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#ifndef ACHIEVEMENTS_H_ +#define ACHIEVEMENTS_H_ + +#include "../../common/types.h" +#include "../../common/Mutex.h" +#include "../Items/Items.h" +#include +#include + +using namespace std; + +struct AchievementRewards +{ + int32 achievement_id; + string reward; +}; + +struct AchievementRequirements +{ + int32 achievement_id; + string name; + int32 qty_req; +}; + +struct AchievementUpdateItems +{ + int32 achievement_id; + int32 item_update; +}; + +class Achievement { +public: + Achievement(); + Achievement(Achievement *in); + virtual ~Achievement(); + + void SetID(int32 id) {this->id = id;} + void SetTitle(const char *title) {strncpy(this->title, title, sizeof(this->title));} + void SetUncompletedText(const char *uncompleted_text) {strncpy(this->uncompleted_text, uncompleted_text, sizeof(this->uncompleted_text));} + void SetCompletedText(const char *completed_text) {strncpy(this->completed_text, completed_text, sizeof(this->completed_text));} + void SetCategory(const char *category) {strncpy(this->category, category, sizeof(this->category));} + void SetExpansion(const char *expansion) {strncpy(this->expansion, expansion, sizeof(this->expansion));} + void SetIcon(int16 icon) {this->icon = icon;} + void SetPointValue(int32 point_value) {this->point_value = point_value;} + void SetQtyReq(int32 qty_req) {this->qty_req = qty_req;} + void SetHide(bool hide) {this->hide = hide;} + void SetUnknown3a(int32 unknown3a) {this->unknown3a = unknown3a;} + void SetUnknown3b(int32 unknown3b) {this->unknown3b = unknown3b;} + + void AddAchievementRequirement(struct AchievementRequirements *requirements); + void AddAchievementReward(struct AchievementRewards *reward); + + int32 GetID() {return id;} + const char * GetTitle() {return title;} + const char * GetUncompletedText() {return uncompleted_text;} + const char * GetCompletedText() {return completed_text;} + const char * GetCategory() {return category;} + const char * GetExpansion() {return expansion;} + int16 GetIcon() {return icon;} + int32 GetPointValue() {return point_value;} + int32 GetQtyReq() {return qty_req;} + bool GetHide() {return hide;} + int32 GetUnknown3a() {return unknown3a;} + int32 GetUnknown3b() {return unknown3b;} + vector * GetRequirements() {return &requirements;} + vector * GetRewards() {return &rewards;} + +private: + int32 id; + char title[512]; + char uncompleted_text[512]; + char completed_text[512]; + char category[32]; + char expansion[32]; + int16 icon; + int32 point_value; + int32 qty_req; + bool hide; + int32 unknown3a; + int32 unknown3b; + vector requirements; + vector rewards; +}; + +class AchievementUpdate { +public: + AchievementUpdate(); + AchievementUpdate(AchievementUpdate *in); + virtual ~AchievementUpdate(); + + void SetID(int32 id) {this->id = id;} + void SetCompletedDate(int32 completed_date) {this->completed_date = completed_date;} + + void AddAchievementUpdateItems(struct AchievementUpdateItems *update_items); + + int32 GetID() {return id;} + int32 GetCompletedDate() {return completed_date;} + + vector * GetUpdateItems() {return &update_items;} + +private: + int32 id; + int32 completed_date; + vector update_items; +}; + +class MasterAchievementList { +public: + MasterAchievementList(); + virtual ~MasterAchievementList(); + + bool AddAchievement(Achievement *achievement); + Achievement * GetAchievement(int32 achievement_id); + void ClearAchievements(); + int32 Size(); + void CreateMasterAchievementListPacket(); + EQ2Packet * GetAchievementPacket() { return m_packetsCreated ? masterPacket : 0;} + EQ2Packet *masterPacket; +private: + Mutex mutex_achievements; + map achievements; + + bool m_packetsCreated; +}; + +class PlayerAchievementList { +public: + PlayerAchievementList(); + virtual ~PlayerAchievementList(); + + bool AddAchievement(Achievement *achievement); + Achievement * GetAchievement(int32 achievement_id); + void ClearAchievements(); + int32 Size(); + + map * GetAchievements() {return &achievements;} + +private: + map achievements; +}; + +class PlayerAchievementUpdateList { +public: + PlayerAchievementUpdateList(); + virtual ~PlayerAchievementUpdateList(); + + bool AddAchievementUpdate(AchievementUpdate *achievement_update); + void ClearAchievementUpdates(); + int32 Size(); + + map * GetAchievementUpdates() {return &achievement_updates;} + +private: + map achievement_updates; +}; +#endif \ No newline at end of file diff --git a/source/WorldServer/Achievements/AchievementsDB.cpp b/source/WorldServer/Achievements/AchievementsDB.cpp new file mode 100644 index 0000000..b8fd9bd --- /dev/null +++ b/source/WorldServer/Achievements/AchievementsDB.cpp @@ -0,0 +1,241 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Achievements.h" + +extern MasterAchievementList master_achievement_list; + +void WorldDatabase::LoadAchievements() +{ + Achievement *achievement; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 aReqs_total = 0; + int32 aRewards_total = 0; + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`,`title`,`uncompleted_text`,`completed_text`,`category`,`expansion`,`icon`,`point_value`,`qty_req`,`hide_achievement`,`unknown3a`,`unknown3b`\n" + "FROM `achievements`"); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + achievement = new Achievement(); + achievement->SetID(atoul(row[0])); + achievement->SetTitle(row[1]); + achievement->SetUncompletedText(row[2]); + achievement->SetCompletedText(row[3]); + achievement->SetCategory(row[4]); + achievement->SetExpansion(row[5]); + achievement->SetIcon(atoi(row[6])); + achievement->SetPointValue(atoul(row[7])); + achievement->SetQtyReq(atoul(row[8])); + achievement->SetHide( atoi(row[9]) == 0 ? false : true ); + achievement->SetUnknown3a(atoul(row[10])); + achievement->SetUnknown3b(atoul(row[11])); + + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "\tLoading Achievement: '%s' (%u)", achievement->GetTitle(), achievement->GetID()); + + if (!master_achievement_list.AddAchievement(achievement)) + { + LogWrite(ACHIEVEMENT__ERROR, 0, "Achievements", "Error adding achievement '%s' - duplicate ID: %u", achievement->GetTitle(), achievement->GetID()); + safe_delete(achievement); + continue; + } + + aReqs_total += LoadAchievementRequirements(achievement); + aRewards_total += LoadAchievementRewards(achievement); + } + } + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievements", master_achievement_list.Size()); + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement requirements", aReqs_total); + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement rewards", aRewards_total); + +} + +int32 WorldDatabase::LoadAchievementRequirements(Achievement *achievement) +{ + AchievementRequirements *requirements; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int16 total = 0; + + assert(achievement); + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`, `name`, `qty_req` FROM `achievements_requirements` WHERE `achievement_id` = %u", achievement->GetID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + requirements = new AchievementRequirements(); + requirements->achievement_id = atoul(row[0]); + requirements->name = row[1]; + requirements->qty_req = atoul(row[2]); + achievement->AddAchievementRequirement(requirements); + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loading Achievements Requirement '%s'", requirements->name.c_str()); + total++; + } + } + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loaded %u requirements for achievement '%s' ID: %u", total, achievement->GetTitle(), achievement->GetID()); + return total; +} + +int32 WorldDatabase::LoadAchievementRewards(Achievement *achievement) +{ + AchievementRewards *rewards; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int16 total = 0; + + assert(achievement); + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`, `reward` FROM `achievements_rewards` WHERE `achievement_id` = %u", achievement->GetID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + rewards = new AchievementRewards(); + rewards->achievement_id = atoul(row[0]); + rewards->reward = row[1]; + achievement->AddAchievementReward(rewards); + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loading Achievements Reward '%s'", rewards->reward.c_str()); + total++; + } + } + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loaded %u rewards for achievement '%s' ID: %u", total, achievement->GetTitle(), achievement->GetID()); + return total; +} + +void WorldDatabase::LoadPlayerAchievements(Player *player) { + Achievement *achievement; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 aReqs_total = 0; + int32 aRewards_total = 0; + int32 total = 0; + + assert(player); + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`,`title`,`uncompleted_text`,`completed_text`,`category`,`expansion`,`icon`,`point_value`,`qty_req`,`hide_achievement`,`unknown3a`,`unknown3b`\n" + "FROM `achievements`"); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + achievement = new Achievement(); + achievement->SetID(atoul(row[0])); + achievement->SetTitle(row[1]); + achievement->SetUncompletedText(row[2]); + achievement->SetCompletedText(row[3]); + achievement->SetCategory(row[4]); + achievement->SetExpansion(row[5]); + achievement->SetIcon(atoi(row[6])); + achievement->SetPointValue(atoul(row[7])); + achievement->SetQtyReq(atoul(row[8])); + achievement->SetHide( atoi(row[9]) == 0 ? false : true ); + achievement->SetUnknown3a(atoul(row[10])); + achievement->SetUnknown3b(atoul(row[11])); + + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "\tLoading Achievement: '%s' (%u)", achievement->GetTitle(), achievement->GetID()); + + if (!player->GetAchievementList()->AddAchievement(achievement)) + { + LogWrite(ACHIEVEMENT__ERROR, 0, "Achievements", "Error adding achievement '%s' - duplicate ID: %u", achievement->GetTitle(), achievement->GetID()); + safe_delete(achievement); + continue; + } + total++; + aReqs_total += LoadAchievementRequirements(achievement); + aRewards_total += LoadAchievementRewards(achievement); + } + } + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievements for '%s'", total, player->GetName()); + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement requirements for '%s'", aReqs_total, player->GetName()); + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "\tLoaded %u achievement rewards for '%s'", aRewards_total, player->GetName()); +} + +int32 WorldDatabase::LoadPlayerAchievementsUpdates(Player *player) { + AchievementUpdate *update; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 total = 0; + int32 items_total = 0; + + assert(player); + + res = query.RunQuery2(Q_SELECT, "SELECT `char_id`, `achievement_id`, `completed_date` FROM character_achievements WHERE char_id = %u ", player->GetCharacterID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + update = new AchievementUpdate(); + update->SetID(atoul(row[1])); + update->SetCompletedDate(atoul(row[2])); + + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loading Player Achievement Update for Achievement ID: %u ", update->GetID()); + + if (!player->GetAchievementUpdateList()->AddAchievementUpdate(update)) + { + LogWrite(ACHIEVEMENT__ERROR, 0, "Achievements", "Error adding achievement update %u - diplicate ID", update->GetID()); + safe_delete(update); + continue; + } + total++; + items_total += LoadPlayerAchievementsUpdateItems(update, player->GetCharacterID()); + } + } + + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "Loaded %u player achievement updates", total); + + return total; +} + +int32 WorldDatabase::LoadPlayerAchievementsUpdateItems(AchievementUpdate *update, int32 player_id) { + AchievementUpdateItems *update_items; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 total = 0; + + assert(update); + + res = query.RunQuery2(Q_SELECT, "SELECT `achievement_id`, `items` FROM character_achievements_items WHERE char_id = %u AND achievement_id = %u;", player_id, update->GetID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + update_items = new AchievementUpdateItems(); + update_items->achievement_id = atoul(row[0]); + update_items->item_update = atoul(row[1]); + update->AddAchievementUpdateItems(update_items); + + LogWrite(ACHIEVEMENT__DEBUG, 5, "Achievements", "Loading Player Achievement Update Items for Achievement ID: %u ", update_items->achievement_id); + total++; + } + } + LogWrite(ACHIEVEMENT__DEBUG, 0, "Achievements", "Loaded %u player achievement update items", total); + return total; +} \ No newline at end of file diff --git a/source/WorldServer/AltAdvancement/AltAdvancement.cpp b/source/WorldServer/AltAdvancement/AltAdvancement.cpp new file mode 100644 index 0000000..32678d4 --- /dev/null +++ b/source/WorldServer/AltAdvancement/AltAdvancement.cpp @@ -0,0 +1,1707 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#include "AltAdvancement.h" +#include "../../common/ConfigReader.h" +#include "../../common/Log.h" +#include "../Spells.h" +#include "../classes.h" +#include "../Rules/Rules.h" +#include +#include +#include +#include "../../common/DatabaseNew.h" +#include "../WorldDatabase.h" +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern Classes classes; +extern RuleManager rule_manager; +extern MasterAANodeList master_tree_nodes; + +MasterAAList::MasterAAList() +{ + MMasterAAList.SetName("MasterAAList::AAList"); +} + +MasterAAList::~MasterAAList() +{ + DestroyAltAdvancements(); +} + +void MasterAAList::AddAltAdvancement(AltAdvanceData* data) { + MMasterAAList.writelock(__FUNCTION__, __LINE__); + AAList.push_back(data); + MMasterAAList.releasewritelock(__FUNCTION__, __LINE__); +} + +int MasterAAList::Size() { + return AAList.size(); +} + +// Jabantiz: Probably a better way to do this but can't think of it right now +AltAdvanceData* MasterAAList::GetAltAdvancement(int32 spellID) { + vector::iterator itr; + AltAdvanceData* data = NULL; + + MMasterAAList.readlock(__FUNCTION__, __LINE__); + for (itr = AAList.begin(); itr != AAList.end(); itr++) { + if ((*itr)->spellID == spellID) { + data = (*itr); + break; + } + } + MMasterAAList.releasereadlock(__FUNCTION__, __LINE__); + + return data; +} + +void MasterAAList::DestroyAltAdvancements() { + MMasterAAList.writelock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = AAList.begin(); itr != AAList.end(); itr++) + safe_delete(*itr); + AAList.clear(); + MMasterAAList.releasewritelock(__FUNCTION__, __LINE__); +} + +MasterAANodeList::MasterAANodeList() { +} + +MasterAANodeList::~MasterAANodeList() { + DestroyTreeNodes(); +} + +void MasterAANodeList::AddTreeNode(TreeNodeData* data) { + TreeNodeList.push_back(data); +} + +void MasterAANodeList::DestroyTreeNodes() { + vector::iterator itr; + for (itr = TreeNodeList.begin(); itr != TreeNodeList.end(); itr++) + safe_delete(*itr); + TreeNodeList.clear(); +} + +int MasterAANodeList::Size() { + return TreeNodeList.size(); +} + +vector MasterAANodeList::GetTreeNodes() { + return TreeNodeList; +} + +EQ2Packet* MasterAAList::GetAAListPacket(Client* client) +{ + + /* + -- OP_DispatchESMsg -- + 5/24/2011 20:54:15 + 199.108.12.165 -> 192.168.0.197 + 0000: 00 38 3B 00 00 00 FF A3 02 FF FF FF FF 00 00 00 .8;............. + 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0030: 00 00 00 00 00 00 00 00 FF FF FF FF 00 00 00 00 ................ + 0040 00 . + */ + + uchar blah[] = {0xFF,0xE8,0x01, +0x00, //unknown +0x07,0x00,0x00,0x00, //unknown2 +0x07,0x00,0x57,0x61,0x72,0x72,0x69,0x6F,0x72, //class_title_tab +0x0C,0x00, //unknown3 +0x64,0x00,0x00,0x00, //max_class_aa +0xFD,0x74,0xB6,0x73, //class_id +0x00, //kos_req +0x00,0x00,0x00,0x00, //num_class_items + +0x0B,0x00,0x00,0x00, //unknown10 +0x11,0x00,0x00,0x00, //class_points_spent +0x00,0x00,0x3B,0x81,0x01,0x00, //unknown11 +0x00,0x00, //unknown12 +0x00,0x00, //unknown13 +0x00,0x00,0x00,0x00, //unknown14 +0x00,0x00, //unknown15 +0x00,0x00,0x00,0x00,0x00,0x00,0x00, //unknown16 +0x09,0x00,0x42,0x65,0x72,0x73,0x65,0x72,0x6B,0x65,0x72, //subclass_title_tab +0x0E,0x00, //unknown17 +0x64,0x00,0x00,0x00, //max_subclass_aa +0x5F,0xD6,0xAF,0x50, //subclass_id +0x00, //eof_req +0x00,0x00,0x00,0x00, //num_subclass_items + +0x0C,0x00,0x00,0x00, //unknown20 +0x08,0x00,0x00,0x00, //subclass_points_spent +0x00,0x00,0x3B,0x81,0x03,0x14, //unknown21 +0x00,0x00,0x00, //unknown22 +0x1D,0x00,0x3A,0x63,0x65,0x31,0x38,0x36,0x34,0x63,0x37,0x66,0x35,0x33,0x66,0x65,0x62,0x37,0x62,0x5F,0x31,0x3A,0x42,0x65,0x72,0x73,0x65,0x72,0x6B,0x65,0x72, //unknown23 +0x01,0x00,0x00,0x00, //unknown24 +0x1D,0x00,0x3A,0x63,0x65,0x31,0x38,0x36,0x34,0x63,0x37,0x35,0x66,0x39,0x34,0x61,0x32,0x64,0x37,0x5F,0x31,0x3A,0x45,0x78,0x70,0x65,0x72,0x74,0x69,0x73,0x65, //unknown25 +0x00,0x00,0x00,0x00,0x00,0x00, //unknown26 +0x07,0x00,0x53,0x68,0x61,0x64,0x6F,0x77,0x73, //shadows_tab_title +0x2C,0x00, //unknown27 +0x46,0x00,0x00,0x00, //max_shadows_aa +0x53,0x88,0x59,0x62, //shadows_id +0x00, //rok_req +0x00,0x00,0x00,0x00, //num_shadow_items + +0x0E,0x00,0x00,0x00, //unknown30 +0x00,0x00,0x00,0x00, //shadows_points_spent +0x00,0x00,0x3B,0x81,0x03,0x00, //unknown31 +0x00,0x00,0x00, //unknown32 +0x00,0x00, //uknown33 +0x00,0x00,0x00,0x00, //unknown34 +0x00,0x00, //unknown35 +0x00,0x00,0x00,0x00,0x00,0x00, //unknown36 +0x06,0x00,0x48,0x65,0x72,0x6F,0x69,0x63, //heroic_tab_title +0x48,0x00, //unknown37 +0x32,0x00,0x00,0x00, //max_heroic_aa +0xC0,0x6B,0xFC,0x3C, //heroic_id +0x01, //heroic_dov_req +0x00,0x00,0x00,0x00, //num_heroic_items + +0x10,0x00,0x00,0x00, //unknown40 +0x00,0x00,0x00,0x00, //heroic_points_spent +0x00,0x00,0x3B,0x81,0x03,0x00, //unknown41 +0x00,0x00,0x00, //unknown42 +0x00,0x00, //unknown43 +0x00,0x00,0x00,0x00, //unknown44 +0x00,0x00, //unknown45 +0x00,0x00,0x00,0x00,0x00,0x00, //unknown46 +0x0A,0x00,0x54,0x72,0x61,0x64,0x65,0x73,0x6B,0x69,0x6C,0x6C, //tradeskill_tab_title +0x49,0x00, //unknown47 +0x28,0x00,0x00,0x00, //max_tradeskill_aa +0x1E,0xDB,0x41,0x2F, //tradeskill_id +0x00, //exp_req +0x00,0x00,0x00,0x00, //num_tradeskill_items + +0x00,0x00,0x00,0x00, //unknown50 +0x00,0x00,0x00,0x00, //tradeskill_points_spent +0x00,0x00,0x3B,0x81,0x03,0x00, //unknown51 +0x00,0x00,0x00, //unknown52 +0x00,0x00, //unknown53 +0x00,0x00,0x00,0x00, //unknown54 +0x00,0x00, //unknown55 +0x03,0x00,0x00,0x00,0x00,0x00, //unknown56 +0x08,0x00,0x50,0x72,0x65,0x73,0x74,0x69,0x67,0x65, //prestige_tab_title +0x67,0x00, //unknown57 +0x19,0x00,0x00,0x00, //max_prestige_aa +0xC6,0xA8,0x83,0xBD, //prestige_id +0x01, //prestige_dov_req +0x00,0x00,0x00,0x00, //num_prestige_items + +0x10,0x00,0x00,0x00, //unknown60 +0x00,0x00,0x00,0x00, //prestige_points_spent +0x00,0x00,0x3B,0x81,0x03,0x06, //unknown61 +0x00,0x00,0x00, //unknown62 +0x1D,0x00,0x3A,0x34,0x39,0x33,0x64,0x65,0x62,0x62,0x33,0x65,0x36,0x37,0x38,0x62,0x39,0x37,0x37,0x5F,0x35,0x35,0x3A,0x50,0x72,0x65,0x73,0x74,0x69,0x67,0x65, //unknown63 +0x01,0x00,0x00,0x00, //unknown64 +0x27,0x00,0x3A,0x34,0x39,0x33,0x64,0x65,0x62,0x62,0x33,0x65,0x36,0x61,0x38,0x62,0x62,0x37,0x39,0x5F,0x31,0x32,0x3A,0x50,0x72,0x65,0x73,0x74,0x69,0x67,0x65,0x20,0x45,0x78,0x70,0x65,0x72,0x74,0x69,0x73,0x65, //unknown65 +0x02,0x00,0x00,0x00,0x00,0x00, //unknown66 +0x13,0x00,0x54,0x72,0x61,0x64,0x65,0x73,0x6B,0x69,0x6C,0x6C,0x20,0x50,0x72,0x65,0x73,0x74,0x69,0x67,0x65, //tradeskill_prestige_tab_title +0x79,0x00, //unknown67 +0x19,0x00,0x00,0x00, //max_tradeskill_prestige_aa +0x18,0x2C,0x0B,0x74, //tradeskill_prestige_id +0x01, //coe_req +0x00,0x00,0x00,0x00, //num_tradeskill_prestige_items + +0x12,0x00,0x00,0x00, //unknown70 +0x00,0x00,0x00,0x00, //tradeskill_prestige_points_spent +0x00,0x00,0x3B,0x81,0x03,0x00, //unknown71 +0x00,0x00,0x00, //unknown72 +0x00,0x00, //unknown73 +0x00,0x00,0x00,0x00, //unknown74 +0x00,0x00, //unknown75 +0x04,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00, //unknown76 +0x00,0x00,0x00,0x01,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //unknown77 +0x01, //num_templates +0x64, //template_unknown1 +0x03,0x00,0x4E,0x65,0x77, //template_name +0x00, //template_unknown2 +0x00,0x00}; //num_tabs + + return (new EQ2Packet(OP_AdventureList, blah, sizeof(blah))); +} + + + +struct AAEntry { + int8 template_id; + int8 tab_id; + int32 aa_id; + int16 order; + int8 treeid; +}; +void MasterAAList::DisplayAA(Client* client,int8 newtemplate,int8 changemode) { + map AAtree_id; + map >::iterator itr_tree2; + vector::iterator itr_tree3; + map > Nodes; + vector TreeNodeList = master_tree_nodes.GetTreeNodes(); + if (TreeNodeList.size() == 0) + return; + vector > > AAEntryList ; + Query query, query2; + MYSQL_ROW row; + int32 Pid = client->GetCharacterID(); + + AAEntryList.resize(8); // max number of templates + for (int i = 0; i < 8; i++) { + AAEntryList[i].resize(5); // max number of tabs + + } + + // load templates 1-3 Personal + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa_defaults where class = %i order by `order`", client->GetPlayer()->GetAdventureClass()); + + while (result && (row = mysql_fetch_row(result))) { + AAEntry newentry; + newentry.template_id = strtoul(row[0], NULL, 0); + newentry.tab_id = strtoul(row[1], NULL, 0); + newentry.aa_id = strtoul(row[2], NULL, 0); + newentry.order = strtoul(row[3], NULL, 0); + newentry.treeid = strtoul(row[4], NULL, 0); + AAEntryList[newentry.template_id][newentry.tab_id].push_back(newentry); + } + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", AAEntryList.size()); + // load tmplates 4-6 Server + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa where char_id = %i order by `order`", client->GetCharacterID()); + + while (result2 && (row = mysql_fetch_row(result2))) { + AAEntry newentry; + newentry.template_id = strtoul(row[0], NULL, 0); + newentry.tab_id = strtoul(row[1], NULL, 0); + newentry.aa_id = strtoul(row[2], NULL, 0); + newentry.order = strtoul(row[3], NULL, 0); + newentry.treeid = strtoul(row[4], NULL, 0); + AAEntryList[newentry.template_id][newentry.tab_id].push_back(newentry); + } + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", AAEntryList.size()); + + + + + for (int x = 0; x < TreeNodeList.size(); x++) + { + int8 class_id = client->GetPlayer()->GetInfoStruct()->get_class3(); + + if (TreeNodeList[x]->classID == class_id) + { + itr_tree2 = Nodes.lower_bound(TreeNodeList[x]->classID); + if (itr_tree2 != Nodes.end() && !(Nodes.key_comp()(TreeNodeList[x]->classID, itr_tree2->first))) + { + (itr_tree2->second).push_back(TreeNodeList[x]); + LogWrite(SPELL__TRACE, 0, "AA", "Added AA Tree node ID: %u", TreeNodeList[x]->treeID); + } + else + { + vector tmpVec; + tmpVec.push_back(TreeNodeList[x]); + Nodes.insert(make_pair(TreeNodeList[x]->classID, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added AA Tree node ID: %u", TreeNodeList[x]->treeID); + } + } + } + + map >::iterator itr2; + vector::iterator itr3; + + map > ClassTab; + map > SubclassTab; + map > ShadowsTab; + map > HeroicTab; + map > TradeskillTab; + map > PrestigeTab; + map > TradeskillPrestigeTab; + map > DragonTab; + map > DragonclassTab; + map > FarseasTab; + + MMasterAAList.readlock(__FUNCTION__, __LINE__); + // Get Tree Node ID's + map node_id; + map classid; + + + for (itr_tree2 = Nodes.begin(); itr_tree2 != Nodes.end(); itr_tree2++) { + int8 x = 0; + for (itr_tree3 = itr_tree2->second.begin(); itr_tree3 != itr_tree2->second.end(); itr_tree3++, x++ ) { + node_id[x] = (*itr_tree3)->treeID; + classid[(*itr_tree3)->treeID] = (*itr_tree3)->AAtreeID; + } + } + int rrr = 0; + for (int i =0; i < Size(); i++) { + if (AAList[i]->group == node_id[AA_CLASS]) { + itr2 = ClassTab.lower_bound(AAList[i]->group); + if (itr2 != ClassTab.end() && !(ClassTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + ClassTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Subclass Tab + if (AAList[i]->group == node_id[AA_SUBCLASS]) { + itr2 = SubclassTab.lower_bound(AAList[i]->group); + if (itr2 != SubclassTab.end() && !(SubclassTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + SubclassTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Shadows Tab + if (AAList[i]->group == node_id[AA_SHADOW]) { + itr2 = ShadowsTab.lower_bound(AAList[i]->group); + if (itr2 != ShadowsTab.end() && !(ShadowsTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + ShadowsTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Heroic Tab + if (AAList[i]->group == node_id[AA_HEROIC]) { + itr2 = HeroicTab.lower_bound(AAList[i]->group); + if (itr2 != HeroicTab.end() && !(HeroicTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + HeroicTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Tradeskill Tab + if (AAList[i]->group == node_id[AA_TRADESKILL]) { + itr2 = TradeskillTab.lower_bound(AAList[i]->group); + if (itr2 != TradeskillTab.end() && !(TradeskillTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + TradeskillTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Prestige Tab + if (AAList[i]->group == node_id[AA_PRESTIGE]) { + itr2 = PrestigeTab.lower_bound(AAList[i]->group); + if (itr2 != PrestigeTab.end() && !(PrestigeTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + PrestigeTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for TradeskillPrestige Tab + if (AAList[i]->group == node_id[AA_TRADESKILL_PRESTIGE]) { + itr2 = TradeskillPrestigeTab.lower_bound(AAList[i]->group); + if (itr2 != TradeskillPrestigeTab.end() && !(TradeskillPrestigeTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + TradeskillPrestigeTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Dragon Tab + if (AAList[i]->group == node_id[AA_DRAGON]) { + itr2 = DragonTab.lower_bound(AAList[i]->group); + if (itr2 != DragonTab.end() && !(DragonTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + DragonTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Dragon Class Tab + if (AAList[i]->group == node_id[AA_DRAGONCLASS]) { + itr2 = DragonclassTab.lower_bound(AAList[i]->group); + if (itr2 != DragonclassTab.end() && !(DragonclassTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + DragonclassTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + // Sort for Farseas Tab + if (AAList[i]->group == node_id[AA_FARSEAS]) { + itr2 = FarseasTab.lower_bound(AAList[i]->group); + if (itr2 != FarseasTab.end() && !(FarseasTab.key_comp()(AAList[i]->group, itr2->first))) { + (itr2->second).push_back(AAList[i]); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + else { + vector tmpVec; + tmpVec.push_back(AAList[i]); + FarseasTab.insert(make_pair(AAList[i]->group, tmpVec)); + LogWrite(SPELL__TRACE, 0, "AA", "Added...%u ", AAList[i]->spellID); + } + } + } + MMasterAAList.releasereadlock(__FUNCTION__, __LINE__); + + int16 version = 0; + int8 class_num_items = 0; + int8 subclass_num_items = 0; + int8 shadows_num_items = 0; + int8 heroic_num_items = 0; + int8 tradeskill_num_items = 0; + int8 prestige_num_items = 0; + int8 tradeskillprestige_num_items = 0; + int8 dragon_num_items = 0; + int8 dragonclass_num_items = 0; + int8 farseas_num_items = 0; + int8 index = 0; + Spell* spell = 0; + int8 current_rank = 0; + int32 class_node_id = 0; + + if (client) + version = client->GetVersion(); + + + + PacketStruct* packet = configReader.getStruct("WS_AdventureList", version); + + + + if (version >= 58617) { + packet->setDataByName("num_aa_trees", 10);// number of AA tabs + } + else if (version >= 1193) { + packet->setDataByName("num_aa_trees", 7);// number of AA tabs + } + else if (version >= 1096) { + packet->setDataByName("num_aa_trees", 4);// number of AA tabs + } + // since we do not have a proper way of supporting 3 levels of nested arrays the first array is manual here and not looped + + //__________________________________________________________START OF CLASS TREE____________________________________________________________________________________ + // Get the value for num_class_items based on size of ClassTab vector + for (itr2 = ClassTab.begin(); itr2 != ClassTab.end(); itr2++) { + class_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "ClassTab Size...%i ", class_num_items); + index = 0; + packet->setDataByName("class_tab_title", classes.GetClassNameCase(classes.GetSecondaryBaseClass(client->GetPlayer()->GetAdventureClass())).c_str()); + packet->setDataByName("class_tree_node_id", node_id[AA_CLASS]); + packet->setDataByName("class_max_aa", rule_manager.GetGlobalRule(R_Player, MaxClassAA)->GetInt32()); + int32 class_id = TreeNodeList[node_id[AA_CLASS]]->AAtreeID; + class_id = classid[node_id[AA_CLASS]]; + packet->setDataByName("class_id", classid[node_id[AA_CLASS]]); + packet->setDataByName("class_kos_req", 0); + packet->setArrayLengthByName("class_num_items", class_num_items,0); + for (itr2 = ClassTab.begin(); itr2 != ClassTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("class_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("class_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("class_spell_id", (*itr3)->spellID, index); + int myrank = (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1); + packet->setArrayDataByName("class_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); //1= have tier >= 1; 3 = not available for selection; 0 available for selection + packet->setArrayDataByName("class_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("class_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("class_icon", (*itr3)->icon, index); + packet->setArrayDataByName("class_icon2",(*itr3)->icon2, index); + packet->setArrayDataByName("class_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("class_max_rank", (*itr3)->maxRank , index); + packet->setArrayDataByName("class_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("class_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("class_unknown5_numitems", 0,index, 0); + //packet->setSubArrayDataByName("class_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("class_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("class_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("class_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("class_unknown5", 696953971, index, 4); + packet->setArrayDataByName("class_unknown6", 4294967295, index); + packet->setArrayDataByName("class_unknown7", 1, index); + packet->setArrayDataByName("class_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("class_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("class_unknown8", 0, index); + packet->setArrayDataByName("class_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("class_col", (*itr3)->col, index); + packet->setArrayDataByName("class_row", (*itr3)->row, index); + packet->setArrayDataByName("class_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("class_unknown9", ((*itr3)->title_level > 0 ? 258 : 0 ), index); + packet->setArrayDataByName("class_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("class_unknown9b", current_rank, index);// aom + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("class_points_spent", 11); + if (version >= 58617) { + packet->setDataByName("class_unknown10", 11); + + packet->setDataByName("class_unknown11a", 0); + packet->setDataByName("class_unknown11b", 0); + packet->setDataByName("class_unknown11c", 1); + } + else if (version >= 1193) { + packet->setDataByName("class_unknown10", 11); + packet->setDataByName("class_unknown11", 0, 0); + packet->setDataByName("class_unknown11", 1, 1); + packet->setDataByName("class_unknown11", 1, 2); + } + else if (version >= 1096) { + packet->setDataByName("class_unknown10", 11); + packet->setDataByName("class_unknown11", 0, 0); + packet->setDataByName("class_unknown11", 0, 1); + packet->setDataByName("class_unknown11", 1, 2); + packet->setDataByName("class_unknown11", 0, 3); + packet->setDataByName("class_unknown11", 1, 4); + } + else { // this will change if there is ever a lower client supported + packet->setDataByName("class_unknown10", 11); + packet->setDataByName("class_unknown11", 0, 0); + packet->setDataByName("class_unknown11", 0, 1); + packet->setDataByName("class_unknown11", 1, 2); + packet->setDataByName("class_unknown11", 0, 3); + packet->setDataByName("class_unknown11", 1, 4); + } + + + + + + //__________________________________________________________START OF SUBCLASS TREE____________________________________________________________________________________ + // Get the value for num_class_items based on size of SubclassTab vector + for (itr2 = SubclassTab.begin(); itr2 != SubclassTab.end(); itr2++) { + subclass_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "SubclassTab Size...%i ", subclass_num_items); + index = 0; + packet->setDataByName("subclass_tab_title", classes.GetClassNameCase(client->GetPlayer()->GetAdventureClass()).c_str()); + packet->setDataByName("subclass_tree_node_id", node_id[AA_SUBCLASS]); + packet->setDataByName("subclass_max_aa", rule_manager.GetGlobalRule(R_Player, MaxSubclassAA)->GetInt32()); + int32 unknown3 = TreeNodeList[node_id[AA_SUBCLASS]]->AAtreeID; + + packet->setDataByName("subclass_id", classid[node_id[AA_SUBCLASS]]); + packet->setDataByName("subclass_eof_req", 0); + packet->setArrayLengthByName("subclass_num_items", subclass_num_items, 0); + for (itr2 = SubclassTab.begin(); itr2 != SubclassTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("subclass_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("subclass_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("subclass_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("subclass_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("subclass_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("subclass_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("subclass_icon", (*itr3)->icon, index); + packet->setArrayDataByName("subclass_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("subclass_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("subclass_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("subclass_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("subclass_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("subclass_unknown5_numitems", 0,index,0); + //packet->setSubArrayDataByName("subclass_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("subclass_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("subclass_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("subclass_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("subclass_unknown5", 696953971, index, 4); + packet->setArrayDataByName("subclass_unknown6", 4294967295, index); + packet->setArrayDataByName("subclass_unknown7", 1, index); + packet->setArrayDataByName("subclass_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("subclass_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("subclass_unknown8", 0, index); + packet->setArrayDataByName("subclass_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("subclass_col", (*itr3)->col, index); + packet->setArrayDataByName("subclass_row", (*itr3)->row, index); + packet->setArrayDataByName("subclass_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("subclass_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("subclass_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("subclass_unknown9b", 0, index); //added with 68617 AOM something to do with points + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("subclass_points_spent", 12); // to change the 34 to a track value + + + if (version >= 58617) { + packet->setDataByName("subclass_unknown10", 12); + + packet->setDataByName("subclass_unknown11a", 0); + packet->setDataByName("subclass_unknown11b", 50386); + packet->setDataByName("subclass_unknown11c", 5123); + + packet->setDataByName("subclass_unknown12", 0, 0); + packet->setDataByName("subclass_unknown12", 0, 1); + packet->setDataByName("subclass_unknown12", 0, 2); + packet->setDataByName("subclass_unknown13",":493debb3e678b977_91:test_unknown13");// this is based on class + packet->setDataByName("subclass_unknown14", 1); + packet->setDataByName("subclass_unknown15", ":ce1864c75f94a2d7_14:Expertise"); + packet->setDataByName("subclass_unknown16", 0, 0); + packet->setDataByName("subclass_unknown16", 0, 1); + packet->setDataByName("subclass_unknown16", 0, 2); + packet->setDataByName("subclass_unknown16", 0, 3); + packet->setDataByName("subclass_unknown16", 0, 4); + packet->setDataByName("subclass_unknown16", 0, 5); + } + else if (version >= 1193) { + packet->setDataByName("subclass_unknown10", 12); + packet->setDataByName("subclass_unknown11", 0, 0); + packet->setDataByName("subclass_unknown11", 1, 1); + packet->setDataByName("subclass_unknown11", 5123, 2); + packet->setDataByName("subclass_unknown12", 0, 0); + packet->setDataByName("subclass_unknown12", 0, 1); + packet->setDataByName("subclass_unknown12", 0, 2); + packet->setDataByName("subclass_unknown13", ":493debb3e678b977_91:test_unknown13");// this is based on class + packet->setDataByName("subclass_unknown14", 1); + packet->setDataByName("subclass_unknown15", ":ce1864c75f94a2d7_14:Expertise"); + packet->setDataByName("subclass_unknown16", 111, 0); + packet->setDataByName("subclass_unknown16", 108, 1); + packet->setDataByName("subclass_unknown16", 108, 2); + packet->setDataByName("subclass_unknown16", 1101, 3); + packet->setDataByName("subclass_unknown16", 121, 4); + packet->setDataByName("subclass_unknown16", 129, 5); + } + else if (version >= 1096) { + packet->setDataByName("subclass_unknown10", 12); + packet->setDataByName("subclass_unknown11", 0, 0); + packet->setDataByName("subclass_unknown11", 0, 1); + packet->setDataByName("subclass_unknown11", 1, 2); + packet->setDataByName("subclass_unknown11", 0, 3); + packet->setDataByName("subclass_unknown11", 3, 4); + } + else { // this will change if there is ever a lower client supported + packet->setDataByName("subclass_unknown10", 12); + packet->setDataByName("subclass_unknown11", 0, 0); + packet->setDataByName("subclass_unknown11", 0, 1); + packet->setDataByName("subclass_unknown11", 1, 2); + packet->setDataByName("subclass_unknown11", 0, 3); + packet->setDataByName("subclass_unknown11", 3, 4); + } + //__________________________________________________________START OF SHADOWS TREE____________________________________________________________________________________ + + for (itr2 = ShadowsTab.begin(); itr2 != ShadowsTab.end(); itr2++) { + shadows_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "ShadowsTab Size...%i ", shadows_num_items); + index = 0; + packet->setDataByName("shadows_tab_title", "Shadows"); + packet->setDataByName("shadows_tree_node_id", node_id[AA_SHADOW]); + packet->setDataByName("shadows_max_aa", rule_manager.GetGlobalRule(R_Player, MaxShadowsAA)->GetInt32()); + packet->setDataByName("shadows_id", classid[node_id[AA_SHADOW]]); + packet->setDataByName("shadows_eof_req", 0); + packet->setArrayLengthByName("shadows_num_items", shadows_num_items, 0); + //packet->PrintPacket(); + for (itr2 = ShadowsTab.begin(); itr2 != ShadowsTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("shadows_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("shadows_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("shadows_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("shadows_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("shadows_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("shadows_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("shadows_icon", (*itr3)->icon, index); + packet->setArrayDataByName("shadows_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("shadows_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("shadows_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("shadows_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("shadows_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("shadows_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("shadows_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("shadows_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("shadows_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("shadows_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("shadows_unknown5", 696953971, index, 4); + packet->setArrayDataByName("shadows_unknown6", 4294967295, index); + packet->setArrayDataByName("shadows_unknown7", 1, index); + packet->setArrayDataByName("shadows_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("shadows_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("shadows_unknown8", 0, index); + packet->setArrayDataByName("shadows_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("shadows_col", (*itr3)->col, index); + packet->setArrayDataByName("shadows_row", (*itr3)->row, index); + packet->setArrayDataByName("shadows_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("shadows_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("shadows_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("shadows_unknown9b", 0, index); + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("shadows_points_spent", 14); + + + if (version >= 58617) { + packet->setDataByName("shadows_unknown10", 14); + + packet->setDataByName("shadows_unknown11a", 0); + packet->setDataByName("shadows_unknown11b", 50386); + packet->setDataByName("shadows_unknown11c", 3); + + packet->setDataByName("shadows_unknown12", 0, 0); + packet->setDataByName("shadows_unknown12", 0, 1); + packet->setDataByName("shadows_unknown12", 0, 2); + } + else if (version >= 1193) { + packet->setDataByName("shadows_unknown10", 14); + packet->setDataByName("shadows_unknown11", 0, 0); + packet->setDataByName("shadows_unknown11", 1, 1); + packet->setDataByName("shadows_unknown11", 3, 2); + packet->setDataByName("shadows_unknown12", 0, 0); + packet->setDataByName("shadows_unknown12", 0, 1); + packet->setDataByName("shadows_unknown12", 0, 2); + } + else if (version >= 1096) { + packet->setDataByName("shadows_unknown11", 0, 0); + packet->setDataByName("shadows_unknown11", 0, 1); + packet->setDataByName("shadows_unknown11", 1, 2); + packet->setDataByName("shadows_unknown11", 0, 3); + packet->setDataByName("shadows_unknown11", 3, 4); + } + else { // this will change if there is ever a lower client supported + packet->setDataByName("shadows_unknown11", 0, 0); + packet->setDataByName("shadows_unknown11", 0, 1); + packet->setDataByName("shadows_unknown11", 1, 2); + packet->setDataByName("shadows_unknown11", 0, 3); + packet->setDataByName("shadows_unknown11", 3, 4); + packet->setDataByName("shadows_unknown12", 103, 0); + packet->setDataByName("shadows_unknown12", 101, 1); + packet->setDataByName("shadows_unknown12", 114, 2); + packet->setDataByName("shadows_unknown14", 1835365408); + packet->setDataByName("shadows_unknown16", 114, 0); + packet->setDataByName("shadows_unknown16", 97, 1); + packet->setDataByName("shadows_unknown16", 114, 2); + packet->setDataByName("shadows_unknown16", 121, 3); + packet->setDataByName("shadows_unknown16", 32, 4); + packet->setDataByName("shadows_unknown16", 98, 5); + } + //__________________________________________________________START OF HEROIC TREE____________________________________________________________________________________ + for (itr2 = HeroicTab.begin(); itr2 != HeroicTab.end(); itr2++) { + heroic_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "HeroicTab Size...%i ", heroic_num_items); + index = 0; + packet->setDataByName("heroic_tab_title", "Heroic"); + packet->setDataByName("heroic_tree_node_id", node_id[AA_HEROIC]); + packet->setDataByName("heroic_max_aa", rule_manager.GetGlobalRule(R_Player, MaxHeroicAA)->GetInt32()); + packet->setDataByName("heroic_id", classid[node_id[AA_HEROIC]]); + packet->setDataByName("heroic_eof_req", 0); + packet->setArrayLengthByName("heroic_num_items", heroic_num_items, 0); + for (itr2 = HeroicTab.begin(); itr2 != HeroicTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("heroic_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("heroic_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("heroic_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("heroic_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("heroic_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("heroic_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("heroic_icon", (*itr3)->icon, index); + packet->setArrayDataByName("heroic_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("heroic_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("heroic_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("heroic_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("heroic_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("heroic_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("heroic_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("heroic_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("heroic_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("heroic_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("heroic_unknown5", 696953971, index, 4); + packet->setArrayDataByName("heroic_unknown6", 4294967295, index); + packet->setArrayDataByName("heroic_unknown7", 1, index); + packet->setArrayDataByName("heroic_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("heroic_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("heroic_unknown8", 0, index); + packet->setArrayDataByName("heroic_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("heroic_col", (*itr3)->col, index); + packet->setArrayDataByName("heroic_row", (*itr3)->row, index); + packet->setArrayDataByName("heroic_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("heroic_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("heroic_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("heroic_unknown9b", 0, index); + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("heroic_points_spent", 16); + + if (version >= 58617) { + packet->setDataByName("heroic_unknown10", 16); + + packet->setDataByName("heroic_unknown11a", 0); + packet->setDataByName("heroic_unknown11b", 50386); + packet->setDataByName("heroic_unknown11c", 3); + + packet->setDataByName("heroic_unknown12", 0, 0); + packet->setDataByName("heroic_unknown12", 0, 1); + packet->setDataByName("heroic_unknown12", 0, 2); + + packet->setDataByName("heroic_unknown14", 0); + packet->setDataByName("heroic_unknown16", 39, 0); + packet->setDataByName("heroic_unknown16", 115, 1); + packet->setDataByName("heroic_unknown16", 32, 2); + packet->setDataByName("heroic_unknown16", 115, 3); + packet->setDataByName("heroic_unknown16", 101, 4); + packet->setDataByName("heroic_unknown16", 108, 5); + } + else if (version >= 1193) { + packet->setDataByName("heroic_unknown10", 16); + packet->setDataByName("heroic_unknown11", 0, 0); + packet->setDataByName("heroic_unknown11", 1, 1); + packet->setDataByName("heroic_unknown11", 3, 2); + packet->setDataByName("heroic_unknown12", 0, 0); + packet->setDataByName("heroic_unknown12", 0, 1); + packet->setDataByName("heroic_unknown12", 0, 2); + packet->setDataByName("heroic_unknown14", 0); + packet->setDataByName("heroic_unknown16", 0, 0); + packet->setDataByName("heroic_unknown16", 0, 1); + packet->setDataByName("heroic_unknown16", 0, 2); + packet->setDataByName("heroic_unknown16", 0, 3); + packet->setDataByName("heroic_unknown16", 0, 4); + packet->setDataByName("heroic_unknown16", 0, 5); + } + else if (version >= 1096) { + packet->setDataByName("heroic_unknown11", 0, 0); + packet->setDataByName("heroic_unknown11", 0, 1); + packet->setDataByName("heroic_unknown11", 1, 2); + packet->setDataByName("heroic_unknown11", 0, 3); + packet->setDataByName("heroic_unknown11", 3, 4); + } + else { // this will change if there is ever a lower client supported + packet->setDataByName("heroic_unknown11", 0, 0); + packet->setDataByName("heroic_unknown11", 0, 1); + packet->setDataByName("heroic_unknown11", 1, 2); + packet->setDataByName("heroic_unknown11", 0, 3); + packet->setDataByName("heroic_unknown11", 3, 4); + } + if (version >= 1193) { + + //__________________________________________________________START OF TRADESKILL TREE____________________________________________________________________________________ + for (itr2 = TradeskillTab.begin(); itr2 != TradeskillTab.end(); itr2++) { + tradeskill_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "TradeskillTab Size...%i ", tradeskill_num_items); + index = 0; + packet->setDataByName("tradeskill_tab_title", "Tradeskill"); + packet->setDataByName("tradeskill_tree_node_id", node_id[AA_TRADESKILL]); + packet->setDataByName("tradeskill_max_aa", rule_manager.GetGlobalRule(R_Player, MaxTradeskillAA)->GetInt32()); + packet->setDataByName("tradeskill_id", classid[node_id[AA_TRADESKILL]]); + packet->setDataByName("tradeskill_eof_req", 0); + packet->setArrayLengthByName("tradeskill_num_items", tradeskill_num_items, 0); + for (itr2 = TradeskillTab.begin(); itr2 != TradeskillTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("tradeskill_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("tradeskill_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("tradeskill_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("tradeskill_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("tradeskill_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("tradeskill_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("tradeskill_icon", (*itr3)->icon, index); + packet->setArrayDataByName("tradeskill_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("tradeskill_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("tradeskill_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("tradeskill_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("tradeskill_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("tradeskill_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("tradeskill_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("tradeskill_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("tradeskill_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("tradeskill_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("tradeskill_unknown5", 696953971, index, 4); + packet->setArrayDataByName("tradeskill_unknown6", 4294967295, index); + packet->setArrayDataByName("tradeskill_unknown7", 0, index); + packet->setArrayDataByName("tradeskill_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("tradeskill_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("tradeskill_unknown8", 0, index); + packet->setArrayDataByName("tradeskill_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("tradeskill_col", (*itr3)->col, index); + packet->setArrayDataByName("tradeskill_row", (*itr3)->row, index); + packet->setArrayDataByName("tradeskill_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("tradeskill_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("tradeskill_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("tradeskill_unknown9b", 0, index); + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + packet->setDataByName("tradeskill_unknown10", 0); + packet->setDataByName("tradeskill_points_spent", 22); + if (version >= 58617) { + packet->setDataByName("tradeskill_unknown10", 11); + + packet->setDataByName("tradeskill_unknown11a", 0); + packet->setDataByName("tradeskill_unknown11b", 50386); + packet->setDataByName("tradeskill_unknown11c", 3); + + packet->setDataByName("tradeskill_unknown12", 0, 0); + packet->setDataByName("tradeskill_unknown12", 0, 1); + packet->setDataByName("tradeskill_unknown12", 0, 2); + + packet->setDataByName("tradeskill_unknown14", 0); + packet->setDataByName("tradeskill_unknown16", 3, 0); + packet->setDataByName("tradeskill_unknown16", 0, 1); + packet->setDataByName("tradeskill_unknown16", 0, 2); + packet->setDataByName("tradeskill_unknown16", 0, 3); + packet->setDataByName("tradeskill_unknown16", 0, 4); + packet->setDataByName("tradeskill_unknown16", 0, 5); + } + else if (version >= 1193) { + packet->setDataByName("tradeskill_unknown10", 0); + packet->setDataByName("tradeskill_unknown11", 0, 0); + packet->setDataByName("tradeskill_unknown11", 1, 1); + packet->setDataByName("tradeskill_unknown11", 3, 2); + packet->setDataByName("tradeskill_unknown12", 0, 0); + packet->setDataByName("tradeskill_unknown12", 0, 1); + packet->setDataByName("tradeskill_unknown12", 0, 2); + packet->setDataByName("tradeskill_unknown14", 0); + packet->setDataByName("tradeskill_unknown16", 3, 0); + packet->setDataByName("tradeskill_unknown16", 0, 1); + packet->setDataByName("tradeskill_unknown16", 0, 2); + packet->setDataByName("tradeskill_unknown16", 0, 3); + packet->setDataByName("tradeskill_unknown16", 0, 4); + packet->setDataByName("tradeskill_unknown16", 0, 5); + } + + //__________________________________________________________START OF PRESTIGE TREE____________________________________________________________________________________ + for (itr2 = PrestigeTab.begin(); itr2 != PrestigeTab.end(); itr2++) { + prestige_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "PrestigeTab Size...%i ", prestige_num_items); + index = 0; + packet->setDataByName("prestige_tab_title", "Prestige"); + packet->setDataByName("prestige_tree_node_id", node_id[AA_PRESTIGE]); + packet->setDataByName("prestige_max_aa", rule_manager.GetGlobalRule(R_Player, MaxPrestigeAA)->GetInt32()); + packet->setDataByName("prestige_id", classid[node_id[AA_PRESTIGE]]); + packet->setDataByName("prestige_eof_req", 0); + packet->setArrayLengthByName("prestige_num_items", prestige_num_items, 0); + for (itr2 = PrestigeTab.begin(); itr2 != PrestigeTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("prestige_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("prestige_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("prestige_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("prestige_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("prestige_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("prestige_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("prestige_icon", (*itr3)->icon, index); + packet->setArrayDataByName("prestige_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("prestige_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("prestige_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("prestige_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("prestige_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("prestige_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("prestige_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("prestige_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("prestige_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("prestige_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("prestige_unknown5", 696953971, index, 4); + packet->setArrayDataByName("prestige_unknown6", 4294967295, index); + packet->setArrayDataByName("prestige_unknown7", 1, index); + packet->setArrayDataByName("prestige_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("prestige_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("prestige_unknown8", 0, index); + packet->setArrayDataByName("prestige_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("prestige_col", (*itr3)->col, index); + packet->setArrayDataByName("prestige_row", (*itr3)->row, index); + packet->setArrayDataByName("prestige_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("prestige_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("prestige_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("prestige_unknown9b", 0, index); + + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("prestige_points_spent", 34); + + if (version >= 58617) { + packet->setDataByName("prestige_unknown10", 16); + + packet->setDataByName("prestige_unknown11a", 0); + packet->setDataByName("prestige_unknown11b", 50386); + packet->setDataByName("prestige_unknown11c", 1539); + + packet->setDataByName("prestige_unknown12", 0, 0); + packet->setDataByName("prestige_unknown12", 0, 1); + packet->setDataByName("prestige_unknown12", 0, 2); + + packet->setDataByName("prestige_unknown13", ":493debb3e678b977_91:Prestige"); + packet->setDataByName("prestige_unknown14", 1); + packet->setDataByName("prestige_unknown15", ":493debb3e6a8bb79_20:Prestige Expertise"); + packet->setDataByName("prestige_unknown16", 2, 0); + packet->setDataByName("prestige_unknown16", 0, 1); + packet->setDataByName("prestige_unknown16", 0, 2); + packet->setDataByName("prestige_unknown16", 0, 3); + packet->setDataByName("prestige_unknown16", 0, 4); + packet->setDataByName("prestige_unknown16", 0, 5); + } + else if (version >= 1193) { + packet->setDataByName("prestige_unknown10", 16); + packet->setDataByName("prestige_unknown11", 0, 0); + packet->setDataByName("prestige_unknown11", 1, 1); + packet->setDataByName("prestige_unknown11", 1539, 2); + packet->setDataByName("prestige_unknown12", 0, 0); + packet->setDataByName("prestige_unknown12", 0, 1); + packet->setDataByName("prestige_unknown12", 0, 2); + packet->setDataByName("prestige_unknown13", ":493debb3e678b977_91:Prestige"); + packet->setDataByName("prestige_unknown14", 1); + packet->setDataByName("prestige_unknown15", ":493debb3e6a8bb79_20:Prestige Expertise"); + packet->setDataByName("prestige_unknown16", 2, 0); + packet->setDataByName("prestige_unknown16", 0, 1); + packet->setDataByName("prestige_unknown16", 0, 2); + packet->setDataByName("prestige_unknown16", 0, 3); + packet->setDataByName("prestige_unknown16", 0, 4); + packet->setDataByName("prestige_unknown16", 0, 5); + } + + //__________________________________________________________START OF TRADESKILL PRESTIGE TREE____________________________________________________________________________________ + for (itr2 = TradeskillPrestigeTab.begin(); itr2 != TradeskillPrestigeTab.end(); itr2++) { + tradeskillprestige_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "TradeskillPrestigeTab Size...%i ", tradeskillprestige_num_items); + index = 0; + if (version >= 58617) { + packet->setDataByName("tradeskillprestige_tab_title", "General"); + } + else { + packet->setDataByName("tradeskillprestige_tab_title", "Tradeskill Prestige"); + } + packet->setDataByName("tradeskillprestige_tree_node_id", node_id[AA_TRADESKILL_PRESTIGE]); + packet->setDataByName("tradeskillprestige_max_aa", rule_manager.GetGlobalRule(R_Player, MaxPrestigeAA)->GetInt32()); + packet->setDataByName("tradeskillprestige_id", classid[node_id[AA_TRADESKILL_PRESTIGE]]); + packet->setDataByName("tradeskillprestige_eof_req", 0); + packet->setArrayLengthByName("tradeskillprestige_num_items", tradeskillprestige_num_items, 0); + for (itr2 = PrestigeTab.begin(); itr2 != PrestigeTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("tradeskillprestige_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("tradeskillprestige_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("tradeskillprestige_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("tradeskillprestige_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("tradeskillprestige_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_icon", (*itr3)->icon, index); + packet->setArrayDataByName("tradeskillprestige_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("tradeskillprestige_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("tradeskillprestige_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("tradeskillprestige_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("tradeskillprestige_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("tradeskillprestige_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("tradeskillprestige_unknown5", 696953971, index, 4); + packet->setArrayDataByName("tradeskillprestige_unknown6", 4294967295, index); + packet->setArrayDataByName("tradeskillprestige_unknown7", 1, index); + packet->setArrayDataByName("tradeskillprestige_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("tradeskillprestige_unknown8", 0, index); + packet->setArrayDataByName("tradeskillprestige_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_col", (*itr3)->col, index); + packet->setArrayDataByName("tradeskillprestige_row", (*itr3)->row, index); + packet->setArrayDataByName("tradeskillprestige_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("tradeskillprestige_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("tradeskillprestige_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("tradeskillprestige_unknown9b", 0, index); + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("tradeskillprestige_points_spent", 18); + if (version >= 58617) { + packet->setDataByName("tradeskillprestige_unknown10", 18); + + packet->setDataByName("tradeskillprestige_unknown11a", 0); + packet->setDataByName("tradeskillprestige_unknown11b", 50386); + packet->setDataByName("tradeskillprestige_unknown11c", 3); + + packet->setDataByName("tradeskillprestige_unknown12", 0, 0); + packet->setDataByName("tradeskillprestige_unknown12", 0, 1); + packet->setDataByName("tradeskillprestige_unknown12", 0, 2); + + packet->setDataByName("tradeskillprestige_unknown14", 0); + packet->setDataByName("tradeskillprestige_unknown16", 4, 0); + packet->setDataByName("tradeskillprestige_unknown16", 0, 1); + packet->setDataByName("tradeskillprestige_unknown16", 0, 2); + packet->setDataByName("tradeskillprestige_unknown16", 0, 3); + packet->setDataByName("tradeskillprestige_unknown16", 0, 4); + packet->setDataByName("tradeskillprestige_unknown16", 0, 5); + } + else if (version >= 1193) { + packet->setDataByName("tradeskillprestige_unknown10", 16); + packet->setDataByName("tradeskillprestige_unknown11", 0, 0); + packet->setDataByName("tradeskillprestige_unknown11", 1, 1); + packet->setDataByName("tradeskillprestige_unknown11", 3, 2); + + packet->setDataByName("tradeskillprestige_unknown12", 0, 0); + packet->setDataByName("tradeskillprestige_unknown12", 0, 1); + packet->setDataByName("tradeskillprestige_unknown12", 0, 2); + + packet->setDataByName("tradeskillprestige_unknown16", 4, 0); + packet->setDataByName("tradeskillprestige_unknown16", 0, 1); + packet->setDataByName("tradeskillprestige_unknown16", 0, 2); + packet->setDataByName("tradeskillprestige_unknown16", 0, 3); + packet->setDataByName("tradeskillprestige_unknown16", 0, 4); + packet->setDataByName("tradeskillprestige_unknown16", 0, 5); + } + } + if (version >= 58617) { + //__________________________________________________________START OF DRAGON TREE____________________________________________________________________________________ + for (itr2 = DragonTab.begin(); itr2 != DragonTab.end(); itr2++) { + dragon_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "DragonTab Size...%i ", dragon_num_items); + index = 0; + packet->setDataByName("dragon_tab_title", "Dragon"); + packet->setDataByName("dragon_tree_node_id", node_id[AA_DRAGON]); + packet->setDataByName("dragon_max_aa", rule_manager.GetGlobalRule(R_Player, MaxDragonAA)->GetInt32()); + packet->setDataByName("dragon_id", classid[node_id[AA_DRAGON]]); + packet->setDataByName("dragon_eof_req", 0); + packet->setArrayLengthByName("dragon_num_items", dragon_num_items, 0); + for (itr2 = DragonTab.begin(); itr2 != DragonTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + + + //if (spell) { + packet->setArrayDataByName("dragon_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("dragon_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("dragon_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("dragon_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("dragon_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("dragon_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("dragon_icon", (*itr3)->icon, index); + packet->setArrayDataByName("dragon_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("dragon_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("dragon_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("dragon_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("dragon_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("dragon_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("dragon_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("dragon_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("dragon_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("dragon_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("dragon_unknown5", 696953971, index, 4); + packet->setArrayDataByName("dragon_unknown6", 4294967295, index); + packet->setArrayDataByName("dragon_unknown7", 1, index); + packet->setArrayDataByName("dragon_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("dragon_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("dragon_unknown8", 0, index); + packet->setArrayDataByName("dragon_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("dragon_col", (*itr3)->col, index); + packet->setArrayDataByName("dragon_row", (*itr3)->row, index); + packet->setArrayDataByName("dragon_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("dragon_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("dragon_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("dragon_unknown9b", 0, index); + + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("dragon_points_spent", 19); + + if (version >= 58617) { + packet->setDataByName("dragon_unknown10", 19); + + packet->setDataByName("dragon_unknown11a", 0); + packet->setDataByName("dragon_unknown11b", 50386); + packet->setDataByName("dragon_unknown11c", 1); + + packet->setDataByName("dragon_unknown12", 0, 0); + packet->setDataByName("dragon_unknown12", 0, 1); + packet->setDataByName("dragon_unknown12", 0, 2); + + packet->setDataByName("dragon_unknown14", 0); + packet->setDataByName("dragon_unknown16", 0, 0); + packet->setDataByName("dragon_unknown16", 0, 1); + packet->setDataByName("dragon_unknown16", 0, 2); + packet->setDataByName("dragon_unknown16", 0, 3); + packet->setDataByName("dragon_unknown16", 0, 4); + packet->setDataByName("dragon_unknown16", 0, 5); + } + //__________________________________________________________START OF DRAGON CLASS TREE____________________________________________________________________________________ + for (itr2 = DragonclassTab.begin(); itr2 != DragonclassTab.end(); itr2++) { + dragonclass_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "DragonclassTab Size...%i ", dragonclass_num_items); + index = 0; + packet->setDataByName("dragonclass_tab_title", classes.GetClassNameCase(client->GetPlayer()->GetAdventureClass()).c_str()); + packet->setDataByName("dragonclass_tree_node_id", node_id[AA_DRAGONCLASS]); + packet->setDataByName("dragonclass_max_aa", rule_manager.GetGlobalRule(R_Player, MaxDragonAA)->GetInt32()); + packet->setDataByName("dragonclass_id", classid[node_id[AA_DRAGONCLASS]]); + packet->setDataByName("dragonclass_eof_req", 0); + packet->setArrayLengthByName("dragonclass_num_items", dragonclass_num_items, 0); + for (itr2 = DragonclassTab.begin(); itr2 != DragonclassTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("dragonclass_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("dragonclass_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("dragonclass_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("dragonclass_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("dragonclass_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("dragonclass_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("dragonclass_icon", (*itr3)->icon, index); + packet->setArrayDataByName("dragonclass_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("dragonclass_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("dragonclass_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("dragonclass_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("dragonclass_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("dragonclass_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("dragonclass_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("dragonclass_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("dragonclass_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("dragonclass_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("dragonclass_unknown5", 696953971, index, 4); + packet->setArrayDataByName("dragonclass_unknown6", 4294967295, index); + packet->setArrayDataByName("dragonclass_unknown7", 1, index); + packet->setArrayDataByName("dragonclass_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("dragonclass_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("dragonclass_unknown8", 0, index); + packet->setArrayDataByName("dragonclass_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("dragonclass_col", (*itr3)->col, index); + packet->setArrayDataByName("dragonclass_row", (*itr3)->row, index); + packet->setArrayDataByName("dragonclass_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("dragonclass_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("dragonclass_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("dragonclass_unknown9b", 0, index); + + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("dragonclass_points_spent",20); + + if (version >= 58617) { + packet->setDataByName("dragonclass_unknown10", 20); + + packet->setDataByName("dragonclass_unknown11a", 0); + packet->setDataByName("dragonclass_unknown11b", 50386); + packet->setDataByName("dragonclass_unknown11c", 3); + + packet->setDataByName("dragonclass_unknown12", 0, 0); + packet->setDataByName("dragonclass_unknown12", 0, 1); + packet->setDataByName("dragonclass_unknown12", 0, 2); + + packet->setDataByName("dragonclass_unknown14", 0); + packet->setDataByName("dragonclass_unknown16", 2, 0); + packet->setDataByName("dragonclass_unknown16", 0, 1); + packet->setDataByName("dragonclass_unknown16", 0, 2); + packet->setDataByName("dragonclass_unknown16", 0, 3); + packet->setDataByName("dragonclass_unknown16", 0, 4); + packet->setDataByName("dragonclass_unknown16", 0, 5); + } + + //__________________________________________________________START OF FARSEAS TREE____________________________________________________________________________________ + for (itr2 = FarseasTab.begin(); itr2 != FarseasTab.end(); itr2++) { + farseas_num_items += (itr2->second).size(); + } + LogWrite(SPELL__DEBUG, 0, "AA", "FarseasTab Size...%i ", farseas_num_items); + index = 0; + packet->setDataByName("farseas_tab_title", "Farseas"); + packet->setDataByName("farseas_tree_node_id", node_id[AA_FARSEAS]); + packet->setDataByName("farseas_max_aa", rule_manager.GetGlobalRule(R_Player, MaxDragonAA)->GetInt32()); + packet->setDataByName("farseas_id", classid[node_id[AA_FARSEAS]]); + packet->setDataByName("farseas_eof_req", 0); + packet->setArrayLengthByName("farseas_num_items", farseas_num_items, 0); + for (itr2 = FarseasTab.begin(); itr2 != FarseasTab.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + //spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + //current_rank = client->GetPlayer()->GetSpellTier((*itr3)->spellID); + if (index == 0) + class_node_id = (*itr3)->spellID; + //if (spell) { + packet->setArrayDataByName("farseas_parent_id", (*itr3)->rankPrereqID, index); + packet->setArrayDataByName("farseas_req_tier", (*itr3)->rankPrereq, index); + packet->setArrayDataByName("farseas_spell_id", (*itr3)->spellID, index); + packet->setArrayDataByName("farseas_active", (current_rank == 0 ? 0 : (current_rank >= (*itr3)->maxRank) ? 2 : 1), index); + packet->setArrayDataByName("farseas_spell_name", (*itr3)->name.c_str(), index); + packet->setArrayDataByName("farseas_spell_description", (*itr3)->description.c_str(), index); + packet->setArrayDataByName("farseas_icon", (*itr3)->icon, index); + packet->setArrayDataByName("farseas_icon2", (*itr3)->icon2, index); + packet->setArrayDataByName("farseas_current_rank", current_rank, index); // TODO: need to get this value from the DB + packet->setArrayDataByName("farseas_max_rank", (*itr3)->maxRank, index); + packet->setArrayDataByName("farseas_rank_cost", (*itr3)->rankCost, index); + packet->setArrayDataByName("farseas_min_lev", (*itr3)->min_level, index); + packet->setSubArrayLengthByName("farseas_unknown5_num_items", 0, index, 0); + //packet->setSubArrayDataByName("farseas_unknown5", 308397057, index, 0); + //packet->setSubArrayDataByName("farseas_unknown5", 3215564363, index, 1); + //packet->setSubArrayDataByName("farseas_unknown5", 445192837, index, 2); + //packet->setSubArrayDataByName("farseas_unknown5", 3345493294, index, 3); + //packet->setSubArrayDataByName("farseas_unknown5", 696953971, index, 4); + packet->setArrayDataByName("farseas_unknown6", 4294967295, index); + packet->setArrayDataByName("farseas_unknown7", 1, index); + packet->setArrayDataByName("farseas_classification1", (*itr3)->class_name.c_str(), index); + packet->setArrayDataByName("farseas_points_req", (*itr3)->req_points, index); + packet->setArrayDataByName("farseas_unknown8", 0, index); + packet->setArrayDataByName("farseas_classification2", (*itr3)->subclass_name.c_str(), index); + packet->setArrayDataByName("farseas_col", (*itr3)->col, index); + packet->setArrayDataByName("farseas_row", (*itr3)->row, index); + packet->setArrayDataByName("farseas_line_title", (*itr3)->line_title.c_str(), index); + packet->setArrayDataByName("farseas_unknown9", ((*itr3)->title_level > 0 ? 258 : 0), index); + packet->setArrayDataByName("farseas_points_to_unlock", (*itr3)->req_tree_points, index); + packet->setArrayDataByName("farseas_unknown9b", 0, index); + + //} + //else + //LogWrite(SPELL__ERROR, 0, "AltAdvancement","Could not find Spell ID %u and Tier %i in Master Spell List", (*itr3)->spellID, (*itr3)->tier); + } + } + + packet->setDataByName("farseas_points_spent",20); + + if (version >= 58617) { + packet->setDataByName("farseas_unknown10", 20); + + packet->setDataByName("farseas_unknown11a", 0); + packet->setDataByName("farseas_unknown11b", 50386); + packet->setDataByName("farseas_unknown11c", 3); + + packet->setDataByName("farseas_unknown12", 0, 0); + packet->setDataByName("farseas_unknown12", 0, 1); + packet->setDataByName("farseas_unknown12", 0, 2); + + packet->setDataByName("farseas_unknown14", 0); + packet->setDataByName("farseas_unknown16", 4, 0); + packet->setDataByName("farseas_unknown16", 0, 1); + packet->setDataByName("farseas_unknown16", 0, 2); + packet->setDataByName("farseas_unknown16", 0, 3); + packet->setDataByName("farseas_unknown16", 0, 4); + packet->setDataByName("farseas_unknown16", 0, 5); + } + int8 tt = 0; + int8 numtabs = 0; + int xxx = 0; + bool sendblanktabs = false; + packet->setDataByName("template_unknown1", (changemode == 2 ? 255 : 25)); + packet->setDataByName("template_unknown2a", 0); + packet->setDataByName("template_unknown2b", 0); + packet->setDataByName("template_unknown2c", (changemode == 3 ? 1 : 0)); + packet->setDataByName("template_unknown2d", (changemode == 1 || changemode == 2 ? 1 :0)); + packet->setDataByName("template_unknown3", (changemode == 2 ? 0 : 4294967295)); //4294967295); + packet->setDataByName("template_unknown4", newtemplate);// active template ID + packet->setDataByName("template_unknown5", 0); + + packet->setDataByName("num_templates", 7); + packet->setDataByName("slot1_template_id", 0); + packet->setDataByName("slot1_name", "Unused Slot 1"); + packet->setDataByName("slot1_active", 0); //0 is server type ,1 = personal type + tt = 1; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot1_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1 ) {continue;} + packet->setArrayDataByName("slot1_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot1_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot1_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot1_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot1_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot1_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot2_template_id", 1); + packet->setDataByName("slot2_name", "Unused Slot 2"); + packet->setDataByName("slot2_active", 0); + tt = 2; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot2_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot2_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot2_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot2_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot2_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot2_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot2_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot3_template_id", 2); + packet->setDataByName("slot3_name", "Unused Slot 3"); + packet->setDataByName("slot3_active", 0); + tt = 3; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot3_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot3_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot3_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot3_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot3_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot3_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot3_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot4_template_id", 20); + packet->setDataByName("slot4_name", "Basic Leveling Profile - Solo"); + packet->setDataByName("slot4_active", 1); + tt = 4; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot4_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot4_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot4_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot4_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot4_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot4_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot4_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + + packet->setDataByName("slot5_template_id", 21); + packet->setDataByName("slot5_name", "Basic Leveling Profile - Group"); + packet->setDataByName("slot5_active", 1); + tt = 5; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot5_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot5_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot5_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot5_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot5_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot5_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot5_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot6_template_id", 22); + packet->setDataByName("slot6_name", "Basic Leveling Profile - PVP"); + packet->setDataByName("slot6_active", 1); + tt = 6; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot6_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot6_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot6_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot6_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot6_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot6_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot6_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + + packet->setDataByName("slot7_template_id",100); + packet->setDataByName("slot7_name", "New"); + packet->setDataByName("slot7_active", 0); + tt = 7; // template # + numtabs = 0; + if (AAEntryList[tt][0].size() > 0) { numtabs++; } + if (AAEntryList[tt][1].size() > 0) { numtabs++; } + if (AAEntryList[tt][2].size() > 0) { numtabs++; } + if (AAEntryList[tt][3].size() > 0) { numtabs++; } + if (AAEntryList[tt][4].size() > 0) { numtabs++; } + + if (sendblanktabs == true) { + numtabs = 4; + } + packet->setArrayLengthByName("slot7_num_tabs", numtabs); + xxx = 0; + for (int xx = 0; xx < AAEntryList[tt].size(); xx++) { + + if (sendblanktabs == false && AAEntryList[tt][xx].size() < 1) { continue; } + packet->setArrayDataByName("slot7_tab_typenum", xx, xxx); + packet->setArrayDataByName("slot7_tab_unknown2", 1, xxx); + packet->setSubArrayLengthByName("slot7_num_items", AAEntryList[tt][xx].size(), xxx); + for (int yy = 0; yy < AAEntryList[tt][xx].size(); yy++) { + packet->setSubArrayDataByName("slot7_item_order", AAEntryList[tt][xx][yy].order, xxx, yy); + packet->setSubArrayDataByName("slot7_item_treeid", AAEntryList[tt][xx][yy].treeid, xxx, yy); + packet->setSubArrayDataByName("slot7_item_id", AAEntryList[tt][xx][yy].aa_id, xxx, yy); + } + xxx++; + } + //packet->PrintPacket(); + } + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + EQ2Packet* app = new EQ2Packet(OP_AdventureList, data->pBuffer, data->size); + //DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet); + safe_delete(data); +} \ No newline at end of file diff --git a/source/WorldServer/AltAdvancement/AltAdvancement.h b/source/WorldServer/AltAdvancement/AltAdvancement.h new file mode 100644 index 0000000..9b0ed93 --- /dev/null +++ b/source/WorldServer/AltAdvancement/AltAdvancement.h @@ -0,0 +1,118 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#ifndef __AltAdvancement__ +#define __AltAdvancement__ + +#include +#include "../../common/types.h" +#include "../../common/EQPacket.h" +#include "../client.h" + +// defines for AA tabs based on group # from DB +#define AA_CLASS 0 +#define AA_SUBCLASS 1 +#define AA_SHADOW 2 +#define AA_HEROIC 3 +#define AA_TRADESKILL 4 +#define AA_PRESTIGE 5 +#define AA_TRADESKILL_PRESTIGE 6 +#define AA_DRAGON 7 +#define AA_DRAGONCLASS 8 +#define AA_FARSEAS 9 +struct AltAdvanceData +{ + int32 spellID; + int8 min_level; + int32 spell_crc; + string name; + string description; + int8 group; + int16 icon; + int16 icon2; + int8 col; + int8 row; + int8 rankCost; + int8 maxRank; + int32 rankPrereqID; + int8 rankPrereq; + int8 class_req; + int8 tier; + int8 req_points; + int16 req_tree_points; + string class_name; + string subclass_name; + string line_title; + int8 title_level; + int32 node_id; +}; + + +class MasterAAList +{ +public: + MasterAAList(); + ~MasterAAList(); + ///

Sorts the Alternate Advancements for the given client, creates and sends the OP_AdventureList packet. + /// The Client calling this function + /// EQ2Packet* + EQ2Packet* GetAAListPacket(Client* client); + + /// Add Alternate Advancement data to the global list. + /// The Alternate Advancement data to add. + void AddAltAdvancement(AltAdvanceData* data); + + /// Get the total number of Alternate Advancements in the global list. + int Size(); + + /// Get the Alternate Advancement data for the given spell. + /// Spell ID to get Alternate Advancement data for. + AltAdvanceData* GetAltAdvancement(int32 spellID); + + /// empties the master Alternate Advancement list + void DestroyAltAdvancements(); + void DisplayAA(Client* client,int8 newtemplate,int8 changemode); +private: + vector AAList; + Mutex MMasterAAList; +}; + +struct TreeNodeData +{ + int32 classID; + int32 treeID; + int32 AAtreeID; +}; + +class MasterAANodeList +{ +public: + MasterAANodeList(); + ~MasterAANodeList(); + void AddTreeNode(TreeNodeData* data); + int Size(); + void DestroyTreeNodes(); + vector GetTreeNodes(); + +private: + vector TreeNodeList; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/AltAdvancement/AltAdvancementDB.cpp b/source/WorldServer/AltAdvancement/AltAdvancementDB.cpp new file mode 100644 index 0000000..b7f3da9 --- /dev/null +++ b/source/WorldServer/AltAdvancement/AltAdvancementDB.cpp @@ -0,0 +1,102 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../../common/DatabaseNew.h" +#include "../WorldDatabase.h" +#include "AltAdvancement.h" + +extern MasterAAList master_aa_list; +extern MasterAANodeList master_tree_nodes; + +void WorldDatabase::LoadAltAdvancements() +{ + Query query; + MYSQL_ROW row; + AltAdvanceData* data; + + //MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`, `group`, `icon`, `icon2`, `col`, `row`, `rank_cost`, `max_cost`, `rank_prereq_id`, `rank_prereq`, `class_req`, `tier`, `class_name`, `subclass_name`, `line_title` FROM spell_aa"); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `nodeid`,`minlevel`, `spellcrc`, `name`, `description`, `aa_list_fk`, `icon_id`, `icon_backdrop`, `xcoord`, `ycoord`, `pointspertier`, `maxtier`, `firstparentid`, `firstparentrequiredtier`, `displayedclassification`,`requiredclassification`, `classificationpointsrequired`, `pointsspentintreetounlock`, `title`,`titlelevel` FROM spell_aa_nodelist"); + while (result && (row = mysql_fetch_row(result))) { + data = new AltAdvanceData; + int8 i = 0; + data->spellID = strtoul(row[0], NULL, 0); + data->min_level = atoi(row[++i]); + data->spell_crc = strtoul(row[++i], NULL, 0); + data->name = string(row[++i]); + data->description = string(row[++i]); + data->group = atoi(row[++i]); + data->icon = atoi(row[++i]); + data->icon2 = atoi(row[++i]); + data->col = atoi(row[++i]); + data->row = atoi(row[++i]); + data->rankCost = atoi(row[++i]); + data->maxRank = atoi(row[++i]); + data->rankPrereqID = strtoul(row[++i], NULL, 0); + data->rankPrereq = atoi(row[++i]); + data->tier = 1; + data->class_name = string(row[++i]); + data->subclass_name = string(row[i]); + data->req_points = atoi(row[++i]); + data->req_tree_points = atoi(row[++i]); + data->line_title = string(row[++i]); + data->title_level = atoi(row[++i]); + + master_aa_list.AddAltAdvancement(data); + } + + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u Alternate Advancement(s)", master_aa_list.Size()); + +} + +void WorldDatabase::LoadTreeNodes() +{ + Query query; + MYSQL_ROW row; + TreeNodeData* data; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT class_id, tree_node, aa_tree_id FROM spell_aa_class_list"); + while (result && (row = mysql_fetch_row(result))) { + data = new TreeNodeData; + data->classID = strtoul(row[0], NULL, 0); + data->treeID = strtoul(row[1], NULL, 0); + data->AAtreeID = strtoul(row[2], NULL, 0); + master_tree_nodes.AddTreeNode(data); + } + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", master_tree_nodes.Size()); +} +void WorldDatabase::LoadPlayerAA(Player *player) +{ + Query query; + MYSQL_ROW row; + + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa where char_id = %i order by `order`",player->id); + while (result && (row = mysql_fetch_row(result))) { + + } + LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", master_tree_nodes.Size()); +} \ No newline at end of file diff --git a/source/WorldServer/Appearances.h b/source/WorldServer/Appearances.h new file mode 100644 index 0000000..11f1de6 --- /dev/null +++ b/source/WorldServer/Appearances.h @@ -0,0 +1,87 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include + +using namespace std; + +// Appearances must use a hash table because of the large amount that exists and the large spacing +// between their ID's. String and character arrays could not be used for the first iterator because +// it would require the same pointer to access it from the hash table, which is obviously not possible +// since the text is from the client. + +// maximum amount of iterations it will attempt to find a entree +#define HASH_SEARCH_MAX 20 + +class Appearance +{ +public: + // JA: someday add the min_client_version to the map to determine which appearance_id to set per client version + Appearance(int32 inID, const char *inName, int16 inVer) + { + if( !inName ) + return; + name = string(inName); + id = inID; + min_client = inVer; + } + + int32 GetID() { return id; } + const char* GetName() { return name.c_str(); } + int16 GetMinClientVersion() { return min_client; } + string GetNameString() { return name; } + +private: + int32 id; + string name; + int16 min_client; +}; + +class Appearances +{ +public: + ~Appearances(){ + Reset(); + } + + void Reset(){ + ClearAppearances(); + } + + void ClearAppearances(){ + map::iterator map_list; + for(map_list = appearanceMap.begin(); map_list != appearanceMap.end(); map_list++ ) + safe_delete(map_list->second); + appearanceMap.clear(); + } + + void InsertAppearance(Appearance* a){ + appearanceMap[a->GetID()] = a; + } + + Appearance* FindAppearanceByID(int32 id){ + if(appearanceMap.count(id) > 0) + return appearanceMap[id]; + return 0; + } + +private: + map appearanceMap; +}; + diff --git a/source/WorldServer/Bots/Bot.cpp b/source/WorldServer/Bots/Bot.cpp new file mode 100644 index 0000000..953aa32 --- /dev/null +++ b/source/WorldServer/Bots/Bot.cpp @@ -0,0 +1,732 @@ +#include "Bot.h" +#include "BotBrain.h" +#include "../Trade.h" +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "../classes.h" +#include "../SpellProcess.h" + +extern WorldDatabase database; +extern MasterSpellList master_spell_list; +extern World world; +extern Classes classes; + +Bot::Bot() : NPC() { + SetBrain(new BotBrain(this)); + BotID = 0; + ShowHelm = true; + ShowCloak = true; + CanTaunt = false; + combat_target = 0; + main_tank = 0; + + //AddPrimaryEntityCommand("hail", 10000, "hail", "", 0, 0); + AddSecondaryEntityCommand("invite bot", 10000, "invite", "", 0, 0); + AddSecondaryEntityCommand("bot inventory", 10000, "bot inv list", "", 0, 0); + + InfoStruct* info = GetInfoStruct(); + info->set_str_base(50); + info->set_sta_base(20); + info->set_wis_base(20); + info->set_intel_base(20); + info->set_agi_base(20); + + camping = false; + immediate_camp = false; +} + +Bot::~Bot() { +} + +void Bot::GiveItem(int32 item_id) { + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + if (master_item) + item = new Item(master_item); + if (item) { + int8 slot = GetEquipmentList()->GetFreeSlot(item); + if (slot != 255) { + GetEquipmentList()->AddItem(slot, item); + SetEquipment(item, slot); + database.SaveBotItem(BotID, item_id, slot); + if (slot == 0) { + ChangePrimaryWeapon(); + if (IsBot()) + LogWrite(PLAYER__ERROR, 0, "Bot", "Changing bot primary weapon."); + } + + CalculateBonuses(); + } + } +} + +void Bot::GiveItem(Item* item) { + if (item) { + int8 slot = GetEquipmentList()->GetFreeSlot(item); + if (slot != 255) { + GetEquipmentList()->AddItem(slot, item); + SetEquipment(item, slot); + database.SaveBotItem(BotID, item->details.item_id, slot); + if (slot == 0) { + ChangePrimaryWeapon(); + if (IsBot()) + LogWrite(PLAYER__ERROR, 0, "Bot", "Changing bot primary weapon."); + } + + CalculateBonuses(); + } + } +} + +void Bot::RemoveItem(Item* item) { + int8 slot = GetEquipmentList()->GetSlotByItem(item); + if (slot != 255) { + GetEquipmentList()->RemoveItem(slot, true); + SetEquipment(0, slot); + } +} + +void Bot::TradeItemAdded(Item* item) { + int8 slot = GetEquipmentList()->GetFreeSlot(item); + if (slot == 255 && item->slot_data.size() > 0) { + slot = item->slot_data[0]; + AddItemToTrade(slot); + } +} + +void Bot::AddItemToTrade(int8 slot) { + Item* item = GetEquipmentList()->GetItem(slot); + if (trading_slots.count(slot) == 0 && item && trade) { + trade->AddItemToTrade(this, item, 1, 255); + trading_slots.insert(slot); + } +} + +bool Bot::CheckTradeItems(map* list) { + if (!list) { + LogWrite(PLAYER__ERROR, 0, "Bot", "CheckTradeItems did not recieve a valid list of items"); + return false; + } + + bool ret = true; + map::iterator itr; + for (itr = list->begin(); itr != list->end(); itr++) { + Item* item = itr->second.item; + if (item) { + if (!CanEquipItem(item)) { + // No slots means not equipable so reject the trade + ret = false; + break; + } + } + } + + return ret; +} + +void Bot::FinishTrade() { + trading_slots.clear(); +} + +bool Bot::CanEquipItem(Item* item) { + if (item) { + if (item->IsArmor() || item->IsWeapon() || item->IsFood() || item->IsRanged() || item->IsShield() || item->IsBauble() || item->IsAmmo() || item->IsThrown()) { + int16 override_level = item->GetOverrideLevel(GetAdventureClass(), GetTradeskillClass()); + if (override_level > 0 && override_level <= GetLevel()) { + LogWrite(PLAYER__ERROR, 0, "Bot", "Passed in override_level check"); + return true; + } + if (item->CheckClass(GetAdventureClass(), GetTradeskillClass())) + if (item->CheckLevel(GetAdventureClass(), GetTradeskillClass(), GetLevel())) { + LogWrite(PLAYER__ERROR, 0, "Bot", "Passed in normal check"); + return true; + } + } + } + return false; +} + +void Bot::MessageGroup(string msg) { + GroupMemberInfo* gmi = GetGroupMemberInfo(); + if (gmi) + world.GetGroupManager()->GroupChatMessage(gmi->group_id, this, 0, msg.c_str()); +} + +void Bot::GetNewSpells() { + vector spells; + vector* spells1 = master_spell_list.GetSpellListByAdventureClass(GetAdventureClass(), GetLevel(), 1); + vector* spells2 = master_spell_list.GetSpellListByAdventureClass(classes.GetBaseClass(GetAdventureClass()), GetLevel(), 1); + vector* spells3 = master_spell_list.GetSpellListByAdventureClass(classes.GetSecondaryBaseClass(GetAdventureClass()), GetLevel(), 1); + + spells.insert(spells.end(), spells1->begin(), spells1->end()); + spells.insert(spells.end(), spells2->begin(), spells2->end()); + spells.insert(spells.end(), spells3->begin(), spells3->end()); + + vector::iterator itr; + map* spell_list = 0; + for (itr = spells.begin(); itr != spells.end(); itr++) { + switch ((*itr)->GetSpellData()->spell_type) { + case SPELL_TYPE_DD: + spell_list = &dd_spells; + break; + case SPELL_TYPE_DOT: + spell_list = &dot_spells; + break; + case SPELL_TYPE_HEAL: + spell_list = &heal_spells; + break; + case SPELL_TYPE_HOT_WARD: + spell_list = &hot_ward_spells; + break; + case SPELL_TYPE_DEBUFF: + spell_list = &debuff_spells; + break; + case SPELL_TYPE_BUFF: + spell_list = &buff_spells; + break; + case SPELL_TYPE_COMBATBUFF: + spell_list = &combat_buff_spells; + break; + case SPELL_TYPE_TAUNT: + spell_list = &taunt_spells; + break; + case SPELL_TYPE_DETAUNT: + spell_list = &detaunt_spells; + break; + case SPELL_TYPE_REZ: + LogWrite(PLAYER__ERROR, 0, "Bot", "Adding rez spell."); + spell_list = &rez_spells; + break; + case SPELL_TYPE_CURE: + spell_list = &cure_spells; + break; + default: + spell_list = 0; + break; + } + if (spell_list && spell_list->count((*itr)->GetSpellID()) == 0) + (*spell_list)[(*itr)->GetSpellID()] = 1; + } + + safe_delete(spells1); + safe_delete(spells2); + safe_delete(spells3); +} + +Entity* Bot::GetCombatTarget() { + Spawn* target = GetZone()->GetSpawnByID(combat_target); + if (target && target->IsEntity()) + return (Entity*)target; + + combat_target = 0; + return 0; +} + +Spell* Bot::SelectSpellToCast(float distance) { + Spell* spell = 0; + map::iterator itr; + + // Heal + spell = GetHealSpell(); + if (spell) + return spell; + + // Taunt + spell = GetTauntSpell(); + if (spell) + return spell; + + // Detaunt + spell = GetDetauntSpell(); + if (spell) + return spell; + + // Hot/Ward + spell = GetHoTWardSpell(); + if (spell) + return spell; + + // Debuff + spell = GetDebuffSpell(); + if (spell) + return spell; + + // Combat Buff + spell = GetCombatBuffSpell(); + if (spell) + return spell; + + // DoT + spell = GetDoTSpell(); + if (spell) + return spell; + + // DD + spell = GetDDSpell(); + if (spell) + return spell; + + return 0; +} + +void Bot::SetRecast(Spell* spell, int32 time) { + recast_times[spell->GetSpellID()] = time; +} + +bool Bot::IsSpellReady(Spell* spell) { + if (recast_times.count(spell->GetSpellID()) > 0) { + if (recast_times[spell->GetSpellID()] > Timer::GetCurrentTime2()) + return false; + } + + return true; +} + +Spell* Bot::GetDDSpell() { + if (dd_spells.size() == 0) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = dd_spells.begin(); itr != dd_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + return spell; + } + } + + return 0; +} + +Spell* Bot::GetHealSpell() { + if (heal_spells.size() == 0) + return 0; + + // Get an available heal spell + Spell* spell = 0; + map::iterator itr; + for (itr = heal_spells.begin(); itr != heal_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + break; + } + } + + // No heal available, return out + if (!spell) + return 0; + + + // There was a heal spell so find a group member that needs healing + int8 threshold = GetHealThreshold(); + GroupMemberInfo* gmi = GetGroupMemberInfo(); + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if(!member) + continue; + + if (!member->Alive()) + continue; + + int8 percent = (int8)(((float)member->GetHP() / member->GetTotalHP()) * 100); + if (percent <= threshold) { + if (spell) { + SetTarget(member); + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + return spell; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + + return 0; +} + +Spell* Bot::GetTauntSpell() { + if (taunt_spells.size() == 0) + return 0; + + // If not the main tank and taunts are turned off return out + if (main_tank != this && !CanTaunt) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = taunt_spells.begin(); itr != taunt_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + return spell; + } + } + + return 0; +} + +Spell* Bot::GetDetauntSpell() { + if (detaunt_spells.size() == 0) + return 0; + + if (!GetTarget() || !GetTarget()->IsNPC()) + return 0; + + NPC* target = (NPC*)GetTarget(); + Entity* hated = target->Brain()->GetMostHated(); + if (hated == this) { + Spell* spell = 0; + map::iterator itr; + for (itr = detaunt_spells.begin(); itr != detaunt_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + return spell; + } + } + } + + return 0; +} + +Spell* Bot::GetHoTWardSpell() { + if (hot_ward_spells.size() == 0) + return 0; + + // Get an available spell + Spell* spell = 0; + map::iterator itr; + for (itr = hot_ward_spells.begin(); itr != hot_ward_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + break; + } + } + + // No spell available, return out + if (!spell) + return 0; + + // There was a spell so find a group member that needs healing + int8 threshold = GetHealThreshold(); + GroupMemberInfo* gmi = GetGroupMemberInfo(); + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + + if(!member) + continue; + + int8 percent = 0; + if (member->GetHP() > 0) + percent = (int8)(((float)member->GetHP() / member->GetTotalHP()) * 100); + + if (percent <= 99 && percent > threshold) { + if (spell) { + SetTarget(member); + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + return spell; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} +Spell* Bot::GetDebuffSpell() { + if (debuff_spells.size() == 0) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = debuff_spells.begin(); itr != debuff_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + // If target already has this effect on them then continue to the next spell + if (((Entity*)GetTarget())->GetSpellEffect(itr->first)) + continue; + + return spell; + } + } + + return 0; +} + +Spell* Bot::GetCombatBuffSpell() { + return 0; +} + +Spell* Bot::GetDoTSpell() { + if (dot_spells.size() == 0) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = dot_spells.begin(); itr != dot_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + // If target already has this effect on them then continue to the next spell + if (((Entity*)GetTarget())->GetSpellEffect(itr->first)) + continue; + + return spell; + } + } + + return 0; +} + +Spell* Bot::GetBuffSpell() { + if (buff_spells.size() == 0) + return 0; + + Spell* spell = 0; + Entity* target = 0; + map::iterator itr; + for (itr = buff_spells.begin(); itr != buff_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + target = 0; + + if (spell->GetSpellData()->target_type == SPELL_TARGET_SELF) + target = this; + if (spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE) + target = this; + if (spell->GetSpellData()->target_type == SPELL_TARGET_ENEMY && spell->GetSpellData()->friendly_spell == 1) + target = (main_tank != NULL) ? main_tank : GetOwner(); + if (!target) + continue; + if (!target->Alive()) + continue; + + // If target already has this effect on them then continue to the next spell + if (target->GetSpellEffect(itr->first)) + continue; + + SetTarget(target); + return spell; + } + } + + return 0; +} + +Spell* Bot::GetRezSpell() { + if (rez_spells.size() == 0) + return 0; + + GroupMemberInfo* gmi = GetGroupMemberInfo(); + if (!gmi) + return 0; + + Entity* target = 0; + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (member && !member->Alive() && member->IsPlayer()) { + PendingResurrection* rez = members->at(i)->client->GetCurrentRez(); + if (rez->active) + continue; + + target = member; + break; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + if (!target) + return 0; + + Spell* spell = 0; + map::iterator itr; + for (itr = rez_spells.begin(); itr != rez_spells.end(); itr++) { + spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell && IsSpellReady(spell)) { + SetTarget(target); + return spell; + } + } + + return 0; +} + +Spell* Bot::GetCureSpell() { + return 0; +} + +int8 Bot::GetHealThreshold() { + int8 ret = 0; + + switch (GetAdventureClass()) { + case PRIEST: + case CLERIC: + case TEMPLAR: + case INQUISITOR: + case DRUID: + case WARDEN: + case FURY: + case SHAMAN: + case MYSTIC: + case DEFILER: + ret = 70; + break; + default: + ret = 30; + break; + } + + return ret; +} + +bool Bot::ShouldMelee() { + bool ret = true; + + switch (GetAdventureClass()) { + case PRIEST: + case CLERIC: + case TEMPLAR: + case INQUISITOR: + case DRUID: + case WARDEN: + case FURY: + case SHAMAN: + case MYSTIC: + case DEFILER: + case MAGE: + case SORCERER: + case WIZARD: + case WARLOCK: + case ENCHANTER: + case ILLUSIONIST: + case COERCER: + case SUMMONER: + case CONJUROR: + case NECROMANCER: + ret = false; + break; + default: + ret = true; + break; + } + + if (GetTarget() == GetOwner()) + ret = false; + + return ret; +} + +void Bot::Camp(bool immediate) { + // Copy from COMMAND_GROUP_LEAVE + camping = true; + immediate_camp = immediate; +} + +void Bot::ChangeLevel(int16 old_level, int16 new_level) { + if (new_level < 1) + return; + + if (GetLevel() != new_level) { + SetLevel(new_level); + if (GetGroupMemberInfo()) { + UpdateGroupMemberInfo(); + world.GetGroupManager()->SendGroupUpdate(GetGroupMemberInfo()->group_id); + } + } + + if (GetPet()) { + NPC* pet = (NPC*)GetPet(); + if (pet->GetMaxPetLevel() == 0 || new_level <= pet->GetMaxPetLevel()) { + pet->SetLevel(new_level); + GetZone()->PlayAnimation(pet, 1753); + } + } + + // level up animation + GetZone()->PlayAnimation(this, 1753); + + //player->GetSkills()->IncreaseAllSkillCaps(5 * (new_level - old_level)); + GetNewSpells(); + //SendNewSpells(player->GetAdventureClass()); + //SendNewSpells(classes.GetBaseClass(player->GetAdventureClass())); + //SendNewSpells(classes.GetSecondaryBaseClass(player->GetAdventureClass())); + + GetInfoStruct()->set_level(new_level); + UpdateWeapons(); + // GetPlayer()->SetLevel(new_level); + + LogWrite(MISC__TODO, 1, "TODO", "Get new HP/POWER/stat based on default values from DB\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + SetTotalHPBase(new_level*new_level * 2 + 40); + SetTotalPowerBase((sint32)(new_level*new_level*2.1 + 45)); + CalculateBonuses(); + SetHP(GetTotalHP()); + SetPower(GetTotalPower()); + + GetInfoStruct()->set_agi_base(new_level * 2 + 15); + GetInfoStruct()->set_intel_base(new_level * 2 + 15); + GetInfoStruct()->set_wis_base(new_level * 2 + 15); + GetInfoStruct()->set_str_base(new_level * 2 + 15); + GetInfoStruct()->set_sta_base(new_level * 2 + 15); + GetInfoStruct()->set_cold_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_heat_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_disease_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_mental_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_magic_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_divine_base((int16)(new_level*1.5 + 10)); + GetInfoStruct()->set_poison_base((int16)(new_level*1.5 + 10)); + /*UpdateTimeStampFlag(LEVEL_UPDATE_FLAG); + GetPlayer()->SetCharSheetChanged(true); + + Message(CHANNEL_COLOR_EXP, "You are now level %i!", new_level); + LogWrite(WORLD__DEBUG, 0, "World", "Player: %s leveled from %u to %u", GetPlayer()->GetName(), old_level, new_level); + GetPlayer()->GetSkills()->SetSkillCapsByType(1, 5 * new_level); + GetPlayer()->GetSkills()->SetSkillCapsByType(3, 5 * new_level); + GetPlayer()->GetSkills()->SetSkillCapsByType(6, 5 * new_level); + GetPlayer()->GetSkills()->SetSkillCapsByType(13, 5 * new_level); + */ +} + +void Bot::Begin_Camp() { + GroupMemberInfo* gmi = GetGroupMemberInfo(); + if (gmi) { + int32 group_id = gmi->group_id; + world.GetGroupManager()->RemoveGroupMember(group_id, this); + if (!world.GetGroupManager()->IsGroupIDValid(group_id)) { + // leader->Message(CHANNEL_COLOR_GROUP, "%s has left the group.", client->GetPlayer()->GetName()); + } + else { + world.GetGroupManager()->GroupMessage(group_id, "%s has left the group.", GetName()); + } + } + + if(!immediate_camp) + { + GetZone()->PlayAnimation(this, 538); + SetVisualState(540); + GetZone()->Despawn(this, 5000); + } + + if (!GetOwner()) + return; + + if (GetOwner()->IsPlayer()) + ((Player*)GetOwner())->SpawnedBots.erase(BotIndex); + + camping = false; + immediate_camp = true; +} \ No newline at end of file diff --git a/source/WorldServer/Bots/Bot.h b/source/WorldServer/Bots/Bot.h new file mode 100644 index 0000000..a6008bf --- /dev/null +++ b/source/WorldServer/Bots/Bot.h @@ -0,0 +1,95 @@ +#pragma once + +#include "../NPC.h" +#include + +struct TradeItemInfo; + +class Bot : public NPC { +public: + Bot(); + ~Bot(); + + int32 BotID; // DB id + int32 BotIndex; // Bot id with its owner (player) + bool IsBot() { return true; } + + void GiveItem(int32 item_id); + void GiveItem(Item* item); + void RemoveItem(Item* item); + void TradeItemAdded(Item* item); + void AddItemToTrade(int8 slot); + bool CheckTradeItems(map* list); + void FinishTrade(); + void GetNewSpells(); + map* GetBotSpells() { return &dd_spells; } + + bool ShowHelm; + bool ShowCloak; + bool CanTaunt; + + Entity* GetCombatTarget(); + void SetCombatTarget(int32 target) { combat_target = target; } + + Spell* SelectSpellToCast(float distance); + + void MessageGroup(string msg); + + void SetRecast(Spell* spell, int32 time); + bool ShouldMelee(); + + Spell* GetNextBuffSpell(Spawn* target = 0) { return GetBuffSpell(); } + Spell* GetHealSpell(); + Spell* GetRezSpell(); + + void SetMainTank(Entity* tank) { main_tank = tank; } + + void Camp(bool immediate=false); + void ChangeLevel(int16 old_level, int16 new_level); + + bool IsCamping() { return camping; } + bool IsImmediateCamp() { return immediate_camp; } + void Begin_Camp(); +private: + bool CanEquipItem(Item* item); + bool IsSpellReady(Spell* spell); + + + Spell* GetTauntSpell(); + Spell* GetDetauntSpell(); + Spell* GetHoTWardSpell(); + Spell* GetDebuffSpell(); + Spell* GetCombatBuffSpell(); + Spell* GetDoTSpell(); + Spell* GetDDSpell(); + + Spell* GetBuffSpell(); + Spell* GetCureSpell(); + + + int8 GetHealThreshold(); + + set trading_slots; + int32 combat_target; + + Entity* main_tank; + + map bot_spells; + map dd_spells; + map dot_spells; + map heal_spells; + map hot_ward_spells; + map debuff_spells; + map buff_spells; + map combat_buff_spells; + map taunt_spells; + map detaunt_spells; + map rez_spells; + map cure_spells; + + // First int32 = spell id (change to timer id later), second int32 is time the spell is available to cast again + map recast_times; + std::atomic camping; + std::atomic immediate_camp; + +}; diff --git a/source/WorldServer/Bots/BotBrain.cpp b/source/WorldServer/Bots/BotBrain.cpp new file mode 100644 index 0000000..69d19b0 --- /dev/null +++ b/source/WorldServer/Bots/BotBrain.cpp @@ -0,0 +1,205 @@ +#include "BotBrain.h" +#include "../Combat.h" +#include "../Spells.h" +#include "../../common/Log.h" +#include "../Rules/Rules.h" + +extern RuleManager rule_manager; + +BotBrain::BotBrain(Bot* body) : Brain(body) { + Body = body; +} + +BotBrain::~BotBrain() { + +} + +void BotBrain::Think() { + // No ownder do nothing, probably despawn as owner should never be empty for bots + if (!m_body->GetOwner()) + return; + + // Not in a group yet then do nothing + if (!m_body->GetGroupMemberInfo()) + return; + + if (!Body->Alive()) + return; + + if (Body->IsMezzedOrStunned()) + return; + + // If combat was processed we can return out + if (ProcessCombat()) + return; + + // Combat failed to process so do out of combat tasks like follow the player + if (ProcessOutOfCombatSpells()) + return; + + // put htis here so bots don't try to follow the owner while in combat + if (Body->EngagedInCombat()) + return; + + // Set target to owner + Spawn* target = GetBody()->GetFollowTarget(); + + if(target) + { + // Get distance from the owner + float distance = GetBody()->GetDistance(target); + + // If out of melee range then move closer + if (distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + MoveCloser(target); + } +} + +bool BotBrain::ProcessCombat() { + SetTarget(); + + if (Body->GetTarget() && Body->EngagedInCombat()) { + if (Body->GetTarget() && Body->GetTarget()->IsEntity() && Body->AttackAllowed((Entity*)Body->GetTarget())) { + Entity* target = (Entity*)Body->GetTarget(); + float distance = Body->GetDistance(target); + + if (!ProcessSpell(target, distance)) { + if (Body->ShouldMelee()) + ProcessMelee(target, distance); + } + + NPC* pet = (NPC*)Body->GetPet(); + if (pet) { + if (pet->Brain()->GetHate(target) == 0) + pet->AddHate(target, 1); + } + } + + return true; + } + + return false; +} + +void BotBrain::SetTarget() { + // The target issued from /bot attack + if (Body->GetCombatTarget() && Body->GetCombatTarget()->Alive()) { + Body->SetTarget(Body->GetCombatTarget()); + Body->InCombat(true); + return; + } + + // Assist + Entity* owner = Body->GetOwner(); + if (owner && owner->EngagedInCombat()) { + if (owner->GetTarget() && owner->GetTarget()->IsEntity() && owner->GetTarget()->Alive() && owner->AttackAllowed((Entity*)owner->GetTarget())) { + Body->SetTarget(owner->GetTarget()); + Body->InCombat(true); + // Add some hate to keep the bot attacking if + // the player toggles combat off + if (GetHate((Entity*)Body->GetTarget()) == 0) + AddHate((Entity*)Body->GetTarget(), 1); + return; + } + } + + // Most hated + Entity* hated = GetMostHated(); + if (hated && hated->Alive()) { + if (hated == Body->GetOwner()) { + ClearHate(hated); + } + else { + Body->SetTarget(hated); + Body->InCombat(true); + return; + } + } + + // None of the above true so clear target and turn combat off + Body->SetTarget(0); + Body->InCombat(false); +} + +bool BotBrain::ProcessSpell(Entity* target, float distance) { + if (Body->IsStifled() || Body->IsFeared()) + return false; + + if (Body->IsCasting()) + return false; + + if (!HasRecovered()) + return false; + + Spell* spell = Body->SelectSpellToCast(distance); + if (spell) { + // Target can change (heals for example) so recalculate distance and if out of range move closer + float distance = Body->GetDistance(Body->GetTarget()); + if (distance > spell->GetSpellData()->range) { + if (Body->GetTarget()->IsEntity()) + MoveCloser((Spawn*)Body->GetTarget()); + } + else { + // stop movement if spell can't be cast while moving + if (!spell->GetSpellData()->cast_while_moving) + Body->CalculateRunningLocation(true); + + Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget()); + m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000); + // recast time + int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body)); + Body->SetRecast(spell, time); + + string str = "I am casting "; + str += spell->GetName(); + Body->MessageGroup(str); + } + return true; + } + + return false; +} + +bool BotBrain::ProcessOutOfCombatSpells() { + if (Body->IsStifled() || Body->IsFeared()) + return false; + + if (Body->IsCasting()) + return false; + + if (!HasRecovered()) + return false; + + Spell* spell = Body->GetHealSpell(); + if (!spell) + spell = Body->GetRezSpell(); + if (!spell) + spell = Body->GetNextBuffSpell(); + + if (spell) { + // stop movement if spell can't be cast while moving + if (!spell->GetSpellData()->cast_while_moving) + Body->CalculateRunningLocation(true); + + // See if we are in range of target, if not move closer + float distance = Body->GetDistance(Body->GetTarget()); + if (distance > spell->GetSpellData()->range) { + if (Body->GetTarget()->IsEntity()) + MoveCloser((Spawn*)Body->GetTarget()); + } + else { + Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget()); + m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000); + // recast time + int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body)); + Body->SetRecast(spell, time); + + string str = "I am casting "; + str += spell->GetName(); + Body->MessageGroup(str); + } + return true; + } + + return false; +} \ No newline at end of file diff --git a/source/WorldServer/Bots/BotBrain.h b/source/WorldServer/Bots/BotBrain.h new file mode 100644 index 0000000..aa8911f --- /dev/null +++ b/source/WorldServer/Bots/BotBrain.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../NPC_AI.h" +#include "Bot.h" + +class BotBrain : public Brain { +public: + BotBrain(Bot* body); + virtual ~BotBrain(); + void Think(); + bool ProcessSpell(Entity* target, float distance); + + bool ProcessOutOfCombatSpells(); + +private: + Bot* Body; + + bool ProcessCombat(); + void SetTarget(); +}; \ No newline at end of file diff --git a/source/WorldServer/Bots/BotCommands.cpp b/source/WorldServer/Bots/BotCommands.cpp new file mode 100644 index 0000000..494edc0 --- /dev/null +++ b/source/WorldServer/Bots/BotCommands.cpp @@ -0,0 +1,830 @@ +#include "../Commands/Commands.h" +#include "../WorldDatabase.h" +#include "../classes.h" +#include "../races.h" +#include "../Bots/Bot.h" +#include "../../common/Log.h" +#include "../Trade.h" +#include "../PlayerGroups.h" +#include "../World.h" +#include "../../common/GlobalHeaders.h" + +extern WorldDatabase database; +extern ConfigReader configReader; +extern World world; +extern MasterSpellList master_spell_list; + +void Commands::Command_Bot(Client* client, Seperator* sep) { + + if (sep && sep->IsSet(0)) { + if (strncasecmp("camp", sep->arg[0], 4) == 0) { + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot"); + return; + } + + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->GetOwner() != client->GetPlayer()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only camp your own bots."); + return; + } + + bot->Camp(); + return; + } + else if (strncasecmp("attack", sep->arg[0], 6) == 0) { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity() && client->GetPlayer()->GetTarget()->Alive()) { + Entity* target = (Entity*)client->GetPlayer()->GetTarget(); + + if (client->GetPlayer()->GetDistance(target) <= 50) { + if (client->GetPlayer()->AttackAllowed(target)) { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (gmi) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + + for (itr = members->begin(); itr != members->end(); itr++) { +//devn00b compile says this is no good, commenting out for now. +//if(!member) +// continue; + if ((*itr)->member && (*itr)->member->IsBot() && ((Bot*)(*itr)->member)->GetOwner() == client->GetPlayer()) { + + ((Bot*)(*itr)->member)->SetCombatTarget(target->GetID()); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can not attack that target."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target is to far away."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Not a valid target."); + + return; + } + else if (strncasecmp("spells", sep->arg[0], 6) == 0) { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) { + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + + map* spells = bot->GetBotSpells(); + map::iterator itr; + string output; + for (itr = spells->begin(); itr != spells->end(); itr++) { + Spell* spell = master_spell_list.GetSpell(itr->first, itr->second); + if (spell) { + output += spell->GetName(); + output += "\n"; + } + } + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, output.c_str()); + return; + } + } + else if (strncasecmp("maintank", sep->arg[0], 8) == 0) { + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsEntity()) { + client->SimpleMessage(CHANNEL_COMMAND_TEXT, "Not a valid target."); + return; + } + + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (!gmi) { + client->SimpleMessage(CHANNEL_COMMAND_TEXT, "You are not in a group."); + return; + } + + Entity* target = (Entity*)client->GetPlayer()->GetTarget(); + if (!world.GetGroupManager()->IsInGroup(gmi->group_id, target)) { + client->SimpleMessage(CHANNEL_COMMAND_TEXT, "Target is not in your group."); + return; + } + + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + for (int8 i = 0; i < members->size(); i++) { + GroupMemberInfo* gmi2 = members->at(i); + if(!gmi2 || !gmi2->member) + continue; + if (gmi2->member->IsBot() && ((Bot*)gmi2->member)->GetOwner() == client->GetPlayer()) { + ((Bot*)gmi2->member)->SetMainTank(target); + client->Message(CHANNEL_COMMAND_TEXT, "Setting main tank for %s to %s", gmi2->member->GetName(), target->GetName()); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + return; + } + else if (strncasecmp("delete", sep->arg[0], 6) == 0) { + if (sep->IsSet(1) && sep->IsNumber(1)) { + int32 index = atoi(sep->arg[1]); + + // Check if bot is currently spawned and if so camp it out + if (client->GetPlayer()->SpawnedBots.count(index) > 0) { + Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]); + if (bot && bot->IsBot()) + ((Bot*)bot)->Camp(); + } + + database.DeleteBot(client->GetCharacterID(), index); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot has been deleted."); + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to delete a bot"); + return; + } + } + else if (strncasecmp("follow", sep->arg[0], 6) == 0) { + if (sep->IsSet(1) && sep->IsNumber(1)) { + int32 index = atoi(sep->arg[1]); + + // Check if bot is currently spawned and if so camp it out + if (client->GetPlayer()->SpawnedBots.count(index) > 0) { + Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]); + if (bot && bot->IsBot()) + ((Bot*)bot)->SetFollowTarget(client->GetPlayer(), 5); + } + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to have a bot follow you"); + return; + } + } + else if (strncasecmp("stopfollow", sep->arg[0], 10) == 0) { + if (sep->IsSet(1) && sep->IsNumber(1)) { + int32 index = atoi(sep->arg[1]); + + // Check if bot is currently spawned and if so camp it out + if (client->GetPlayer()->SpawnedBots.count(index) > 0) { + Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]); + if (bot && bot->IsBot()) + ((Bot*)bot)->SetFollowTarget(nullptr); + } + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to stop a following bot"); + return; + } + } + else if (strncasecmp("summon", sep->arg[0], 6) == 0) { + if (sep->IsSet(1) && strncasecmp("group", sep->arg[1], 5) == 0) { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (gmi) { + Player* player = client->GetPlayer(); + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + + if(!member) + continue; + + if (member->IsBot() && ((Bot*)member)->GetOwner() == player) { + if(member->GetZone() && member->GetLocation() != player->GetLocation()) { + member->SetLocation(player->GetLocation()); + } + member->SetX(player->GetX()); + member->SetY(player->GetY()); + member->SetZ(player->GetZ()); + client->Message(CHANNEL_COLOR_YELLOW, "Summoning %s.", member->GetName()); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not in a group."); + return; + } + } + else { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) { + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + Player* player = client->GetPlayer(); + if (bot && bot->GetOwner() == player) { + bot->SetLocation(player->GetLocation()); + bot->SetX(player->GetX()); + bot->SetY(player->GetY()); + bot->SetZ(player->GetZ()); + client->Message(CHANNEL_COLOR_YELLOW, "Summoning %s.", bot->GetName()); + return; + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only summon your own bots."); + return; + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot."); + return; + } + } + } + else if (strncasecmp("test", sep->arg[0], 4) == 0) { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) { + ((Bot*)client->GetPlayer()->GetTarget())->MessageGroup("Test message"); + return; + } + } + } + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "BotCommands:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot create [race] [gender] [class] [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot customize - customize the appearance of the bot"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot list - list all the bots you have created with this character"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot spawn [id] - spawns a bot into the world, id obtained from /bot list"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot inv [give/list/remove] - manage bot equipment, for remove a slot must be provided"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot settings [helm/hood/cloak/taunt] [0/1] - Turn setting on (1) or off(0)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot camp - removes the bot from your group and despawns them"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot attack - commands your bots to attack your target"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot spells - lists bot spells, not fully implemented yet"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot maintank - sets targeted group member as the main tank for your bots"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot delete [id] - deletes the bot with the given id (obtained from /bot list)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help"); +} + +void Commands::Command_Bot_Create(Client* client, Seperator* sep) { + int8 race = BARBARIAN; + int8 gender = 0; + int8 advClass = GUARDIAN; + string name; + + if (sep) { + if (sep->IsSet(0) && sep->IsNumber(0)) + race = atoi(sep->arg[0]); + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "First param of \"/bot create\" needs to be a number"); + return; + } + + if (sep->IsSet(1) && sep->IsNumber(1)) + gender = atoi(sep->arg[1]); + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Second param of \"/bot create\" needs to be a number"); + return; + } + + if (sep->IsSet(2) && sep->IsNumber(2)) + advClass = atoi(sep->arg[2]); + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Third param of \"/bot create\" needs to be a number"); + return; + } + + if (sep->IsSet(3)) { + name = string(sep->arg[3]); + transform(name.begin(), name.begin() + 1, name.begin(), ::toupper); + transform(name.begin() + 1, name.end(), name.begin() + 1, ::tolower); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Fourth param (name) of \"/bot create\" is required"); + return; + } + + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /bot create [race ID] [Gender ID] [class ID] [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "All parameters are required. /bot help race or /bot help class for ID's."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Gender ID's: 0 = Female, 1 = Male"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /bot create 0 0 3 Botty"); + return; + } + + int8 result = database.CheckNameFilter(name.c_str()); + if (result == BADNAMELENGTH_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name length is invalid, must be greater then 3 characters and less then 16."); + return; + } + else if (result == NAMEINVALID_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is invalid, can only contain letters."); + return; + } + else if (result == NAMETAKEN_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is already taken, please choose another."); + return; + } + else if (result == NAMEFILTER_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name failed the filter check."); + return; + } + else if (result == UNKNOWNERROR_REPLY) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unknown error while checking the name."); + return; + } + + string race_string; + switch (race) { + case BARBARIAN: + race_string = "/barbarian/barbarian"; + break; + case DARK_ELF: + race_string = "/darkelf/darkelf"; + break; + case DWARF: + race_string = "/dwarf/dwarf"; + break; + case ERUDITE: + race_string = "/erudite/erudite"; + break; + case FROGLOK: + race_string = "/froglok/froglok"; + break; + case GNOME: + race_string = "/gnome/gnome"; + break; + case HALF_ELF: + race_string = "/halfelf/halfelf"; + break; + case HALFLING: + race_string = "/halfling/halfling"; + break; + case HIGH_ELF: + race_string = "/highelf/highelf"; + break; + case HUMAN: + race_string = "/human/human"; + break; + case IKSAR: + race_string = "/iksar/iksar"; + break; + case KERRA: + race_string = "/kerra/kerra"; + break; + case OGRE: + race_string = "/ogre/ogre"; + break; + case RATONGA: + race_string = "/ratonga/ratonga"; + break; + case TROLL: + race_string = "/troll/troll"; + break; + case WOOD_ELF: + race_string = "/woodelf/woodelf"; + break; + case FAE: + race_string = "/fae/fae_light"; + break; + case ARASAI: + race_string = "/fae/fae_dark"; + break; + case SARNAK: + gender == 1 ? race_string = "01/sarnak_male/sarnak" : race_string = "01/sarnak_female/sarnak"; + break; + case VAMPIRE: + race_string = "/vampire/vampire"; + break; + case AERAKYN: + race_string = "/aerakyn/aerakyn"; + break; + } + + if (race_string.length() > 0) { + string gender_string; + Bot* bot = 0; + + gender == 1 ? gender_string = "male" : gender_string = "female"; + + vector* id_list = database.GetAppearanceIDsLikeName("ec/pc" + race_string + "_" + gender_string); + + if (id_list) { + bot = new Bot(); + memset(&bot->appearance, 0, sizeof(bot->appearance)); + bot->appearance.pos.collision_radius = 32; + bot->secondary_command_list_id = 0; + bot->primary_command_list_id = 0; + bot->appearance.display_name = 1; + bot->appearance.show_level = 1; + bot->appearance.attackable = 1; + bot->appearance.show_command_icon = 1; + bot->appearance.targetable = 1; + bot->appearance.race = race; + bot->appearance.gender = gender; + bot->SetID(Spawn::NextID()); + bot->SetX(client->GetPlayer()->GetX()); + bot->SetY(client->GetPlayer()->GetY()); + bot->SetZ(client->GetPlayer()->GetZ()); + bot->SetHeading(client->GetPlayer()->GetHeading()); + bot->SetSpawnOrigX(bot->GetX()); + bot->SetSpawnOrigY(bot->GetY()); + bot->SetSpawnOrigZ(bot->GetZ()); + bot->SetSpawnOrigHeading(bot->GetHeading()); + bot->SetLocation(client->GetPlayer()->GetLocation()); + bot->SetInitialState(16512); + bot->SetModelType(id_list->at(0)); + bot->SetAdventureClass(advClass); + bot->SetLevel(client->GetPlayer()->GetLevel()); + bot->SetName(name.c_str()); + bot->SetDifficulty(6); + bot->size = 32; + if (bot->GetTotalHP() == 0) { + bot->SetTotalHP(25 * bot->GetLevel() + 1); + bot->SetTotalHPBaseInstance(bot->GetTotalHP()); + bot->SetHP(25 * bot->GetLevel() + 1); + } + if (bot->GetTotalPower() == 0) { + bot->SetTotalPower(25 * bot->GetLevel() + 1); + bot->SetTotalPowerBaseInstance(bot->GetTotalPower()); + bot->SetPower(25 * bot->GetLevel() + 1); + } + bot->SetOwner(client->GetPlayer()); + bot->GetNewSpells(); + client->GetCurrentZone()->AddSpawn(bot); + + int32 index; + int32 bot_id = database.CreateNewBot(client->GetCharacterID(), name, race, advClass, gender, id_list->at(0), index); + if (bot_id == 0) { + LogWrite(PLAYER__ERROR, 0, "Player", "Error saving bot to DB. Bot was not saved!"); + client->SimpleMessage(CHANNEL_ERROR, "Error saving bot to DB. Bot was not saved!"); + } + else { + bot->BotID = bot_id; + bot->BotIndex = index; + client->GetPlayer()->SpawnedBots[bot->BotIndex] = bot->GetID(); + + // Add Items + database.SetBotStartingItems(bot, advClass, race); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "Error finding the id list for your race, please verify the race id."); + } + + safe_delete(id_list); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error finding the race string, please verify the race id."); +} + +void Commands::Command_Bot_Customize(Client* client, Seperator* sep) { + Bot* bot = 0; + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) + bot = (Bot*)client->GetPlayer()->GetTarget(); + + + client->Message(CHANNEL_COLOR_RED, "This command is disabled and requires new implementation."); + + /*if (bot && bot->GetOwner() == client->GetPlayer()) { + PacketStruct* packet = configReader.getStruct("WS_OpenCharCust", client->GetVersion()); + if (packet) { + + AppearanceData* botApp = &bot->appearance; + CharFeatures* botFeatures = &bot->features; + + AppearanceData* playerApp = &client->GetPlayer()->appearance; + CharFeatures* playerFeatures = &client->GetPlayer()->features; + + memcpy(&client->GetPlayer()->SavedApp, playerApp, sizeof(AppearanceData)); + memcpy(&client->GetPlayer()->SavedFeatures, playerFeatures, sizeof(CharFeatures)); + + client->GetPlayer()->custNPC = true; + client->GetPlayer()->custNPCTarget = bot; + memcpy(playerApp, botApp, sizeof(AppearanceData)); + memcpy(playerFeatures, botFeatures, sizeof(CharFeatures)); + client->GetPlayer()->changed = true; + client->GetPlayer()->info_changed = true; + client->GetCurrentZone()->SendSpawnChanges(client->GetPlayer(), client); + packet->setDataByName("race_id", 255); + client->QueuePacket(packet->serialize()); + } + }*/ +} + +void Commands::Command_Bot_Spawn(Client* client, Seperator* sep) { + + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + int32 bot_id = atoi(sep->arg[0]); + if (client->GetPlayer()->SpawnedBots.count(bot_id) > 0) { + client->Message(CHANNEL_COLOR_YELLOW, "The bot with id %u is already spawned.", bot_id); + return; + } + + Bot* bot = new Bot(); + memset(&bot->appearance, 0, sizeof(bot->appearance)); + + if (database.LoadBot(client->GetCharacterID(), bot_id, bot)) { + bot->SetFollowTarget(client->GetPlayer(), 5); + bot->appearance.pos.collision_radius = 32; + bot->secondary_command_list_id = 0; + bot->primary_command_list_id = 0; + bot->appearance.display_name = 1; + bot->appearance.show_level = 1; + bot->appearance.attackable = 1; + bot->appearance.show_command_icon = 1; + bot->appearance.targetable = 1; + bot->SetID(Spawn::NextID()); + bot->SetX(client->GetPlayer()->GetX()); + bot->SetY(client->GetPlayer()->GetY()); + bot->SetZ(client->GetPlayer()->GetZ()); + bot->SetHeading(client->GetPlayer()->GetHeading()); + bot->SetSpawnOrigX(bot->GetX()); + bot->SetSpawnOrigY(bot->GetY()); + bot->SetSpawnOrigZ(bot->GetZ()); + bot->SetSpawnOrigHeading(bot->GetHeading()); + bot->SetLocation(client->GetPlayer()->GetLocation()); + bot->SetInitialState(16512); + bot->SetLevel(client->GetPlayer()->GetLevel()); + bot->SetDifficulty(6); + bot->size = 32; + if (bot->GetTotalHP() == 0) { + bot->SetTotalHP(25 * bot->GetLevel() + 1); + bot->SetHP(25 * bot->GetLevel() + 1); + } + if (bot->GetTotalPower() == 0) { + bot->SetTotalPower(25 * bot->GetLevel() + 1); + bot->SetPower(25 * bot->GetLevel() + 1); + } + bot->SetOwner(client->GetPlayer()); + bot->UpdateWeapons(); + bot->CalculateBonuses(); + bot->GetNewSpells(); + client->GetCurrentZone()->AddSpawn(bot); + + if (sep->IsSet(1) && sep->IsNumber(1) && atoi(sep->arg[1]) == 1) { + client->GetCurrentZone()->SendSpawn(bot, client); + + int8 result = world.GetGroupManager()->Invite(client->GetPlayer(), bot); + + if (result == 0) + client->Message(CHANNEL_COMMANDS, "You invite %s to group with you.", bot->GetName()); + else if (result == 1) + client->SimpleMessage(CHANNEL_COMMANDS, "That player is already in a group."); + else if (result == 2) + client->SimpleMessage(CHANNEL_COMMANDS, "That player has been invited to another group."); + else if (result == 3) + client->SimpleMessage(CHANNEL_COMMANDS, "Your group is already full."); + else if (result == 4) + client->SimpleMessage(CHANNEL_COMMANDS, "You have a pending invitation, cancel it first."); + else if (result == 5) + client->SimpleMessage(CHANNEL_COMMANDS, "You cannot invite yourself!"); + else if (result == 6) + client->SimpleMessage(CHANNEL_COMMANDS, "Could not locate the player."); + else + client->SimpleMessage(CHANNEL_COMMANDS, "Group invite failed, unknown error!"); + } + + client->GetPlayer()->SpawnedBots[bot_id] = bot->GetID(); + } + else { + client->Message(CHANNEL_ERROR, "Error spawning bot (%u)", bot_id); + } + } + else { + Command_Bot(client, sep); + } +} + +void Commands::Command_Bot_List(Client* client, Seperator* sep) { + string bot_list; + bot_list = database.GetBotList(client->GetCharacterID()); + if (!bot_list.empty()) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, bot_list.c_str()); +} + +void Commands::Command_Bot_Inv(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + if (strncasecmp("give", sep->arg[0], 4) == 0) { + if (client->GetPlayer()->trade) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already trading."); + return; + } + + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot"); + return; + } + + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->trade) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot is already in a trade..."); + return; + } + + if (bot->GetOwner() != client->GetPlayer()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only trade with your own bot."); + return; + } + + Trade* trade = new Trade(client->GetPlayer(), bot); + client->GetPlayer()->trade = trade; + bot->trade = trade; + } + else if (strncasecmp("list", sep->arg[0], 4) == 0) { + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot"); + return; + } + + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->GetOwner() != client->GetPlayer()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only see the inventory of your own bot."); + return; + } + + string item_list = "Bot Items:\nSlot\tName\n"; + for (int8 i = 0; i < NUM_SLOTS; i++) { + Item* item = bot->GetEquipmentList()->GetItem(i); + if (item) { + //\\aITEM %u %u:%s\\/a + item_list += to_string(i) + ":\t" + item->CreateItemLink(client->GetVersion(), true) + "\n"; + } + } + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, item_list.c_str()); + } + else if (strncasecmp("remove", sep->arg[0], 6) == 0) { + if (sep->IsSet(1) && sep->IsNumber(1)) { + int8 slot = atoi(sep->arg[1]); + if (slot >= NUM_SLOTS) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid slot"); + return; + } + + if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot."); + return; + } + + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->GetOwner() != client->GetPlayer()) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only remove items from your own bot."); + return; + } + + + if (client->GetPlayer()->trade) { + Trade* trade = client->GetPlayer()->trade; + if (trade->GetTradee(client->GetPlayer()) != bot) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already in a trade."); + return; + } + + bot->AddItemToTrade(slot); + } + else { + if (bot->trade) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Your bot is already trading..."); + return; + } + + Trade* trade = new Trade(client->GetPlayer(), bot); + client->GetPlayer()->trade = trade; + bot->trade = trade; + + bot->AddItemToTrade(slot); + } + } + } + else + Command_Bot(client, sep); + } + else + Command_Bot(client, sep); +} + +void Commands::Command_Bot_Settings(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0) && sep->IsSet(1) && sep->IsNumber(1)) { + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) { + Bot* bot = (Bot*)client->GetPlayer()->GetTarget(); + if (bot->GetOwner() == client->GetPlayer()) { + if (strncasecmp("helm", sep->arg[0], 4) == 0) { + bot->ShowHelm = (atoi(sep->arg[1]) == 1) ? true : false; + bot->info_changed = true; + bot->changed = true; + bot->GetZone()->SendSpawnChanges(bot); + } + else if (strncasecmp("cloak", sep->arg[0], 5) == 0) { + bot->ShowCloak = (atoi(sep->arg[1]) == 1) ? true : false; + bot->info_changed = true; + bot->changed = true; + bot->GetZone()->SendSpawnChanges(bot); + } + else if (strncasecmp("taunt", sep->arg[0], 5) == 0) { + bot->CanTaunt = (atoi(sep->arg[1]) == 1) ? true : false; + } + else if (strncasecmp("hood", sep->arg[0], 4) == 0) { + bot->SetHideHood((atoi(sep->arg[0]) == 1) ? 0 : 1); + } + else + Command_Bot(client, sep); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only change settings on your own bot."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot."); + } + else + Command_Bot(client, sep); +} + +void Commands::Command_Bot_Help(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + if (strncasecmp("race", sep->arg[0], 4) == 0) { + string title = "Race ID's"; + string details; + details += "0\tBarbarian\n"; + details += "1\tDark Elf\n"; + details += "2\tDwarf\n"; + details += "3\tErudite\n"; + details += "4\tFroglok\n"; + details += "5\tGnome\n"; + details += "6\tHalf Elf\n"; + details += "7\tHalfling\n"; + details += "8\tHigh Elf\n"; + details += "9\tHuman\n"; + details += "10\tIksar\n"; + details += "11\tKerra\n"; + details += "12\tOgre\n"; + details += "13\tRatonga\n"; + details += "14\tTroll\n"; + details += "15\tWood Elf\n"; + details += "16\tFae\n"; + details += "17\tArasai\n"; + details += "18\tSarnak\n"; + details += "19\tVampire\n"; + details += "20\tAerakyn\n"; + client->SendShowBook(client->GetPlayer(), title, 0, 1, details); + return; + } + else if (strncasecmp("class", sep->arg[0], 5) == 0) { + string title = "Class ID's"; + string details; + details += "0\tCOMMONER\n"; + details += "1\tFIGHTER\n"; + details += "2\tWARRIOR\n"; + details += "3\tGUARDIAN\n"; + details += "4\tBERSERKER\n"; + details += "5\tBRAWLER\n"; + details += "6\tMONK\n"; + details += "7\tBRUISER\n"; + details += "8\tCRUSADER\n"; + details += "9\tSHADOWKNIGHT\n"; + details += "10\tPALADIN\n"; + details += "11\tPRIEST\n"; + details += "12\tCLERIC\n"; + details += "13\tTEMPLAR\n"; + details += "14\tINQUISITOR\n"; + details += "15\tDRUID\n"; + details += "16\tWARDEN\n"; + details += "17\tFURY\n"; + details += "18\tSHAMAN\n"; + details += "19\tMYSTIC\n"; + details += "20\tDEFILER\n"; + + string details2 = "21\tMAGE\n"; + details2 += "22\tSORCERER\n"; + details2 += "23\tWIZARD\n"; + details2 += "24\tWARLOCK\n"; + details2 += "25\tENCHANTER\n"; + details2 += "26\tILLUSIONIST\n"; + details2 += "27\tCOERCER\n"; + details2 += "28\tSUMMONER\n"; + details2 += "29\tCONJUROR\n"; + details2 += "30\tNECROMANCER\n"; + details2 += "31\tSCOUT\n"; + details2 += "32\tROGUE\n"; + details2 += "33\tSWASHBUCKLER\n"; + details2 += "34\tBRIGAND\n"; + details2 += "35\tBARD\n"; + details2 += "36\tTROUBADOR\n"; + details2 += "37\tDIRGE\n"; + details2 += "38\tPREDATOR\n"; + details2 += "39\tRANGER\n"; + details2 += "40\tASSASSIN\n"; + + string details3 = "\\#FF0000Following aren't implemented yet.\\#000000\n"; + details3 += "41\tANIMALIST\n"; + details3 += "42\tBEASTLORD\n"; + details3 += "43\tSHAPER\n"; + details3 += "44\tCHANNELER\n"; + + client->SendShowBook(client->GetPlayer(), title, 0, 3, details, details2, details3); + return; + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot help is WIP."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help race - race id list"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help class - class id list"); + } +} diff --git a/source/WorldServer/Bots/BotDB.cpp b/source/WorldServer/Bots/BotDB.cpp new file mode 100644 index 0000000..86afc51 --- /dev/null +++ b/source/WorldServer/Bots/BotDB.cpp @@ -0,0 +1,467 @@ +#include "../WorldDatabase.h" +#include "../../common/Log.h" +#include "Bot.h" +#include "../classes.h" +#include "../races.h" + +extern Classes classes; +extern Races races; + +int32 WorldDatabase::CreateNewBot(int32 char_id, string name, int8 race, int8 advClass, int8 gender, int16 model_id, int32& index) { + DatabaseResult result; + index = 0; + + if (!database_new.Select(&result, "SELECT MAX(`bot_id`) FROM `bots` WHERE `char_id` = %u", char_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return 0; + } + + if (result.Next()) { + if (result.IsNull(0)) + index = 1; + else + index = result.GetInt32(0) + 1; + } + + if (!database_new.Query("INSERT INTO `bots` (`char_id`, `bot_id`, `name`, `race`, `class`, `gender`, `model_type`) VALUES (%u, %u, \"%s\", %u, %u, %u, %u)", char_id, index, name.c_str(), race, advClass, gender, model_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return 0; + } + int32 ret = database_new.LastInsertID(); + + LogWrite(PLAYER__DEBUG, 0, "Player", "New bot (%s) created for player (%u)", name.c_str(), char_id); + return ret; +} + +void WorldDatabase::SaveBotAppearance(Bot* bot) { + SaveBotColors(bot->BotID, "skin_color", bot->features.skin_color); + SaveBotColors(bot->BotID, "model_color", bot->features.model_color); + SaveBotColors(bot->BotID, "eye_color", bot->features.eye_color); + SaveBotColors(bot->BotID, "hair_color1", bot->features.hair_color1); + SaveBotColors(bot->BotID, "hair_color2", bot->features.hair_color2); + SaveBotColors(bot->BotID, "hair_highlight", bot->features.hair_highlight_color); + SaveBotColors(bot->BotID, "hair_type_color", bot->features.hair_type_color); + SaveBotColors(bot->BotID, "hair_type_highlight_color", bot->features.hair_type_highlight_color); + SaveBotColors(bot->BotID, "hair_face_color", bot->features.hair_face_color); + SaveBotColors(bot->BotID, "hair_face_highlight_color", bot->features.hair_face_highlight_color); + SaveBotColors(bot->BotID, "wing_color1", bot->features.wing_color1); + SaveBotColors(bot->BotID, "wing_color2", bot->features.wing_color2); + SaveBotColors(bot->BotID, "shirt_color", bot->features.shirt_color); + //SaveBotColors(bot->BotID, "unknown_chest_color", ); + SaveBotColors(bot->BotID, "pants_color", bot->features.pants_color); + //SaveBotColors(bot->BotID, "unknown_legs_color", ); + //SaveBotColors(bot->BotID, "unknown9", ); + SaveBotFloats(bot->BotID, "eye_type", bot->features.eye_type[0], bot->features.eye_type[1], bot->features.eye_type[2]); + SaveBotFloats(bot->BotID, "ear_type", bot->features.ear_type[0], bot->features.ear_type[1], bot->features.ear_type[2]); + SaveBotFloats(bot->BotID, "eye_brow_type", bot->features.eye_brow_type[0], bot->features.eye_brow_type[1], bot->features.eye_brow_type[2]); + SaveBotFloats(bot->BotID, "cheek_type", bot->features.cheek_type[0], bot->features.cheek_type[1], bot->features.cheek_type[2]); + SaveBotFloats(bot->BotID, "lip_type", bot->features.lip_type[0], bot->features.lip_type[1], bot->features.lip_type[2]); + SaveBotFloats(bot->BotID, "chin_type", bot->features.chin_type[0], bot->features.chin_type[1], bot->features.chin_type[2]); + SaveBotFloats(bot->BotID, "nose_type", bot->features.nose_type[0], bot->features.nose_type[1], bot->features.nose_type[2]); + + SaveBotFloats(bot->BotID, "body_size", bot->features.body_size, 0, 0); + SaveBotFloats(bot->BotID, "body_age", bot->features.body_age, 0, 0); + + SaveBotColors(bot->BotID, "soga_skin_color", bot->features.soga_skin_color); + SaveBotColors(bot->BotID, "soga_model_color", bot->features.soga_model_color); + SaveBotColors(bot->BotID, "soga_eye_color", bot->features.soga_eye_color); + SaveBotColors(bot->BotID, "soga_hair_color1", bot->features.soga_hair_color1); + SaveBotColors(bot->BotID, "soga_hair_color2", bot->features.soga_hair_color2); + SaveBotColors(bot->BotID, "soga_hair_highlight", bot->features.soga_hair_highlight_color); + SaveBotColors(bot->BotID, "soga_hair_type_color", bot->features.soga_hair_type_color); + SaveBotColors(bot->BotID, "soga_hair_type_highlight_color", bot->features.soga_hair_type_highlight_color); + SaveBotColors(bot->BotID, "soga_hair_face_color", bot->features.soga_hair_face_color); + SaveBotColors(bot->BotID, "soga_hair_face_highlight_color", bot->features.soga_hair_face_highlight_color); + SaveBotColors(bot->BotID, "soga_wing_color1", bot->features.wing_color1); + SaveBotColors(bot->BotID, "soga_wing_color2", bot->features.wing_color2); + SaveBotColors(bot->BotID, "soga_shirt_color", bot->features.shirt_color); + //SaveBotColors(bot->BotID, "soga_unknown_chest_color", ); + SaveBotColors(bot->BotID, "soga_pants_color", bot->features.pants_color); + //SaveBotColors(bot->BotID, "soga_unknown_legs_color", ); + //SaveBotColors(bot->BotID, "soga_unknown13", ); + SaveBotFloats(bot->BotID, "soga_eye_type", bot->features.soga_eye_type[0], bot->features.soga_eye_type[1], bot->features.soga_eye_type[2]); + SaveBotFloats(bot->BotID, "soga_ear_type", bot->features.soga_ear_type[0], bot->features.soga_ear_type[1], bot->features.soga_ear_type[2]); + SaveBotFloats(bot->BotID, "soga_eye_brow_type", bot->features.soga_eye_brow_type[0], bot->features.soga_eye_brow_type[1], bot->features.soga_eye_brow_type[2]); + SaveBotFloats(bot->BotID, "soga_cheek_type", bot->features.soga_cheek_type[0], bot->features.soga_cheek_type[1], bot->features.soga_cheek_type[2]); + SaveBotFloats(bot->BotID, "soga_lip_type", bot->features.soga_lip_type[0], bot->features.soga_lip_type[1], bot->features.soga_lip_type[2]); + SaveBotFloats(bot->BotID, "soga_chin_type", bot->features.soga_chin_type[0], bot->features.soga_chin_type[1], bot->features.soga_chin_type[2]); + SaveBotFloats(bot->BotID, "soga_nose_type", bot->features.soga_nose_type[0], bot->features.soga_nose_type[1], bot->features.soga_nose_type[2]); + + if (!database_new.Query("UPDATE `bots` SET `model_type` = %u, `hair_type` = %u, `face_type` = %u, `wing_type` = %u, `chest_type` = %u, `legs_type` = %u, `soga_model_type` = %u, `soga_hair_type` = %u, `soga_face_type` = %u WHERE `id` = %u", + bot->GetModelType(), bot->GetHairType(), bot->GetFacialHairType(), bot->GetWingType(), bot->GetChestType(), bot->GetLegsType(), bot->GetSogaModelType(), bot->GetSogaHairType(), bot->GetSogaFacialHairType(), bot->BotID)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } +} + +void WorldDatabase::SaveBotColors(int32 bot_id, const char* type, EQ2_Color color) { + if (!database_new.Query("INSERT INTO `bot_appearance` (`bot_id`, `type`, `red`, `green`, `blue`) VALUES (%i, '%s', %i, %i, %i) ON DUPLICATE KEY UPDATE `red` = %i, `blue` = %i, `green` = %i", bot_id, type, color.red, color.green, color.blue, color.red, color.blue, color.green)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } +} + +void WorldDatabase::SaveBotFloats(int32 bot_id, const char* type, float float1, float float2, float float3) { + if (!database_new.Query("INSERT INTO `bot_appearance` (`bot_id`, `type`, `red`, `green`, `blue`, `signed_value`) VALUES (%i, '%s', %i, %i, %i, 1) ON DUPLICATE KEY UPDATE `red` = %i, `blue` = %i, `green` = %i", bot_id, type, float1, float2, float3, float1, float2, float3)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } +} + +bool WorldDatabase::LoadBot(int32 char_id, int32 bot_index, Bot* bot) { + DatabaseResult result; + + if (!database_new.Select(&result, "SELECT * FROM bots WHERE `char_id` = %u AND `bot_id` = %u", char_id, bot_index)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return false; + } + + if (result.Next()) { + bot->BotID = result.GetInt32(0); + bot->BotIndex = result.GetInt32(2); + bot->SetName(result.GetString(3)); + bot->SetRace(result.GetInt8(4)); + bot->SetAdventureClass(result.GetInt8(5)); + bot->SetGender(result.GetInt8(6)); + bot->SetModelType(result.GetInt16(7)); + bot->SetHairType(result.GetInt16(8)); + bot->SetFacialHairType(result.GetInt16(9)); + bot->SetWingType(result.GetInt16(10)); + bot->SetChestType(result.GetInt16(11)); + bot->SetLegsType(result.GetInt16(12)); + bot->SetSogaModelType(result.GetInt16(13)); + bot->SetSogaHairType(result.GetInt16(14)); + bot->SetSogaFacialHairType(result.GetInt16(15)); + } + else + return false; + + LoadBotAppearance(bot); + LoadBotEquipment(bot); + return true; +} + +void WorldDatabase::LoadBotAppearance(Bot* bot) { + DatabaseResult result; + string type; + map appearance_types; + EQ2_Color color; + color.red = 0; + color.green = 0; + color.blue = 0; + + if (!database_new.Select(&result, "SELECT distinct `type` FROM bot_appearance WHERE length(`type`) > 0 AND `bot_id` = %u", bot->BotID)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + type = result.GetString(0); + appearance_types[type] = GetAppearanceType(type); + if (appearance_types[type] == 255) + LogWrite(WORLD__ERROR, 0, "Appearance", "Unknown appearance type '%s' in LoadBotAppearances.", type.c_str()); + } + + if (!database_new.Select(&result, "SELECT `type`, `signed_value`, `red`, `green`, `blue` FROM bot_appearance WHERE length(`type`) > 0 AND bot_id = %u", bot->BotID)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + type = result.GetString(0); + if (appearance_types[type] < APPEARANCE_SOGA_EBT) { + color.red = result.GetInt8(2); + color.green = result.GetInt8(3); + color.blue = result.GetInt8(4); + } + switch (appearance_types[type]) { + case APPEARANCE_SOGA_HFHC: { + bot->features.soga_hair_face_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HTHC: { + bot->features.soga_hair_type_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HFC: { + bot->features.soga_hair_face_color = color; + break; + } + case APPEARANCE_SOGA_HTC: { + bot->features.soga_hair_type_color = color; + break; + } + case APPEARANCE_SOGA_HH: { + bot->features.soga_hair_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HC1: { + bot->features.soga_hair_color1 = color; + break; + } + case APPEARANCE_SOGA_HC2: { + bot->features.soga_hair_color2 = color; + break; + } + case APPEARANCE_SOGA_SC: { + bot->features.soga_skin_color = color; + break; + } + case APPEARANCE_SOGA_EC: { + bot->features.soga_eye_color = color; + break; + } + case APPEARANCE_HTHC: { + bot->features.hair_type_highlight_color = color; + break; + } + case APPEARANCE_HFHC: { + bot->features.hair_face_highlight_color = color; + break; + } + case APPEARANCE_HTC: { + bot->features.hair_type_color = color; + break; + } + case APPEARANCE_HFC: { + bot->features.hair_face_color = color; + break; + } + case APPEARANCE_HH: { + bot->features.hair_highlight_color = color; + break; + } + case APPEARANCE_HC1: { + bot->features.hair_color1 = color; + break; + } + case APPEARANCE_HC2: { + bot->features.hair_color2 = color; + break; + } + case APPEARANCE_WC1: { + bot->features.wing_color1 = color; + break; + } + case APPEARANCE_WC2: { + bot->features.wing_color2 = color; + break; + } + case APPEARANCE_SC: { + bot->features.skin_color = color; + break; + } + case APPEARANCE_EC: { + bot->features.eye_color = color; + break; + } + case APPEARANCE_SOGA_EBT: { + for (int i = 0; i < 3; i++) + bot->features.soga_eye_brow_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_CHEEKT: { + for (int i = 0; i < 3; i++) + bot->features.soga_cheek_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_NT: { + for (int i = 0; i < 3; i++) + bot->features.soga_nose_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_CHINT: { + for (int i = 0; i < 3; i++) + bot->features.soga_chin_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_LT: { + for (int i = 0; i < 3; i++) + bot->features.soga_lip_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_EART: { + for (int i = 0; i < 3; i++) + bot->features.soga_ear_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SOGA_EYET: { + for (int i = 0; i < 3; i++) + bot->features.soga_eye_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_EBT: { + for (int i = 0; i < 3; i++) + bot->features.eye_brow_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_CHEEKT: { + for (int i = 0; i < 3; i++) + bot->features.cheek_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_NT: { + for (int i = 0; i < 3; i++) + bot->features.nose_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_CHINT: { + for (int i = 0; i < 3; i++) + bot->features.chin_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_EART: { + for (int i = 0; i < 3; i++) + bot->features.ear_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_EYET: { + for (int i = 0; i < 3; i++) + bot->features.eye_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_LT: { + for (int i = 0; i < 3; i++) + bot->features.lip_type[i] = result.GetSInt8(2 + i); + break; + } + case APPEARANCE_SHIRT: { + bot->features.shirt_color = color; + break; + } + case APPEARANCE_UCC: { + break; + } + case APPEARANCE_PANTS: { + bot->features.pants_color = color; + break; + } + case APPEARANCE_ULC: { + break; + } + case APPEARANCE_U9: { + break; + } + case APPEARANCE_BODY_SIZE: { + bot->features.body_size = color.red; + break; + } + case APPEARANCE_SOGA_WC1: { + break; + } + case APPEARANCE_SOGA_WC2: { + break; + } + case APPEARANCE_SOGA_SHIRT: { + break; + } + case APPEARANCE_SOGA_UCC: { + break; + } + case APPEARANCE_SOGA_PANTS: { + break; + } + case APPEARANCE_SOGA_ULC: { + break; + } + case APPEARANCE_SOGA_U13: { + break; + } + case APPEARANCE_BODY_AGE: { + bot->features.body_age = color.red; + break; + } + case APPEARANCE_MC:{ + bot->features.model_color = color; + break; + } + case APPEARANCE_SMC:{ + bot->features.soga_model_color = color; + break; + } + case APPEARANCE_SBS: { + bot->features.soga_body_size = color.red; + break; + } + case APPEARANCE_SBA: { + bot->features.soga_body_age = color.red; + break; + } + } + } +} + +void WorldDatabase::SaveBotItem(int32 bot_id, int32 item_id, int8 slot) { + if (!database_new.Query("INSERT INTO `bot_equipment` (`bot_id`, `slot`, `item_id`) VALUES (%u, %u, %u) ON DUPLICATE KEY UPDATE `item_id` = %u", bot_id, slot, item_id, item_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } +} + +void WorldDatabase::LoadBotEquipment(Bot* bot) { + DatabaseResult result; + + if (!database_new.Select(&result, "SELECT `slot`, `item_id` FROM `bot_equipment` WHERE `bot_id` = %u", bot->BotID)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + Item* master_item = 0; + Item* item = 0; + + while (result.Next()) { + int8 slot = result.GetInt8(0); + int32 item_id = result.GetInt32(1); + + master_item = master_item_list.GetItem(item_id); + if (master_item) { + item = new Item(master_item); + if (item) { + bot->GetEquipmentList()->AddItem(slot, item); + bot->SetEquipment(item, slot); + } + } + } +} + +string WorldDatabase::GetBotList(int32 char_id) { + DatabaseResult result; + string ret; + + if (!database_new.Select(&result, "SELECT `bot_id`, `name`, `race`, `class` FROM `bots` WHERE `char_id` = %u", char_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return ret; + } + + while (result.Next()) { + ret += to_string(result.GetInt32(0)) + ": "; + ret += result.GetString(1); + ret += " the "; + ret += races.GetRaceNameCase(result.GetInt8(2)); + ret += " "; + ret += classes.GetClassNameCase(result.GetInt8(3)) + "\n"; + } + + return ret; +} + +void WorldDatabase::DeleteBot(int32 char_id, int32 bot_index) { + if (!database_new.Query("DELETE FROM `bots` WHERE `char_id` = %u AND `bot_id` = %u", char_id, bot_index)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + } +} + +void WorldDatabase::SetBotStartingItems(Bot* bot, int8 class_id, int8 race_id) { + int32 bot_id = bot->BotID; + LogWrite(PLAYER__DEBUG, 0, "Bot", "Adding default items for race: %u, class: %u for bot_id: %u", race_id, class_id, bot_id); + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT item_id FROM starting_items WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) ORDER BY id", classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + bot->GiveItem(result.GetInt32(0)); + } +} \ No newline at end of file diff --git a/source/WorldServer/Chat/Chat.cpp b/source/WorldServer/Chat/Chat.cpp new file mode 100644 index 0000000..8c41c25 --- /dev/null +++ b/source/WorldServer/Chat/Chat.cpp @@ -0,0 +1,372 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "Chat.h" +#include "../../common/Log.h" +#include "../../common/ConfigReader.h" +#include "../../common/PacketStruct.h" + #include "../Rules/Rules.h" + extern RuleManager rule_manager; + +//devn00b +#ifdef DISCORD + #ifndef WIN32 + #include + #include "ChatChannel.h" + + extern ChatChannel channel; + #endif +#endif + + +extern ConfigReader configReader; + + + +Chat::Chat() { + m_channels.SetName("Chat::Channels"); +} + +Chat::~Chat() { + vector::iterator itr; + + m_channels.writelock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) + safe_delete(*itr); + m_channels.releasewritelock(__FUNCTION__, __LINE__); +} + +void Chat::AddChannel(ChatChannel *channel) { + m_channels.writelock(__FUNCTION__, __LINE__); + channels.push_back(channel); + m_channels.releasewritelock(__FUNCTION__, __LINE__); +} + +unsigned int Chat::GetNumChannels() { + unsigned int ret; + + m_channels.readlock(__FUNCTION__, __LINE__); + ret = (unsigned int)channels.size(); + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +EQ2Packet * Chat::GetWorldChannelList(Client *client) { + PacketStruct *packet_struct = configReader.getStruct("WS_AvailWorldChannels", client->GetVersion()); + Player *player = client->GetPlayer(); + vector channels_to_send; + vector::iterator itr; + ChatChannel *channel; + EQ2Packet *packet; + int32 i = 0; + bool add; + + if (packet_struct == NULL) { + LogWrite(CHAT__ERROR, 0, "Chat", "Could not find packet 'WS_AvailWorldChannels' for client %s on version %i\n", player->GetName(), client->GetVersion()); + return NULL; + } + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + channel = *itr; + + if (channel->GetType() == CHAT_CHANNEL_TYPE_WORLD) { + add = true; + + if (add && !channel->CanJoinChannelByLevel(player->GetLevel())) + add = false; + if (add && !channel->CanJoinChannelByRace(player->GetRace())) + add = false; + if (add && !channel->CanJoinChannelByClass(player->GetAdventureClass())) + add = false; + + if (add) + channels_to_send.push_back(channel); + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + packet_struct->setArrayLengthByName("num_channels", channels_to_send.size()); + for (itr = channels_to_send.begin(); itr != channels_to_send.end(); itr++, i++) { + packet_struct->setArrayDataByName("channel_name", (*itr)->GetName(), i); + packet_struct->setArrayDataByName("unknown", 0, i); + } + + packet = packet_struct->serialize(); + safe_delete(packet_struct); + + return packet; +} + +bool Chat::ChannelExists(const char *channel_name) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = true; + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::HasPassword(const char *channel_name) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->HasPassword(); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::PasswordMatches(const char *channel_name, const char *password) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->PasswordMatches(password); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::CreateChannel(const char *channel_name) { + return CreateChannel(channel_name, NULL); +} + +bool Chat::CreateChannel(const char *channel_name, const char *password) { + LogWrite(CHAT__DEBUG, 0, "Chat", "Channel %s being created", channel_name); + + ChatChannel *channel = new ChatChannel(); + channel->SetName(channel_name); + channel->SetType(CHAT_CHANNEL_TYPE_CUSTOM); + if (password != NULL) + channel->SetPassword(password); + + m_channels.writelock(__FUNCTION__, __LINE__); + channels.push_back(channel); + m_channels.releasewritelock(__FUNCTION__, __LINE__); + + return true; +} + +bool Chat::IsInChannel(Client *client, const char *channel_name) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->IsInChannel(client->GetCharacterID()); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::JoinChannel(Client *client, const char *channel_name) { + vector::iterator itr; + bool ret = false; + + LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is joining channel %s", client->GetPlayer()->GetName(), channel_name); + + m_channels.writelock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->JoinChannel(client); + break; + } + } + m_channels.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::LeaveChannel(Client *client, const char *channel_name) { + vector::iterator itr; + bool ret = false; + + LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is leaving channel %s", client->GetPlayer()->GetName(), channel_name); + + m_channels.writelock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->LeaveChannel(client); + + if ((*itr)->GetType() == CHAT_CHANNEL_TYPE_CUSTOM && (*itr)->GetNumClients() == 0) { + LogWrite(CHAT__DEBUG, 0, "Chat", "Custom channel %s has 0 clients left, deleting channel", channel_name); + safe_delete(*itr); + channels.erase(itr); + } + + break; + } + } + m_channels.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::LeaveAllChannels(Client *client) { + vector::iterator itr; + ChatChannel *channel; + bool erased; + + m_channels.writelock(__FUNCTION__, __LINE__); + itr = channels.begin(); + while (itr != channels.end()) { + channel = *itr; + erased = false; + + if (channel->IsInChannel(client->GetCharacterID())) { + LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is leaving channel %s", client->GetPlayer()->GetName(), channel->GetName()); + channel->LeaveChannel(client); + + if (channel->GetType() == CHAT_CHANNEL_TYPE_CUSTOM && channel->GetNumClients() == 0) { + LogWrite(CHAT__DEBUG, 0, "Chat", "Custom channel %s has 0 clients left, deleting channel", channel->GetName()); + safe_delete(*itr); + itr = channels.erase(itr); + erased = true; + } + } + + if (!erased) + itr++; + } + m_channels.releasewritelock(__FUNCTION__, __LINE__); + + return true; +} + +bool Chat::TellChannel(Client *client, const char *channel_name, const char *message, const char* name) { + vector::iterator itr; + bool ret = false; + bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool(); + const char* discordchan = rule_manager.GetGlobalRule(R_Discord, DiscordChannel)->GetString(); + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + if (client && name) + ret = (*itr)->TellChannelClient(client, message, name); + else + ret = (*itr)->TellChannel(client, message, name); + + if(enablediscord == true && client){ + + if (strcmp(channel_name, discordchan) != 0){ + m_channels.releasereadlock(__FUNCTION__, __LINE__); + return ret; + } +#ifdef DISCORD + if (client) { + std::string whofrom = client->GetPlayer()->GetName(); + std::string msg = string(message); + ret = PushDiscordMsg(msg.c_str(), whofrom.c_str()); + } +#endif + } + + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Chat::SendChannelUserList(Client *client, const char *channel_name) { + vector::iterator itr; + bool ret = false; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr)->SendChannelUserList(client); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +ChatChannel* Chat::GetChannel(const char *channel_name) { + vector::iterator itr; + ChatChannel* ret = 0; + + m_channels.readlock(__FUNCTION__, __LINE__); + for (itr = channels.begin(); itr != channels.end(); itr++) { + if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) { + ret = (*itr); + break; + } + } + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +#ifdef DISCORD +//this sends chat from EQ2EMu to Discord. Currently using webhooks. Makes things simpler code wise. +int Chat::PushDiscordMsg(const char* msg, const char* from) { + bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool(); + + if(enablediscord == false) { + LogWrite(INIT__INFO, 0,"Discord","Bot Disabled By Rule..."); + return 0; + } + + m_channels.readlock(__FUNCTION__, __LINE__); + const char* hook = rule_manager.GetGlobalRule(R_Discord, DiscordWebhookURL)->GetString(); + std::string servername = net.GetWorldName(); + char ourmsg[4096]; + + //form our message + sprintf(ourmsg,"[%s] [%s] Says: %s",from, servername.c_str(), msg); + + /* send a message with this webhook */ + dpp::cluster bot(""); + dpp::webhook wh(hook); + bot.execute_webhook(wh, dpp::message(ourmsg)); + m_channels.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} +#endif \ No newline at end of file diff --git a/source/WorldServer/Chat/Chat.h b/source/WorldServer/Chat/Chat.h new file mode 100644 index 0000000..fe6c4e2 --- /dev/null +++ b/source/WorldServer/Chat/Chat.h @@ -0,0 +1,119 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef CHAT_CHAT_H_ +#define CHAT_CHAT_H_ + +#include +#include "../../common/types.h" +#include "../../common/EQPacket.h" +#include "../../common/Mutex.h" +#include "../client.h" +#include "ChatChannel.h" + +#ifdef DISCORD + #ifndef WIN32 + #pragma once + #include + #endif +#endif + +using namespace std; +/* + +CREATING A CHANNEL + +-- OP_RemoteCmdMsg -- +3/14/2012 20:17:06 +192.168.1.198 -> 69.174.200.73 +0000: 00 09 05 9A 2A 0E 00 0F 00 63 75 73 74 6F 6D 20 ....*....custom +0010 70 61 73 73 77 6F 72 64 password + + +TALKING IN A CHANNEL + +[11:52.23] <@Xinux> -- OP_RemoteCmdMsg -- +[11:52.23] <@Xinux> 3/14/2012 20:17:25 +[11:52.23] <@Xinux> 192.168.1.198 -> 69.174.200.73 +[11:52.23] <@Xinux> 0000: 00 09 06 2D 2A 11 00 21 00 63 75 73 74 6F 6D 20 ...-*..!.custom +[11:52.23] <@Xinux> 0010: 20 74 68 69 73 20 69 73 20 6D 79 20 63 75 73 74 this is my cust +[11:52.23] <@Xinux> 0020 6F 6D 20 63 68 61 6E 6E 65 6C om channel + + +[08:37.46] <@Xinux_Work> 00 09 05 8B 00 3A 53 00 00 00 FF 3C 02 00 00 FF .....:S....<.... +[08:37.46] <@Xinux_Work> FF FF FF FF FF FF FF 06 00 4C 65 69 68 69 61 07 .........Leihia. +[08:37.46] <@Xinux_Work> 00 4B 6F 65 63 68 6F 68 00 02 00 00 00 00 01 00 .Koechoh........ +[08:37.46] <@Xinux_Work> 00 00 22 00 18 00 62 65 74 74 65 72 20 74 68 61 .."...better tha +[08:37.46] <@Xinux_Work> 6E 20 61 20 72 65 64 20 6F 6E 65 20 3A 50 09 00 n a red one :P.. +[08:37.46] <@Xinux_Work> 4C 65 76 65 6C 5F 31 2D 39 01 01 00 00 Level_1-9.... + +OTHERS LEAVING AND JOINING A CHANNEL + +-- OP_ClientCmdMsg::OP_EqChatChannelUpdateCmd -- +3/14/2012 20:17:06 +69.174.200.73 -> 192.168.1.198 +0000: 00 3A 18 00 00 00 FF 88 02 03 09 00 4C 65 76 65 .:..........Leve +0010 6C 5F 31 2D 39 07 00 53 68 61 77 6E 61 68 l_1-9..Shawnah + + +-- OP_ClientCmdMsg::OP_EqChatChannelUpdateCmd -- +3/14/2012 20:17:06 +69.174.200.73 -> 192.168.1.198 +0000: 00 3A 16 00 00 00 FF 88 02 03 07 00 41 75 63 74 .:..........Auct +0010 69 6F 6E 07 00 53 68 61 77 6E 61 68 ion..Shawnah + +OP_EqChatChannelUpdateCmd +unknown=0 unknown1=blank join +unknown=1 unknown1=blank leave +unknown=2 unknown2=player join/leave? +unknown=3 unknown2=player join/leave? +*/ + + +class Chat{ +public: + Chat(); + virtual ~Chat(); + + void AddChannel(ChatChannel *channel); + unsigned int GetNumChannels(); + + EQ2Packet * GetWorldChannelList(Client *client); + + bool ChannelExists(const char *channel_name); + bool HasPassword(const char *channel_name); + bool PasswordMatches(const char *channel_name, const char *password); + bool CreateChannel(const char *channel_name); + bool CreateChannel(const char *channel_name, const char *password); + bool IsInChannel(Client *client, const char *channel_name); + bool JoinChannel(Client *client, const char *channel_name); + bool LeaveChannel(Client *client, const char *channel_name); + bool LeaveAllChannels(Client *client); + bool TellChannel(Client *client, const char *channel_name, const char *message, const char* name = 0); + bool SendChannelUserList(Client *client, const char *channel_name); + //devn00b + int PushDiscordMsg(const char*, const char*); + ChatChannel* GetChannel(const char* channel_name); + +private: + Mutex m_channels; + vector channels; +}; + +#endif diff --git a/source/WorldServer/Chat/ChatChannel.cpp b/source/WorldServer/Chat/ChatChannel.cpp new file mode 100644 index 0000000..e0fbf9e --- /dev/null +++ b/source/WorldServer/Chat/ChatChannel.cpp @@ -0,0 +1,227 @@ +#include +#include "../../common/Log.h" +#include "../../common/ConfigReader.h" +#include "../../common/PacketStruct.h" +#include "../World.h" +#include "ChatChannel.h" + +extern ConfigReader configReader; +extern ZoneList zone_list; + +#define CHAT_CHANNEL_JOIN 0 +#define CHAT_CHANNEL_LEAVE 1 +#define CHAT_CHANNEL_OTHER_JOIN 2 +#define CHAT_CHANNEL_OTHER_LEAVE 3 + +ChatChannel::ChatChannel() { + memset(name, 0, sizeof(name)); + memset(password, 0, sizeof(password)); + type = CHAT_CHANNEL_TYPE_NONE; + level_restriction = 0; + races = 0; + classes = 0; +} + +ChatChannel::~ChatChannel() { +} + +bool ChatChannel::IsInChannel(int32 character_id) { + vector::iterator itr; + + for (itr = clients.begin(); itr != clients.end(); itr++) { + if (character_id == *itr) + return true; + } + + return false; +} + +bool ChatChannel::JoinChannel(Client *client) { + PacketStruct *packet_struct; + vector::iterator itr; + Client *to_client; + + //send the player join packet to the joining client + if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", client->GetVersion())) == NULL) { + LogWrite(CHAT__ERROR, 0, "Chat", "Could not find packet 'WS_ChatChannelUpdate' when client %s was trying to join channel %s", client->GetPlayer()->GetName(), name); + return false; + } + + packet_struct->setDataByName("action", CHAT_CHANNEL_JOIN); + packet_struct->setDataByName("channel_name", name); + client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + clients.push_back(client->GetCharacterID()); + + //loop through everyone else in the channel and send the "other" player join packet + for (itr = clients.begin(); itr != clients.end(); itr++) { + if (client->GetCharacterID() == *itr) + continue; + + if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL) + continue; + + if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", to_client->GetVersion())) == NULL) + continue; + + packet_struct->setDataByName("action", CHAT_CHANNEL_OTHER_JOIN); + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("player_name", client->GetPlayer()->GetName()); + to_client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + } + + return true; +} + +bool ChatChannel::LeaveChannel(Client *client) { + vector::iterator itr; + PacketStruct *packet_struct; + Client *to_client; + bool ret = false; + + for (itr = clients.begin(); itr != clients.end(); itr++) { + if (client->GetCharacterID() == *itr) { + clients.erase(itr); + ret = true; + break; + } + } + + if (ret) { + //send the packet to the leaving client + if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", client->GetVersion())) == NULL) + return false; + + packet_struct->setDataByName("action", CHAT_CHANNEL_LEAVE); + packet_struct->setDataByName("channel_name", name); + + client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + + //send the leave packet to all other clients in the channel + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL) + continue; + if (to_client == client) // don't need to send to self. + continue; + + if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", to_client->GetVersion())) == NULL) + continue; + + packet_struct->setDataByName("action", CHAT_CHANNEL_OTHER_LEAVE); + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("player_name", client->GetPlayer()->GetName()); + to_client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + } + } + + return ret; +} + +bool ChatChannel::TellChannel(Client *client, const char *message, const char* name2) { + vector::iterator itr; + PacketStruct *packet_struct; + Client *to_client; + + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL) + continue; + if ((packet_struct = configReader.getStruct("WS_HearChat", to_client->GetVersion())) == NULL) + continue; + + packet_struct->setDataByName("unknown", 0); + packet_struct->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet_struct->setDataByName("to_spawn_id", 0xFFFFFFFF); + + if (client != NULL){ + packet_struct->setDataByName("from", client->GetPlayer()->GetName()); + } else { + char name3[128]; + sprintf(name3,"[%s] from discord",name2); + packet_struct->setDataByName("from", name3); + } + + packet_struct->setDataByName("to", to_client->GetPlayer()->GetName()); + packet_struct->setDataByName("channel", to_client->GetMessageChannelColor(CHANNEL_CUSTOM_CHANNEL)); + + if(client != NULL){ + packet_struct->setDataByName("language", client->GetPlayer()->GetCurrentLanguage()); + }else{ + packet_struct->setDataByName("language", 0); + } + packet_struct->setDataByName("message", message); + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("show_bubble", 1); + + if(client != NULL){ + if (client->GetPlayer()->GetCurrentLanguage() == 0 || to_client->GetPlayer()->HasLanguage(client->GetPlayer()->GetCurrentLanguage())) { + packet_struct->setDataByName("understood", 1); + } + } else { + packet_struct->setDataByName("understood", 1); + } + + packet_struct->setDataByName("unknown4", 0); + + to_client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + } + + return true; +} + +bool ChatChannel::TellChannelClient(Client* to_client, const char* message, const char* name2) { + PacketStruct *packet_struct; + + if (string(name2).find('[') != string::npos) + return true; + + packet_struct = configReader.getStruct("WS_HearChat", to_client->GetVersion()); + if (packet_struct) { + packet_struct->setDataByName("unknown", 0); + packet_struct->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet_struct->setDataByName("to_spawn_id", 0xFFFFFFFF); + packet_struct->setDataByName("from", name2); + packet_struct->setDataByName("to", to_client->GetPlayer()->GetName()); + packet_struct->setDataByName("channel", to_client->GetMessageChannelColor(CHANNEL_CUSTOM_CHANNEL)); + packet_struct->setDataByName("language", 0); + packet_struct->setDataByName("message", message); + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("show_bubble", 1); + packet_struct->setDataByName("understood", 1); + packet_struct->setDataByName("unknown4", 0); + + to_client->QueuePacket(packet_struct->serialize()); + } + safe_delete(packet_struct); + + return true; +} + +bool ChatChannel::SendChannelUserList(Client *client) { + vector::iterator itr; + PacketStruct *packet_struct; + Client *to_client; + int8 i = 0; + + if ((packet_struct = configReader.getStruct("WS_WhoChannelQueryReply", client->GetVersion())) == NULL) + return false; + + packet_struct->setDataByName("channel_name", name); + packet_struct->setDataByName("unknown", 0); + packet_struct->setArrayLengthByName("num_players", clients.size()); + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((to_client = zone_list.GetClientByCharID(*itr)) != NULL) + packet_struct->setArrayDataByName("player_name", client->GetPlayer()->GetName(), i++); + else + packet_struct->setArrayDataByName("player_name", "", i++); + } + + client->QueuePacket(packet_struct->serialize()); + safe_delete(packet_struct); + + return true; +} + diff --git a/source/WorldServer/Chat/ChatChannel.h b/source/WorldServer/Chat/ChatChannel.h new file mode 100644 index 0000000..8dce202 --- /dev/null +++ b/source/WorldServer/Chat/ChatChannel.h @@ -0,0 +1,79 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifndef CHAT_CHATCHANNEL_H_ +#define CHAT_CHATCHANNEL_H_ + +#include "../../common/types.h" +#include "../client.h" +#include + +using namespace std; + +#define CHAT_CHANNEL_MAX_NAME 100 +#define CHAT_CHANNEL_MAX_PASSWORD 100 + +enum ChatChannelType { + CHAT_CHANNEL_TYPE_NONE = 0, + CHAT_CHANNEL_TYPE_WORLD, + CHAT_CHANNEL_TYPE_CUSTOM +}; + +class ChatChannel { +public: + ChatChannel(); + virtual ~ChatChannel(); + + void SetName(const char *name) {strncpy(this->name, name, CHAT_CHANNEL_MAX_NAME);} + void SetPassword(const char *password) {strncpy(this->password, password, CHAT_CHANNEL_MAX_PASSWORD);} + void SetType(ChatChannelType type) {this->type = type;} + void SetLevelRestriction(int16 level_restriction) {this->level_restriction = level_restriction;} + void SetRacesAllowed(int64 races) {this->races = races;} + void SetClassesAllowed(int64 classes) {this->classes = classes;} + + const char * GetName() {return name;} + ChatChannelType GetType() {return type;} + unsigned int GetNumClients() {return clients.size();} + + bool HasPassword() {return password[0] != '\0';} + bool PasswordMatches(const char *password) {return strncmp(this->password, password, CHAT_CHANNEL_MAX_PASSWORD) == 0;} + bool CanJoinChannelByLevel(int16 level) {return level >= level_restriction;} + bool CanJoinChannelByRace(int8 race_id) {return races == 0 || (1 << race_id) & races;} + bool CanJoinChannelByClass(int8 class_id) {return classes == 0 || (1 << class_id) & classes;} + + bool IsInChannel(int32 character_id); + bool JoinChannel(Client *client); + bool LeaveChannel(Client *client); + bool TellChannel(Client *client, const char *message, const char* name2 = 0); + bool TellChannelClient(Client* to_client, const char* message, const char* name2 = 0); + bool SendChannelUserList(Client *client); + + +private: + char name[CHAT_CHANNEL_MAX_NAME + 1]; + char password[CHAT_CHANNEL_MAX_PASSWORD + 1]; + ChatChannelType type; + vector clients; + int16 level_restriction; + int64 races; + int64 classes; +}; + +#endif diff --git a/source/WorldServer/Chat/ChatDB.cpp b/source/WorldServer/Chat/ChatDB.cpp new file mode 100644 index 0000000..297ecf7 --- /dev/null +++ b/source/WorldServer/Chat/ChatDB.cpp @@ -0,0 +1,46 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + + +#include "../../common/Log.h" +#include "Chat.h" +#include "../WorldDatabase.h" + +extern Chat chat; + +void WorldDatabase::LoadChannels() { + DatabaseResult result; + ChatChannel *channel; + + if (database_new.Select(&result, "SELECT `name`,`password`,`level_restriction`,`classes`,`races` FROM `channels`")) { + while (result.Next()) { + channel = new ChatChannel(); + channel->SetName(result.GetString(0)); + if (!result.IsNull(1)) + channel->SetPassword(result.GetString(1)); + channel->SetLevelRestriction(result.GetInt16(2)); + channel->SetClassesAllowed(result.GetInt64(3)); + channel->SetRacesAllowed(result.GetInt64(4)); + channel->SetType(CHAT_CHANNEL_TYPE_WORLD); + + chat.AddChannel(channel); + } + } +} \ No newline at end of file diff --git a/source/WorldServer/ClientPacketFunctions.cpp b/source/WorldServer/ClientPacketFunctions.cpp new file mode 100644 index 0000000..8f18143 --- /dev/null +++ b/source/WorldServer/ClientPacketFunctions.cpp @@ -0,0 +1,475 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "ClientPacketFunctions.h" +#include "WorldDatabase.h" +#include "../common/ConfigReader.h" +#include "Variables.h" +#include "World.h" +#include "classes.h" +#include "../common/Log.h" +#include "Traits/Traits.h" + +extern Classes classes; +extern Commands commands; +extern WorldDatabase database; +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern Variables variables; +extern World world; + +void ClientPacketFunctions::SendFinishedEntitiesList ( Client* client ){ + + EQ2Packet* finishedEntitiesApp = new EQ2Packet(OP_DoneSendingInitialEntitiesMsg, 0, 0); + client->QueuePacket(finishedEntitiesApp); + +} + +void ClientPacketFunctions::SendSkillSlotMappings(Client* client){ + EQ2Packet* app = client->GetPlayer()->GetSpellSlotMappingPacket(client->GetVersion()); + if(app) + client->QueuePacket(app); +} + +void ClientPacketFunctions::SendLoginDenied ( Client* client ){ + PacketStruct* packet = configReader.getStruct("LS_LoginResponse", 1); + if(packet){ + packet->setDataByName("reply_code", 1); + packet->setDataByName("unknown03", 0xFFFFFFFF); + packet->setDataByName("unknown04", 0xFFFFFFFF); + EQ2Packet* app = packet->serialize(); + client->QueuePacket(app); + safe_delete(packet); + } +} + +void ClientPacketFunctions::SendLoginAccepted ( Client* client ){ + LogWrite(PACKET__DEBUG, 0, "Packet", "Sending Login Accepted packet (LS_LoginResponse, %i)", client->GetVersion()); + PacketStruct* response_packet = configReader.getStruct("LS_LoginResponse", client->GetVersion()); + if(response_packet){ + response_packet->setDataByName("unknown02", 1); + response_packet->setDataByName("unknown05", -959971393); + response_packet->setDataByName("unknown08", 2); + response_packet->setDataByName("unknown09", 585); + response_packet->setDataByName("unknown10", 1597830); + response_packet->setDataByName("accountid", 3); //client->GetAccountID()); + EQ2Packet* outapp = response_packet->serialize(); + client->QueuePacket(outapp); + safe_delete(response_packet); + } +} + +void ClientPacketFunctions::SendCommandList ( Client* client ){ + EQ2Packet* app = commands.GetRemoteCommands()->serialize(client->GetVersion()); + client->QueuePacket(app); +} + +void ClientPacketFunctions::SendGameWorldTime ( Client* client ){ + PacketStruct* packet = world.GetWorldTime(client->GetVersion()); + if(packet){ + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + //opcode 501 was the selection display opcode +} + +void ClientPacketFunctions::SendCharacterData ( Client* client ){ + client->GetPlayer()->SetCharacterID(client->GetCharacterID()); + if(!client->IsReloadingZone()) { + EQ2Packet* outapp = client->GetPlayer()->serialize(client->GetPlayer(), client->GetVersion()); + //DumpPacket(outapp); + client->QueuePacket(outapp); + } +} + +void ClientPacketFunctions::SendCharacterSheet ( Client* client ){ + EQ2Packet* app = client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion()); + client->QueuePacket(app); + + if (client->GetVersion() >= 1188) { + EQ2Packet* app2 = client->GetPlayer()->GetPlayerInfo()->serializePet(client->GetVersion()); + if (app2) + client->QueuePacket(app2); + } +} + +void ClientPacketFunctions::SendSkillBook ( Client* client ){ + EQ2Packet* app = client->GetPlayer()->skill_list.GetSkillPacket(client->GetVersion()); + if(app) + client->QueuePacket(app); +} + +// Jabantiz: Attempt to get the char trait list working +void ClientPacketFunctions::SendTraitList(Client* client) { + if (client->GetVersion() >= 562) { + EQ2Packet* traitApp = master_trait_list.GetTraitListPacket(client); + //DumpPacket(traitApp); + if (traitApp) { + client->QueuePacket(traitApp); + } + } +} + +void ClientPacketFunctions::SendAbilities ( Client* client ){ + LogWrite(MISC__TODO, 1, "TODO", " Add SendAbilities functionality\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + // this is the featherfall ability data + // later this would loop through and send all abilities + /*uchar abilityData[] ={0x11,0x00,0x00,0x00,0xff,0x15,0x02,0x00,0x0b,0x00,0x46,0x65,0x61,0x74 + ,0x68,0x65,0x72,0x66,0x61,0x6c,0x6c}; + EQ2Packet* abilityApp = new EQ2Packet(OP_ClientCmdMsg, abilityData, sizeof(abilityData)); + client->QueuePacket(abilityApp);*/ +} + +void ClientPacketFunctions::SendCommandNamePacket ( Client* client ){ + LogWrite(MISC__TODO, 1, "TODO", " fix, this is actually quest/collection information\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + /* + PacketStruct* command_packet = configReader.getStruct("WS_CommandName", client->GetVersion()); + if(command_packet){ + command_packet->setDataByName("unknown03", 0x221bfb47); + + char* charName = { "BogusName" }; + command_packet->setMediumStringByName("character_name",charName); + EQ2Packet* outapp = command_packet->serialize(); + client->QueuePacket(outapp); + safe_delete(command_packet); + } + */ +} + +void ClientPacketFunctions::SendQuickBarInit ( Client* client ){ + int32 count = database.LoadPlayerSkillbar(client); + if(count == 0) { + LogWrite(PACKET__DEBUG, 0, "Packet", "No character quickbar found!"); + database.UpdateStartingSkillbar(client->GetCharacterID(), client->GetPlayer()->GetAdventureClass(), client->GetPlayer()->GetRace()); + database.LoadPlayerSkillbar(client); + } + EQ2Packet* quickbarApp = client->GetPlayer()->GetQuickbarPacket(client->GetVersion()); + if(quickbarApp) + client->QueuePacket(quickbarApp); +} + +void ClientPacketFunctions::SendCharacterMacros(Client* client) { + LogWrite(PACKET__DEBUG, 0, "Packet", "Sending Character Macro packet (WS_MacroInit, %i)", client->GetVersion()); + map >* macros = database.LoadCharacterMacros(client->GetCharacterID()); + if (macros) { + PacketStruct* macro_packet = configReader.getStruct("WS_MacroInit", client->GetVersion()); + if (macro_packet) { + map >::iterator itr; + macro_packet->setArrayLengthByName("macro_count", macros->size()); + int8 x = 0; + for (itr = macros->begin(); itr != macros->end(); itr++, x++) { + macro_packet->setArrayDataByName("number", itr->first, x); + if (itr->second.size() > 0) { + LogWrite(PACKET__DEBUG, 5, "Packet", "Loading Macro %i, name: %s", itr->first, itr->second[0]->name.c_str()); + macro_packet->setArrayDataByName("name", itr->second[0]->name.c_str(), x); + } + if (client->GetVersion() > 373) { + char tmp_details_count[25] = { 0 }; + sprintf(tmp_details_count, "macro_details_count_%i", x); + macro_packet->setArrayLengthByName(tmp_details_count, itr->second.size()); + for (int8 i = 0; i < itr->second.size(); i++) { + char tmp_command[15] = { 0 }; + sprintf(tmp_command, "command%i", x); + LogWrite(PACKET__DEBUG, 5, "Packet", "\tLoading Command %i: %s", itr->first, x, itr->second[i]->text.c_str()); + macro_packet->setArrayDataByName(tmp_command, itr->second[i]->text.c_str(), i); + if ( i > 0 ) // itr->second[0] used below, we will delete it later + safe_delete(itr->second[i]); // delete MacroData* + } + macro_packet->setArrayDataByName("unknown2", 2, x); + macro_packet->setArrayDataByName("unknown3", 0xFFFFFFFF, x); + } + else { + if (itr->second.size() > 0) + macro_packet->setArrayDataByName("command", itr->second[0]->text.c_str(), x); + } + macro_packet->setArrayDataByName("icon", itr->second[0]->icon, x); + client->GetPlayer()->macro_icons[itr->first] = itr->second[0]->icon; + + // remove itr->second[0] now that we are done with it + safe_delete(itr->second[0]); // delete MacroData* + } + EQ2Packet* packet = macro_packet->serialize(); + client->QueuePacket(packet); + safe_delete(macro_packet); + } + safe_delete(macros); + } +} + +void ClientPacketFunctions::SendMOTD ( Client* client ){ + + const char* motd = 0; + + // fetch MOTD from `variables` table + Variable* var = variables.FindVariable("motd"); + + if( var == NULL || strlen (var->GetValue()) == 0) { + LogWrite(WORLD__WARNING, 0, "World", "No MOTD set. Sending generic message..."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Message of the Day: Welcome to EQ2Emulator! Customize this message in the `variables`.`motd` data!"); + } + else { + motd = var->GetValue(); + LogWrite(WORLD__DEBUG, 0, "World", "Send MOTD..."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, motd); + } + +} + +void ClientPacketFunctions::SendUpdateSpellBook ( Client* client ){ + if(client->IsReadyForSpawns()){ + EQ2Packet* app = client->GetPlayer()->GetSpellBookUpdatePacket(client->GetVersion()); + if(app) + client->QueuePacket(app); + } + client->GetPlayer()->UnlockAllSpells(true); +} + +void ClientPacketFunctions::SendServerControlFlagsClassic(Client* client, int32 param, int32 value) { + PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", client->GetVersion()); + if(packet) { + packet->setDataByName("parameter", param); + packet->setDataByName("value", value); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} +void ClientPacketFunctions::SendServerControlFlags(Client* client, int8 param, int8 param_val, int8 value) { + PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", client->GetVersion()); + if(packet) { + if (param == 1) + packet->setDataByName("parameter1", param_val); + else if (param == 2) + packet->setDataByName("parameter2", param_val); + else if (param == 3) + packet->setDataByName("parameter3", param_val); + else if (param == 4) + packet->setDataByName("parameter4", param_val); + else if (param == 5) + packet->setDataByName("parameter5", param_val); + else { + safe_delete(packet); + return; + } + packet->setDataByName("value", value); + client->QueuePacket(packet->serialize()); + /* + Some other values for this packet + first param: + 01 flymode + 02 collisons off + 04 unknown + 08 heading movement only + 16 forward/reverse movement only + 32 low gravity + 64 sit + + second + 2 crouch + + + third: + 04 float when trying to jump, no movement + 08 jump high, no movement + 128 walk underwater + + fourth: + 01 moon jump underwater + 04 fear + 16 moon jumps + 32 safe fall (float to ground) + 64 cant move + + fifth: + 01 die + 08 hover (fae) + 32 flymode2? + + */ + } + safe_delete(packet); +} + +void ClientPacketFunctions::SendInstanceList(Client* client) { + if (client->GetPlayer()->GetCharacterInstances()->GetInstanceCount() > 0) { + PacketStruct* packet = configReader.getStruct("WS_InstanceCreated", client->GetVersion()); + if (packet) { + vector persist = client->GetPlayer()->GetCharacterInstances()->GetPersistentInstances(); + vector lockout = client->GetPlayer()->GetCharacterInstances()->GetLockoutInstances(); + + packet->setArrayLengthByName("num_instances", lockout.size()); + for (int32 i = 0; i < lockout.size(); i++) { + InstanceData data = lockout.at(i); + + packet->setArrayDataByName("unknown1", data.db_id, i); // unique id per player + packet->setArrayDataByName("instance_zone_name", data.zone_name.c_str(), i); + packet->setArrayDataByName("unknown2", 0x0B, i); // Always set to 0x0B on live packets + packet->setArrayDataByName("success_last", data.last_success_timestamp, i); + packet->setArrayDataByName("last_failure", data.last_failure_timestamp, i); + packet->setArrayDataByName("failure", data.failure_lockout_time, i); + packet->setArrayDataByName("success", data.success_lockout_time, i); + } + + packet->setArrayLengthByName("num_persistent", persist.size()); + for (int32 i = 0; i < persist.size(); i++) { + InstanceData data = persist.at(i); + + packet->setArrayDataByName("unknown1a", data.db_id, i); // unique id per player + packet->setArrayDataByName("persistent_zone_name", data.zone_name.c_str(), i); + packet->setArrayDataByName("unknown2a", 0x0B, i); // set to 0x0B in all live packets + packet->setArrayDataByName("persist_success_timestamp", data.last_success_timestamp, i); + packet->setArrayDataByName("persist_failure_timestamp", data.last_failure_timestamp, i); + + // Check min duration (last success + failure) + //if (Timer::GetUnixTimeStamp() < data.last_success_timestamp + data.failure_lockout_time*/) + //packet->setArrayDataByName("unknown3b", 1, i); + packet->setArrayDataByName("unknown3b", 1, i); + + packet->setArrayDataByName("minimum_duration", data.failure_lockout_time, i); + packet->setArrayDataByName("maximum_duration", data.success_lockout_time, i); + + packet->setArrayDataByName("unknown4a", 1800, i); // All live logs have 0x0708 + } + + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } +} + +void ClientPacketFunctions::SendMaintainedExamineUpdate(Client* client, int8 slot_pos, int32 update_value, int8 update_type){ + if (!client) + return; + + PacketStruct* packet = configReader.getStruct("WS_UpdateMaintainedExamine", client->GetVersion()); + + if (packet){ + packet->setSubstructDataByName("info_header", "show_name", 1); + packet->setSubstructDataByName("info_header", "packettype", 19710); + packet->setSubstructDataByName("info_header", "packetsubtype", 5); + packet->setDataByName("time_stamp", Timer::GetCurrentTime2()); + packet->setDataByName("slot_pos", slot_pos); + packet->setDataByName("update_value", update_value > 0 ? update_value : 0xFFFFFFFF); + packet->setDataByName("update_type", update_type); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void ClientPacketFunctions::SendZoneChange(Client* client, char* zone_ip, int16 zone_port, int32 key) { + if (!client) + return; + + PacketStruct* packet = configReader.getStruct("WS_ZoneChangeMsg", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("key", key); + packet->setDataByName("ip_address", zone_ip); + packet->setDataByName("port", zone_port); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} + +void ClientPacketFunctions::SendStateCommand(Client* client, int32 spawn_id, int32 state) { + if (!client || !spawn_id) { + return; + } + + PacketStruct* packet = configReader.getStruct("WS_StateCmd", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", spawn_id); + packet->setDataByName("state", state); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} + +void ClientPacketFunctions::SendFlyMode(Client* client, int8 flymode, bool updateCharProperty) +{ + if (updateCharProperty) + database.insertCharacterProperty(client, CHAR_PROPERTY_FLYMODE, (char*)std::to_string(flymode).c_str()); + if(client->GetVersion() <= 561) { + if(flymode) { + // old flymode + SendServerControlFlagsClassic(client, flymode, 1); + if(flymode == 1) { + // disable noclip + SendServerControlFlagsClassic(client, 2, 0); + } + } + else { + // disable flymode and noclip + SendServerControlFlagsClassic(client, 2, 0); + SendServerControlFlagsClassic(client, 1, 0); + } + } + else { + if(flymode == 2) { + // new flymode + noclip + SendServerControlFlags(client, 5, 32, 1); + SendServerControlFlags(client, 1, 2, 1); + } + else if(flymode == 1) { + // new flymode + SendServerControlFlags(client, 5, 32, 1); + SendServerControlFlags(client, 1, 2, 0); + } + else { + // disable flymode and noclip + SendServerControlFlags(client, 5, 32, 0); + SendServerControlFlags(client, 1, 2, 0); + } + } + + client->Message(CHANNEL_STATUS, "Flymode %s, No Clip %s", flymode > 0 ? "on" : "off", flymode > 1 ? "on" : "off"); + /* + CLASSIC/DOF ONLY HAS THE FIRST SET OF FLAGS + Some other values for this packet + first param: + 01 flymode + 02 collisons off + 04 unknown + 08 forward movement + 16 heading movement + 32 low gravity + 64 sit + + EVERYTHING BELOW NOT SUPPORTED BY CLASSIC/DOF + second + 2 crouch + + + third: + 04 float when trying to jump, no movement + 08 jump high, no movement + + fourth: + 04 autorun (fear?) + 16 moon jumps + 32 safe fall (float to ground) + 64 cant move + + fifth: + 01 die + 08 hover (fae) + 32 flymode2? + + */ +} \ No newline at end of file diff --git a/source/WorldServer/ClientPacketFunctions.h b/source/WorldServer/ClientPacketFunctions.h new file mode 100644 index 0000000..3c4578d --- /dev/null +++ b/source/WorldServer/ClientPacketFunctions.h @@ -0,0 +1,92 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#pragma once +#include "client.h" + +struct HouseZone; +struct PlayerHouse; +struct HeroicOP; + +class ClientPacketFunctions +{ +public: + static void SendFinishedEntitiesList ( Client* client ); + + static void SendLoginDenied ( Client* client ); + static void SendLoginAccepted ( Client* client ); + + static void SendCommandList ( Client* client ); + + static void SendGameWorldTime ( Client* client ); + + static void SendCharacterData ( Client* client ); + static void SendCharacterSheet ( Client* client ); + static void SendSkillBook ( Client* client ); + static void SendTraitList ( Client* client ); + static void SendAbilities ( Client* client ); + + static void SendCommandNamePacket ( Client* client ); + + static void SendQuickBarInit ( Client* client ); + + static void SendMOTD ( Client* client ); + + static void SendCharacterMacros(Client* client); + + static void SendUpdateSpellBook ( Client* client ); + + static void SendSkillSlotMappings(Client* client); + + static void SendRestartZoneMsg(Client* client); + + static void SendServerControlFlags(Client* client, int8 param, int8 param_val, int8 value); + + static void SendServerControlFlagsClassic(Client* client, int32 param, int32 value); + + static void SendInstanceList(Client* client); + + static void SendZoneChange(Client* client, char* zone_ip, int16 zone_port, int32 key); + + static void SendStateCommand(Client* client, int32 spawn_id, int32 state); + + static void SendFlyMode(Client* client, int8 flymode, bool updateCharProperty=true); + + /* Tradeskills (/Tradeskills/TradeskillsPackets.cpp) */ + static void SendCreateFromRecipe(Client* client, int32 recipeID); + static void SendItemCreationUI(Client* client, Recipe* recipe); + static void StopCrafting(Client* client); + static void CounterReaction(Client* client, bool countered); + + static void SendAchievementList(Client* client); + + /* Housing (/Housing/HousingPackets.cpp) */ + static void SendHousePurchase(Client* client, HouseZone* hz, int32 spawnID); + static void SendHousingList(Client* client); + static void SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID); + static void SendHouseVisitWindow(Client* client, vector houses); + static void SendLocalizedTextMessage(Client* client); + + /* Heroic OP's (/HeroicOp/HeroicOpPackets.cpp) */ + static void SendHeroicOPUpdate(Client* client, HeroicOP* ho); + + //UI updates for trigger count and damage remaining on maintained spells + static void SendMaintainedExamineUpdate(Client* client, int8 slot_pos, int32 update_value, int8 update_type); +}; + diff --git a/source/WorldServer/Collections/Collections.cpp b/source/WorldServer/Collections/Collections.cpp new file mode 100644 index 0000000..041d3a6 --- /dev/null +++ b/source/WorldServer/Collections/Collections.cpp @@ -0,0 +1,317 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ +#include "Collections.h" + +#include "../../common/Log.h" +#include + +extern MasterCollectionList master_collection_list; + +Collection::Collection() { + id = 0; + memset(name, 0, sizeof(name)); + memset(category, 0, sizeof(category)); + level = 0; + reward_coin = 0; + reward_xp = 0; + completed = false; + save_needed = false; +} + +Collection::Collection(Collection *in) { + vector *collection_items_in; + vector *reward_items_in; + vector::iterator itr; + vector::iterator itr2; + struct CollectionItem *collection_item; + struct CollectionRewardItem *reward_item; + + assert(in); + + id = in->GetID(); + strncpy(name, in->GetName(), sizeof(name)); + strncpy(category, in->GetCategory(), sizeof(category)); + level = in->GetLevel(); + reward_coin = in->GetRewardCoin(); + reward_xp = in->GetRewardXP(); + completed = in->GetCompleted(); + save_needed = in->GetSaveNeeded(); + + collection_items_in = in->GetCollectionItems(); + for (itr = collection_items_in->begin(); itr != collection_items_in->end(); itr++) { + collection_item = new struct CollectionItem; + collection_item->item = (*itr)->item; + collection_item->index = (*itr)->index; + collection_item->found = (*itr)->found; + collection_items.push_back(collection_item); + } + + reward_items_in = in->GetRewardItems(); + for (itr2 = reward_items_in->begin(); itr2 != reward_items_in->end(); itr2++) { + reward_item = new struct CollectionRewardItem; + reward_item->item = (*itr2)->item; + reward_item->quantity = (*itr2)->quantity; + reward_items.push_back(reward_item); + } + + reward_items_in = in->GetSelectableRewardItems(); + for (itr2 = reward_items_in->begin(); itr2 != reward_items_in->end(); itr2++) { + reward_item = new struct CollectionRewardItem; + reward_item->item = (*itr2)->item; + reward_item->quantity = (*itr2)->quantity; + selectable_reward_items.push_back(reward_item); + } +} + +Collection::~Collection() { + vector::iterator itr; + vector::iterator itr2; + + for (itr = collection_items.begin(); itr != collection_items.end(); itr++) + safe_delete(*itr); + for (itr2 = reward_items.begin(); itr2 != reward_items.end(); itr2++) + safe_delete(*itr2); + for (itr2 = selectable_reward_items.begin(); itr2 != selectable_reward_items.end(); itr2++) + safe_delete(*itr2); +} + +void Collection::AddCollectionItem(struct CollectionItem *collection_item) { + assert(collection_item); + + collection_items.push_back(collection_item); +} + +void Collection::AddRewardItem(struct CollectionRewardItem *reward_item) { + assert(reward_item); + + reward_items.push_back(reward_item); +} + +void Collection::AddSelectableRewardItem(struct CollectionRewardItem *reward_item) { + assert(reward_item); + + selectable_reward_items.push_back(reward_item); +} + +bool Collection::NeedsItem(Item *item) { + vector::iterator itr; + struct CollectionItem *collection_item; + + assert(item); + + if (completed) + return false; + + for (itr = collection_items.begin(); itr != collection_items.end(); itr++) { + collection_item = *itr; + if (collection_item->item == item->details.item_id) { + if (collection_item->found) + return false; + else + return true; + } + } + + /* item is not required by this collection at all */ + return false; +} + +struct CollectionItem * Collection::GetCollectionItemByItemID(int32 item_id) { + vector::iterator itr; + struct CollectionItem *collection_item; + + for (itr = collection_items.begin(); itr != collection_items.end(); itr++) { + collection_item = *itr; + if (collection_item->item == item_id) + return collection_item; + } + + return 0; +} + +bool Collection::GetIsReadyToTurnIn() { + vector::iterator itr; + + if (completed) + return false; + + for (itr = collection_items.begin(); itr != collection_items.end(); itr++) { + if (!(*itr)->found) + return false; + } + + return true; +} + +MasterCollectionList::MasterCollectionList() { + mutex_collections.SetName("MasterCollectionList::collections"); +} + +MasterCollectionList::~MasterCollectionList() { + ClearCollections(); +} + +bool MasterCollectionList::AddCollection(Collection *collection) { + bool ret = false; + + assert(collection); + + mutex_collections.writelock(__FUNCTION__, __LINE__); + if (collections.count(collection->GetID()) == 0) { + collections[collection->GetID()] = collection; + ret = true; + } + mutex_collections.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +Collection * MasterCollectionList::GetCollection(int32 collection_id) { + Collection *collection = 0; + + mutex_collections.readlock(__FUNCTION__, __LINE__); + if (collections.count(collection_id) > 0) + collection = collections[collection_id]; + mutex_collections.releasereadlock(__FUNCTION__, __LINE__); + + return collection; +} + +void MasterCollectionList::ClearCollections() { + map::iterator itr; + + mutex_collections.writelock(__FUNCTION__, __LINE__); + for (itr = collections.begin(); itr != collections.end(); itr++) + safe_delete(itr->second); + collections.clear(); + mutex_collections.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 MasterCollectionList::Size() { + int32 size; + + mutex_collections.readlock(__FUNCTION__, __LINE__); + size = collections.size(); + mutex_collections.releasereadlock(__FUNCTION__, __LINE__); + + return size; +} + +bool MasterCollectionList::NeedsItem(Item *item) { + map::iterator itr; + bool ret = false; + + assert(item); + + mutex_collections.readlock(__FUNCTION__, __LINE__); + for (itr = collections.begin(); itr != collections.end(); itr++) { + if (itr->second->NeedsItem(item)) { + ret = true; + break; + } + } + mutex_collections.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerCollectionList::PlayerCollectionList() { +} + +PlayerCollectionList::~PlayerCollectionList() { + ClearCollections(); +} + +bool PlayerCollectionList::AddCollection(Collection *collection) { + assert(collection); + + if (collections.count(collection->GetID()) == 0) { + collections[collection->GetID()] = collection; + return true; + } + + return false; +} + +Collection * PlayerCollectionList::GetCollection(int32 collection_id) { + if (collections.count(collection_id) > 0) + return collections[collection_id]; + + return 0; +} + +void PlayerCollectionList::ClearCollections() { + map::iterator itr; + + for (itr = collections.begin(); itr != collections.end(); itr++) + safe_delete(itr->second); + collections.clear(); +} + +int32 PlayerCollectionList::Size() { + return collections.size(); +} + +bool PlayerCollectionList::NeedsItem(Item *item) { + map *master_collections; + map::iterator itr; + Collection *collection; + Mutex *master_mutex; + bool ret = false; + + assert(item); + + for (itr = collections.begin(); itr != collections.end(); itr++) { + if (itr->second->NeedsItem(item)) { + ret = true; + break; + } + } + + /* if the player doesnt have a collection that needs the item, check the master collection list to see if there's a collection + * in there that needs the item that the player does not have yet */ + if (!ret) { + master_mutex = master_collection_list.GetMutex(); + master_collections = master_collection_list.GetCollections(); + + master_mutex->readlock(__FUNCTION__, __LINE__); + for (itr = master_collections->begin(); itr != master_collections->end(); itr++) { + collection = itr->second; + if (collection->NeedsItem(item) && !GetCollection(collection->GetID())) { + ret = true; + break; + } + } + master_mutex->releasereadlock(__FUNCTION__, __LINE__); + } + + return ret; +} + +bool PlayerCollectionList::HasCollectionsToHandIn() { + map::iterator itr; + + for (itr = collections.begin(); itr != collections.end(); itr++) { + if (itr->second->GetIsReadyToTurnIn()) + return true; + } + + return false; +} \ No newline at end of file diff --git a/source/WorldServer/Collections/Collections.h b/source/WorldServer/Collections/Collections.h new file mode 100644 index 0000000..eb39873 --- /dev/null +++ b/source/WorldServer/Collections/Collections.h @@ -0,0 +1,107 @@ +#ifndef COLLECTIONS_H_ +#define COLLECTIONS_H_ + +#include "../../common/types.h" +#include "../../common/Mutex.h" +#include "../Items/Items.h" +#include +#include + +using namespace std; + +struct CollectionItem { + int32 item; + int8 index; + int8 found; +}; + +struct CollectionRewardItem { + Item *item; + int8 quantity; +}; + +class Collection { +public: + Collection(); + Collection(Collection *in); + virtual ~Collection(); + + void SetID(int32 id) {this->id = id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + void SetCategory(const char *category) {strncpy(this->category, category, sizeof(this->category));} + void SetLevel(int8 level) {this->level = level;} + void SetCompleted(bool completed) {this->completed = completed;} + void SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;} + void AddCollectionItem(struct CollectionItem *collection_item); + void AddRewardItem(struct CollectionRewardItem *reward_item); + void AddSelectableRewardItem(struct CollectionRewardItem *reward_item); + void SetRewardCoin(int64 reward_coin) {this->reward_coin = reward_coin;} + void SetRewardXP(int64 reward_xp) {this->reward_xp = reward_xp;} + bool NeedsItem(Item *item); + struct CollectionItem * GetCollectionItemByItemID(int32 item_id); + + int32 GetID() {return id;} + const char * GetName() {return name;} + const char * GetCategory() {return category;} + int8 GetLevel() {return level;} + bool GetIsReadyToTurnIn(); + bool GetCompleted() {return completed;} + bool GetSaveNeeded() {return save_needed;} + vector * GetCollectionItems() {return &collection_items;} + vector * GetRewardItems() {return &reward_items;} + vector * GetSelectableRewardItems() {return &selectable_reward_items;} + int64 GetRewardCoin() {return reward_coin;} + int64 GetRewardXP() {return reward_xp;} + +private: + int32 id; + char name[512]; + char category[512]; + int8 level; + int64 reward_coin; + int64 reward_xp; + bool completed; + bool save_needed; + vector collection_items; + vector reward_items; + vector selectable_reward_items; +}; + +class MasterCollectionList { +public: + MasterCollectionList(); + virtual ~MasterCollectionList(); + + bool AddCollection(Collection *collection); + Collection * GetCollection(int32 collection_id); + void ClearCollections(); + int32 Size(); + bool NeedsItem(Item *item); + + Mutex * GetMutex() {return &mutex_collections;} + map * GetCollections() {return &collections;} + +private: + Mutex mutex_collections; + map collections; +}; + +class PlayerCollectionList { +public: + PlayerCollectionList(); + virtual ~PlayerCollectionList(); + + bool AddCollection(Collection *collection); + Collection * GetCollection(int32 collection_id); + void ClearCollections(); + int32 Size(); + bool NeedsItem(Item *item); + bool HasCollectionsToHandIn(); + + map * GetCollections() {return &collections;} + +private: + map collections; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Collections/CollectionsDB.cpp b/source/WorldServer/Collections/CollectionsDB.cpp new file mode 100644 index 0000000..3e39cbb --- /dev/null +++ b/source/WorldServer/Collections/CollectionsDB.cpp @@ -0,0 +1,295 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Collections.h" + +extern MasterCollectionList master_collection_list; + + +void WorldDatabase::LoadCollections() +{ + Collection *collection; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 cItems_total = 0; + int32 cItems_rewards = 0; + + res = query.RunQuery2(Q_SELECT, "SELECT `id`,`collection_name`,`collection_category`,`level`\n" + "FROM `collections`"); + if (res) + { + while ((row = mysql_fetch_row(res))) + { + collection = new Collection(); + collection->SetID(atoul(row[0])); + collection->SetName(row[1]); + collection->SetCategory(row[2]); + collection->SetLevel(atoi(row[3])); + + LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection: '%s' (%u)", collection->GetName(),collection->GetID()); + + if (!master_collection_list.AddCollection(collection)) + { + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection '%s' - duplicate ID: %u", collection->GetName(),collection->GetID()); + safe_delete(collection); + continue; + } + + cItems_total += LoadCollectionItems(collection); + cItems_rewards += LoadCollectionRewards(collection); + } + } + LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collections", master_collection_list.Size()); + LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collection items", cItems_total); + LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collection rewards", cItems_rewards); +} + +int32 WorldDatabase::LoadCollectionItems(Collection *collection) +{ + struct CollectionItem *collection_item; + Item *item; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 total = 0; + + assert(collection); + + res = query.RunQuery2(Q_SELECT, "SELECT `item_id`,`item_index`\n" + "FROM `collection_details`\n" + "WHERE `collection_id`=%u\n" + "ORDER BY `item_index` ASC", + collection->GetID()); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + if ((item = master_item_list.GetItem(atoul(row[0])))) + { + collection_item = new struct CollectionItem; + collection_item->item = atoul(row[0]); + collection_item->index = atoi(row[1]); + collection_item->found = 0; + LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Item: (%u)", atoul(row[0])); //LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Item: '%s' (%u)", master_item_list.GetItem(collection_item->item)->name.c_str(), atoul(row[0])); + collection->AddCollectionItem(collection_item); + total++; + } + } + } + if(query.GetErrorNumber()) + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Collection Items, Query: %s, Error: %s", query.GetQuery(), query.GetError()); + + return total; +} + +int32 WorldDatabase::LoadCollectionRewards(Collection *collection) +{ + struct CollectionRewardItem *reward_item; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 total = 0; + + assert(collection); + + res = query.RunQuery2(Q_SELECT, "SELECT `reward_type`,`reward_value`,`reward_quantity`\n" + "FROM `collection_rewards`\n" + "WHERE `collection_id`=%u", + collection->GetID()); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Reward: Type: %s, Val: %s, Qty: %u", row[0], row[1], atoi(row[2])); + + if (!strcasecmp(row[0], "Item")) + { + reward_item = new struct CollectionRewardItem; + reward_item->item = master_item_list.GetItem(atoul(row[1])); + reward_item->quantity = atoi(row[2]); + collection->AddRewardItem(reward_item); + total++; + } + else if (!strcasecmp(row[0], "Selectable")) + { + reward_item = new struct CollectionRewardItem; + reward_item->item = master_item_list.GetItem(atoul(row[1])); + reward_item->quantity = atoi(row[2]); + collection->AddSelectableRewardItem(reward_item); + total++; + } + else if (!strcasecmp(row[0], "Coin")) + { + collection->SetRewardCoin(atoi64(row[1])); + total++; + } + else if (!strcasecmp(row[0], "XP")) + { + collection->SetRewardXP(atoi64(row[1])); + total++; + } + else + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection reward to collection '%s'. Unknown reward type '%s'", collection->GetName(), row[0]); + } + } + + if(query.GetErrorNumber()) + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Collection Rewards, Query: %s, Error: %s", query.GetQuery(), query.GetError()); + + return total; +} + +void WorldDatabase::LoadPlayerCollections(Player *player) +{ + Collection *collection; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + assert(player); + + res = query.RunQuery2(Q_SELECT, "SELECT `collection_id`,`completed` FROM `character_collections` WHERE `char_id`=%u", player->GetCharacterID()); + if (res) + { + while ((row = mysql_fetch_row(res))) + { + collection = new Collection(master_collection_list.GetCollection(atoul(row[0]))); + collection->SetCompleted(atoi(row[1])); + + if (!player->GetCollectionList()->AddCollection(collection)) + { + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection %u to player '%s' - duplicate ID\n", collection->GetID(), player->GetName()); + safe_delete(collection); + continue; + } + + LoadPlayerCollectionItems(player, collection); + } + } + if(query.GetErrorNumber()) + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Character Collections, Query: %s, Error: %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::LoadPlayerCollectionItems(Player *player, Collection *collection) +{ + struct CollectionItem *collection_item; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + assert(player); + assert(collection); + + res = query.RunQuery2(Q_SELECT, "SELECT `collection_item_id`\n" + "FROM `character_collection_items`\n" + "WHERE `char_id`=%u\n" + "AND `collection_id`=%u", + player->GetCharacterID(), collection->GetID()); + + if (res) + { + while ((row = mysql_fetch_row(res))) + { + if ((collection_item = collection->GetCollectionItemByItemID(atoul(row[0])))) + collection_item->found = true; + else + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading character collection items. Item ID %u does not exist in collection %s", atoul(row[0]), collection->GetName()); + } + } + if(query.GetErrorNumber()) + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Character Collection Items, Query: %s, Error: %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::SavePlayerCollections(Client *client) +{ + map *collections; + map::iterator itr; + Collection *collection; + + assert(client); + + collections = client->GetPlayer()->GetCollectionList()->GetCollections(); + for (itr = collections->begin(); itr != collections->end(); itr++) + { + collection = itr->second; + if (collection->GetSaveNeeded()) + { + SavePlayerCollection(client, collection); + SavePlayerCollectionItems(client, collection); + collection->SetSaveNeeded(false); + } + } +} + +void WorldDatabase::SavePlayerCollection(Client *client, Collection *collection) +{ + Query query; + + assert(client); + assert(collection); + + query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "INSERT INTO `character_collections` (`char_id`,`collection_id`,`completed`)\n" + "VALUES (%u,%u,0)\n" + "ON DUPLICATE KEY UPDATE `completed`=%i", + client->GetPlayer()->GetCharacterID(), collection->GetID(), + collection->GetCompleted() ? 1 : 0); +} + +void WorldDatabase::SavePlayerCollectionItems(Client *client, Collection *collection) +{ + vector *collection_items; + vector::iterator itr; + struct CollectionItem *collection_item; + + assert(client); + assert(collection); + + collection_items = collection->GetCollectionItems(); + for (itr = collection_items->begin(); itr != collection_items->end(); itr++) + { + collection_item = *itr; + if (collection_item->found > 0) + SavePlayerCollectionItem(client, collection, collection_item->item); + } +} + +void WorldDatabase::SavePlayerCollectionItem(Client *client, Collection *collection, int32 item_id) +{ + Query query; + + assert(client); + assert(collection); + //assert(item); + + query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "INSERT IGNORE INTO `character_collection_items` (`char_id`,`collection_id`,`collection_item_id`)\n" + "VALUES (%u,%u,%u)", + client->GetPlayer()->GetCharacterID(), collection->GetID(), item_id); +} + diff --git a/source/WorldServer/Combat.cpp b/source/WorldServer/Combat.cpp new file mode 100644 index 0000000..dc0e522 --- /dev/null +++ b/source/WorldServer/Combat.cpp @@ -0,0 +1,1980 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Combat.h" +#include "client.h" +#include "../common/ConfigReader.h" +#include "classes.h" +#include "../common/debug.h" +#include "../common/Log.h" +#include "zoneserver.h" +#include "Skills.h" +#include "classes.h" +#include "World.h" +#include "LuaInterface.h" +#include "Rules/Rules.h" +#include "SpellProcess.h" +#include "World.h" +#include + +extern Classes classes; +extern ConfigReader configReader; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; +extern LuaInterface* lua_interface; +extern World world; + +/* ****************************************************************************** + +DamageSpawn() - Damage equation +MeleeAttack() - Melee auto attacks +RangeAttack() - Range auto attacks +DetermineHit() - ToHit chance as well as defender parry / dodge / block / riposte +CheckInterruptSpell() - Interrupt equations + + +No mitigation equations yet + +****************************************************************************** */ + +/* New Combat code */ + +bool Entity::PrimaryWeaponReady() { + //Can only be ready if no ranged timer + if (GetPrimaryLastAttackTime() == 0 || (Timer::GetCurrentTime2() >= (GetPrimaryLastAttackTime() + GetPrimaryAttackDelay()))) { + if (GetRangeLastAttackTime() == 0 || Timer::GetCurrentTime2() >= (GetRangeLastAttackTime() + GetRangeAttackDelay())) + return true; + } + + return false; +} + +bool Entity::SecondaryWeaponReady() { + //Can only be ready if no ranged timer + // if(IsDualWield() && (GetPrimaryLastAttackTime() + if (IsDualWield() && (GetSecondaryLastAttackTime() == 0 || (Timer::GetCurrentTime2() >= (GetSecondaryLastAttackTime() + GetSecondaryAttackDelay())))) { + if(GetRangeLastAttackTime() == 0 || Timer::GetCurrentTime2() >= (GetRangeLastAttackTime() + GetRangeAttackDelay())) + return true; + } + + return false; +} + +bool Entity::RangeWeaponReady() { + //Ranged can only be ready if no other attack timers are active + if(GetRangeLastAttackTime() == 0 || (Timer::GetCurrentTime2() >= (GetRangeLastAttackTime() + GetRangeAttackDelay()))) { + if((GetPrimaryLastAttackTime() == 0 || (Timer::GetCurrentTime2() >= (GetPrimaryLastAttackTime() + GetPrimaryAttackDelay()))) && (GetSecondaryLastAttackTime() == 0 || Timer::GetCurrentTime2() >= (GetSecondaryLastAttackTime() + GetSecondaryAttackDelay()))){ + if(!IsPlayer() || ((Player*)this)->GetRangeAttack()) { + return true; + } + } + } + + return false; +} + +bool Entity::AttackAllowed(Entity* target, float distance, bool range_attack) { + Entity* attacker = this; + Client* client = 0; + if(!target || IsMezzedOrStunned() || IsDazed()) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: no target, mezzed, stunned or dazed"); + return false; + } + if (IsPlayer()) + client = ((Player*)this)->GetClient(); + + if (IsPet()) + attacker = ((NPC*)this)->GetOwner(); + if (target->IsNPC() && ((NPC*)target)->IsPet()){ + if (((NPC*)target)->GetOwner()) + target = ((NPC*)target)->GetOwner(); + } + + if((IsBot() || (client || (attacker && (attacker->IsPlayer() || attacker->IsBot())))) && !target->IsPlayer() && !target->GetAttackable()) { + return false; + } + + if (attacker == target) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: attacker tried to attack himself or his pet."); + return false; + } + + if (IsPlayer() && target->GetAttackable() == 0) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: target is not attackable"); + return false; + } + + if (IsPlayer() && target->IsBot()) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: players are not allowed to attack bots"); + return false; + } + + if(rule_manager.GetGlobalRule(R_Combat, LockedEncounterNoAttack)->GetBool()) { + if(target->IsNPC() && (target->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED || target->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED) && + !attacker->IsEngagedBySpawnID(target->GetID())) { + return false; + } + else if(IsNPC() && (GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED || GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED) && + !target->IsEngagedBySpawnID(GetID())) { + return false; + } + } + + if (attacker->IsPlayer() && target->IsPlayer()) + { + bool pvp_allowed = rule_manager.GetGlobalRule(R_PVP, AllowPVP)->GetBool(); + if (!pvp_allowed) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: pvp is not allowed"); + return false; + } + else + { + sint32 pvpLevelRange = rule_manager.GetGlobalRule(R_PVP, LevelRange)->GetSInt32(); + int32 attackerLevel = attacker->GetLevel(); + int32 defenderLevel = target->GetLevel(); + if ((sint32)abs((sint32)attackerLevel - (sint32)defenderLevel) > pvpLevelRange) + { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: pvp range of %i exceeded abs(%i-%i).", pvpLevelRange, attackerLevel, defenderLevel); + return false; + } + } + } + + if (target->GetHP() <= 0) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: target is dead"); + return false; + } + + if(range_attack && distance != 0) { + Item* weapon = 0; + Item* ammo = 0; + if(attacker->IsPlayer()) { + weapon = ((Player*)attacker)->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); + ammo = GetAmmoFromSlot(true, true); + } + if(weapon && weapon->IsRanged() && ammo && ammo->IsAmmo() && ammo->IsThrown()) { + // Distance is less then min weapon range + if(distance < weapon->ranged_info->range_low) { + if (client) + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "Your target is too close! Move back!"); + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: range attack, target to close"); + return false; + } + // Distance is greater then max weapon range + if (distance > (weapon->ranged_info->range_high + ammo->thrown_info->range)) { + if (client) + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "Your target is too far away! Move closer!"); + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: range attack, target is to far"); + return false; + } + } + } + else if (distance != 0) { + if(distance >= rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Failed to attack: distance is beyond melee range"); + return false; + } + } + + if((target->IsPrivateSpawn() && !target->AllowedAccess(this)) || (IsPrivateSpawn() && AllowedAccess(target))) { + LogWrite(COMBAT__DEBUG, 3, "AttackAllowed", "Access to spawn disallowed."); + return false; + } + + return true; +} + +Item* Entity::GetAmmoFromSlot(bool is_ammo, bool is_thrown) { + Item* ammo = GetEquipmentList()->GetItem(EQ2_AMMO_SLOT); + if(ammo && ammo->IsBag() && IsPlayer()) { + Item* ammo_bag = ammo; + vector* items = ((Player*)this)->GetPlayerItemList()->GetItemsInBag(ammo_bag); + + vector::iterator itr; + int16 i = 0; + Item* tmp_bag_item = 0; + for (itr = items->begin(); itr != items->end(); itr++) { + tmp_bag_item = *itr; + if (tmp_bag_item) { + if(is_ammo && !tmp_bag_item->IsAmmo()) + continue; + if(is_thrown && !tmp_bag_item->IsThrown()) + continue; + ammo = tmp_bag_item; + break; + } + } + } + + if(ammo && is_ammo && !ammo->IsAmmo()) + ammo = nullptr; + if(ammo && is_thrown && !ammo->IsThrown()) + ammo = nullptr; + + return ammo; +} + +void Entity::MeleeAttack(Spawn* victim, float distance, bool primary, bool multi_attack) { + if(!victim) + return; + + int8 damage_type = 0; + int32 min_damage = 0; + int32 max_damage = 0; + if(primary) { + damage_type = GetPrimaryWeaponType(); + min_damage = GetPrimaryWeaponMinDamage(); + max_damage = GetPrimaryWeaponMaxDamage(); + } + else { + damage_type = GetSecondaryWeaponType(); + min_damage = GetSecondaryWeaponMinDamage(); + max_damage = GetSecondaryWeaponMaxDamage(); + } + if (IsStealthed() || IsInvis()) + CancelAllStealth(); + + int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, damage_type, 0, false); + + if(victim->IsEntity()) { + CheckEncounterState((Entity*)victim); + } + + if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL){ + /*if(GetAdventureClass() == MONK){ + max_damage*=3; + crit_chance = GetLevel()/4+5; + } + else if(GetAdventureClass() == BRUISER){ + min_damage = GetLevel(); + max_damage*=3; + crit_chance = GetLevel()/3+5; + } + if(rand()%100 <=crit_chance){ + max_damage*= 2; + DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG, damage_type, min_damage, max_damage, 0); + } + else*/ + DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, damage_type, min_damage, max_damage, 0); + if (!multi_attack) { + CheckProcs(PROC_TYPE_OFFENSIVE, victim); + CheckProcs(PROC_TYPE_PHYSICAL_OFFENSIVE, victim); + } + } + else{ + + GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, hit_result, damage_type, 0, 0); + if(hit_result == DAMAGE_PACKET_RESULT_RIPOSTE && victim->IsEntity()) + ((Entity*)victim)->MeleeAttack(this, distance, true); + } + + //Multi Attack roll + if(!multi_attack){ + float multi_attack = info_struct.get_multi_attack(); + if(multi_attack > 0){ + float chance = multi_attack; + if (multi_attack > 100){ + int8 automatic_multi = (int8)floor((float)(multi_attack / 100)); + chance = (multi_attack - (floor((float) ((multi_attack / 100) * 100)))); + while(automatic_multi > 0){ + MeleeAttack(victim, 100, primary, true); + automatic_multi--; + } + } + if (MakeRandomFloat(0, 100) <= chance) + MeleeAttack(victim, 100, primary, true); + } + } + + //Apply attack speed mods + if(!multi_attack) + SetAttackDelay(primary); + + if(victim->IsNPC() && victim->EngagedInCombat() == false) { + ((NPC*)victim)->AddHate(this, 50); + } + + if (victim->IsEntity() && victim->GetHP() > 0 && ((Entity*)victim)->HasPet()) { + Entity* pet = 0; + bool AddHate = false; + if (victim->IsPlayer()) { + if (((Player*)victim)->GetInfoStruct()->get_pet_behavior() & 1) + AddHate = true; + } + else + AddHate = true; + + if (AddHate) { + pet = ((Entity*)victim)->GetPet(); + if (pet) + pet->AddHate(this, 1); + pet = ((Entity*)victim)->GetCharmedPet(); + if (pet) + pet->AddHate(this, 1); + } + } +} + +void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack) { + if(!victim) + return; + + if(weapon && weapon->IsRanged() && ammo && ammo->IsAmmo() && ammo->IsThrown()) { + if(weapon->ranged_info->range_low <= distance && (weapon->ranged_info->range_high + ammo->thrown_info->range) >= distance) { + int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, ammo->thrown_info->damage_type, ammo->thrown_info->hit_bonus, false); + if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) { + DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, ammo->thrown_info->damage_type, weapon->ranged_info->weapon_info.damage_low3, weapon->ranged_info->weapon_info.damage_high3+ammo->thrown_info->damage_modifier, 0); + if (!multi_attack) { + CheckProcs(PROC_TYPE_OFFENSIVE, victim); + CheckProcs(PROC_TYPE_PHYSICAL_OFFENSIVE, victim); + } + } + else + GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_RANGE_DAMAGE, hit_result, ammo->thrown_info->damage_type, 0, 0); + + // If is a player subtract ammo + if (IsPlayer()) { + if (ammo->details.count > 1) { + ammo->details.count -= 1; + ammo->save_needed = true; + } + else { + if(ammo->details.inv_slot_id >= 6) { + ((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, false); + ((Player*)this)->item_list.DestroyItem(ammo->details.index); + } + else { + ((Player*)this)->equipment_list.RemoveItem(ammo->details.slot_id, true); + } + } + + Client* client = ((Player*)this)->GetClient(); + if(client) { + EQ2Packet* outapp = ((Player*)this)->GetEquipmentList()->serialize(client->GetVersion(), (Player*)this); + if(outapp) + client->QueuePacket(outapp); + + if(ammo->details.inv_slot_id > 6) { + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + } + } + + if(victim->IsNPC() && victim->EngagedInCombat() == false) { + ((NPC*)victim)->AddHate(this, 50); + } + + if (victim->IsEntity() && victim->GetHP() > 0 && ((Entity*)victim)->HasPet()) { + Entity* pet = 0; + bool AddHate = false; + if (victim->IsPlayer()) { + if (((Player*)victim)->GetInfoStruct()->get_pet_behavior() & 1) + AddHate = true; + } + else + AddHate = true; + + if (AddHate) { + pet = ((Entity*)victim)->GetPet(); + if (pet) + pet->AddHate(this, 1); + pet = ((Entity*)victim)->GetCharmedPet(); + if (pet) + pet->AddHate(this, 1); + } + } + // Check Ranged attack proc + CheckProcs(PROC_TYPE_RANGED_ATTACK, victim); + + // Check Ranged defence proc + if (victim->IsEntity()) + ((Entity*)victim)->CheckProcs(PROC_TYPE_RANGED_DEFENSE, this); + + SetRangeLastAttackTime(Timer::GetCurrentTime2()); + } + } + //Multi Attack roll + if(!multi_attack){ + float multi_attack = info_struct.get_multi_attack(); + if(multi_attack > 0){ + float chance = multi_attack; + if (multi_attack > 100){ + int8 automatic_multi = (int8)floor((float)(multi_attack / 100)); + chance = (multi_attack - (floor((float)(multi_attack / 100) * 100))); + while(automatic_multi > 0){ + RangeAttack(victim, 100, weapon, ammo, true); + automatic_multi--; + } + } + if (MakeRandomFloat(0, 100) <= chance) + RangeAttack(victim, 100, weapon, ammo, true); + } + } + + //Apply attack speed mods + if(!multi_attack) + SetAttackDelay(false, true); +} + +bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod, bool no_calcs, int8 override_packet_type, bool take_power){ + if(!victim || !luaspell || !luaspell->spell) + return false; + + Spell* spell = luaspell->spell; + Skill* skill = nullptr; + int8 packet_type = DAMAGE_PACKET_TYPE_SPELL_DAMAGE; + + if(override_packet_type) { + packet_type = override_packet_type; + } + + int8 hit_result = 0; + bool is_tick = false; // if spell is already active, this is a tick + if (GetZone()->GetSpellProcess()->GetActiveSpells()->count(luaspell)){ + hit_result = DAMAGE_PACKET_RESULT_SUCCESSFUL; + is_tick = true; + } + else if(spell->GetSpellData()->type == SPELL_BOOK_TYPE_COMBAT_ART) + hit_result = DetermineHit(victim, packet_type, damage_type, 0, false, luaspell); + else + hit_result = DetermineHit(victim, packet_type, damage_type, 0, true, luaspell); + + if(victim->IsEntity()) { + CheckEncounterState((Entity*)victim); + } + bool successful_hit = true; + if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) { + luaspell->last_spellattack_hit = true; + //If this spell is a tick and has already crit, force the tick to crit + if(is_tick){ + if(luaspell->crit) + crit_mod = 1; + else + crit_mod = 2; + } + DamageSpawn((Entity*)victim, packet_type, damage_type, low_damage, high_damage, spell->GetName(), crit_mod, is_tick, no_calcs, false, take_power, luaspell); + + CheckProcs(PROC_TYPE_OFFENSIVE, victim); + CheckProcs(PROC_TYPE_MAGICAL_OFFENSIVE, victim); + + if(spell->GetSpellData()->success_message.length() > 0){ + Client* client = nullptr; + if(IsPlayer()) + client = ((Player*)this)->GetClient(); + if(client){ + string success_message = spell->GetSpellData()->success_message; + if(success_message.find("%t") < 0xFFFFFFFF) + success_message.replace(success_message.find("%t"), 2, victim->GetName()); + client->Message(CHANNEL_YOU_CAST, success_message.c_str()); + //commented out the following line as it was causing a duplicate message EmemJR 5/4/2019 + //GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, hit_result, damage_type, 0, spell->GetName()); + } + } + if(spell->GetSpellData()->effect_message.length() > 0){ + string effect_message = spell->GetSpellData()->effect_message; + if(effect_message.find("%t") < 0xFFFFFFFF) + effect_message.replace(effect_message.find("%t"), 2, victim->GetName()); + GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_message.c_str(), victim, 50); + } + } + else { + successful_hit = false; + if(hit_result == DAMAGE_PACKET_RESULT_RESIST) + luaspell->resisted = true; + if(victim->IsNPC()) + ((NPC*)victim)->AddHate(this, 5); + luaspell->last_spellattack_hit = false; + GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, hit_result, damage_type, 0, spell->GetName()); + } + if(EngagedInCombat() == false) + { + LogWrite(MISC__TODO, 1, "TODO", "//It would probably be better to add a column to the spells table for 'starts autoattack'\nfile: %s, func: %s, Line: %i", __FILE__, __FUNCTION__, __LINE__); + int8 class1_ = GetInfoStruct()->get_class1(); + if(class1_ == COMMONER || + class1_ == FIGHTER || + class1_ == WARRIOR || + class1_ == GUARDIAN || + class1_ == BERSERKER || + class1_ == BRAWLER || + class1_ == MONK || + class1_ == BRUISER || + class1_ == CRUSADER || + class1_ == SHADOWKNIGHT || + class1_ == PALADIN || + class1_ == SCOUT || + class1_ == ROGUE || + class1_ == SWASHBUCKLER || + class1_ == BRIGAND || + class1_ == BARD || + class1_ == TROUBADOR || + class1_ == DIRGE || + class1_ == PREDATOR || + class1_ == RANGER || + class1_ == ASSASSIN || + class1_ == ANIMALIST || + class1_ == BEASTLORD || + class1_ == SHAPER || + class1_ == CHANNELER) //note: it would probably be better to add a column to the spells table for "starts autoattack". + { + if (victim->IsNPC()) + ((NPC*)victim)->AddHate(this, 5); + else { + Client* client = 0; + if(IsPlayer()) + client = ((Player*)this)->GetClient(); + if(client) { + client->GetPlayer()->InCombat(true, client->GetPlayer()->GetRangeAttack()); + } + InCombat(true); + } + } + } + + if (victim->IsEntity() && victim->GetHP() > 0 && ((Entity*)victim)->HasPet()) { + Entity* pet = 0; + bool AddHate = false; + if (victim->IsPlayer()) { + if (((Player*)victim)->GetInfoStruct()->get_pet_behavior() & 1) + AddHate = true; + } + else + AddHate = true; + + if (AddHate) { + pet = ((Entity*)victim)->GetPet(); + if (pet) + pet->AddHate(this, 1); + pet = ((Entity*)victim)->GetCharmedPet(); + if (pet) + pet->AddHate(this, 1); + } + } + + return successful_hit; +} + +bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg) { + int8 hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, 0, true); + + if (hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) { + DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, low_damage, high_damage, name.c_str()); + + if (success_msg.length() > 0) { + Client* client = 0; + if(IsPlayer()) + client = ((Player*)this)->GetClient(); + if(client) { + if(success_msg.find("%t") < 0xFFFFFFFF) + success_msg.replace(success_msg.find("%t"), 2, victim->GetName()); + client->Message(CHANNEL_YOU_CAST, success_msg.c_str()); + } + } + if (effect_msg.length() > 0) { + if(effect_msg.find("%t") < 0xFFFFFFFF) + effect_msg.replace(effect_msg.find("%t"), 2, victim->GetName()); + GetZone()->SimpleMessage(CHANNEL_SPELLS, effect_msg.c_str(), victim, 50); + } + } + else { + if(victim->IsNPC()) + ((NPC*)victim)->AddHate(this, 5); + GetZone()->SendDamagePacket(this, victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, hit_result, damage_type, 0, name.c_str()); + } + + + if (victim->IsEntity() && victim->GetHP() > 0 && ((Entity*)victim)->HasPet()) { + Entity* pet = 0; + bool AddHate = false; + if (victim->IsPlayer()) { + if (((Player*)victim)->GetInfoStruct()->get_pet_behavior() & 1) + AddHate = true; + } + else + AddHate = true; + + if (AddHate) { + pet = ((Entity*)victim)->GetPet(); + if (pet) + pet->AddHate(this, 1); + pet = ((Entity*)victim)->GetCharmedPet(); + if (pet) + pet->AddHate(this, 1); + } + } + + return true; +} + +// this is used exclusively by LUA, heal_type is forced lower case via boost::lower(heal_type); in the LUA Functions used by this +bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod, bool no_calcs, string custom_spell_name){ + if(!target || !luaspell || !luaspell->spell || (target->IsPrivateSpawn() && !target->AllowedAccess(this))) + return false; + + if (!target->Alive()) + return false; + + if (target->GetHP() == target->GetTotalHP()) + return true; + + int32 heal_amt = 0; + bool crit = false; + + if(high_heal < low_heal) + high_heal = low_heal; + if(high_heal == low_heal) + heal_amt = high_heal; + else + heal_amt = MakeRandomInt(low_heal, high_heal); + + if(!no_calcs){ + // if spell is already active, this is a tick + bool is_tick = GetZone()->GetSpellProcess()->GetActiveSpells()->count(luaspell); + + //if is a tick and the spell has crit, force crit, else disable + if(is_tick){ + if(luaspell->crit) + crit_mod = 1; + else + crit_mod = 2; + } + + if (heal_amt > 0){ + if(target->IsEntity()) + heal_amt = (int32)CalculateHealAmount((Entity*)target, (sint32)heal_amt, crit_mod, &crit); + else + heal_amt = (int32)CalculateHealAmount(nullptr, (sint32)heal_amt, crit_mod, &crit); + } + } + + int16 type = 0; + if (heal_type == "heal") { + if(crit) + type = HEAL_PACKET_TYPE_CRIT_HEAL; + else + type = HEAL_PACKET_TYPE_SIMPLE_HEAL; + //apply heal + + if (target->GetHP() + (sint32)heal_amt > target->GetTotalHP()) + heal_amt = target->GetTotalHP() - target->GetHP(); + target->SetHP(target->GetHP() + heal_amt); + + /* + if (target->GetHP() + (sint32)heal_amt > target->GetTotalHP()) + target->SetHP(target->GetTotalHP()); + else + target->SetHP(target->GetHP() + heal_amt); + */ + } + else if (heal_type == "power"){ + if(crit) + type = HEAL_PACKET_TYPE_CRIT_MANA; + else + type = HEAL_PACKET_TYPE_SIMPLE_MANA; + //give power + if (target->GetPower() + (sint32)heal_amt > target->GetTotalPower()) + heal_amt = target->GetTotalPower() - target->GetPower(); + target->SetPower(GetPower() + heal_amt); + + /* + if (target->GetPower() + (sint32)heal_amt > target->GetTotalPower()) + target->SetPower(target->GetTotalPower()); + else + target->SetPower(GetPower() + heal_amt); + */ + } + /*else if (heal_type == "Savagery"){ + if(crit) + type = HEAL_PACKET_TYPE_CRIT_SAVAGERY; + else + type = HEAL_PACKET_TYPE_SAVAGERY; + } + else if (heal_type == "Repair"){ + if(crit) + type = HEAL_PACKET_TYPE_CRIT_REPAIR; + else + type = HEAL_PACKET_TYPE_REPAIR; + }*/ + else{ //default to heal if type cannot be determined + if(crit) + type = HEAL_PACKET_TYPE_CRIT_HEAL; + else + type = HEAL_PACKET_TYPE_SIMPLE_HEAL; + + if (target->GetHP() + (sint32)heal_amt > target->GetTotalHP()) + heal_amt = target->GetTotalHP() - target->GetHP(); + target->SetHP(target->GetHP() + heal_amt); + /* + if (target->GetHP() + (sint32)heal_amt > target->GetTotalHP()) + target->SetHP(target->GetTotalHP()); + else + target->SetHP(target->GetHP() + heal_amt); + */ + } + + target->GetZone()->TriggerCharSheetTimer(); + if (heal_amt > 0) + GetZone()->SendHealPacket(this, target, type, heal_amt, custom_spell_name.length() > 0 ? (char*)custom_spell_name.c_str() : luaspell->spell->GetName()); + CheckProcs(PROC_TYPE_HEALING, target); + CheckProcs(PROC_TYPE_BENEFICIAL, target); + + if (target->IsEntity()) { + int32 hate_amt = heal_amt / 2; + set::iterator itr; + ((Entity*)target)->MHatedBy.lock(); + set hatedByCopy(((Entity*)target)->HatedBy); + ((Entity*)target)->MHatedBy.unlock(); + for (itr = hatedByCopy.begin(); itr != hatedByCopy.end(); itr++) { + Spawn* spawn = GetZone()->GetSpawnByID(*itr); + if (spawn && spawn->IsEntity() && target != this) { + CheckEncounterState((Entity*)spawn); + ((Entity*)spawn)->AddHate(this, hate_amt); + } + } + } + + return true; +} + +int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell){ + if(lua_spell) { + lua_spell->is_damage_spell = true; + } + if(!victim) { + return DAMAGE_PACKET_RESULT_MISS; + } + + if(victim->GetInvulnerable()) { + return DAMAGE_PACKET_RESULT_INVULNERABLE; + } + + bool behind = false; + + // move this above the if statement to assure skill-ups on successful damage return + Skill* skill = GetSkillByWeaponType(type, damage_type, true); + + // Monk added with Brawler to 360 degree support per KoS Prima Official eGuide Fighter: Monk, pg 138, denoted '360-Degree Avoidance!' + if(!victim->IsEntity() || (!is_caster_spell && victim->GetAdventureClass() != BRAWLER && victim->GetAdventureClass() != MONK && (behind = BehindTarget(victim)))) { + return DAMAGE_PACKET_RESULT_SUCCESSFUL; + } + + float bonus = ToHitBonus; + + float skillAddedByWeapon = 0.0f; + if(skill) + { + int16 skillID = master_item_list.GetItemStatIDByName(skill->name.data); + if(skillID != 0xFFFFFFFF) + { + MStats.lock(); + skillAddedByWeapon = stats[skillID]; + if(!is_caster_spell) { + float item_stat_weapon_skill = stats[ITEM_STAT_WEAPON_SKILLS]; + skillAddedByWeapon += item_stat_weapon_skill; + } + MStats.unlock(); + + float max_bonus_skill = GetRuleSkillMaxBonus(); + if(skillAddedByWeapon > max_bonus_skill) { + skillAddedByWeapon = max_bonus_skill; + } + } + } + + if (skill) + bonus += (skill->current_val+skillAddedByWeapon) / 25; + + if(is_caster_spell && lua_spell) { + if(lua_spell->spell->GetSpellData()->resistibility > 0) + bonus -= (1.0f - lua_spell->spell->GetSpellData()->resistibility)*100.0f; + + LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellResist: resistibility %f, bonus %f", lua_spell->spell->GetSpellData()->resistibility, bonus); + // Here we take into account Subjugation, Disruption and Ordination (debuffs) + if(lua_spell->spell->GetSpellData()->mastery_skill) { + int32 master_skill_reduce = rule_manager.GetGlobalRule(R_Spells, MasterSkillReduceSpellResist)->GetInt32(); + if(master_skill_reduce < 1) + master_skill_reduce = 25; + if(IsPlayer() && lua_spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_TRADESKILL && + !((Player*)this)->GetSkills()->HasSkill(lua_spell->spell->GetSpellData()->mastery_skill)) { + ((Player*)this)->AddSkill(lua_spell->spell->GetSpellData()->mastery_skill, 1, ((Player*)this)->GetLevel() * 5, true); + } + + Skill* master_skill = GetSkillByID(lua_spell->spell->GetSpellData()->mastery_skill, true); + if(master_skill && (lua_spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_TRADESKILL || + ((master_skill->name.data == "Subjugation" || master_skill->name.data == "Disruption" || master_skill->name.data == "Ordination" || master_skill->name.data == "Aggression")))) { + float item_stat_bonus = 0.0f; + int32 item_stat = master_item_list.GetItemStatIDByName(::ToLower(master_skill->name.data)); + if(item_stat != 0xFFFFFFFF) { + item_stat_bonus = GetStat(item_stat); + } + bonus += (master_skill->current_val + item_stat_bonus) / master_skill_reduce; + LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellResistMasterySkill: skill %u, stat bonus %f, mastery skill reduce %u, bonus %f", master_skill->current_val, item_stat_bonus, master_skill_reduce, bonus); + + } + } + + } + + if (victim->IsEntity()) + bonus -= ((Entity*)victim)->GetDamageTypeResistPercentage(damage_type); + + LogWrite(COMBAT__DEBUG, 9, "Combat", "DamageResistPercent: type %u, bonus %f", damage_type, bonus); + + + Entity* entity_victim = (Entity*)victim; + float chance = 80 + bonus; //80% base chance that the victim will get hit (plus bonus) + sint16 roll_chance = 100; + if(is_caster_spell) { + chance += CalculateLevelStatBonus(GetWis()); + } + if(skill) + roll_chance -= skill->current_val / 10; + + LogWrite(COMBAT__DEBUG, 9, "Combat", "Chance: fchance %f, roll_chance %i", chance, roll_chance); + + if(!is_caster_spell){ // melee or range attack + skill = GetSkillByName("Offense", true); //add this skill for NPCs + if(skill) + roll_chance -= skill->current_val / 25; + + if(entity_victim->IsImmune(IMMUNITY_TYPE_RIPOSTE)) + return DAMAGE_PACKET_RESULT_RIPOSTE; + + // Avoidance Instructions: https://forums.daybreakgames.com/eq2/index.php?threads/avoidance-faq.482979/ + + /*Parry: reads as parry in the avoidance tooltip, increased by items with +parry on them + Caps at 70%. For plate tanks, works in the front quadrant only, for brawlers this is 360 degrees. + A small % of parries will be ripostes, which not only avoid the attack but also damage your attacker + */ + + skill = entity_victim->GetSkillByName("Parry", true); + if(skill){ + float parryChance = entity_victim->GetInfoStruct()->get_parry(); + float chanceValue = (100.0f - parryChance); + + if(chanceValue < 10.0f) { // min default per https://eq2.fandom.com/wiki/Update:29 + chanceValue = 10.0f; // this becomes 5.0f at EoF + } + + if(rand()%roll_chance >= chanceValue){ //successful parry + /* You may only riposte things in the front quadrant. + Riposte is based off of parry: a certain % of parries turn into ripostes. + */ + if(!behind && victim->InFrontSpawn((Spawn*)this, victim->GetX(), victim->GetZ())) { // if the attacker is not behind the victim, and the victim is facing the attacker (in front of spawn) then we can riposte + float riposteChanceValue = parryChance / 7.0f; // Riposte is based off of parry: a certain % of parries turn into ripostes. Unknown what the value is divided by, 7 to make it 10% even. + if(rand()%100 <= riposteChanceValue) { + entity_victim->CheckProcs(PROC_TYPE_RIPOSTE, this); + return DAMAGE_PACKET_RESULT_RIPOSTE; + } + } + entity_victim->CheckProcs(PROC_TYPE_PARRY, this); + return DAMAGE_PACKET_RESULT_PARRY; + } + } + + skill = nullptr; + + + float blockChance = 0.0f; + if(victim->GetAdventureClass() == BRAWLER) + skill = entity_victim->GetSkillByName("Deflection", true); + + blockChance = entity_victim->GetInfoStruct()->get_block(); + + if(blockChance > 0.0f) + { + blockChance += (blockChance*(entity_victim->GetInfoStruct()->get_block_chance()/100.0f)); + float chanceValue = (100.0f - blockChance); + \ + if(chanceValue < 30.0f) { // min default per https://eq2.fandom.com/wiki/Update:29 + chanceValue = 30.0f; // this becomes 25.0f at EoF + } + /* Non-brawlers may only block things in the front quadrant. + Riposte is based off of parry: a certain % of parries turn into ripostes. + */ + float rnd = rand()%roll_chance; + if(rnd >= chanceValue){ //successful block + if((victim->GetAdventureClass() == BRAWLER || victim->GetAdventureClass() == MONK) || (!behind && victim->InFrontSpawn((Spawn*)this, victim->GetX(), victim->GetZ()))) { // if the attacker is not behind the victim, and the victim is facing the attacker (in front of spawn) then we can block + entity_victim->CheckProcs(PROC_TYPE_BLOCK, this); + return (victim->GetAdventureClass() == BRAWLER) ? DAMAGE_PACKET_RESULT_DEFLECT : DAMAGE_PACKET_RESULT_BLOCK; + } + } + } + + skill = entity_victim->GetSkillByName("Defense", true); + + // calculated in Entity::CalculateBonuses + float dodgeChance = entity_victim->GetInfoStruct()->get_avoidance_base(); + if(dodgeChance > 0.0f) + { + float chanceValue = (100.0f - dodgeChance); + float rnd = rand()%roll_chance; + if(rnd >= chanceValue){ //successful dodge + entity_victim->CheckProcs(PROC_TYPE_EVADE, this); + return DAMAGE_PACKET_RESULT_DODGE;//successfully dodged + } + } + if(rand() % roll_chance >= chance) + return DAMAGE_PACKET_RESULT_MISS; //successfully avoided + } + else{ + float focus_skill_with_bonus = entity_victim->CalculateSkillWithBonus("Spell Avoidance", ITEM_STAT_SPELL_AVOIDANCE, true); + int16 effective_level = entity_victim->GetInfoStruct()->get_effective_level() != 0 ? entity_victim->GetInfoStruct()->get_effective_level() : entity_victim->GetLevel(); + focus_skill_with_bonus += entity_victim->CalculateLevelStatBonus(entity_victim->GetWis()); + chance -= ((focus_skill_with_bonus)/10); + LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellAvoidChance: fchance %f, focus with skill bonus %f", chance, focus_skill_with_bonus); + if(rand()%roll_chance >= chance) { + return DAMAGE_PACKET_RESULT_RESIST; //successfully resisted + } + } + + return DAMAGE_PACKET_RESULT_SUCCESSFUL; +} + +float Entity::GetDamageTypeResistPercentage(int8 damage_type) { + float ret = 1; + + switch(damage_type) { + case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH: + case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE: + case DAMAGE_PACKET_DAMAGE_TYPE_SLASH: { + Skill* skill = GetSkillByName("Defense", true); + if(skill) + ret += skill->current_val / 25; + if(IsNPC()) + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Crush/Pierce/Slash (%i)", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_HEAT: { + ret += GetInfoStruct()->get_heat() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Heat (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_COLD: { + ret += GetInfoStruct()->get_cold() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Cold (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_MAGIC: { + ret += GetInfoStruct()->get_magic() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Magic (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_MENTAL: { + ret += GetInfoStruct()->get_mental() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Mental (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_DIVINE: { + ret += GetInfoStruct()->get_divine() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Divine (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_DISEASE: { + ret += GetInfoStruct()->get_disease() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Disease (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_POISON: { + ret += GetInfoStruct()->get_poison() / 50; + LogWrite(COMBAT__DEBUG, 3, "Combat", "DamageType: Poison (%i), Amt: %.2f", damage_type, ret); + break; + } + case DAMAGE_PACKET_DAMAGE_TYPE_FOCUS: { + ret = 0.0f; + break; + } + } + + return ret; +} + +Skill* Entity::GetSkillByWeaponType(int8 type, int8 damage_type, bool update) { + if(type == DAMAGE_PACKET_TYPE_RANGE_DAMAGE) { + return GetSkillByName("Ranged", update); + } + switch(damage_type) { + case DAMAGE_PACKET_DAMAGE_TYPE_SLASH: // slash + return GetSkillByName("Slashing", update); + case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH: // crush + return GetSkillByName("Crushing", update); + case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE: // pierce + return GetSkillByName("Piercing", update); + case DAMAGE_PACKET_DAMAGE_TYPE_HEAT: + case DAMAGE_PACKET_DAMAGE_TYPE_COLD: + case DAMAGE_PACKET_DAMAGE_TYPE_MAGIC: + case DAMAGE_PACKET_DAMAGE_TYPE_MENTAL: + case DAMAGE_PACKET_DAMAGE_TYPE_DIVINE: + case DAMAGE_PACKET_DAMAGE_TYPE_DISEASE: + case DAMAGE_PACKET_DAMAGE_TYPE_POISON: + return GetSkillByName("Disruption", update); + case DAMAGE_PACKET_DAMAGE_TYPE_FOCUS: + return GetSkillByName("Focus", update); + } + + return 0; +} + +bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, bool take_power, LuaSpell* spell) { + if(spell) { + spell->is_damage_spell = true; + } + + bool has_damaged = false; + + if(!victim || !victim->Alive() || victim->GetHP() == 0) + return false; + + int8 hit_result = 0; + int16 blow_type = 0; + sint32 damage = 0; + sint32 damage_before_crit = 0; + bool crit = false; + + if(low_damage > high_damage) + high_damage = low_damage; + if(low_damage == high_damage) + damage = low_damage; + else + damage = MakeRandomInt(low_damage, high_damage); + + if(!no_calcs) { + //this can be simplified by taking out the / 2, but I wanted the damage to be more consistent + //damage = (rand()%((int)(high_damage/2-low_damage/2) + low_damage/2)) + (rand()%((int)(high_damage/2-low_damage/2) + low_damage/2)); + //damage = (rand()%((int)(high_damage-low_damage) + low_damage)) + (rand()%((int)(high_damage-low_damage) + low_damage)); + + //DPS mod is only applied to auto attacks + if (type == DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE || type == DAMAGE_PACKET_TYPE_RANGE_DAMAGE) { + if(info_struct.get_dps_multiplier() != 0.0f) { + damage *= (info_struct.get_dps_multiplier()); + } + } + //Potency and ability mod is only applied to spells/CAs + else { + damage = CalculateDamageAmount(victim, damage, type, damage_type, spell); + } + + if(!crit_mod || crit_mod == 1){ + //force crit if crit_mod == 1 + if(crit_mod == 1) + crit = true; + + // Crit Roll + else { + victim->MStats.lock(); + float chance = max((float)0, (info_struct.get_crit_chance() - victim->stats[ITEM_STAT_CRITAVOIDANCE])); + victim->MStats.unlock(); + if (MakeRandomFloat(0, 100) <= chance) + crit = true; + } + if(crit){ + damage_before_crit = damage; + //Apply total crit multiplier with crit bonus + if(info_struct.get_crit_bonus() > 0) + damage *= (1.3 + (info_struct.get_crit_bonus() / 100)); + else + damage *= 1.3; + + // Change packet type to crit + if (type == DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE) + type = DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG; + else if (type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE) + type = DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG; + } + } + + if(type == DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE || type == DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG || type == DAMAGE_PACKET_TYPE_RANGE_DAMAGE) { + int16 effective_level_attacker = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + float mit_percentage = CalculateMitigation(type, damage_type, effective_level_attacker, (IsPlayer() && victim->IsPlayer())); + sint32 damage_to_reduce = (damage * mit_percentage); + if(damage_to_reduce > damage) + damage = 0; + else + damage -= damage_to_reduce; + + // if we reduce damage back below crit level then its no longer a crit, but we don't go below base damage + if(type == DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG && damage <= damage_before_crit) { + damage = damage_before_crit; + type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE; + } + } + } + + bool useWards = false; + + if(damage <= 0){ + hit_result = DAMAGE_PACKET_RESULT_NO_DAMAGE; + damage = 0; + + if(victim->IsNPC() && victim->GetHP() > 0) + ((Entity*)victim)->AddHate(this, damage); + } + else{ + hit_result = DAMAGE_PACKET_RESULT_SUCCESSFUL; + sint32 return_damage = 0; + if(GetZone()->CallSpawnScript(victim, SPAWN_SCRIPT_HEALTHCHANGED, this, 0, false, damage, &return_damage) && return_damage != 0) + { + // anything not 0 (no return) becomes considered 'immune' to the damage + if(return_damage < 0) { + damage = 0; + hit_result = DAMAGE_PACKET_RESULT_NO_DAMAGE; + } + else if(return_damage != 0) { + // otherwise we use what was given back to us (either less or greater) + damage = return_damage; + } + } + + if(victim->IsNPC() && victim->GetHP() > 0) + ((Entity*)victim)->AddHate(this, damage); + + if(damage) { + int32 prevDmg = damage; + damage = victim->CheckWards(this, damage, damage_type); + + if (damage < (sint64)prevDmg) + useWards = true; + if(damage > 0 && spell) { + has_damaged = true; + spell->has_damaged = true; + } + if(take_power) { + sint32 curPower = victim->GetPower(); + if(curPower < damage) + curPower = 0; + else + curPower -= damage; + victim->SetPower(curPower); + } + else { + victim->TakeDamage(damage); + } + victim->CheckProcs(PROC_TYPE_DAMAGED, this); + + if (IsPlayer()) { + switch (damage_type) { + case DAMAGE_PACKET_DAMAGE_TYPE_SLASH: + case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH: + case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE: + if (((Player*)this)->GetPlayerStatisticValue(STAT_PLAYER_HIGHEST_MELEE_HIT) < damage) + ((Player*)this)->UpdatePlayerStatistic(STAT_PLAYER_HIGHEST_MELEE_HIT, damage, true); + victim->CheckProcs(PROC_TYPE_DAMAGED_MELEE, this); + break; + case DAMAGE_PACKET_DAMAGE_TYPE_HEAT: + case DAMAGE_PACKET_DAMAGE_TYPE_COLD: + case DAMAGE_PACKET_DAMAGE_TYPE_MAGIC: + case DAMAGE_PACKET_DAMAGE_TYPE_MENTAL: + case DAMAGE_PACKET_DAMAGE_TYPE_DIVINE: + case DAMAGE_PACKET_DAMAGE_TYPE_DISEASE: + case DAMAGE_PACKET_DAMAGE_TYPE_POISON: + if (((Player*)this)->GetPlayerStatisticValue(STAT_PLAYER_HIGHEST_MAGIC_HIT) < damage) + ((Player*)this)->UpdatePlayerStatistic(STAT_PLAYER_HIGHEST_MAGIC_HIT, damage, true); + victim->CheckProcs(PROC_TYPE_DAMAGED_MAGIC, this); + break; + } + } + } + } + + Entity* attacker = nullptr; + if(!ignore_attacker) + attacker = this; + if (damage > 0) { + GetZone()->SendDamagePacket(attacker, victim, type, hit_result, damage_type, damage, spell_name); + if (IsStealthed() || IsInvis()) + CancelAllStealth(); + + if (victim->IsEntity()) + ((Entity*)victim)->CheckInterruptSpell(this); + } + else if (useWards) + { + GetZone()->SendDamagePacket(attacker, victim, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, DAMAGE_PACKET_RESULT_NO_DAMAGE, damage_type, 0, spell_name); + } + + if (victim->GetHP() <= 0) + KillSpawn(victim, type, damage_type, blow_type); + else { + victim->CheckProcs(PROC_TYPE_DEFENSIVE, this); + if (spell_name) + victim->CheckProcs(PROC_TYPE_MAGICAL_DEFENSIVE, this); + else + victim->CheckProcs(PROC_TYPE_PHYSICAL_DEFENSIVE, this); + } + if(spell) + spell->crit = crit; + return has_damaged; +} + +float Entity::CalculateMitigation(int8 type, int8 damage_type, int16 effective_level_attacker, bool for_pvp) { + int16 effective_level_victim = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + if(effective_level_attacker < 1 && effective_level_victim) + effective_level_attacker = effective_level_victim; + else + effective_level_attacker = 1; + + int32 effective_mit_cap = effective_level_victim * rule_manager.GetGlobalRule(R_Combat, EffectiveMitigationCapLevel)->GetInt32(); + float max_mit = (float)GetInfoStruct()->get_max_mitigation(); + if(max_mit == 0.0f) + max_mit = effective_level_victim * 100.0f; + + int32 mit_to_use = 0; + switch(type) { + + case DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE: + case DAMAGE_PACKET_TYPE_RANGE_DAMAGE: + mit_to_use = GetInfoStruct()->get_cur_mitigation(); + break; + case DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG: + // since critical mitigation is a percentage we will reverse the mit value so we can add skill from specific types of weapons + mit_to_use = (int32)((float)GetInfoStruct()->get_max_mitigation() * (float)(GetInfoStruct()->get_critical_mitigation()/100.0f)); + break; + + } + + switch(damage_type) { + case DAMAGE_PACKET_DAMAGE_TYPE_SLASH: + mit_to_use += GetInfoStruct()->get_mitigation_skill1(); // slash + break; + case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE: + mit_to_use += GetInfoStruct()->get_mitigation_skill2(); // pierce + break; + case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH: + mit_to_use += GetInfoStruct()->get_mitigation_skill3(); // crush + break; + case DAMAGE_PACKET_DAMAGE_TYPE_FOCUS: + return 0.0f; // focus cannot be mitigated, just break out of this now + break; + default: + // do nothing + break; + } + + if(for_pvp) { + mit_to_use += effective_level_victim * rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetInt32(); + } + + if(mit_to_use > effective_mit_cap) { + mit_to_use = effective_mit_cap; + } + float level_diff = ((float)effective_level_victim / (float)effective_level_attacker); + if(level_diff > rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat()) { + level_diff = rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat(); + } + else if(level_diff < rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat()) { + level_diff = rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMin)->GetFloat(); + } + float mit_percentage = ((float)mit_to_use / max_mit) * level_diff; + + if(!for_pvp && mit_percentage > rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowed)->GetFloat()) { + mit_percentage = rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowed)->GetFloat(); + } + else if(for_pvp && mit_percentage > rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowedPVP)->GetFloat()) { + mit_percentage = rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowedPVP)->GetFloat(); + } + + return mit_percentage; +} + +void Entity::AddHate(Entity* attacker, sint32 hate) { + if(!attacker || GetHP() <= 0 || attacker->GetHP() <= 0) + return; + + // If a players pet and protect self is off + if (IsPet() && ((NPC*)this)->GetOwner() && ((NPC*)this)->GetOwner()->IsPlayer() && ((((Player*)((NPC*)this)->GetOwner())->GetInfoStruct()->get_pet_behavior() & 2) == 0)) + return; + + hate = attacker->CalculateHateAmount(this, hate); + + if (IsNPC() && ((NPC*)this)->Brain()) { + LogWrite(COMBAT__DEBUG, 3, "Combat", "Add NPC_AI Hate: Victim '%s', Attacker '%s', Hate: %i", GetName(), attacker->GetName(), hate); + ((NPC*)this)->Brain()->AddHate(attacker, hate); + int8 loot_state = ((NPC*)this)->GetLockedNoLoot(); + // if encounter size is 0 then add the attacker to the encounter + if ((loot_state != ENCOUNTER_STATE_AVAILABLE && loot_state != ENCOUNTER_STATE_BROKEN && ((NPC*)this)->Brain()->GetEncounterSize() == 0)) { + ((NPC*)this)->Brain()->AddToEncounter(attacker); + AddTargetToEncounter(attacker); + } + } + + if (attacker->GetThreatTransfer() && hate > 0) { + Spawn* transfer_target = (Entity*)GetZone()->GetSpawnByID(attacker->GetThreatTransfer()->Target); + if (transfer_target && transfer_target->IsEntity()) { + sint32 transfered_hate = hate * (attacker->GetThreatTransfer()->Amount / 100); + hate -= transfered_hate; + this->AddHate((Entity*)transfer_target, transfered_hate); + } + } + + // If pet is adding hate add some to the pets owner as well + if (attacker->IsNPC() && ((NPC*)attacker)->IsPet()) + AddHate(((NPC*)attacker)->GetOwner(), 1); + + // If player and player has a pet and protect master is set add hate to the pet + if (IsPlayer() && HasPet() && (((Player*)this)->GetInfoStruct()->get_pet_behavior() & 1)) { + // If we have a combat pet add hate to it + if (((Player*)this)->GetPet()) + AddHate(((Player*)this)->GetPet(), 1); + if (((Player*)this)->GetCharmedPet()) + AddHate(((Player*)this)->GetCharmedPet(), 1); + } + + // If this spawn has a spawn group then add the attacker to the hate list of the other + // group members if not already in their list + if (HasSpawnGroup()) { + vector* group = GetSpawnGroup(); + vector::iterator itr; + for (itr = group->begin(); itr != group->end(); itr++) { + if (!(*itr)->IsNPC()) + continue; + NPC* spawn = (NPC*)(*itr); + if (spawn->Brain()->GetHate(attacker) == 0) + spawn->Brain()->AddHate(attacker, 1); + } + safe_delete(group); + } +} + +bool Entity::CheckFizzleSpell(LuaSpell* spell) { + if(!spell || !rule_manager.GetGlobalRule(R_Spells, EnableFizzleSpells)->GetInt8() + || spell->spell->GetSpellData()->can_fizzle == false) + return false; + + float fizzleMaxSkill = rule_manager.GetGlobalRule(R_Spells, FizzleMaxSkill)->GetFloat(); + float baseFizzle = rule_manager.GetGlobalRule(R_Spells, DefaultFizzleChance)->GetFloat()/100.0f; // 10% + float skillObtained = rule_manager.GetGlobalRule(R_Spells, FizzleDefaultSkill)->GetFloat(); // default of .2f so we don't go over the threshold if no skill + Skill* skill = GetSkillByID(spell->spell->GetSpellData()->mastery_skill, false); + if(skill && spell->spell->GetSpellData()->min_class_skill_req > 0) + { + float skillObtained = skill->current_val / spell->spell->GetSpellData()->min_class_skill_req; + if(skillObtained > fizzleMaxSkill) // 120% over the skill value + { + skillObtained = fizzleMaxSkill; + } + + baseFizzle = (fizzleMaxSkill - skillObtained) * baseFizzle; + + float totalSuccessChance = 1.0f - baseFizzle; + + float randResult = MakeRandomFloat(0.0f, 1.0f); + if(randResult > totalSuccessChance) + return true; + } + + return false; +} + +bool Entity::CheckInterruptSpell(Entity* attacker) { + if(!IsCasting()) + return false; + + Spell* spell = GetZone()->GetSpell(this); + if(!spell || spell->GetSpellData()->interruptable == 0) + return false; + + if(GetInfoStruct()->get_no_interrupt()) + return false; + + //originally base of 30 percent chance to continue casting if attacked + //modified to 50% and added global rule, 30% was too small at starting levels + int8 percent = rule_manager.GetGlobalRule(R_Spells, NoInterruptBaseChance)->GetInt32(); + + float focus_skill_with_bonus = CalculateSkillWithBonus("Focus", ITEM_STAT_FOCUS, true); + + percent += ((1 + focus_skill_with_bonus)/6); + + if(MakeRandomInt(1, 100) > percent) { + LogWrite(COMBAT__DEBUG, 0, "Combat", "'%s' interrupted spell for '%s': %i%%", attacker->GetName(), GetName(), percent); + GetZone()->Interrupted(this, attacker, SPELL_ERROR_INTERRUPTED); + return true; + } + + LogWrite(COMBAT__DEBUG, 0, "Combat", "'%s' failed to interrupt spell for '%s': %i%%", attacker->GetName(), GetName(), percent); + return false; +} + +void Entity::KillSpawn(Spawn* dead, int8 type, int8 damage_type, int16 kill_blow_type) { + if(!dead) + return; + + if (IsPlayer()) { + Client* client = ((Player*)this)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_EnterCombat", client->GetVersion()); + if (packet) { + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + + ((Player*)this)->InCombat(false); + } + + if (IsPlayer() && dead->IsEntity()) + GetZone()->GetSpellProcess()->KillHOBySpawnID(dead->GetID()); + + /* just for sake of not knowing if we are in a read lock, write lock, or no lock + ** say spawnlist is locked (DismissPet arg 3 true), which means RemoveSpawn will remove the id from the spawn_list outside of the potential lock + */ + if (dead->IsPet() && ((NPC*)dead)->GetOwner()) + ((NPC*)dead)->GetOwner()->DismissPet((NPC*)dead, true, true); + else if (dead->IsEntity()) { + // remove all pets for this entity + ((Entity*)dead)->DismissAllPets(false, true); + } + + if (IsCasting()) + GetZone()->Interrupted(this, dead, SPELL_ERROR_NOT_ALIVE); + + LogWrite(COMBAT__DEBUG, 3, "Combat", "Killing '%s'", dead->GetName()); + + // Kill movement for the dead npc so the corpse doesn't move + GetZone()->movementMgr->StopNavigation((Entity*)dead); + dead->ClearRunningLocations(); + dead->CalculateRunningLocation(true); + + GetZone()->KillSpawn(true, dead, this, true, type, damage_type, kill_blow_type); +} + +void Entity::HandleDeathExperienceDebt(Spawn* killer) +{ + if(!IsPlayer()) + return; + + float ruleDebt = 0.0f; + + if(killer && killer->IsPlayer()) + ruleDebt = rule_manager.GetGlobalRule(R_Combat, PVPDeathExperienceDebt)->GetFloat()/100.0f; + else + ruleDebt = rule_manager.GetGlobalRule(R_Combat, DeathExperienceDebt)->GetFloat()/100.0f; + + if(ruleDebt > 0.0f) + { + bool groupDebt = rule_manager.GetGlobalRule(R_Combat, GroupExperienceDebt)->GetBool(); + if(groupDebt && ((Player*)this)->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)this)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + int32 size = members->size(); + float xpDebtPerMember = ruleDebt/(float)size; + deque::iterator itr; + for (itr = members->begin(); itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client && gmi->client->GetPlayer()) { + gmi->client->GetPlayer()->GetInfoStruct()->set_xp_debt(gmi->client->GetPlayer()->GetInfoStruct()->get_xp_debt()+xpDebtPerMember); + gmi->client->GetPlayer()->SetCharSheetChanged(true); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + { + ((Player*)this)->GetInfoStruct()->set_xp_debt(((Player*)this)->GetInfoStruct()->get_xp_debt()+ruleDebt); + ((Player*)this)->SetCharSheetChanged(true); + } + } +} + +void Entity::ProcessCombat() { + // This is a virtual function so when a NPC calls this it will use the NPC::ProcessCombat() version + // and a player will use the Player::ProcessCombat() version, leave this function blank. +} + +void NPC::ProcessCombat() { + MBrain.writelock(__FUNCTION__, __LINE__); + // Check to see if it is time to call the AI again + if (m_brain && GetHP() > 0 && Timer::GetCurrentTime2() >= (m_brain->LastTick() + m_brain->Tick())) { + // Probably never want to use the following log, will spam the console for every NPC in a zone 4 times a second + //LogWrite(NPC_AI__DEBUG, 9, "NPC_AI", "%s is thinking...", GetName()); + m_brain->Think(); + // Set the time for when the brain was last called + m_brain->SetLastTick(Timer::GetCurrentTime2()); + } + MBrain.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::ProcessCombat() { + // if not in combat OR casting a spell OR dazed OR feared return out + if (!EngagedInCombat() || IsCasting() || IsDazed() || IsFeared()) + return; + + //If no target delete combat_target and return out + Spawn* Target = GetZone()->GetSpawnByID(target); + if (!Target) { + combat_target = 0; + if (target > 0) { + SetTarget(0); + } + return; + } + // If is not an entity return out + if (!Target->IsEntity()) + return; + + // Reset combat target + combat_target = 0; + + if (Target->HasTarget()) { + if (Target->IsPlayer() || (Target->IsNPC() && Target->IsPet() && ((NPC*)Target)->GetOwner() && ((NPC*)Target)->GetOwner()->IsPlayer())){ + Spawn* secondary_target = Target->GetTarget(); + if (secondary_target->IsNPC() && secondary_target->appearance.attackable) { + if (!secondary_target->IsPet() || (secondary_target->IsPet() && ((NPC*)secondary_target)->GetOwner() && ((NPC*)secondary_target)->GetOwner()->IsNPC())) { + combat_target = secondary_target; + } + } + } + } + + // If combat_target wasn't set in the above if set it to the original target + if (!combat_target) + combat_target = Target; + + // this if may not be required as at the min combat_target will be Target, which we already check at the begining + if(!combat_target) + return; + + float distance = 0; + distance = GetDistance(combat_target); + + // Check to see if we are doing ranged auto attacks if not check to see if we are in melee range + if (GetRangeAttack()) { + // We are doing ranged auto attacks + + //check to see if we can attack the target AND the ranged weapon is ready + if(AttackAllowed((Entity*)combat_target, distance, true) && RangeWeaponReady()) { + Item* weapon = 0; + Item* ammo = 0; + // Get the currently equiped weapon and ammo for the ranged attack + weapon = GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); + ammo = GetAmmoFromSlot(true, true); + LogWrite(COMBAT__DEBUG, 1, "Combat", "Weapon '%s', Ammo '%s'", ( weapon )? weapon->name.c_str() : "None", ( ammo ) ? ammo->name.c_str() : "None"); + + // If weapon and ammo are both valid perform the ranged attack else send a message to the client + if(weapon && ammo) { + LogWrite(COMBAT__DEBUG, 1, "Combat", "Weapon: Primary, Fighter: '%s', Target: '%s', Distance: %.2f", GetName(), combat_target->GetName(), distance); + RangeAttack(combat_target, distance, weapon, ammo); + } + else { + Client* client = ((Player*)this)->GetClient(); + if (client) { + // Need to get messages from live, made these up so the player knows what is wrong in game if weapon or ammo are not valid + if (!ammo) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Out of ammo."); + if (!weapon) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No ranged weapon found."); + + } + } + } + } + else if(distance <= rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) { + // We are doing melee auto attacks and are within range + + // Check to see if we can attack the target + if(AttackAllowed((Entity*)combat_target)) { + // Check to see if the primary melee weapon is ready + if(PrimaryWeaponReady()) { + // Set the time of the last melee attack with the primary weapon and perform the melee attack with primary weapon + SetPrimaryLastAttackTime(Timer::GetCurrentTime2()); + MeleeAttack(combat_target, distance, true); + } + // Check to see if the secondary weapon is ready + if(SecondaryWeaponReady()) { + // set the time of the last melee attack with the secondary weapon and perform the melee attack with the secondary weapon + SetSecondaryLastAttackTime(Timer::GetCurrentTime2()); + MeleeAttack(combat_target, distance, false); + } + } + } +} + +void Entity::SetAttackDelay(bool primary, bool ranged) { + float mod = CalculateAttackSpeedMod(); + bool dual_wield = IsDualWield(); + + //Note: Capping all attack speed increases at 125% normal speed (from function CalculateAttackSpeedMod()) + //Add 33% longer delay if dual wielding + if(dual_wield && ! ranged) { + if(primary) + SetPrimaryAttackDelay((GetPrimaryWeaponDelay() * 1.33) / mod); + else + SetSecondaryAttackDelay((GetSecondaryWeaponDelay() * 1.33) / mod); + } + else { + if(primary) + SetPrimaryAttackDelay(GetPrimaryWeaponDelay() / mod); + else if(ranged) + SetRangeAttackDelay(GetRangeWeaponDelay() / mod); + else + SetSecondaryAttackDelay(GetSecondaryWeaponDelay() / mod); + } +} + +float Entity::CalculateAttackSpeedMod(){ + float aspeed = info_struct.get_attackspeed(); + + if(aspeed > 0) { + if (aspeed <= 100) + return (aspeed / 100 + 1); + else if (aspeed <= 200) + return 2.25; + } + return 1; +} + +void Entity::AddProc(int8 type, float chance, Item* item, LuaSpell* spell, int8 damage_type, int8 hp_ratio, bool below_health, bool target_health, bool extended_version) { + if (type == 0) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::AddProc called with an invalid type."); + return; + } + + if (!item && !spell) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::AddProc must have a valid item or spell."); + return; + } + + MProcList.writelock(__FUNCTION__, __LINE__); + Proc* proc = new Proc(); + proc->chance = chance; + proc->item = item; + proc->spell = spell; + proc->spellid = spell->spell->GetSpellID(); + proc->health_ratio = hp_ratio; + proc->below_health = below_health; + proc->damage_type = damage_type; + proc->target_health = target_health; + proc->extended_version = extended_version; + m_procList[type].push_back(proc); + MProcList.releasewritelock(__FUNCTION__, __LINE__); +} + +void Entity::RemoveProc(Item* item, LuaSpell* spell) { + if (!item && !spell) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::RemoveProc must have a valid item or spell."); + return; + } + + MProcList.writelock(__FUNCTION__, __LINE__); + map >::iterator proc_itr; + vector::iterator itr; + for (proc_itr = m_procList.begin(); proc_itr != m_procList.end(); proc_itr++) { + itr = proc_itr->second.begin(); + while (itr != proc_itr->second.end()) { + Proc* proc = *itr; + + if ((item && proc->item == item) || (spell && proc->spell == spell)) { + itr = proc_itr->second.erase(itr); + safe_delete(proc); + } + else + itr++; + } + } + MProcList.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Entity::CastProc(Proc* proc, int8 type, Spawn* target) { + lua_State* state = 0; + bool item_proc = false; + int8 num_args = 3; + + if (proc->spell) { + state = proc->spell->state; + } + else if (proc->item) { + state = lua_interface->GetItemScript(proc->item->GetItemScript()); + item_proc = true; + } + + if (!state) { + LogWrite(COMBAT__ERROR, 0, "Proc", "No valid lua_State* found"); + return false; + } + + if(proc->extended_version) { + lua_getglobal(state, "proc_ext"); + } + else { + lua_getglobal(state, "proc"); + } + if (item_proc) { + num_args++; + lua_interface->SetItemValue(state, proc->item); + } + + lua_interface->SetSpawnValue(state, this); + lua_interface->SetSpawnValue(state, target); + lua_interface->SetInt32Value(state, type); + lua_interface->SetInt32Value(state, proc->damage_type); + + /* + Add spell data from db in case of a spell proc here... + */ + if (!item_proc) { + // Append spell data to the param list + vector* data = proc->spell->spell->GetLUAData(); + for(int32 i = 0; i < data->size(); i++) { + switch(data->at(i)->type) { + case 0:{ + lua_interface->SetSInt32Value(proc->spell->state, data->at(i)->int_value); + break; + } + case 1:{ + lua_interface->SetFloatValue(proc->spell->state, data->at(i)->float_value); + break; + } + case 2:{ + lua_interface->SetBooleanValue(proc->spell->state, data->at(i)->bool_value); + break; + } + case 3:{ + lua_interface->SetStringValue(proc->spell->state, data->at(i)->string_value.c_str()); + break; + } + default:{ + LogWrite(SPELL__ERROR, 0, "Spell", "Error: Unknown LUA Type '%i' in Entity::CastProc for Spell '%s'", (int)data->at(i)->type, proc->spell->spell->GetName()); + return false; + } + } + num_args++; + } + } + + if (lua_pcall(state, num_args, 0, 0) != 0) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Unable to call the proc function for spell %i tier %i", proc->spell->spell->GetSpellID(), proc->spell->spell->GetSpellTier()); + lua_pop(state, 1); + return false; + } + + return true; +} + +void Entity::CheckProcs(int8 type, Spawn* target) { + if (type == 0) { + LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::CheckProcs called with an invalid type."); + return; + } + + vector tmpList; + + MProcList.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < m_procList[type].size(); i++) { + // roll per proc, not overall + float roll = MakeRandomFloat(0, 100); + Proc* proc = m_procList[type].at(i); + if (roll <= proc->chance && + ((!proc->extended_version || proc->health_ratio == 0) || + (proc->below_health && !proc->target_health && proc->health_ratio < (int8)GetIntHPRatio()) || + (!proc->below_health && !proc->target_health && proc->health_ratio >= (int8)GetIntHPRatio()) || + (target && proc->below_health && proc->target_health && proc->health_ratio < (int8)target->GetIntHPRatio()) || + (target && !proc->below_health && proc->target_health && proc->health_ratio >= (int8)target->GetIntHPRatio())) + ) + { + Proc* tmpProc = new Proc(); + tmpProc->chance = proc->chance; + tmpProc->item = proc->item; + tmpProc->spell = proc->spell; + tmpProc->spellid = proc->spellid; + tmpProc->damage_type = proc->damage_type; + tmpProc->health_ratio = proc->health_ratio; + tmpProc->below_health = proc->below_health; + tmpProc->target_health = proc->target_health; + tmpProc->extended_version = proc->extended_version; + tmpList.push_back(tmpProc); + } + } + MProcList.releasereadlock(__FUNCTION__, __LINE__); + + + vector::iterator proc_itr; + for (proc_itr = tmpList.begin(); proc_itr != tmpList.end();) { + Proc* tmpProc = *proc_itr; + CastProc(tmpProc, type, target); + proc_itr++; + safe_delete(tmpProc); + } +} + +void Entity::ClearProcs() { + MProcList.writelock(__FUNCTION__, __LINE__); + + map >::iterator proc_itr; + vector::iterator itr; + for (proc_itr = m_procList.begin(); proc_itr != m_procList.end(); proc_itr++) { + itr = proc_itr->second.begin(); + while (itr != proc_itr->second.end()) { + safe_delete(*itr); + itr = proc_itr->second.erase(itr); + } + proc_itr->second.clear(); + } + m_procList.clear(); + + MProcList.releasewritelock(__FUNCTION__, __LINE__); +} + +sint32 Entity::CalculateHateAmount(Spawn* target, sint32 amt) { + amt = CalculateFormulaByStat(amt, ITEM_STAT_TAUNT_AMOUNT); + + amt = CalculateFormulaByStat(amt, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE); + + amt = CalculateFormulaByStat(amt, ITEM_STAT_ABILITY_MODIFIER); + + return amt; +} + +sint32 Entity::CalculateHealAmount(Spawn* target, sint32 amt, int8 crit_mod, bool* crit, bool skip_crit_mod) { + amt = CalculateFormulaByStat(amt, ITEM_STAT_HEAL_AMOUNT); + + amt = CalculateFormulaByStat(amt, ITEM_STAT_SPELL_AND_HEAL); + + //Potency Mod + amt = CalculateFormulaByStat(amt, ITEM_STAT_POTENCY); + + //Ability Mod + amt += (int32)min((int32)GetInfoStruct()->get_ability_modifier(), (int32)(amt / 2)); + + if(!skip_crit_mod){ + if(!crit_mod || crit_mod == 1){ + if(crit_mod == 1) + *crit = true; + else if(!*crit) { + // Crit Roll + float chance = (float)max((float)0, (float)GetInfoStruct()->get_crit_chance()); + *crit = (MakeRandomFloat(0, 100) <= chance); + } + if(*crit){ + //Apply total crit multiplier with crit bonus + amt *= ((GetInfoStruct()->get_crit_bonus() / 100) + 1.3); + } + } + } + + return amt; +} + +sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, LuaSpell* spell) { + return CalculateDamageAmount(target, damage, base_type, damage_type, (spell && spell->spell) ? spell->spell->GetSpellData()->target_type : 0); +} + +sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, int8 target_type) { + if(damage_type == DAMAGE_PACKET_DAMAGE_TYPE_FOCUS) { + return damage; // cannot avoid focus damage + } + + // only spells may add spell damage item stat + if(damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_HEAT && damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_POISON) + { + // https://forums.daybreakgames.com/eq2/index.php?threads/potency-and-ability-mod-what.4316/ + // Spell damage model assuming 100% crit chance: + // (Spell base damage * Base damage modifier * Int bonus * Skill bonus * Potency + Ability modifier) * Crit bonus * Spell double attack + /** Spell base damage: Get all master spells + Base damage modifier: Very very rare. Available from Wizard aa Wisdom line(Brainstorm). Get it, cherish it. + Int bonus: Past 1200 or so int, you will get a 10% increase in spell damage per 30% increase in int. Look at int tooltip to see the numerical value. + Skill bonus: Past skill cap, 100 points skill is worth 2% increase in minimum spell damage, which translates to 1% overall increase. Looks at the skill that the spell uses, for wizards mostly disruption. Cap is 6.5*level. + Potency: A straight damage modifier, the more you can get the better. No practical cap. + Ability modifier: A straight damage modifier. Limited to 50% of the spell base damage, but for a wizard this usually has little consequence. However note that this is not affected by Potency. + Crit bonus: A straight damage modifier, the more you can get the better. No practical cap. Wizards got 50% intrinsic Crit Bonus that does not show up in the stat window, just add 50 to stat value for calculations. + Spell double cast: A straight damage modifier, the more you can get the better. You won't be able to get very much of this. + Makes the spell cast twice with some limitations. + **/ + damage = damage + damage * CalculateLevelStatBonus(GetInt()); // lvl 70 * 1200 int = 84000, log10f(84000) = 4.924 / 50.0f = 0.09848 + damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_DAMAGE); + } + + if(base_type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE) { + damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE); + + if(target && target->IsEntity() && damage > 0) { + int16 effective_level_attacker = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + float resistBase = ((Entity*)target)->GetDamageTypeResistPercentage(damage_type); + if(resistBase > 1.0f) { + float dmgReduction = ((Entity*)target)->CalculateSpellDamageReduction((float)damage, resistBase - 1.0f, effective_level_attacker); + float newDamage = static_cast(damage) - dmgReduction; + if(newDamage < 0.0f) + newDamage = 0.0f; + damage = static_cast(std::floor(newDamage)); + } + } + } + + // combat abilities only bonus + if(damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE) { + damage = CalculateFormulaByStat(damage, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE); + } + + // Potency mod + damage = CalculateFormulaByStat(damage, ITEM_STAT_POTENCY); + + int32 modifier = 2; + if(target_type == SPELL_TARGET_GROUP_AE || target_type == SPELL_TARGET_RAID_AE || target_type == SPELL_TARGET_OTHER_GROUP_AE) + { + modifier = 3; + } + + // Ability mod can only give up to half of damage after potency + int32 mod = (int32)min(info_struct.get_ability_modifier(), (float)(damage / modifier)); + damage += mod; + + return damage; +} + +sint32 Entity::CalculateFormulaByStat(sint32 value, int16 stat) { + sint32 outValue = value; + MStats.lock(); + std::map::iterator itr = stats.find(stat); + if(itr != stats.end()) + outValue = (sint32)((float)value * ((itr->second / 100.0f) + 1.0f)); + MStats.unlock(); + + return outValue; +} + +int32 Entity::CalculateFormulaByStat(int32 value, int16 stat) { + int32 outValue = value; + MStats.lock(); + std::map::iterator itr = stats.find(stat); + if(itr != stats.end()) + outValue = (int32)((float)value * ((itr->second / 100.0f) + 1.0f)); + MStats.unlock(); + + return outValue; +} + +int32 Entity::CalculateFormulaBonus(int32 value, float percent_bonus) { + return (int32)((float)value * ((percent_bonus / 100.0f) + 1.0f)); +} + +float Entity::CalculateSpellDamageReduction(float spellDamage, float resistancePercentage, int16 attackerLevel) { + int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + float levelDifference = std::abs(effective_level - attackerLevel); + float logFactor = 1.0f / (1.0f + 0.1f * levelDifference); // Adjust the factor for smoother reduction + + // Calculate the actual damage reduction based on resistance percentage, logarithmic factor, and maximum reduction + float reducedDamage = spellDamage * (1.0f - resistancePercentage) * logFactor * GetInfoStruct()->get_max_spell_reduction(); + return reducedDamage; +} \ No newline at end of file diff --git a/source/WorldServer/Combat.h b/source/WorldServer/Combat.h new file mode 100644 index 0000000..627aae1 --- /dev/null +++ b/source/WorldServer/Combat.h @@ -0,0 +1,39 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_COMBAT_H__ +#define __EQ2_COMBAT_H__ +#include "Player.h" +#include "../common/timer.h" +#include "NPC_AI.h" +#include "MutexList.h" + +#define COMBAT_NORMAL_FIGHTER 0 +#define COMBAT_ADD_FIGHTER 1 +#define COMBAT_REMOVE_FIGHTER 2 +// replace with rule rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat() +//#define MAX_COMBAT_RANGE 3 + +class ZoneServer; +class SpellProcess; +class LuaSpell; + + +#endif + diff --git a/source/WorldServer/Commands/Commands.cpp b/source/WorldServer/Commands/Commands.cpp new file mode 100644 index 0000000..349f019 --- /dev/null +++ b/source/WorldServer/Commands/Commands.cpp @@ -0,0 +1,12380 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ +#include +#include +#include + +#include "Commands.h" +#include "../ClientPacketFunctions.h" +#include "../../common/version.h" +#include "../../common/seperator.h" +#include "../../common/servertalk.h" +#include "../WorldDatabase.h" +#include "../World.h" +#include "../../common/ConfigReader.h" +#include "../VisualStates.h" +#include "../../common/debug.h" +#include "../LuaInterface.h" +#include "../Quests.h" +#include "../client.h" +#include "../NPC.h" +#include "../Guilds/Guild.h" +#include "../SpellProcess.h" +#include "../Tradeskills/Tradeskills.h" +#include "../../common/Log.h" +#include "../../common/MiscFunctions.h" +#include "../Languages.h" +#include "../Traits/Traits.h" +#include "../Chat/Chat.h" +#include "../Rules/Rules.h" +#include "../AltAdvancement/AltAdvancement.h" +#include "../RaceTypes/RaceTypes.h" +#include "../classes.h" +#include "../Transmute.h" +#include "../Bots/Bot.h" + +extern WorldDatabase database; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern MasterRecipeList master_recipe_list; +extern MasterRecipeBookList master_recipebook_list; +extern World world; +extern ClientList client_list; +extern ConfigReader configReader; +extern VisualStates visual_states; +extern ZoneList zone_list; +extern LuaInterface* lua_interface; +extern MasterQuestList master_quest_list; +extern MasterCollectionList master_collection_list; +extern MasterSkillList master_skill_list; +extern MasterFactionList master_faction_list; +extern GuildList guild_list; +extern MasterLanguagesList master_languages_list; +extern Chat chat; +extern RuleManager rule_manager; +extern MasterAAList master_aa_list; +extern MasterRaceTypeList race_types_list; +extern Classes classes; + +//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp +#if defined(__GNUC__) +#define stricmp strcasecmp +#define strnicmp strncasecmp +#endif + + +EQ2Packet* RemoteCommands::serialize(int16 version){ + buffer.clear(); + vector::iterator command_list; + buffer.append((char*)&num_commands, sizeof(int16)); + for( command_list = commands.begin(); command_list != commands.end(); command_list++ ) { + AddDataCommand(&(*command_list)); + } + EQ2Packet* app = new EQ2Packet(OP_SetRemoteCmdsMsg, (uchar*)buffer.c_str(), buffer.length() + 1); + return app; +} + +Commands::Commands(){ + remote_commands = new RemoteCommands(); + spawn_set_values["list"] = SPAWN_SET_VALUE_LIST; + spawn_set_values["name"] = SPAWN_SET_VALUE_NAME; + spawn_set_values["level"] = SPAWN_SET_VALUE_LEVEL; // TODO: Fix this, min_level, max_level + spawn_set_values["difficulty"] = SPAWN_SET_VALUE_DIFFICULTY; + spawn_set_values["model_type"] = SPAWN_SET_VALUE_MODEL_TYPE; + spawn_set_values["class"] = SPAWN_SET_VALUE_CLASS; + spawn_set_values["gender"] = SPAWN_SET_VALUE_GENDER; + spawn_set_values["show_name"] = SPAWN_SET_VALUE_SHOW_NAME; + spawn_set_values["attackable"] = SPAWN_SET_VALUE_ATTACKABLE; + spawn_set_values["show_level"] = SPAWN_SET_VALUE_SHOW_LEVEL; + spawn_set_values["targetable"] = SPAWN_SET_VALUE_TARGETABLE; + spawn_set_values["show_command_icon"] = SPAWN_SET_VALUE_SHOW_COMMAND_ICON; + spawn_set_values["display_hand_icon"] = SPAWN_SET_VALUE_HAND_ICON; + spawn_set_values["hair_type"] = SPAWN_SET_VALUE_HAIR_TYPE; + spawn_set_values["facial_hair_type"] = SPAWN_SET_VALUE_FACIAL_HAIR_TYPE; + spawn_set_values["wing_type"] = SPAWN_SET_VALUE_WING_TYPE; + spawn_set_values["chest_type"] = SPAWN_SET_VALUE_CHEST_TYPE; + spawn_set_values["legs_type"] = SPAWN_SET_VALUE_LEGS_TYPE; + spawn_set_values["soga_hair_type"] = SPAWN_SET_VALUE_SOGA_HAIR_TYPE; + spawn_set_values["soga_facial_hair_type"] = SPAWN_SET_VALUE_SOGA_FACIAL_HAIR_TYPE; + spawn_set_values["soga_model_type"] = SPAWN_SET_VALUE_SOGA_MODEL_TYPE; + spawn_set_values["size"] = SPAWN_SET_VALUE_SIZE; + spawn_set_values["hp"] = SPAWN_SET_VALUE_HP; + spawn_set_values["power"] = SPAWN_SET_VALUE_POWER; + spawn_set_values["heroic"] = SPAWN_SET_VALUE_HEROIC; + spawn_set_values["respawn"] = SPAWN_SET_VALUE_RESPAWN; + spawn_set_values["x"] = SPAWN_SET_VALUE_X; + spawn_set_values["y"] = SPAWN_SET_VALUE_Y; + spawn_set_values["z"] = SPAWN_SET_VALUE_Z; + spawn_set_values["heading"] = SPAWN_SET_VALUE_HEADING; + spawn_set_values["location"] = SPAWN_SET_VALUE_LOCATION; + spawn_set_values["command_primary"] = SPAWN_SET_VALUE_COMMAND_PRIMARY; + spawn_set_values["command_secondary"] = SPAWN_SET_VALUE_COMMAND_SECONDARY; + spawn_set_values["visual_state"] = SPAWN_SET_VALUE_VISUAL_STATE; + spawn_set_values["action_state"] = SPAWN_SET_VALUE_ACTION_STATE; + spawn_set_values["mood_state"] = SPAWN_SET_VALUE_MOOD_STATE; + spawn_set_values["initial_state"] = SPAWN_SET_VALUE_INITIAL_STATE; + spawn_set_values["activity_state"] = SPAWN_SET_VALUE_ACTIVITY_STATE; + spawn_set_values["collision_radius"] = SPAWN_SET_VALUE_COLLISION_RADIUS; + spawn_set_values["faction"] = SPAWN_SET_VALUE_FACTION; + spawn_set_values["spawn_script"] = SPAWN_SET_VALUE_SPAWN_SCRIPT; + spawn_set_values["spawnentry_script"] = SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT; + spawn_set_values["spawnlocation_script"] = SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT; + spawn_set_values["sub_title"] = SPAWN_SET_VALUE_SUB_TITLE; + spawn_set_values["expire"] = SPAWN_SET_VALUE_EXPIRE; + spawn_set_values["expire_offset"] = SPAWN_SET_VALUE_EXPIRE_OFFSET; + spawn_set_values["x_offset"] = SPAWN_SET_VALUE_X_OFFSET; + spawn_set_values["y_offset"] = SPAWN_SET_VALUE_Y_OFFSET; + spawn_set_values["z_offset"] = SPAWN_SET_VALUE_Z_OFFSET; + spawn_set_values["device_id"] = SPAWN_SET_VALUE_DEVICE_ID; + spawn_set_values["pitch"] = SPAWN_SET_VALUE_PITCH; + spawn_set_values["roll"] = SPAWN_SET_VALUE_ROLL; + spawn_set_values["hide_hood"] = SPAWN_SET_VALUE_HIDE_HOOD; + spawn_set_values["emote_state"] = SPAWN_SET_VALUE_EMOTE_STATE; + spawn_set_values["icon"] = SPAWN_SET_VALUE_ICON; + spawn_set_values["prefix"] = SPAWN_SET_VALUE_PREFIX; + spawn_set_values["suffix"] = SPAWN_SET_VALUE_SUFFIX; + spawn_set_values["lastname"] = SPAWN_SET_VALUE_LASTNAME; + spawn_set_values["expansion_flag"] = SPAWN_SET_VALUE_EXPANSION_FLAG; + spawn_set_values["holiday_flag"] = SPAWN_SET_VALUE_HOLIDAY_FLAG; + spawn_set_values["merchant_min_level"] = SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL; + spawn_set_values["merchant_max_level"] = SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL; + spawn_set_values["skin_color"] = SPAWN_SET_SKIN_COLOR; + spawn_set_values["aaxp_rewards"] = SPAWN_SET_AAXP_REWARDS; + + spawn_set_values["hair_color1"] = SPAWN_SET_HAIR_COLOR1; + spawn_set_values["hair_color2"] = SPAWN_SET_HAIR_COLOR2; + spawn_set_values["hair_type_color"] = SPAWN_SET_HAIR_TYPE_COLOR; + spawn_set_values["hair_face_color"] = SPAWN_SET_HAIR_FACE_COLOR; + spawn_set_values["hair_type_highlight_color"] = SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR; + spawn_set_values["face_hairlight_color"] = SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR; + spawn_set_values["hair_highlight"] = SPAWN_SET_HAIR_HIGHLIGHT; + spawn_set_values["model_color"] = SPAWN_SET_MODEL_COLOR; + spawn_set_values["eye_color"] = SPAWN_SET_EYE_COLOR; + + spawn_set_values["soga_skin_color"] = SPAWN_SET_SOGA_SKIN_COLOR; + spawn_set_values["soga_hair_color1"] = SPAWN_SET_SOGA_HAIR_COLOR1; + spawn_set_values["soga_hair_color2"] = SPAWN_SET_SOGA_HAIR_COLOR2; + spawn_set_values["soga_hair_type_color"] = SPAWN_SET_SOGA_HAIR_TYPE_COLOR; + spawn_set_values["soga_hair_face_color"] = SPAWN_SET_SOGA_HAIR_FACE_COLOR; + spawn_set_values["soga_hair_type_highlight_color"] = SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR; + spawn_set_values["soga_face_hairlight_color"] = SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR; + spawn_set_values["soga_hair_highlight"] = SPAWN_SET_SOGA_HAIR_HIGHLIGHT; + spawn_set_values["soga_model_color"] = SPAWN_SET_SOGA_MODEL_COLOR; + spawn_set_values["soga_eye_color"] = SPAWN_SET_SOGA_EYE_COLOR; + + spawn_set_values["cheek_type"] = SPAWN_SET_CHEEK_TYPE; + spawn_set_values["chin_type"] = SPAWN_SET_CHIN_TYPE; + spawn_set_values["ear_type"] = SPAWN_SET_EAR_TYPE; + spawn_set_values["eye_brow_type"] = SPAWN_SET_EYE_BROW_TYPE; + spawn_set_values["eye_type"] = SPAWN_SET_EYE_TYPE; + spawn_set_values["lip_type"] = SPAWN_SET_LIP_TYPE; + spawn_set_values["nose_type"] = SPAWN_SET_NOSE_TYPE; + spawn_set_values["body_size"] = SPAWN_SET_BODY_SIZE; + spawn_set_values["body_age"] = SPAWN_SET_BODY_AGE; + spawn_set_values["soga_cheek_type"] = SPAWN_SET_SOGA_CHEEK_TYPE; + spawn_set_values["soga_chin_type"] = SPAWN_SET_SOGA_CHIN_TYPE; + spawn_set_values["soga_ear_type"] = SPAWN_SET_SOGA_EAR_TYPE; + spawn_set_values["soga_eye_brow_type"] = SPAWN_SET_SOGA_EYE_BROW_TYPE; + spawn_set_values["soga_eye_type"] = SPAWN_SET_SOGA_EYE_TYPE; + spawn_set_values["soga_lip_type"] = SPAWN_SET_SOGA_LIP_TYPE; + spawn_set_values["soga_nose_type"] = SPAWN_SET_SOGA_NOSE_TYPE; + spawn_set_values["soga_body_size"] = SPAWN_SET_SOGA_BODY_SIZE; + spawn_set_values["soga_body_age"] = SPAWN_SET_SOGA_BODY_AGE; + spawn_set_values["attack_type"] = SPAWN_SET_ATTACK_TYPE; + spawn_set_values["race_type"] = SPAWN_SET_RACE_TYPE; + spawn_set_values["loot_tier"] = SPAWN_SET_LOOT_TIER; + spawn_set_values["loot_drop_type"] = SPAWN_SET_LOOT_DROP_TYPE; + spawn_set_values["scared_strong_players"] = SPAWN_SET_SCARED_STRONG_PLAYERS; + + zone_set_values["expansion_id"] = ZONE_SET_VALUE_EXPANSION_ID; + zone_set_values["name"] = ZONE_SET_VALUE_NAME; + zone_set_values["file"] = ZONE_SET_VALUE_FILE ; + zone_set_values["description"] = ZONE_SET_VALUE_DESCRIPTION; + zone_set_values["safe_x"] = ZONE_SET_VALUE_SAFE_X; + zone_set_values["safe_y"] = ZONE_SET_VALUE_SAFE_Y; + zone_set_values["safe_z"] = ZONE_SET_VALUE_SAFE_Z; + zone_set_values["underworld"] = ZONE_SET_VALUE_UNDERWORLD; + zone_set_values["min_recommended"] = ZONE_SET_VALUE_MIN_RECOMMENDED; + zone_set_values["max_recommended"] = ZONE_SET_VALUE_MAX_RECOMMENDED; + zone_set_values["zone_type"] = ZONE_SET_VALUE_ZONE_TYPE; + zone_set_values["always_loaded"] = ZONE_SET_VALUE_ALWAYS_LOADED; + zone_set_values["city_zone"] = ZONE_SET_VALUE_CITY_ZONE; + zone_set_values["weather_allowed"] = ZONE_SET_VALUE_WEATHER_ALLOWED; + zone_set_values["min_status"] = ZONE_SET_VALUE_MIN_STATUS; + zone_set_values["min_level"] = ZONE_SET_VALUE_MIN_LEVEL; + zone_set_values["start_zone"] = ZONE_SET_VALUE_START_ZONE; + zone_set_values["instance_type"] = ZONE_SET_VALUE_INSTANCE_TYPE; + zone_set_values["default_reenter_time"] = ZONE_SET_VALUE_DEFAULT_REENTER_TIME; + zone_set_values["default_reset_time"] = ZONE_SET_VALUE_DEFAULT_RESET_TIME; + zone_set_values["default_lockout_time"] = ZONE_SET_VALUE_DEFAULT_LOCKOUT_TIME; + zone_set_values["force_group_to_zone"] = ZONE_SET_VALUE_FORCE_GROUP_TO_ZONE; + zone_set_values["lua_script"] = ZONE_SET_VALUE_LUA_SCRIPT; + zone_set_values["shutdown_timer"] = ZONE_SET_VALUE_SHUTDOWN_TIMER; + zone_set_values["zone_motd"] = ZONE_SET_VALUE_ZONE_MOTD; +} + +Commands::~Commands() { + safe_delete(remote_commands); +} + +int32 Commands::GetSpawnSetType(string val){ + if(spawn_set_values.count(val) > 0) + return spawn_set_values[val]; + return 0xFFFFFFFF; +} + +bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value, bool send_update, bool temporary, string* temp_value, int8 index){ + if(!target) + return false; + int32 val = 0; + try{ + if(type != SPAWN_SET_VALUE_NAME && + !(type >= SPAWN_SET_VALUE_SPAWN_SCRIPT && type <= SPAWN_SET_VALUE_SUB_TITLE) && !(type >= SPAWN_SET_VALUE_PREFIX && type <= SPAWN_SET_VALUE_EXPANSION_FLAG || type == SPAWN_SET_VALUE_HOLIDAY_FLAG)) + { + switch(type) + { + case SPAWN_SET_SKIN_COLOR: + case SPAWN_SET_HAIR_COLOR1: + case SPAWN_SET_HAIR_COLOR2: + case SPAWN_SET_HAIR_TYPE_COLOR: + case SPAWN_SET_HAIR_FACE_COLOR: + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_HIGHLIGHT: + case SPAWN_SET_EYE_COLOR: + case SPAWN_SET_SOGA_SKIN_COLOR: + case SPAWN_SET_SOGA_HAIR_COLOR1: + case SPAWN_SET_SOGA_HAIR_COLOR2: + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + case SPAWN_SET_SOGA_EYE_COLOR: + case SPAWN_SET_RACE_TYPE: + // ignore these are colors can't pass as a integer value + break; + default: + val = atoul(value); + } + } + } + catch(...){ + if(client) + client->Message(CHANNEL_COLOR_RED, "Invalid numeric spawn value: %s", value); + return false; + } + if(temporary){ + char tmp[128] = {0}; + switch(type){ + case SPAWN_SET_VALUE_NAME:{ + sprintf(tmp, "%s", target->GetName()); + target->SetName(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_X_OFFSET: + { + sprintf(tmp, "%f", target->GetXOffset()); + target->SetXOffset(float(val)); + break; + } + case SPAWN_SET_VALUE_Y_OFFSET: + { + sprintf(tmp, "%f", target->GetYOffset()); + target->SetYOffset(float(val)); + break; + } + case SPAWN_SET_VALUE_Z_OFFSET: + { + sprintf(tmp, "%f", target->GetZOffset()); + target->SetZOffset(float(val)); + break; + } + case SPAWN_SET_VALUE_EXPIRE: { + sprintf(tmp, "%u", target->GetExpireTime()); + target->SetExpireTime(val); + break; + } + case SPAWN_SET_VALUE_EXPIRE_OFFSET: { + sprintf(tmp, "%u", target->GetExpireOffsetTime()); + target->SetExpireOffsetTime(val); + break; + } + case SPAWN_SET_VALUE_SUB_TITLE: { + sprintf(tmp, "%s", target->GetSubTitle()); + target->SetSubTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_LEVEL:{ + sprintf(tmp, "%i", target->GetLevel()); + target->SetLevel(val, send_update); + break; + } + case SPAWN_SET_VALUE_DIFFICULTY:{ + sprintf(tmp, "%i", target->GetDifficulty()); + target->SetDifficulty(val, send_update); + break; + } + case SPAWN_SET_VALUE_MODEL_TYPE:{ + sprintf(tmp, "%i", target->GetModelType()); + target->SetModelType(val, send_update); + break; + } + case SPAWN_SET_VALUE_CLASS:{ + sprintf(tmp, "%i", target->GetAdventureClass()); + target->SetAdventureClass(val, send_update); + break; + } + case SPAWN_SET_VALUE_GENDER:{ + sprintf(tmp, "%i", target->GetGender()); + target->SetGender(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_NAME:{ + sprintf(tmp, "%i", target->GetShowName()); + target->SetShowName(val, send_update); + break; + } + case SPAWN_SET_VALUE_ATTACKABLE:{ + sprintf(tmp, "%i", target->GetAttackable()); + target->SetAttackable(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_LEVEL:{ + sprintf(tmp, "%i", target->GetShowLevel()); + target->SetShowLevel(val, send_update); + break; + } + case SPAWN_SET_VALUE_TARGETABLE:{ + sprintf(tmp, "%i", target->GetTargetable()); + target->SetTargetable(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_COMMAND_ICON:{ + sprintf(tmp, "%i", target->GetShowCommandIcon()); + target->SetShowCommandIcon(val, send_update); + break; + } + case SPAWN_SET_VALUE_HAND_ICON:{ + sprintf(tmp, "%i", target->GetShowHandIcon()); + target->SetShowHandIcon(val, send_update); + break; + } + case SPAWN_SET_VALUE_HAIR_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetHairType()); + ((Entity*)target)->SetHairType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_FACIAL_HAIR_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetFacialHairType()); + ((Entity*)target)->SetFacialHairType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_WING_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetWingType()); + ((Entity*)target)->SetWingType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_CHEST_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetChestType()); + ((Entity*)target)->SetChestType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_LEGS_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetLegsType()); + ((Entity*)target)->SetLegsType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_SOGA_HAIR_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetSogaHairType()); + ((Entity*)target)->SetSogaHairType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_SOGA_FACIAL_HAIR_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->GetSogaFacialHairType()); + ((Entity*)target)->SetSogaFacialHairType(val, send_update); + } + break; + } + case SPAWN_SET_VALUE_SOGA_MODEL_TYPE:{ + sprintf(tmp, "%i", target->GetSogaModelType()); + target->SetSogaModelType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SIZE:{ + sprintf(tmp, "%i", target->GetSize()); + target->SetSize(val, send_update); + break; + } + case SPAWN_SET_VALUE_HP:{ + sprintf(tmp, "%i", target->GetHP()); + target->SetTotalHPBase(val); + target->SetHP(val, send_update); + break; + } + case SPAWN_SET_VALUE_POWER:{ + sprintf(tmp, "%i", target->GetPower()); + target->SetTotalPowerBase(val); + target->SetPower(val, send_update); + break; + } + case SPAWN_SET_VALUE_HEROIC:{ + sprintf(tmp, "%i", target->GetHeroic()); + target->SetHeroic(val, send_update); + break; + } + case SPAWN_SET_VALUE_RESPAWN:{ + sprintf(tmp, "%u", target->GetRespawnTime()); + target->SetRespawnTime(val); + break; + } + case SPAWN_SET_VALUE_X:{ + sprintf(tmp, "%f", target->GetX()); + target->SetX(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_Y:{ + sprintf(tmp, "%f", target->GetY()); + target->SetY(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_Z:{ + sprintf(tmp, "%f", target->GetZ()); + target->SetZ(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_HEADING:{ + sprintf(tmp, "%f", target->GetHeading()); + target->SetHeading(atof(value) + 360, send_update); + break; + } + case SPAWN_SET_VALUE_VISUAL_STATE:{ + sprintf(tmp, "%i", target->GetVisualState()); + target->SetVisualState(val, send_update); + break; + } + case SPAWN_SET_VALUE_ACTION_STATE:{ + sprintf(tmp, "%i", target->GetActionState()); + target->SetActionState(val, send_update); + break; + } + case SPAWN_SET_VALUE_MOOD_STATE:{ + sprintf(tmp, "%i", target->GetMoodState()); + target->SetMoodState(val, send_update); + break; + } + case SPAWN_SET_VALUE_INITIAL_STATE:{ + sprintf(tmp, "%i", target->GetInitialState()); + target->SetInitialState(val, send_update); + break; + } + case SPAWN_SET_VALUE_ACTIVITY_STATE:{ + sprintf(tmp, "%i", target->GetActivityStatus()); + target->SetActivityStatus(val, send_update); + break; + } + case SPAWN_SET_VALUE_COLLISION_RADIUS:{ + sprintf(tmp, "%i", target->GetCollisionRadius()); + target->SetCollisionRadius(val, send_update); + break; + } + case SPAWN_SET_VALUE_DEVICE_ID: { + if (target->IsObject()) { + sprintf(tmp, "%i", ((Object*)target)->GetDeviceID()); + ((Object*)target)->SetDeviceID(val); + } + break; + } + case SPAWN_SET_VALUE_PITCH: { + sprintf(tmp, "%f", target->GetPitch()); + target->SetPitch(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_ROLL: { + sprintf(tmp, "%f", target->GetRoll()); + target->SetRoll(atof(value), send_update); + break; + } + case SPAWN_SET_VALUE_HIDE_HOOD: { + sprintf(tmp, "%i", target->appearance.hide_hood); + target->SetHideHood(val); + break; + } + case SPAWN_SET_VALUE_EMOTE_STATE: { + sprintf(tmp, "%i", target->appearance.emote_state); + target->SetEmoteState(val); + break; + } + case SPAWN_SET_VALUE_ICON: { + sprintf(tmp, "%i", target->GetIconValue()); + target->SetIcon(val); + break; + } + + case SPAWN_SET_VALUE_PREFIX: { + sprintf(tmp, "%s", target->GetPrefixTitle()); + target->SetPrefixTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + + case SPAWN_SET_VALUE_SUFFIX: { + sprintf(tmp, "%s", target->GetSuffixTitle()); + target->SetSuffixTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + + case SPAWN_SET_VALUE_LASTNAME: { + sprintf(tmp, "%s", target->GetLastName()); + target->SetLastName(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_EXPANSION_FLAG: + case SPAWN_SET_VALUE_HOLIDAY_FLAG: { + // nothing to do must reload spawns + break; + } + case SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL: { + sprintf(tmp, "%i", target->GetMerchantMinLevel()); + target->SetMerchantLevelRange(atoul(value), target->GetMerchantMaxLevel()); + break; + } + case SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL: { + sprintf(tmp, "%i", target->GetMerchantMaxLevel()); + target->SetMerchantLevelRange(target->GetMerchantMinLevel(), atoul(value)); + break; + } + case SPAWN_SET_VALUE_FACTION:{ + sprintf(tmp, "%i", target->faction_id); + ZoneServer* zone = target->GetZone(); + if (!zone && client) + zone = client->GetCurrentZone(); + + target->faction_id = atoul(value); + if(zone) + { + zone->RemoveDeadEnemyList(target); + if(target->IsNPC()) + zone->AddEnemyList((NPC*)target); + } + break; + } + case SPAWN_SET_SKIN_COLOR: + case SPAWN_SET_HAIR_COLOR1: + case SPAWN_SET_HAIR_COLOR2: + case SPAWN_SET_HAIR_TYPE_COLOR: + case SPAWN_SET_HAIR_FACE_COLOR: + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_HIGHLIGHT: + case SPAWN_SET_MODEL_COLOR: + case SPAWN_SET_EYE_COLOR: + case SPAWN_SET_SOGA_SKIN_COLOR: + case SPAWN_SET_SOGA_HAIR_COLOR1: + case SPAWN_SET_SOGA_HAIR_COLOR2: + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + case SPAWN_SET_SOGA_MODEL_COLOR: + case SPAWN_SET_SOGA_EYE_COLOR: + { + if (target->IsEntity()) + { + Seperator* skinsep = new Seperator(value, ' ', 3, 500, true); + if (skinsep->IsNumber(0) && skinsep->IsNumber(1) && skinsep->IsNumber(2)) + { + EQ2_Color clr; + clr.red = atoul(skinsep->arg[0]); + clr.green = atoul(skinsep->arg[1]); + clr.blue = atoul(skinsep->arg[2]); + + switch(type) + { + case SPAWN_SET_SKIN_COLOR: + ((Entity*)target)->SetSkinColor(clr); + break; + case SPAWN_SET_HAIR_COLOR1: + ((Entity*)target)->SetHairColor1(clr); + break; + case SPAWN_SET_HAIR_COLOR2: + ((Entity*)target)->SetHairColor2(clr); + break; + case SPAWN_SET_HAIR_TYPE_COLOR: + ((Entity*)target)->SetHairColor(clr); + break; + case SPAWN_SET_HAIR_FACE_COLOR: + ((Entity*)target)->SetFacialHairColor(clr); + break; + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetHairTypeHighlightColor(clr); + break; + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetFacialHairHighlightColor(clr); + break; + case SPAWN_SET_HAIR_HIGHLIGHT: + ((Entity*)target)->SetHairHighlightColor(clr); + break; + case SPAWN_SET_MODEL_COLOR: + ((Entity*)target)->SetModelColor(clr); + break; + case SPAWN_SET_EYE_COLOR: + ((Entity*)target)->SetEyeColor(clr); + break; + case SPAWN_SET_SOGA_SKIN_COLOR: + ((Entity*)target)->SetSogaSkinColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_COLOR1: + ((Entity*)target)->SetSogaHairColor1(clr); + break; + case SPAWN_SET_SOGA_HAIR_COLOR2: + ((Entity*)target)->SetSogaHairColor2(clr); + break; + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + ((Entity*)target)->SetSogaHairColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + ((Entity*)target)->SetSogaFacialHairColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetSogaHairTypeHighlightColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetSogaFacialHairHighlightColor(clr); + break; + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + ((Entity*)target)->SetSogaHairHighlightColor(clr); + break; + case SPAWN_SET_SOGA_MODEL_COLOR: + ((Entity*)target)->SetSogaModelColor(clr); + break; + case SPAWN_SET_SOGA_EYE_COLOR: + ((Entity*)target)->SetSogaEyeColor(clr); + break; + } + } + safe_delete(skinsep); + } + break; + } + case SPAWN_SET_AAXP_REWARDS: { + target->SetAAXPRewards(atoul(value)); + break; + } + case SPAWN_SET_CHEEK_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.cheek_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.cheek_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_CHIN_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.chin_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.chin_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_EAR_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.ear_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.ear_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_EYE_BROW_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.eye_brow_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.eye_brow_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_EYE_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.eye_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.eye_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_LIP_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.lip_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.lip_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_NOSE_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.nose_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.nose_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_BODY_SIZE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->features.body_size); + int8 new_value = atoul(value); + ((Entity*)target)->features.body_size = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_BODY_AGE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->features.body_age); + int8 new_value = atoul(value); + ((Entity*)target)->features.body_age = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_CHEEK_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_cheek_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_cheek_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_CHIN_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_chin_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_chin_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_EAR_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_ear_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_ear_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_EYE_BROW_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_eye_brow_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_eye_brow_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_EYE_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_eye_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_eye_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_LIP_TYPE:{ + if(target->IsEntity() && index < 3){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_lip_type[index]); + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_lip_type[index] = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_BODY_SIZE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_body_size); + int8 new_value = atoul(value); + ((Entity*)target)->features.soga_body_size = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_SOGA_BODY_AGE:{ + if(target->IsEntity()){ + sprintf(tmp, "%i", ((Entity*)target)->features.soga_body_age); + int8 new_value = atoul(value); + ((Entity*)target)->features.soga_body_age = new_value; + ((Entity*)target)->info_changed = true; + } + break; + } + case SPAWN_SET_ATTACK_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%u", ((Entity*)target)->GetInfoStruct()->get_attack_type()); + int8 new_value = atoul(value); + ((Entity*)target)->GetInfoStruct()->set_attack_type(new_value); + } + break; + } + case SPAWN_SET_RACE_TYPE:{ + if(target->GetModelType() > 0){ + Seperator* tmpsep = new Seperator(value, ' ', 3, 500, true); + if (tmpsep->IsNumber(0)) + { + int16 race_type = atoul(value); + const char* category = tmpsep->IsSet(1) ? tmpsep->arg[1] : "NULL"; + const char* subcategory = tmpsep->IsSet(2) ? tmpsep->arg[2] : "NULL"; + const char* model_name = tmpsep->IsSet(3) ? tmpsep->arg[3] : "NULL"; + if(race_types_list.AddRaceType(target->GetModelType(), race_type, category, subcategory, model_name)) { + if(client) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Race type added to memory, however not saved to database."); + } + + } + else if(client) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Model type already on the race_types_list, use spawn set database to override and update database."); + } + } + safe_delete(tmpsep); + } + break; + } + case SPAWN_SET_LOOT_TIER:{ + if(target->IsEntity()){ + sprintf(tmp, "%u", target->GetLootTier()); + int32 new_value = atoul(value); + target->SetLootTier(new_value); + } + break; + } + case SPAWN_SET_LOOT_DROP_TYPE:{ + if(target->IsEntity()){ + sprintf(tmp, "%u", target->GetLootDropType()); + int32 new_value = atoul(value); + target->SetLootDropType(new_value); + } + break; + } + case SPAWN_SET_SCARED_STRONG_PLAYERS:{ + if(target->IsNPC()){ + sprintf(tmp, "%u", target->IsScaredByStrongPlayers()); + int32 new_value = atoul(value); + target->SetScaredByStrongPlayers(new_value); + } + break; + } + + if(temp_value) + *temp_value = string(tmp); + } + } + else{ + /**** NOT TEMPORARY ELSE STATEMENT ****/ + /**** MUST RE-DEFINE WHAT IS ALREADY IN THE IF TEMPORARY BLOCK HERE ****/ + + switch(type){ + case SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL: { + target->SetMerchantLevelRange(atoul(value), target->GetMerchantMaxLevel()); + break; + } + case SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL: { + target->SetMerchantLevelRange(target->GetMerchantMinLevel(), atoul(value)); + break; + } + case SPAWN_SET_VALUE_EXPANSION_FLAG: { + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set expansion_flag=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_VALUE_HOLIDAY_FLAG: { + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set holiday_flag=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_AAXP_REWARDS: { + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set aaxp_rewards=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_VALUE_NAME:{ + target->SetName(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_SUB_TITLE: { + target->SetSubTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_X_OFFSET: + { + target->SetXOffset(atof(value)); + break; + } + case SPAWN_SET_VALUE_Y_OFFSET: + { + target->SetYOffset(atof(value)); + break; + } + case SPAWN_SET_VALUE_Z_OFFSET: + { + target->SetZOffset(atof(value)); + break; + } + case SPAWN_SET_VALUE_EXPIRE: { + target->SetExpireTime(val); + break; + } + case SPAWN_SET_VALUE_EXPIRE_OFFSET: { + target->SetExpireOffsetTime(val); + break; + } + case SPAWN_SET_VALUE_LEVEL:{ + target->SetLevel(val, send_update); + break; + } + case SPAWN_SET_VALUE_DIFFICULTY:{ + target->SetDifficulty(val, send_update); + break; + } + case SPAWN_SET_VALUE_MODEL_TYPE:{ + target->SetModelType(val, send_update); + break; + } + case SPAWN_SET_VALUE_CLASS:{ + target->SetAdventureClass(val, send_update); + break; + } + case SPAWN_SET_VALUE_GENDER:{ + target->SetGender(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_NAME:{ + target->SetShowName(val, send_update); + break; + } + case SPAWN_SET_VALUE_ATTACKABLE:{ + target->SetAttackable(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_LEVEL:{ + target->SetShowLevel(val, send_update); + break; + } + case SPAWN_SET_VALUE_TARGETABLE:{ + target->SetTargetable(val, send_update); + break; + } + case SPAWN_SET_VALUE_SHOW_COMMAND_ICON:{ + target->SetShowCommandIcon(val, send_update); + break; + } + case SPAWN_SET_VALUE_HAND_ICON:{ + target->SetShowHandIcon(val, send_update); + break; + } + case SPAWN_SET_VALUE_HAIR_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetHairType(val, send_update); + break; + } + case SPAWN_SET_VALUE_FACIAL_HAIR_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetFacialHairType(val, send_update); + break; + } + case SPAWN_SET_VALUE_WING_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetWingType(val, send_update); + break; + } + case SPAWN_SET_VALUE_CHEST_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetChestType(val, send_update); + break; + } + case SPAWN_SET_VALUE_LEGS_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetLegsType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SOGA_HAIR_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetSogaHairType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SOGA_FACIAL_HAIR_TYPE:{ + if(target->IsEntity()) + ((Entity*)target)->SetSogaFacialHairType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SOGA_MODEL_TYPE:{ + target->SetSogaModelType(val, send_update); + break; + } + case SPAWN_SET_VALUE_SIZE:{ + target->SetSize(val, send_update); + break; + } + case SPAWN_SET_VALUE_HP:{ + target->SetTotalHPBase(val); + target->SetHP(val, send_update); + break; + } + case SPAWN_SET_VALUE_POWER:{ + target->SetTotalPowerBase(val); + target->SetPower(val, send_update); + break; + } + case SPAWN_SET_VALUE_HEROIC:{ + target->SetHeroic(val, send_update); + break; + } + case SPAWN_SET_VALUE_RESPAWN:{ + target->SetRespawnTime(val); + break; + } + case SPAWN_SET_VALUE_X:{ + target->SetX(atof(value), send_update); + target->SetSpawnOrigX(target->GetX()); + break; + } + case SPAWN_SET_VALUE_Y:{ + target->SetY(atof(value), send_update); + target->SetSpawnOrigY(target->GetY()); + break; + } + case SPAWN_SET_VALUE_Z:{ + target->SetZ(atof(value), send_update); + target->SetSpawnOrigZ(target->GetZ()); + break; + } + case SPAWN_SET_VALUE_HEADING:{ + target->SetHeading(atof(value), send_update); + target->SetSpawnOrigHeading(target->GetHeading()); + break; + } + case SPAWN_SET_VALUE_COMMAND_PRIMARY:{ + ZoneServer* zone = target->GetZone(); + if (!zone && client) + zone = client->GetCurrentZone(); + + if(zone && zone->GetEntityCommandList(val)) + target->SetPrimaryCommands(zone->GetEntityCommandList(val)); + target->primary_command_list_id = val; + break; + } + case SPAWN_SET_VALUE_COMMAND_SECONDARY:{ + ZoneServer* zone = target->GetZone(); + if (!zone && client) + zone = client->GetCurrentZone(); + + if (zone && zone->GetEntityCommandList(val)) + target->SetSecondaryCommands(zone->GetEntityCommandList(val)); + target->secondary_command_list_id = val; + break; + } + case SPAWN_SET_VALUE_VISUAL_STATE:{ + target->SetVisualState(val, send_update); + break; + } + case SPAWN_SET_VALUE_ACTION_STATE:{ + target->SetActionState(val, send_update); + break; + } + case SPAWN_SET_VALUE_MOOD_STATE:{ + target->SetMoodState(val, send_update); + break; + } + case SPAWN_SET_VALUE_INITIAL_STATE:{ + target->SetInitialState(val, send_update); + break; + } + case SPAWN_SET_VALUE_ACTIVITY_STATE:{ + target->SetActivityStatus(val, send_update); + break; + } + case SPAWN_SET_VALUE_COLLISION_RADIUS:{ + target->SetCollisionRadius(val, send_update); + break; + } + case SPAWN_SET_VALUE_FACTION:{ + ZoneServer* zone = target->GetZone(); + if (!zone && client) + zone = client->GetCurrentZone(); + + target->faction_id = val; + if(zone) + { + zone->RemoveDeadEnemyList(target); + if(target->IsNPC()) + zone->AddEnemyList((NPC*)target); + } + break; + } + case SPAWN_SET_VALUE_DEVICE_ID:{ + if (target->IsObject()) + ((Object*)target)->SetDeviceID(val); + break; + } + case SPAWN_SET_VALUE_PITCH: { + target->SetPitch(atof(value), send_update); + target->SetSpawnOrigPitch(atof(value)); + break; + } + case SPAWN_SET_VALUE_ROLL: { + target->SetRoll(atof(value), send_update); + target->SetSpawnOrigRoll(atof(value)); + break; + } + case SPAWN_SET_VALUE_HIDE_HOOD: { + target->SetHideHood(val); + break; + } + case SPAWN_SET_VALUE_EMOTE_STATE: { + target->SetEmoteState(val); + break; + } + case SPAWN_SET_VALUE_ICON: { + target->SetIcon(val); + break; + } + case SPAWN_SET_VALUE_PREFIX: { + target->SetPrefixTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_SUFFIX: { + target->SetSuffixTitle(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_LASTNAME: { + target->SetLastName(value); + if(target->GetZone()) + target->GetZone()->SendUpdateTitles(target); + break; + } + case SPAWN_SET_VALUE_SPAWN_SCRIPT:{ + if(lua_interface && lua_interface->GetSpawnScript(value) == 0){ + if(client){ + client->Message(CHANNEL_COLOR_RED, "Invalid Spawn Script file. Be sure you give the absolute path."); + client->Message(CHANNEL_COLOR_RED, "Example: /spawn set spawn_script 'SpawnScripts/example.lua'"); + } + return false; + } + else if(!database.UpdateSpawnScriptData(target->GetDatabaseID(), 0, 0, value)) + return false; + else{ + if(!world.GetSpawnLocationScript(target->GetSpawnLocationID())) + target->SetSpawnScript(value); + } + break; + } + case SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT:{ + if(lua_interface && lua_interface->GetSpawnScript(value) == 0){ + if(client){ + client->Message(CHANNEL_COLOR_RED, "Invalid Spawn Script file. Be sure you give the absolute path."); + client->Message(CHANNEL_COLOR_RED, "Example: /spawn set spawnlocation_script 'SpawnScripts/example.lua'"); + } + return false; + } + else if(!database.UpdateSpawnScriptData(0, target->GetSpawnLocationID(), 0, value)) + return false; + else{ + if(!world.GetSpawnEntryScript(target->GetSpawnEntryID())) + target->SetSpawnScript(value); + } + break; + } + case SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT:{ + if(lua_interface && lua_interface->GetSpawnScript(value) == 0){ + if(client){ + client->Message(CHANNEL_COLOR_RED, "Invalid Spawn Script file. Be sure you give the absolute path."); + client->Message(CHANNEL_COLOR_RED, "Example: /spawn set spawnentry_script 'SpawnScripts/example.lua'"); + } + return false; + } + else if(!database.UpdateSpawnScriptData(0, 0, target->GetSpawnEntryID(), value)) + return false; + else + target->SetSpawnScript(value); + break; + } + + case SPAWN_SET_SKIN_COLOR: + case SPAWN_SET_HAIR_COLOR1: + case SPAWN_SET_HAIR_COLOR2: + case SPAWN_SET_HAIR_TYPE_COLOR: + case SPAWN_SET_HAIR_FACE_COLOR: + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_HAIR_HIGHLIGHT: + case SPAWN_SET_MODEL_COLOR: + case SPAWN_SET_EYE_COLOR: + case SPAWN_SET_SOGA_SKIN_COLOR: + case SPAWN_SET_SOGA_HAIR_COLOR1: + case SPAWN_SET_SOGA_HAIR_COLOR2: + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + case SPAWN_SET_SOGA_MODEL_COLOR: + case SPAWN_SET_SOGA_EYE_COLOR: { + if (target->IsNPC()) + { + Seperator* skinsep = new Seperator(value, ' ', 3, 500, true); + if (skinsep->IsNumber(0) && skinsep->IsNumber(1) && skinsep->IsNumber(2)) + { + EQ2_Color clr; + clr.red = atoul(skinsep->arg[0]); + clr.green = atoul(skinsep->arg[1]); + clr.blue = atoul(skinsep->arg[2]); + Query replaceSkinQuery; + + string fieldName(""); + switch(type) + { + case SPAWN_SET_SKIN_COLOR: + ((Entity*)target)->SetSkinColor(clr); + fieldName.append("skin_color"); + break; + case SPAWN_SET_HAIR_COLOR1: + ((Entity*)target)->SetHairColor1(clr); + fieldName.append("hair_color1"); + break; + case SPAWN_SET_HAIR_COLOR2: + ((Entity*)target)->SetHairColor2(clr); + fieldName.append("hair_color2"); + break; + case SPAWN_SET_HAIR_TYPE_COLOR: + ((Entity*)target)->SetHairColor(clr); + fieldName.append("hair_type_color"); + break; + case SPAWN_SET_HAIR_FACE_COLOR: + ((Entity*)target)->SetFacialHairColor(clr); + fieldName.append("hair_face_color"); + break; + case SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetHairTypeHighlightColor(clr); + fieldName.append("hair_type_highlight_color"); + break; + case SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetFacialHairHighlightColor(clr); + fieldName.append("hair_face_highlight_color"); + break; + case SPAWN_SET_HAIR_HIGHLIGHT: + ((Entity*)target)->SetHairHighlightColor(clr); + fieldName.append("hair_highlight"); + break; + case SPAWN_SET_MODEL_COLOR: + ((Entity*)target)->SetModelColor(clr); + fieldName.append("model_color"); + break; + case SPAWN_SET_EYE_COLOR: + ((Entity*)target)->SetEyeColor(clr); + fieldName.append("eye_color"); + break; + case SPAWN_SET_SOGA_SKIN_COLOR: + ((Entity*)target)->SetSogaSkinColor(clr); + fieldName.append("soga_skin_color"); + break; + case SPAWN_SET_SOGA_HAIR_COLOR1: + ((Entity*)target)->SetSogaHairColor1(clr); + fieldName.append("soga_hair_color1"); + break; + case SPAWN_SET_SOGA_HAIR_COLOR2: + ((Entity*)target)->SetSogaHairColor2(clr); + fieldName.append("soga_hair_color2"); + break; + case SPAWN_SET_SOGA_HAIR_TYPE_COLOR: + ((Entity*)target)->SetSogaHairColor(clr); + fieldName.append("soga_hair_type_color"); + break; + case SPAWN_SET_SOGA_HAIR_FACE_COLOR: + ((Entity*)target)->SetSogaFacialHairColor(clr); + fieldName.append("soga_hair_face_color"); + break; + case SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetSogaHairTypeHighlightColor(clr); + fieldName.append("soga_hair_type_highlight_color"); + break; + case SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR: + ((Entity*)target)->SetSogaFacialHairHighlightColor(clr); + fieldName.append("soga_hair_face_highlight_color"); + break; + case SPAWN_SET_SOGA_HAIR_HIGHLIGHT: + ((Entity*)target)->SetSogaHairHighlightColor(clr); + fieldName.append("soga_hair_highlight"); + break; + case SPAWN_SET_SOGA_MODEL_COLOR: + ((Entity*)target)->SetSogaModelColor(clr); + fieldName.append("soga_model_color"); + break; + case SPAWN_SET_SOGA_EYE_COLOR: + ((Entity*)target)->SetSogaEyeColor(clr); + fieldName.append("soga_eye_color"); + break; + } + replaceSkinQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from npc_appearance where spawn_id=%u and type='%s'", target->GetDatabaseID(), fieldName.c_str()); + replaceSkinQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into npc_appearance set spawn_id=%u, type='%s', red=%u, green=%u, blue=%u", target->GetDatabaseID(), fieldName.c_str(), clr.red, clr.green, clr.blue); + } + safe_delete(skinsep); + } + break; + } + + case SPAWN_SET_CHEEK_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.cheek_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "cheek_type", ((Entity*)target)->features.cheek_type[0], ((Entity*)target)->features.cheek_type[1], ((Entity*)target)->features.cheek_type[2]); + } + break; + } + case SPAWN_SET_CHIN_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.chin_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "chin_type", ((Entity*)target)->features.chin_type[0], ((Entity*)target)->features.chin_type[1], ((Entity*)target)->features.chin_type[2]); + } + break; + } + case SPAWN_SET_EAR_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.ear_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "ear_type", ((Entity*)target)->features.ear_type[0], ((Entity*)target)->features.ear_type[1], ((Entity*)target)->features.ear_type[2]); + } + break; + } + case SPAWN_SET_EYE_BROW_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.eye_brow_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "eye_brow_type", ((Entity*)target)->features.eye_brow_type[0], ((Entity*)target)->features.eye_brow_type[1], ((Entity*)target)->features.eye_brow_type[2]); + } + break; + } + case SPAWN_SET_EYE_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.eye_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "eye_type", ((Entity*)target)->features.eye_type[0], ((Entity*)target)->features.eye_type[1], ((Entity*)target)->features.eye_type[2]); + } + break; + } + case SPAWN_SET_LIP_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.lip_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "lip_type", ((Entity*)target)->features.lip_type[0], ((Entity*)target)->features.lip_type[1], ((Entity*)target)->features.lip_type[2]); + } + break; + } + case SPAWN_SET_NOSE_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.nose_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "nose_type", ((Entity*)target)->features.nose_type[0], ((Entity*)target)->features.nose_type[1], ((Entity*)target)->features.nose_type[2]); + } + break; + } + case SPAWN_SET_BODY_SIZE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->features.body_size = new_value; + UpdateDatabaseAppearance(client, target, "body_size", ((Entity*)target)->features.body_size, 0, 0); + } + break; + } + case SPAWN_SET_BODY_AGE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->features.body_age = new_value; + UpdateDatabaseAppearance(client, target, "body_age", ((Entity*)target)->features.body_age, 0, 0); + } + break; + } + case SPAWN_SET_SOGA_CHEEK_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_cheek_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_cheek_type", ((Entity*)target)->features.soga_cheek_type[0], ((Entity*)target)->features.soga_cheek_type[1], ((Entity*)target)->features.soga_cheek_type[2]); + } + break; + } + case SPAWN_SET_SOGA_CHIN_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_chin_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_chin_type", ((Entity*)target)->features.soga_chin_type[0], ((Entity*)target)->features.soga_chin_type[1], ((Entity*)target)->features.soga_chin_type[2]); + } + break; + } + case SPAWN_SET_SOGA_EAR_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_ear_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_ear_type", ((Entity*)target)->features.soga_ear_type[0], ((Entity*)target)->features.soga_ear_type[1], ((Entity*)target)->features.soga_ear_type[2]); + } + break; + } + case SPAWN_SET_SOGA_EYE_BROW_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_eye_brow_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_eye_brow_type", ((Entity*)target)->features.soga_eye_brow_type[0], ((Entity*)target)->features.soga_eye_brow_type[1], ((Entity*)target)->features.soga_eye_brow_type[2]); + } + break; + } + case SPAWN_SET_SOGA_EYE_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_eye_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_eye_type", ((Entity*)target)->features.soga_eye_type[0], ((Entity*)target)->features.soga_eye_type[1], ((Entity*)target)->features.soga_eye_type[2]); + } + break; + } + case SPAWN_SET_SOGA_LIP_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_lip_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_lip_type", ((Entity*)target)->features.soga_lip_type[0], ((Entity*)target)->features.soga_lip_type[1], ((Entity*)target)->features.soga_lip_type[2]); + } + break; + } + case SPAWN_SET_SOGA_NOSE_TYPE:{ + if(target->IsEntity() && index < 3){ + sint8 new_value = atoi(value); + ((Entity*)target)->features.soga_nose_type[index] = new_value; + UpdateDatabaseAppearance(client, target, "soga_nose_type", ((Entity*)target)->features.soga_nose_type[0], ((Entity*)target)->features.soga_nose_type[1], ((Entity*)target)->features.soga_nose_type[2]); + } + break; + } + case SPAWN_SET_SOGA_BODY_SIZE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->features.body_size = new_value; + UpdateDatabaseAppearance(client, target, "body_size", ((Entity*)target)->features.body_size, 0, 0); + } + break; + } + case SPAWN_SET_SOGA_BODY_AGE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->features.body_age = new_value; + UpdateDatabaseAppearance(client, target, "body_age", ((Entity*)target)->features.body_age, 0, 0); + } + break; + } + case SPAWN_SET_ATTACK_TYPE:{ + if(target->IsEntity()){ + int8 new_value = atoul(value); + ((Entity*)target)->GetInfoStruct()->set_attack_type(new_value); + if(target->IsNPC()) { + if(target->GetDatabaseID()) { + Query spawnNPCQuery; + spawnNPCQuery.AddQueryAsync(0, &database, Q_INSERT, "update spawn_npcs set attack_type=%u where id=%u", new_value, target->GetDatabaseID()); + } else if(client) { + client->Message(CHANNEL_COLOR_RED, "Invalid spawn to update the database (NPC only) or no database id for the NPC present."); + } + } + } + break; + } + case SPAWN_SET_RACE_TYPE:{ + if(target->GetModelType() > 0){ + Seperator* tmpsep = new Seperator(value, ' ', 3, 500, true); + if (tmpsep->IsNumber(0)) + { + Query typequery; + int16 race_type = atoul(value); + const char* category = tmpsep->IsSet(1) ? tmpsep->arg[1] : "NULL"; + const char* subcategory = tmpsep->IsSet(2) ? tmpsep->arg[2] : "NULL"; + const char* model_name = tmpsep->IsSet(3) ? tmpsep->arg[3] : "NULL"; + if(race_types_list.AddRaceType(target->GetModelType(), race_type, category, subcategory, model_name)) { + if(client) + client->Message(CHANNEL_COLOR_YELLOW, "Model Type %u Race type %u inserted into memory + replaced into database, Model Type: %u set to Race Type: %u, Category: %s, SubCategory: %s, Model Name: %s.", race_type, target->GetModelType(), race_type, category, subcategory, model_name); + typequery.AddQueryAsync(0, &database, Q_INSERT, "insert into race_types (model_type, race_id, category, subcategory, model_name) values(%u, %u, '%s', '%s', '%s')", target->GetModelType(), race_type, category, subcategory, model_name); + } + else { + if(client) + client->Message(CHANNEL_COLOR_RED, "Model Type: %u Race type %u overrided in memory + replaced into database, Model Type: %u set to Race Type: %u, Category: %s, SubCategory: %s, Model Name: %s.", race_type, target->GetModelType(), race_type, category, subcategory, model_name); + race_types_list.AddRaceType(target->GetModelType(), race_type, category, subcategory, model_name, true); + typequery.AddQueryAsync(0, &database, Q_INSERT, "update race_types set race_id=%u, category='%s', subcategory='%s', model_name='%s' where model_type=%u", race_type, category, subcategory, model_name, target->GetModelType()); + } + } + safe_delete(tmpsep); + } + break; + } + case SPAWN_SET_LOOT_TIER:{ + int32 new_value = atoul(value); + target->SetLootTier(new_value); + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set loot_tier=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_LOOT_DROP_TYPE:{ + int32 new_value = atoul(value); + target->SetLootDropType(new_value); + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn set loot_drop_type=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + case SPAWN_SET_SCARED_STRONG_PLAYERS:{ + int32 new_value = atoul(value); + target->SetScaredByStrongPlayers(new_value); + + if (target->GetDatabaseID() > 0) + { + char query[256]; + snprintf(query, 256, "update spawn_npcs set scared_by_strong_players=%u where id=%u", atoul(value), target->GetDatabaseID()); + if (database.RunQuery(query, strnlen(query, 256))) + { + if(client) + client->Message(CHANNEL_COLOR_RED, "Ran query:%s", query); + } + } + break; + } + } + } + return true; +} + +void Commands::UpdateDatabaseAppearance(Client* client, Spawn* target, string fieldName, sint8 r, sint8 g, sint8 b) +{ + Query replaceQuery; + + if(target->IsBot()) + { + Bot* bot = (Bot*)target; + if(bot->BotID) + database.SaveBotFloats(bot->BotID, fieldName.c_str(), r, g, b); + } + else if(target->IsPlayer()) + { + replaceQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from char_colors where char_id=%u and type='%s'", ((Player*)target)->GetCharacterID(), fieldName.c_str()); + replaceQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into char_colors set char_id=%u, type='%s', red=%i, green=%i, blue=%i", ((Player*)target)->GetCharacterID(), fieldName.c_str(), r, g, b); + } + else + { + replaceQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from npc_appearance where spawn_id=%u and type='%s'", target->GetDatabaseID(), fieldName.c_str()); + replaceQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into npc_appearance set spawn_id=%u, type='%s', red=%i, green=%i, blue=%i", target->GetDatabaseID(), fieldName.c_str(), r, g, b); + } + + ((Entity*)target)->changed = true; + ((Entity*)target)->info_changed = true; + if(target->GetZone()) + target->GetZone()->AddChangedSpawn(target); +} + +/* The zone object will be NULL if the zone is not currently running. We pass both of these in so we can update + the database fields always and also update the zone in memory if it's running. */ +bool Commands::SetZoneCommand(Client* client, int32 zone_id, ZoneServer* zone, int8 type, const char* value) { + if (client && zone_id > 0 && type > 0 && value) { + sint32 int_value = 0; + float float_value = 0; + if (type == ZONE_SET_VALUE_SAFE_X || type == ZONE_SET_VALUE_SAFE_Y || type == ZONE_SET_VALUE_SAFE_Z || type == ZONE_SET_VALUE_UNDERWORLD) { + try { + float_value = atof(value); + } + catch (...) { + client->Message(CHANNEL_COLOR_RED, "Error converting '%s' to a float value", value); + return false; + } + } + else if (type != ZONE_SET_VALUE_NAME && type != ZONE_SET_VALUE_FILE && type != ZONE_SET_VALUE_DESCRIPTION && type != ZONE_SET_VALUE_ZONE_TYPE && type != ZONE_SET_VALUE_LUA_SCRIPT && type != ZONE_SET_VALUE_ZONE_MOTD) { + try { + int_value = atoi(value); + } + catch (...) { + client->Message(CHANNEL_COLOR_RED, "Error converting '%s' to an integer value", value); + return false; + } + } + switch (type) { + case ZONE_SET_VALUE_EXPANSION_ID: { + break; + } + case ZONE_SET_VALUE_NAME: { + if (zone) + zone->SetZoneName(const_cast(value)); + database.SaveZoneInfo(zone_id, "name", value); + break; + } + case ZONE_SET_VALUE_FILE: { + if (zone) + zone->SetZoneFile(const_cast(value)); + database.SaveZoneInfo(zone_id, "file", value); + break; + } + case ZONE_SET_VALUE_DESCRIPTION: { + if (zone) + zone->SetZoneDescription(const_cast(value)); + database.SaveZoneInfo(zone_id, "description", value); + break; + } + case ZONE_SET_VALUE_SAFE_X: { + if (zone) + zone->SetSafeX(float_value); + database.SaveZoneInfo(zone_id, "safe_x", float_value); + break; + } + case ZONE_SET_VALUE_SAFE_Y: { + if (zone) + zone->SetSafeY(float_value); + database.SaveZoneInfo(zone_id, "safe_y", float_value); + break; + } + case ZONE_SET_VALUE_SAFE_Z: { + if (zone) + zone->SetSafeZ(float_value); + database.SaveZoneInfo(zone_id, "safe_z", float_value); + break; + } + case ZONE_SET_VALUE_UNDERWORLD: { + if (zone) + zone->SetUnderWorld(float_value); + database.SaveZoneInfo(zone_id, "underworld", float_value); + break; + } + case ZONE_SET_VALUE_MIN_RECOMMENDED: { + break; + } + case ZONE_SET_VALUE_MAX_RECOMMENDED: { + break; + } + case ZONE_SET_VALUE_ZONE_TYPE: { + break; + } + case ZONE_SET_VALUE_ALWAYS_LOADED: { + if (zone) + zone->SetAlwaysLoaded(int_value == 1); + database.SaveZoneInfo(zone_id, "always_loaded", int_value); + break; + } + case ZONE_SET_VALUE_CITY_ZONE: { + if (zone) + zone->SetCityZone(int_value == 1); + database.SaveZoneInfo(zone_id, "city_zone", int_value); + break; + } + case ZONE_SET_VALUE_WEATHER_ALLOWED: { + if (zone) + zone->SetWeatherAllowed(int_value == 1); + database.SaveZoneInfo(zone_id, "weather_allowed", int_value); + break; + } + case ZONE_SET_VALUE_MIN_STATUS: { + if (zone) + zone->SetMinimumStatus(int_value); + database.SaveZoneInfo(zone_id, "min_status", int_value); + break; + } + case ZONE_SET_VALUE_MIN_LEVEL: { + if (zone) + zone->SetMinimumLevel(int_value); + database.SaveZoneInfo(zone_id, "min_level", int_value); + break; + } + case ZONE_SET_VALUE_MAX_LEVEL: { + if (zone) + zone->SetMaximumLevel(int_value); + database.SaveZoneInfo(zone_id, "max_level", int_value); + break; + } + case ZONE_SET_VALUE_START_ZONE: { + database.SaveZoneInfo(zone_id, "start_zone", int_value); + break; + } + case ZONE_SET_VALUE_INSTANCE_TYPE: { + if (zone) + zone->SetInstanceType(int_value); + database.SaveZoneInfo(zone_id, "instance_type", int_value); + break; + } + case ZONE_SET_VALUE_DEFAULT_REENTER_TIME: { + if (zone) + zone->SetDefaultReenterTime(int_value); + database.SaveZoneInfo(zone_id, "default_reenter_time", int_value); + break; + } + case ZONE_SET_VALUE_DEFAULT_RESET_TIME: { + if (zone) + zone->SetDefaultResetTime(int_value); + database.SaveZoneInfo(zone_id, "default_reset_time", int_value); + break; + } + case ZONE_SET_VALUE_DEFAULT_LOCKOUT_TIME: { + if (zone) + zone->SetDefaultLockoutTime(int_value); + database.SaveZoneInfo(zone_id, "default_lockout_time", int_value); + break; + } + case ZONE_SET_VALUE_FORCE_GROUP_TO_ZONE: { + if (zone) + zone->SetForceGroupZoneOption(int_value); + database.SaveZoneInfo(zone_id, "force_group_to_zone", int_value); + break; + } + case ZONE_SET_VALUE_LUA_SCRIPT: { + if (lua_interface && lua_interface->GetZoneScript(value) == 0) { + client->Message(CHANNEL_COLOR_RED, "Invalid Zone Script file. Be sure you give the absolute path."); + client->Message(CHANNEL_COLOR_RED, "Example: /zone set lua_script 'ZoneScripts/QueensColony.lua'"); + return false; + } + else { + world.AddZoneScript(zone_id, const_cast(value)); + database.SaveZoneInfo(zone_id, "lua_script", value); + } + break; + } + case ZONE_SET_VALUE_SHUTDOWN_TIMER: { + if (zone) + zone->SetShutdownTimer(int_value); + database.SaveZoneInfo(zone_id, "shutdown_timer", int_value); + break; + } + case ZONE_SET_VALUE_ZONE_MOTD: { + if (zone) + zone->SetZoneMOTD(string(value)); + database.SaveZoneInfo(zone_id, "zone_motd", value); + break; + } + default: { + client->Message(CHANNEL_COLOR_RED, "Invalid zone attribute %i", type); + return false; + } + } + } + else { + if (client) + client->SimpleMessage(CHANNEL_COLOR_RED, "An error occured saving new zone data."); + return false; + } + return true; +} + +void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* client, Spawn* targetOverride) { + + if (index >= remote_commands->commands.size()) { + LogWrite(COMMAND__ERROR, 0, "Command", "Error, command handler of %u was requested, but max handler is %u", index, remote_commands->commands.size()); + return; + } + + Spawn* cmdTarget = targetOverride ? targetOverride : client->GetPlayer()->GetTarget(); + + EQ2_RemoteCommandString* parent_command = 0; + EQ2_RemoteCommandString* command = &remote_commands->commands[index]; + Seperator* sep = 0; + if (command_parms->size > 0) { + sep = new Seperator(command_parms->data.c_str(), ' ', 10, 500, true); + if (sep && sep->arg[0] && remote_commands->validSubCommand(command->command.data, string(sep->arg[0]))) { + parent_command = command; + command = &(remote_commands->subcommands[command->command.data][string(sep->arg[0])]); + safe_delete(sep); + if (command_parms->data.length() > (command->command.data.length() + 1)) + sep = new Seperator(command_parms->data.c_str() + (command->command.data.length() + 1), ' ', 10, 500, true); + LogWrite(COMMAND__DEBUG, 1, "Command", "Handler: %u, COMMAND: '%s', SUBCOMMAND: '%s'", index, parent_command->command.data.c_str(), command->command.data.c_str()); + } + else + LogWrite(COMMAND__DEBUG, 1, "Command", "Handler: %u, COMMAND: '%s'", index, command->command.data.c_str()); + } + + int ndx = 0; + if (command->required_status > client->GetAdminStatus()) + { + LogWrite(COMMAND__ERROR, 0, "Command", "Player '%s' (%u) needs status %i to use command: %s", client->GetPlayer()->GetName(), client->GetAccountID(), command->required_status, command->command.data.c_str()); + safe_delete(sep); + client->SimpleMessage(3, "Error: Your status is insufficient for this command."); + return; + } + + Player* player = client->GetPlayer(); + LogWrite(COMMAND__DEBUG, 0, "Command", "Player '%s' (%u), Command: %s", player->GetName(), client->GetAccountID(), command->command.data.c_str()); + + switch (command->handler) { + case COMMAND_RELOAD: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reload commands:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload structs"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload items"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload luasystem"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spawnscripts"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spells"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spells npc"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload quests"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload spawns"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload groundspawns"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload zonescripts"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload entity_commands"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload factions"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload mail"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload guilds"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload locations"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload rules"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload transporters"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload startabilities"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload voiceovers"); + break; + } + case COMMAND_RELOADSTRUCTS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Structs..."); + world.SetReloadingSubsystem("Structs"); + configReader.ReloadStructs(); + world.RemoveReloadingSubSystem("Structs"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_QUESTS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Quests..."); + world.SetReloadingSubsystem("Quests"); + master_quest_list.Reload(); + client_list.ReloadQuests(); + zone_list.ReloadClientQuests(); + world.RemoveReloadingSubSystem("Quests"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_SPAWNS: { + client->GetCurrentZone()->ReloadSpawns(); + break; + } + case COMMAND_RELOAD_SPELLS: { + if (sep && sep->arg[0] && strcmp(sep->arg[0], "npc") == 0) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading NPC Spells Lists (Note: Must Reload Spawns/Repop to reset npc spells)..."); + world.PurgeNPCSpells(); + database.LoadNPCSpells(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Spells & NPC Spell Lists (Note: Must Reload Spawns/Repop to reset npc spells)..."); + world.SetReloadingSubsystem("Spells"); + zone_list.DeleteSpellProcess(); + master_spell_list.Reload(); + if (lua_interface) + lua_interface->ReloadSpells(); + zone_list.LoadSpellProcess(); + world.RemoveReloadingSubSystem("Spells"); + world.PurgeNPCSpells(); + database.LoadNPCSpells(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + } + break; + } + case COMMAND_RELOAD_GROUNDSPAWNS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Groundspawn Entries..."); + world.SetReloadingSubsystem("GroundSpawns"); + client->GetCurrentZone()->DeleteGroundSpawnItems(); + client->GetCurrentZone()->LoadGroundSpawnEntries(); + world.RemoveReloadingSubSystem("GroundSpawns"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + + case COMMAND_RELOAD_ZONESCRIPTS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Zone Scripts..."); + world.SetReloadingSubsystem("ZoneScripts"); + world.ResetZoneScripts(); + database.LoadZoneScriptData(); + if (lua_interface) + lua_interface->DestroyZoneScripts(); + world.RemoveReloadingSubSystem("ZoneScripts"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_ENTITYCOMMANDS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Entity Commands..."); + world.SetReloadingSubsystem("EntityCommands"); + client->GetCurrentZone()->ClearEntityCommands(); + database.LoadEntityCommands(client->GetCurrentZone()); + world.RemoveReloadingSubSystem("EntityCommands"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_FACTIONS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Factions..."); + world.SetReloadingSubsystem("Factions"); + master_faction_list.Clear(); + database.LoadFactionList(); + world.RemoveReloadingSubSystem("Factions"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_MAIL: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Mail..."); + zone_list.ReloadMail(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_GUILDS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Guilds..."); + world.ReloadGuilds(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_LOCATIONS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Locations..."); + client->GetPlayer()->GetZone()->RemoveLocationGrids(); + database.LoadLocationGrids(client->GetPlayer()->GetZone()); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_RULES: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Rules..."); + database.LoadRuleSets(true); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_TRANSPORTERS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Transporters in your current zone..."); + database.LoadTransporters(client->GetCurrentZone()); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_STARTABILITIES: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Starting Skills/Spells..."); + world.PurgeStartingLists(); + world.LoadStartingLists(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_RELOAD_VOICEOVERS: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Voiceovers..."); + world.PurgeVoiceOvers(); + world.LoadVoiceOvers(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!"); + break; + } + case COMMAND_READ: { + if (sep && sep->arg[1][0] && sep->IsNumber(1)) { + if (strcmp(sep->arg[0], "read") == 0) { + int32 item_index = atol(sep->arg[1]); + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(item_index); + if (item) { + Spawn* spawn = cmdTarget; + client->SendShowBook(client->GetPlayer(), item->name, item->book_language, item->book_pages); + break; + } + } + } + break; + } + case COMMAND_USEABILITY:{ + if (sep && sep->arg[0][0] && sep->IsNumber(0)) { + if (!client->GetPlayer()->Alive()) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot do that right now."); + } + else { + int32 spell_id = atoul(sep->arg[0]); + int8 spell_tier = 0; + if (sep->arg[1][0] && sep->IsNumber(1)) + spell_tier = atoi(sep->arg[1]); + else + spell_tier = client->GetPlayer()->GetSpellTier(spell_id); + if (!spell_tier) + spell_tier = 1; + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + if (spell) { + if (strncmp(spell->GetName(), "Gathering", 9) == 0 || strncmp(spell->GetName(), "Mining", 6) == 0 || strncmp(spell->GetName(), "Trapping", 8) == 0 || strncmp(spell->GetName(), "Foresting", 9) == 0 || strncmp(spell->GetName(), "Fishing", 7) == 0 || strncmp(spell->GetName(), "Collecting", 10) == 0) + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), cmdTarget, true, true); + else + { + if (cmdTarget) + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), cmdTarget); + else + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()); + } + } + } + } + else if (cmdTarget && cmdTarget->IsWidget()) + { + Widget* widget = (Widget*)cmdTarget; + widget->HandleUse(client, "use", WIDGET_TYPE_DOOR); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /useability {spell_id} [spell_tier]"); + } + break; + } + case COMMAND_INFO:{ + bool check_self_trade = true; + if(sep && sep->arg[1][0] && sep->IsNumber(1)){ + if(strcmp(sep->arg[0], "inventory") == 0){ + int32 item_index = atol(sep->arg[1]); + + LogWrite(COMMAND__DEBUG, 5, "Command", "/info inventory item original index %u", item_index); + if(client->GetVersion() <= 561) { + if(item_index <= 255) { + item_index = 255 - item_index; + } + else { + if(item_index == 256) { // first "new" item to inventory is assigned index 256 by client + item_index = client->GetPlayer()->item_list.GetFirstNewItem(); + } + else { + // otherwise the slot has to be mapped out depending on the amount of new items + index sent in + item_index = client->GetPlayer()->item_list.GetNewItemByIndex((int16)item_index - 255); + } + } + } + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(item_index); + if(item){ + if (item->IsCollectable() && client->SendCollectionsForItem(item)) + break; + + EQ2Packet* app = 0; + if(client->GetVersion() <= 561) { + app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + } + else { + app = item->serialize(client->GetVersion(), (!item->GetItemScript() || !lua_interface), client->GetPlayer()); + } + //DumpPacket(app); + client->QueuePacket(app); + if(item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "examined", item, client->GetPlayer()); + else if(item->generic_info.offers_quest_id > 0){ //leave the current functionality in place if it doesnt have an item script + Quest* quest = master_quest_list.GetQuest(item->generic_info.offers_quest_id, false); + if(quest && client->GetPlayer()->HasQuestBeenCompleted(item->generic_info.offers_quest_id) == 0 && client->GetPlayer()->HasActiveQuest(item->generic_info.offers_quest_id) == 0) + client->AddPendingQuest(new Quest(quest)); // copy quest since we pulled the master quest to see if it existed or not + } + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info inventory: Unknown Index: %u", item_index); + } + else if(strcmp(sep->arg[0], "equipment") == 0){ + int32 item_index = client->GetPlayer()->ConvertSlotFromClient(atol(sep->arg[1]), client->GetVersion()); + Item* item = client->GetPlayer()->GetEquipmentList()->GetItem(item_index); + if(item){ + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + if(item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "examined", item, client->GetPlayer()); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info equipment: Unknown Index: %u", item_index); + } + else if(strcmp(sep->arg[0], "appearance") == 0){ + int32 item_index = client->GetPlayer()->ConvertSlotFromClient(atol(sep->arg[1]), client->GetVersion()); + Item* item = client->GetPlayer()->GetAppearanceEquipmentList()->GetItem(item_index); + if(item){ + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + if(item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "examined", item, client->GetPlayer()); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info appearance: Unknown Index: %u", item_index); + } + else if(strcmp(sep->arg[0], "item") == 0 || strcmp(sep->arg[0], "merchant") == 0 || strcmp(sep->arg[0], "store") == 0 || strcmp(sep->arg[0], "buyback") == 0 || strcmp(sep->arg[0], "consignment") == 0){ + int32 item_id = atoul(sep->arg[1]); + Item* item = master_item_list.GetItem(item_id); + if(item){ + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info item|merchant|store|buyback|consignment: Unknown Item ID: %u", item_id); + } + else if (strcmp(sep->arg[0], "spell") == 0) { + sint32 spell_id = atol(sep->arg[1]); + int8 tier = atoi(sep->arg[3]); + EQ2Packet* outapp = master_spell_list.GetSpellPacket(spell_id, tier, client, true, 0x2A); + if (outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info spell: Unknown Spell ID and/or Tier, ID: %u, Tier: %i", spell_id, tier); + } + else if (strcmp(sep->arg[0], "achievement") == 0) { + sint32 spell_id = atol(sep->arg[2]); + + int8 group = atoi(sep->arg[1]); + AltAdvanceData* data = 0; + SpellBookEntry* spellentry = 0; + int8 tier = client->GetPlayer()->GetSpellTier(spell_id); + + LogWrite(COMMAND__ERROR, 0, "Command", "/info achievement: AA Spell ID and/or Tier, ID: %u, Group: %i", spell_id, group); + EQ2Packet* outapp = master_spell_list.GetAASpellPacket(spell_id, tier, client, true, 0x45); + if (outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info achievement: Unknown Spell ID and/or Tier, ID: %u, Tier: %i", spell_id, group); + } + else if (strcmp(sep->arg[0], "spellbook") == 0) { + sint32 spell_id = atol(sep->arg[1]); + int32 tier = atoi(sep->arg[2]); + if (tier > 255) { + SpellBookEntry* ent = client->GetPlayer()->GetSpellBookSpell(spell_id); + if (ent) + tier = ent->tier; + } + EQ2Packet* outapp = master_spell_list.GetSpellPacket(spell_id, (int8)tier, client, true, 0x2A); + if (outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info spellbook: Unknown Spell ID and/or Tier, ID: %u, Tier: %i", spell_id, tier); + } + else if (strcmp(sep->arg[0], "recipe") == 0) { + sint32 recipe_id = atol(sep->arg[1]); + EQ2Packet* outapp = master_recipe_list.GetRecipePacket(recipe_id, client, true, 0x2C); + if(outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info recipe: Unknown Recipe ID: %u", recipe_id); + } + else if (strcmp(sep->arg[0], "recipe_product") == 0) { + sint32 recipe_id = atol(sep->arg[1]); + + PlayerRecipeList* prl = client->GetPlayer()->GetRecipeList(); + Recipe* recipe = nullptr; + if ((recipe = prl->GetRecipe(recipe_id)) && recipe->GetProductID()) { + RecipeProducts* rp = recipe->products[1]; + if (recipe->GetProductID() > 0) { + Item* item = master_item_list.GetItem(recipe->GetProductID()); + if(item){ + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info recipe_product: Unknown Item ID: %u", recipe->GetProductID()); + } + else { + LogWrite(COMMAND__ERROR, 0, "Command", "/info recipe_product: recipe->GetProductID() has value 0 for recipe id %u.", recipe_id); + } + } + else { + LogWrite(COMMAND__ERROR, 0, "Command", "/info recipe_product: with recipe id %u not found (recipe missing or no product in stage 1 assigned).", recipe_id); + } + } + else if (strcmp(sep->arg[0], "maintained") ==0) { + int32 slot = atol(sep->arg[1]); + int32 spell_id = atol(sep->arg[2]); + LogWrite(COMMAND__DEBUG, 5, "Command", "/info maintained: Spell ID - Slot: %u unknown: %u", slot, spell_id); + //int8 tier = client->GetPlayer()->GetSpellTier(spell_id); + MaintainedEffects* info = client->GetPlayer()->GetMaintainedSpellBySlot(slot); + EQ2Packet* outapp = master_spell_list.GetSpellPacket(info->spell_id, info->tier, client, true, 0x00); + if(outapp) + client->QueuePacket(outapp); + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info maintained: Unknown Spell ID: %u", spell_id); + } + else if (strcmp(sep->arg[0], "effect") == 0) { + int32 spell_id = atol(sep->arg[1]); + LogWrite(COMMAND__DEBUG, 5, "Command", "/info effect: Spell ID: %u", spell_id); + int8 tier = client->GetPlayer()->GetSpellTier(spell_id); + int8 type = 0; + if (client->GetVersion() <= 561) + type = 1; + EQ2Packet* outapp = master_spell_list.GetSpecialSpellPacket(spell_id, tier, client, true, type); + if (outapp){ + client->QueuePacket(outapp); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "/info effect: Unknown Spell ID: %u", spell_id); + } + else if(sep->IsNumber(1) && ((strncasecmp("your_trade", sep->arg[0], 10) == 0) || + (strncasecmp("their_trade", sep->arg[0], 11) == 0))) + { + if(strncasecmp("t", sep->arg[0], 1) == 0) { // their_trade not self trade + check_self_trade = false; + } + + int8 slot_id = atoul(sep->arg[1]); + if(client->GetPlayer()->trade) { + Entity* traderToCheck = client->GetPlayer(); + if(!check_self_trade) { + traderToCheck = client->GetPlayer()->trade->GetTradee(client->GetPlayer()); + } + Item* tradeItem = client->GetPlayer()->trade->GetTraderSlot(traderToCheck, slot_id); + if(tradeItem != nullptr) { + EQ2Packet* app = tradeItem->serialize(client->GetVersion(), true, client->GetPlayer(), true, 0, 0, client->GetVersion() > 561 ? true : false); + client->QueuePacket(app); + } + } + } + } + else if (sep && strcmp(sep->arg[0], "overflow") == 0) { + Item* item = player->item_list.GetOverflowItem(); + if(item) { + EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer()); + client->QueuePacket(app); + } + else + LogWrite(COMMAND__ERROR, 0,"Command", "/info overflow: Unable to retrieve an overflow item."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /info {inventory|equipment|spell} {id}"); + break; + } + case COMMAND_USE_EQUIPPED_ITEM:{ + if (sep && sep->arg[0] && sep->IsNumber(0)){ + int32 slot_id = atoul(sep->arg[0]); + Item* item = player->GetEquipmentList()->GetItem(slot_id); + if (item && item->generic_info.usable && item->GetItemScript()) { + if(!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) { + client->SimpleMessage(CHANNEL_COLOR_RED, "This item is broken and must be repaired at a mender before it can be used"); + } + else if (item->CheckFlag(EVIL_ONLY) && client->GetPlayer()->GetAlignment() != ALIGNMENT_EVIL) { + client->Message(0, "%s requires an evil race.", item->name.c_str()); + } + else if (item->CheckFlag(GOOD_ONLY) && client->GetPlayer()->GetAlignment() != ALIGNMENT_GOOD) { + client->Message(0, "%s requires a good race.", item->name.c_str()); + } + else { + lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, player->GetTarget()); + } + } + } + break; + } + case COMMAND_USE_ITEM: { + if (sep && sep->arg[0] && sep->IsNumber(0)) { + int32 item_index = atoul(sep->arg[0]); + Item* item = player->item_list.GetItemFromIndex(item_index); + client->UseItem(item, client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer()); + } + break; + } + case COMMAND_SCRIBE_SCROLL_ITEM: { + if (sep && sep->arg[0] && sep->IsNumber(0)) { + Item* item = player->item_list.GetItemFromUniqueID(atoul(sep->arg[0])); + if (item) { + LogWrite(ITEM__DEBUG, 0, "Items", "ITEM ID: %u", item->details.item_id); + + if(item->generic_info.item_type == 6) { + Spell* spell = master_spell_list.GetSpell(item->skill_info->spell_id, item->skill_info->spell_tier); + int8 old_slot = 0; + if (spell) { + if(!spell->ScribeAllowed(client->GetPlayer())) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not meet one of the requirements to scribe, due to class or level."); + break; + } + int16 tier_up = player->GetTierUp(spell->GetSpellTier()); + if (rule_manager.GetGlobalRule(R_Spells, RequirePreviousTierScribe)->GetInt8() && !player->HasSpell(spell->GetSpellID(), tier_up, false, true)) + client->SimpleMessage(CHANNEL_COLOR_RED, "You have not scribed the required previous version of this ability."); + else if (!player->HasSpell(spell->GetSpellID(), spell->GetSpellTier(), true)) { + old_slot = player->GetSpellSlot(spell->GetSpellID()); + player->RemoveSpellBookEntry(spell->GetSpellID()); + player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + player->UnlockSpell(spell); + client->SendSpellUpdate(spell); + database.DeleteItem(client->GetCharacterID(), item, 0); + player->item_list.RemoveItem(item, true); + client->QueuePacket(player->GetSpellBookUpdatePacket(client->GetVersion())); + client->QueuePacket(player->SendInventoryUpdate(client->GetVersion())); + + // force purge client cache and display updated spell for hover over + EQ2Packet* app = spell->SerializeSpell(client, false, false); + client->QueuePacket(app); + } + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unknown spell ID: %u and tier: %u", item->skill_info->spell_id, item->skill_info->spell_tier); + } + else if(item->generic_info.item_type == 7){ + if(item->recipebook_info) { + LogWrite(TRADESKILL__DEBUG, 0, "Recipe", "Scribing recipe book %s (%u) for player %s.", item->name.c_str(), item->recipebook_info->recipe_id, player->GetName()); + client->AddRecipeBookToPlayer(item->recipebook_info->recipe_id, item); + } + else { + client->Message(CHANNEL_NARRATIVE, "Recipe book is unavailable! Report to admin recipe id %u could not be retrieved.", item->recipebook_info ? item->recipebook_info->recipe_id : 0); + LogWrite(COMMAND__ERROR, 0, "Command", "Recipe Book %u could not be retrieved for item %s.", item->recipebook_info ? item->recipebook_info->recipe_id : 0, item->name.c_str()); + } + } + else { + LogWrite(COMMAND__ERROR, 0, "Command", "Recipe Book Info is not set for item %s.", item->name.c_str()); + } + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unknown unique item ID: %s", sep->arg[0]); + } + break; + } + case COMMAND_SUMMONITEM: { + if (sep && sep->IsNumber(0)) { + int32 item_id = atol(sep->arg[0]); + int32 quantity = 1; + + if (sep->arg[1] && sep->IsNumber(1)) + quantity = atoi(sep->arg[1]); + + if (sep->arg[2] && strncasecmp(sep->arg[2], "bank", 4) == 0) { + client->AddItemToBank(item_id,quantity); + } + else { + client->AddItem(item_id, quantity); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /summonitem {item_id} [quantity] or"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /summonitem {item_id} {quantity} [location] where location = bank"); + } + break; + } + case COMMAND_COLLECTION_ADDITEM: { + // takes 2 params: collection_id, slot + if (sep && sep->arg[0] && sep->arg[1] && sep->IsNumber(0) && sep->IsNumber(1)) { + Item *item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(atoul(sep->arg[1])); + if (item) + client->HandleCollectionAddItem(atoul(sep->arg[0]), item); + else + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in 'COMMAND_COLLECTION_ADDITEM'. Unable to get item from player '%s' at index %u", client->GetPlayer()->GetName(), atoul(sep->arg[0])); + } + else + LogWrite(COLLECTION__ERROR, 0, "Collect", "Received command 'COMMAND_COLLECTION_ADDITEM' with unhandeled argument types"); + break; + } + case COMMAND_COLLECTION_FILTER_MATCHITEM: { + // takes 1 param: slot + printf("COMMAND_COLLECTION_FILTER_MATCHITEM:\n"); + int i = 0; + while (sep->arg[i] && strlen(sep->arg[i]) > 0) { + printf("\t%u: %s\n", i, sep->arg[i]); + i++; + } + break; + } + case COMMAND_WAYPOINT: { + bool success = false; + client->ClearWaypoint(); + if (sep && sep->IsNumber(0) && sep->IsNumber(1) && sep->IsNumber(2)) { + if (!client->ShowPathToTarget(atof(sep->arg[0]), atof(sep->arg[1]), atof(sep->arg[2]), 0)) + client->Message(CHANNEL_COLOR_RED, "Invalid coordinates given"); + } + else if ( client->GetAdminStatus() > 100 && cmdTarget ) { + if (!client->ShowPathToTarget(cmdTarget->GetX(), cmdTarget->GetY(), cmdTarget->GetZ(), 0)) + client->Message(CHANNEL_COLOR_RED, "Invalid coordinates given"); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "Usage: /waypoint x y z"); + } + break; + } + case COMMAND_WHO:{ + const char* who = 0; + if(sep && sep->arg[0]){ + //cout << "Who query: \n"; + who = sep->argplus[0]; + //cout << who << endl; + } + zone_list.ProcessWhoQuery(who, client); + break; + } + case COMMAND_SELECT_JUNCTION:{ + // transporters (bells birds) use OP_SelectZoneTeleporterDestinatio / ProcessTeleportLocation(app) + // this is only used for revive it seems + int32 choice = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + choice = atoul(sep->arg[0]); + if(!client->GetPlayer()->Alive()){ //dead and this is a revive request + client->HandlePlayerRevive(choice); + } + break; + } + case COMMAND_SPAWN_MOVE: { + if (cmdTarget && cmdTarget->IsPlayer() == false) { + PacketStruct* packet = configReader.getStruct("WS_MoveObjectMode", client->GetVersion()); + if (packet) { + float unknown2_3 = 0; + int8 placement_mode = 0; + client->SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT); + if (sep && sep->arg[0][0]) { + if (strcmp(sep->arg[0], "wall") == 0) { + placement_mode = 2; + unknown2_3 = 150; + } + else if (strcmp(sep->arg[0], "ceiling") == 0) + placement_mode = 1; + else if (strcmp(sep->arg[0], "openheading") == 0 && cmdTarget->IsWidget()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "[PlacementMode] WIDGET OPEN HEADING MODE"); + client->SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::OPEN_HEADING); + } + else if (strcmp(sep->arg[0], "closeheading") == 0 && cmdTarget->IsWidget()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "[PlacementMode] WIDGET CLOSE HEADING MODE"); + client->SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::CLOSE_HEADING); + } + else if (strcmp(sep->arg[0], "myloc") == 0) + { + if (cmdTarget->GetSpawnLocationPlacementID() < 1) { + client->Message(CHANNEL_COLOR_YELLOW, "[PlacementMode] Spawn %s cannot be moved it is not assigned a spawn location placement id.", cmdTarget->GetName()); + safe_delete(packet); + break; + } + + cmdTarget->SetX(client->GetPlayer()->GetX(), true); + cmdTarget->SetY(client->GetPlayer()->GetY(), true); + cmdTarget->SetZ(client->GetPlayer()->GetZ(), true); + cmdTarget->SetHeading(client->GetPlayer()->GetHeading(), true); + + if (database.UpdateSpawnLocationSpawns(cmdTarget)) + client->Message(CHANNEL_COLOR_YELLOW, "[PlacementMode] Spawn %s placed at your location. Updated spawn_location_placement for spawn.", cmdTarget->GetName()); + safe_delete(packet); + break; + } + } + packet->setDataByName("placement_mode", placement_mode); + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(cmdTarget)); + packet->setDataByName("model_type", cmdTarget->GetModelType()); + packet->setDataByName("unknown", 1); //size + packet->setDataByName("unknown2", 1); //size 2 + packet->setDataByName("unknown2", .5, 1); //size 3 + packet->setDataByName("unknown2", 3, 2); + packet->setDataByName("unknown2", unknown2_3, 3); + packet->setDataByName("max_distance", 500); + packet->setDataByName("CoEunknown", 0xFFFFFFFF); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /spawn move (wall OR ceiling)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Moves a spawn anywhere you like. Optionally wall/ceiling can be provided to place wall/ceiling items."); + } + break; + } + case COMMAND_HAIL:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->GetTargetable()) + { + char tmp[75] = {0}; + + sprintf(tmp, "Hail, %s", spawn->GetName()); + + bool show_bubble = true; + + if (spawn->IsNPC()) + show_bubble = false; + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SAY, tmp, HEAR_SPAWN_DISTANCE, 0, show_bubble, client->GetPlayer()->GetCurrentLanguage()); + if(spawn->IsPlayer() == false && spawn->Alive() && spawn->GetDistance(client->GetPlayer()) < rule_manager.GetGlobalRule(R_Spawn, HailDistance)->GetInt32()){ + if(spawn->IsNPC() && ((NPC*)spawn)->EngagedInCombat()) + spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_HAILED_BUSY, client->GetPlayer()); + else + { + bool pauseRunback = false; + // prime runback as the heading or anything can be altered when hailing succeeds + if(spawn->IsNPC() && (spawn->HasMovementLoop() || spawn->HasMovementLocations())) + { + ((NPC*)spawn)->StartRunback(); + pauseRunback = true; + } + + if(spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_HAILED, client->GetPlayer()) && pauseRunback) + spawn->PauseMovement(rule_manager.GetGlobalRule(R_Spawn, HailMovementPause)->GetInt32()); + else if(spawn->IsNPC() && pauseRunback) + ((NPC*)spawn)->ClearRunback(); + } + } + } + else { + string tmp = "Hail"; + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SAY, tmp.c_str(), HEAR_SPAWN_DISTANCE, 0, true, client->GetPlayer()->GetCurrentLanguage()); + } + break; + } + case COMMAND_SAY:{ + if (sep && sep->arg[0][0]) { + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SAY, sep->argplus[0], HEAR_SPAWN_DISTANCE, 0, true, client->GetPlayer()->GetCurrentLanguage()); + if (cmdTarget && !(cmdTarget->IsPlayer())) + client->GetCurrentZone()->CallSpawnScript(cmdTarget, SPAWN_SCRIPT_HEAR_SAY, client->GetPlayer(), sep->argplus[0]); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /say {message}"); + break; + } + case COMMAND_TELL:{ + if(sep && sep->arg[0] && sep->argplus[1]){ + if(!zone_list.HandleGlobalChatMessage(client, sep->arg[0], CHANNEL_PRIVATE_TELL, sep->argplus[1], 0, client->GetPlayer()->GetCurrentLanguage())) + client->Message(CHANNEL_COLOR_RED,"Unable to find client %s",sep->arg[0]); + }else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /tell {character_name} {message}"); + break; + } + case COMMAND_SHOUT:{ + if(sep && sep->arg[0][0]) + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_SHOUT, sep->argplus[0], 0, 0, true, client->GetPlayer()->GetCurrentLanguage()); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /shout {message}"); + break; + } + case COMMAND_AUCTION:{ + if(sep && sep->arg[0][0]) + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_AUCTION, sep->argplus[0], 0, 0, true, client->GetPlayer()->GetCurrentLanguage()); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /auction {message}"); + break; + } + case COMMAND_OOC:{ + //For now ooc will be the global chat channel, eventually when we create more channels we will create a global chat channel + if(sep && sep->arg[0][0]) + zone_list.HandleGlobalChatMessage(client, 0, CHANNEL_OUT_OF_CHARACTER, sep->argplus[0], 0, client->GetPlayer()->GetCurrentLanguage()); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /ooc {message}"); + break; + } + case COMMAND_EMOTE:{ + if(sep && sep->arg[0][0]) + client->GetCurrentZone()->HandleChatMessage(client->GetPlayer(), 0, CHANNEL_EMOTE, sep->argplus[0]); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /emote {action}"); + break; + } + case COMMAND_RACE:{ + if(sep && sep->arg[0][0] && sep->IsNumber(0)){ + if(sep->arg[1][0] && sep->IsNumber(1)){ + client->GetPlayer()->GetInfoStruct()->set_race(atoi(sep->arg[1])); + client->GetPlayer()->SetRace(atoi(sep->arg[1])); + client->UpdateTimeStampFlag ( RACE_UPDATE_FLAG ); + client->GetPlayer()->SetCharSheetChanged(true); + } + client->GetPlayer()->SetModelType(atoi(sep->arg[0])); + //EQ2Packet* outapp = client->GetPlayer()->spawn_update_packet(client->GetPlayer(), client->GetVersion(), client->GetPlayer()->GetFeatures()); + //client->QueuePacket(outapp); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /race {race type id} {race id}"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "{race id} is optional"); + } + break; + } + case COMMAND_BANK_DEPOSIT:{ + if(client->GetBanker() && sep && sep->arg[0]){ + int64 amount = 0; + string deposit = string(sep->arg[0]); + amount = atoi64(deposit.c_str()); + client->BankDeposit(amount); + } + break; + } + case COMMAND_BANK_WITHDRAWAL:{ + if(client->GetBanker() && sep && sep->arg[0] && sep->IsNumber(0)){ + int64 amount = 0; + string deposit = string(sep->arg[0]); + amount = atoi64(deposit.c_str()); + client->BankWithdrawal(amount); + } + break; + } + case COMMAND_BANK_CANCEL:{ + Spawn* banker = cmdTarget; + client->Bank(banker, true); + break; + } + case COMMAND_BANK:{ + LogWrite(PLAYER__DEBUG, 0, "Players", "Open Player Personal Bank..."); + Spawn* banker = cmdTarget; + client->Bank(banker); + break; + } + case COMMAND_GUILD_BANK:{ + LogWrite(GUILD__DEBUG, 0, "Guilds", "Open Guild Bank..."); + //Spawn* banker = client->GetPlayer()->GetTarget(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will eventually open the guild bank!"); + break; + } + case COMMAND_START_MAIL: { + client->SetMailTransaction(cmdTarget); + client->SendMailList(); + break; + } + case COMMAND_CANCEL_MAIL: { + client->SetMailTransaction(0); + break; + } + case COMMAND_GET_MAIL_MESSAGE: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->DisplayMailMessage(atoul(sep->arg[0])); + break; + } + case COMMAND_ADD_MAIL_PLAT: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->AddMailCoin(0, 0, 0, atoul(sep->arg[0])); + break; + } + case COMMAND_ADD_MAIL_GOLD: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->AddMailCoin(0, 0, atoul(sep->arg[0])); + break; + } + case COMMAND_ADD_MAIL_SILVER: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->AddMailCoin(0, atoul(sep->arg[0])); + break; + } + case COMMAND_ADD_MAIL_COPPER: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->AddMailCoin(atoul(sep->arg[0])); + break; + } + case COMMAND_SET_MAIL_ITEM: { + if(sep && sep->IsNumber(0) && sep->IsNumber(1)) + { + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(atoul(sep->arg[0])); + if(item) + { + int16 quantity = atoul(sep->arg[1]); + if(item->CheckFlag(NO_TRADE) || item->CheckFlag(ATTUNED) || item->CheckFlag(ARTIFACT) || item->CheckFlag2(HEIRLOOM)) + { + return; + } + if(item->IsBag()) + { + vector* bag_items = player->GetPlayerItemList()->GetItemsInBag(item); + if(bag_items && bag_items->size() > 0) + { + client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot mail a bag with items inside it."); + safe_delete(bag_items); + return; + } + safe_delete(bag_items); + } + Item* itemtoadd = item; + if(quantity > 0) + { + if(quantity > item->details.count) + return; + + Item* tmpItem = new Item(item); + tmpItem->details.count = quantity; + itemtoadd = tmpItem; + } + + if(client->AddMailItem(itemtoadd)) + { + client->RemoveItem(item, quantity, true); + } + } + } + break; + } + case COMMAND_REMOVE_MAIL_PLAT: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RemoveMailCoin(0, 0, 0, atoul(sep->arg[0])); + break; + } + case COMMAND_REMOVE_MAIL_GOLD: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RemoveMailCoin(0, 0, atoul(sep->arg[0])); + break; + } + case COMMAND_REMOVE_MAIL_SILVER: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RemoveMailCoin(0, atoul(sep->arg[0])); + break; + } + case COMMAND_REMOVE_MAIL_COPPER: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RemoveMailCoin(atoul(sep->arg[0])); + break; + } + case COMMAND_TAKE_MAIL_ATTACHMENTS: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->TakeMailAttachments(atoul(sep->arg[0])); + break; + } + case COMMAND_CANCEL_SEND_MAIL: { + client->ResetSendMail(); + break; + } + case COMMAND_DELETE_MAIL_MESSAGE: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->DeleteMail(atoul(sep->arg[0]), true); + break; + } + case COMMAND_REPORT_SPAM: { + LogWrite(MISC__TODO, 1, "TODO", " received reportspam\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + if (sep && sep->arg[0]) LogWrite(COMMAND__DEBUG, 0, "Command", "%s\n", sep->argplus[0]); + break; + } + case COMMAND_KILL:{ + Spawn* dead = 0; + if (sep && sep->arg[0] && strncasecmp(sep->arg[0],"self",4)==0){ + dead=client->GetPlayer(); + client->GetPlayer()->SetHP(0); + client->GetPlayer()->KillSpawn(dead); + }else{ + dead= cmdTarget; + if(dead && dead->IsPlayer() == false){ + if(dead->IsNPC() && ((NPC*)dead)->Brain()) { + client->GetPlayer()->CheckEncounterState((Entity*)dead); + ((NPC*)dead)->Brain()->AddToEncounter(client->GetPlayer()); + ((NPC*)dead)->AddTargetToEncounter(client->GetPlayer()); + ((NPC*)dead)->Brain()->AddHate(client->GetPlayer(), 1); + } + dead->SetHP(0); + if(sep && sep->arg[0] && sep->IsNumber(0) && atoi(sep->arg[0]) == 1) + client->GetCurrentZone()->RemoveSpawn(dead, true, true, true, true, true); + else + client->GetPlayer()->KillSpawn(dead); + }else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /kill (self)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Kills currently selected non-player target."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Optionally, self may be used to kill yourself"); + } + } + break; + } + case COMMAND_LEVEL:{ + if(sep && sep->arg[ndx][0] && sep->IsNumber(0)){ + int16 new_level = atoi(sep->arg[ndx]); + if (!client->GetPlayer()->CheckLevelStatus(new_level)) + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not have the required status to level up anymore!"); + else { + if (new_level < 1) + new_level = 1; + + if (new_level > 255) + new_level = 255; + + client->ChangeLevel(client->GetPlayer()->GetLevel(), new_level); + } + }else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /level {new_level}"); + break; + } + case COMMAND_SIT: { + if(client->GetPlayer()->GetHP() > 0){ + client->QueuePacket(new EQ2Packet(OP_SitMsg, 0, 0)); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"You sit down."); + client->GetPlayer()->set_character_flag(CF_IS_SITTING); + } + break; + } + case COMMAND_STAND: { + if(client->GetPlayer()->GetHP() > 0){ + client->QueuePacket(new EQ2Packet(OP_StandMsg, 0, 0)); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"You stand up."); + client->GetPlayer()->reset_character_flag(CF_IS_SITTING); + } + break; + } + case COMMAND_CLASS:{ + if(sep && sep->arg[ndx][0]){ + client->GetPlayer()->SetPlayerAdventureClass(atoi(sep->arg[ndx])); + client->UpdateTimeStampFlag ( CLASS_UPDATE_FLAG ); + }else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /class {class_id}"); + break; + } + case COMMAND_FLYMODE:{ + if(sep && sep->arg[0] && sep->IsNumber(0)){ + PrintSep(sep, "COMMAND_FLYMODE"); + int8 val = atoul(sep->arg[0]); + ClientPacketFunctions::SendFlyMode(client, val); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage ON: /flymode [1|2] 1 = fly, 2 = no clip"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage OFF: /flymode 0"); + } + break; + } + case COMMAND_LOOT_LIST:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->IsEntity()){ + if (sep && sep->arg[0]) { + if (!spawn->GetDatabaseID()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "NOTE: Spawn has no database id to assign to loottables."); + } + + if (!spawn->IsNPC()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "these /loot list [add/remove/clearall] sub-commands are only designed for an NPC."); + break; + } + + bool reloadLoot = false; + if (!stricmp(sep->arg[0], "remove") && sep->arg[1] && sep->IsNumber(1)) + { + int32 loottable_id = atoul(sep->arg[1]); + if (loottable_id > 0 && database.RemoveSpawnLootTable(spawn, loottable_id)) + { + client->Message(CHANNEL_COLOR_YELLOW, "Spawn %u loot table %u removed.", spawn->GetDatabaseID(), loottable_id); + reloadLoot = true; + } + else + client->Message(CHANNEL_COLOR_YELLOW, "/loot list remove [loottableid] - could not match any spawn_id entries against loottable_id %u.", loottable_id); + } + else if (!stricmp(sep->arg[0], "add") && sep->arg[1] && sep->IsNumber(1)) + { + + int32 loottable_id = atoul(sep->arg[1]); + if (loottable_id > 0) + { + database.AddLootTableToSpawn(spawn, loottable_id); + client->Message(CHANNEL_COLOR_YELLOW, "Spawn %u loot table %u added.", spawn->GetDatabaseID(), loottable_id); + reloadLoot = true; + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list add [loottableid] - cannot add a loottable id of 0."); + } + else if (!stricmp(sep->arg[0], "clearall")) + { + if (database.RemoveSpawnLootTable(spawn, 0)) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn loot tables removed."); + reloadLoot = true; + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list clearall - could not match any spawn_id entries in loottable_id."); + } + else if (!stricmp(sep->arg[0], "details")) + { + spawn->LockLoot(); + client->SimpleMessage(CHANNEL_COMMANDS, "Loot Window List:"); + if (spawn->GetLootWindowList()->size() > 0) { + std::map::iterator itr; + for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) { + Spawn* looter = client->GetPlayer()->GetZone()->GetSpawnByID(itr->first); + if (looter) { + client->Message(CHANNEL_COLOR_YELLOW, "Looter: %s IsLootWindowOpen: %s, HasCompletedLootWindow: %s.", looter->GetName(), itr->second ? "NO" : "YES", spawn->HasSpawnLootWindowCompleted(itr->first) ? "YES" : "NO"); + } + } + } + spawn->UnlockLoot(); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list argument not supported."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list add [loottableid] - add new loottable to spawn"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list remove [loottableid] - remove existing loottable from spawn"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/loot list clearall - remove all loottables from spawn"); + break; + } + + if (reloadLoot) + { + database.LoadSpawnLoot(spawn->GetZone(), spawn); + if (spawn->IsNPC()) + { + Entity* ent = (Entity*)spawn; + ent->SetLootCoins(0); + ent->ClearLoot(); + spawn->GetZone()->AddLoot((NPC*)spawn); + client->Message(CHANNEL_COLOR_YELLOW, "Spawn %u active loot purged and reloaded.", spawn->GetDatabaseID()); + } + } + break; // nothing further this is the end of these sub commands + } + else + { + vector loot_list = client->GetCurrentZone()->GetSpawnLootList(spawn->GetDatabaseID(), spawn->GetZone()->GetZoneID(), spawn->GetLevel(), race_types_list.GetRaceType(spawn->GetModelType()), spawn); + if (loot_list.size() > 0) { + client->Message(CHANNEL_COLOR_YELLOW, "%s belongs to the following loot lists: ", spawn->GetName()); + vector::iterator list_itr; + LootTable* table = 0; + for (list_itr = loot_list.begin(); list_itr != loot_list.end(); list_itr++) { + table = client->GetCurrentZone()->GetLootTable(*list_itr); + if (table) + client->Message(CHANNEL_COLOR_YELLOW, "%u - %s", *list_itr, table->name.c_str()); + } + } + client->Message(CHANNEL_COLOR_YELLOW, "Coins being carried: %u", spawn->GetLootCoins()); + vector* items = spawn->GetLootItems(); + if (items) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn is carrying the following items: "); + vector::iterator itr; + Item* item = 0; + for (itr = items->begin(); itr != items->end(); itr++) { + item = *itr; + client->Message(CHANNEL_COLOR_YELLOW, "%u - %s", item->details.item_id, item->name.c_str()); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn is not carrying any items."); + } + } + break; + } + case COMMAND_LOOT_SETCOIN:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->IsEntity() && sep && sep->arg[0] && sep->IsNumber(0)){ + spawn->SetLootCoins(atoul(sep->arg[0])); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully set coins."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /loot setcoin {amount}"); + break; + } + case COMMAND_LOOT_ADDITEM:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->IsEntity() && sep && sep->arg[0] && sep->IsNumber(0)){ + int16 charges = 1; + if(sep->arg[1] && sep->IsNumber(1)) + charges = atoi(sep->arg[1]); + spawn->AddLootItem(atoul(sep->arg[0]), charges); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully added item."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /loot additem {item_id}"); + break; + } + case COMMAND_LOOT_REMOVEITEM:{ + Spawn* spawn = cmdTarget; + if(spawn && spawn->IsEntity() && sep && sep->arg[0] && sep->IsNumber(0)){ + Item* item = spawn->LootItem(atoul(sep->arg[0])); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully removed item."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /loot removeitem {item_id}"); + break; + } + case COMMAND_LOOT_CORPSE: + case COMMAND_DISARM: + case COMMAND_LOOT:{ + if (cmdTarget && ((Entity*)cmdTarget)->IsNPC() && cmdTarget->Alive()) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Your target is not dead."); + break; + } + + if(cmdTarget && cmdTarget->IsEntity()){ + if (cmdTarget->GetDistance(client->GetPlayer()) <= rule_manager.GetGlobalRule(R_Loot, LootRadius)->GetFloat()){ + if (!rule_manager.GetGlobalRule(R_Loot, AutoDisarmChest)->GetBool() && command->handler == COMMAND_DISARM ) + client->OpenChest(cmdTarget, true); + else + client->LootSpawnRequest(cmdTarget, rule_manager.GetGlobalRule(R_Loot, AutoDisarmChest)->GetBool()); + if (!(cmdTarget)->HasLoot()){ + if (((Entity*)cmdTarget)->IsNPC()) + client->GetCurrentZone()->RemoveDeadSpawn(cmdTarget); + } + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You are too far away to interact with that"); + } + else if(!cmdTarget || cmdTarget->GetHP() > 0) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid target."); + break; + } + case COMMAND_RELOADSPAWNSCRIPTS:{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Spawn Scripts...."); + if (lua_interface) + lua_interface->SetLuaSystemReloading(true); + world.ResetSpawnScripts(); + database.LoadSpawnScriptData(); + if(lua_interface) { + lua_interface->DestroySpawnScripts(); + lua_interface->SetLuaSystemReloading(false); + } + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Success!"); + break; + } + case COMMAND_RELOADREGIONSCRIPTS:{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Region Scripts...."); + if(lua_interface) { + lua_interface->DestroyRegionScripts(); + } + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Success!"); + break; + } + case COMMAND_RELOADLUASYSTEM:{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Attempting to reload entire LUA system...."); + + world.SetReloadingSubsystem("LuaSystem"); + + if(lua_interface) { + lua_interface->SetLuaSystemReloading(true); + } + + zone_list.DeleteSpellProcess(); + master_spell_list.Reload(); + if (lua_interface) + lua_interface->ReloadSpells(); + zone_list.LoadSpellProcess(); + if(lua_interface){ + map debug_clients = lua_interface->GetDebugClients(); + map::iterator itr; + for(itr = debug_clients.begin(); itr != debug_clients.end(); itr++){ + if(lua_interface) + lua_interface->UpdateDebugClients(itr->first); + } + } + + world.ResetSpawnScripts(); + database.LoadSpawnScriptData(); + + world.ResetZoneScripts(); + database.LoadZoneScriptData(); + + if(lua_interface) { + lua_interface->DestroySpawnScripts(); + lua_interface->DestroyRegionScripts(); + lua_interface->DestroyQuests(); + lua_interface->DestroyItemScripts(); + lua_interface->DestroyZoneScripts(); + } + + int32 quest_count = database.LoadQuests(); + + int32 spell_count = 0; + + if(lua_interface) { + spell_count = database.LoadSpellScriptData(); + lua_interface->SetLuaSystemReloading(false); + } + + world.RemoveReloadingSubSystem("LuaSystem"); + + client->Message(CHANNEL_COLOR_YELLOW, "Successfully loaded %u spell(s), %u quest(s).", spell_count, quest_count); + break; + } + case COMMAND_DECLINE_QUEST:{ + int32 quest_id = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + quest_id = atoul(sep->arg[0]); + if(quest_id > 0){ + client->RemovePendingQuest(quest_id); + } + break; + } + case COMMAND_DELETE_QUEST:{ + int32 quest_id = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + quest_id = atoul(sep->arg[0]); + if(quest_id > 0){ + client->GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(lua_interface && client->GetPlayer()->player_quests.count(quest_id) > 0) { + Quest* quest = client->GetPlayer()->player_quests[quest_id]; + client->GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + if (quest && quest->CanDeleteQuest()) { + lua_interface->CallQuestFunction(quest, "Deleted", client->GetPlayer()); + client->RemovePlayerQuest(quest_id); + client->GetCurrentZone()->SendQuestUpdates(client); + } + } + else { + client->GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + } + } + break; + } + case COMMAND_ACCEPT_QUEST:{ + int32 quest_id = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + quest_id = atoul(sep->arg[0]); + if(quest_id > 0){ + client->AcceptQuest(quest_id); + } + break; + } + case COMMAND_NAME:{ + if(sep && sep->arg[ndx][0]){ + client->GetPlayer()->GetInfoStruct()->set_name(std::string(sep->arg[ndx]).substr(0,39)); + client->GetPlayer()->SetCharSheetChanged(true); + }else + client->Message(CHANNEL_COLOR_YELLOW,"Usage: /name {new_name}"); + + break; + } + case COMMAND_GROUP: { + if(sep && sep->arg[0]) + printf("Group: %s\n",sep->argplus[0]); + break; + } + case COMMAND_GROUPSAY:{ + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if(sep && sep->arg[0] && gmi) + world.GetGroupManager()->GroupChatMessage(gmi->group_id, client->GetPlayer(), client->GetPlayer()->GetCurrentLanguage(), sep->argplus[0]); + break; + } + case COMMAND_GROUPINVITE: { + Entity* target = 0; + Client* target_client = 0; + + if (client->GetPlayer()->GetGroupMemberInfo() && !client->GetPlayer()->GetGroupMemberInfo()->leader) { + client->SimpleMessage(CHANNEL_COMMANDS, "You must be the group leader to invite."); + return; + } + + // name provided with command so it has to be a player, npc not supported here + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 0){ + target_client = zone_list.GetClientByCharName(sep->arg[0]); + if (target_client) { + if (!target_client->IsConnected() || !target_client->IsReadyForSpawns()) + target = 0; + else + target = target_client->GetPlayer(); + } + } + + // if name was not provided use the players target, npc or player supported here + if (!target && client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity()) { + target = (Entity*)client->GetPlayer()->GetTarget(); + if (target->IsPlayer()) { + target_client = ((Player*)target)->GetClient(); + } + } + + int8 result = world.GetGroupManager()->Invite(client->GetPlayer(), target); + + if (result == 0) { + client->Message(CHANNEL_COMMANDS, "You invite %s to group with you.", target->GetName()); + if (target_client) { + PacketStruct* packet = configReader.getStruct("WS_ReceiveOffer", target_client->GetVersion()); + if (packet) { + packet->setDataByName("type", 1); + packet->setDataByName("name", client->GetPlayer()->GetName()); + packet->setDataByName("unknown2", 1); + target_client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + else if (result == 1) + client->SimpleMessage(CHANNEL_COMMANDS, "That player is already in a group."); + else if (result == 2) + client->SimpleMessage(CHANNEL_COMMANDS, "That player has been invited to another group."); + else if (result == 3) + client->SimpleMessage(CHANNEL_COMMANDS, "Your group is already full."); + else if (result == 4) + client->SimpleMessage(CHANNEL_COMMANDS, "You have a pending invitation, cancel it first."); + else if (result == 5) + client->SimpleMessage(CHANNEL_COMMANDS, "You cannot invite yourself!"); + else if (result == 6) + client->SimpleMessage(CHANNEL_COMMANDS, "Could not locate the player."); + else + client->SimpleMessage(CHANNEL_COMMANDS, "Group invite failed, unknown error!"); + + break; + } + case COMMAND_GROUPDISBAND: { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + + if (gmi) { // TODO: Leader check + // world.GetGroupManager()->SimpleGroupMessage(gmi->group_id, "Your group has been disbanded."); + world.GetGroupManager()->RemoveGroup(gmi->group_id); + } + + break; + } + case COMMAND_GROUP_LEAVE: { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + + if (gmi) { + int32 group_id = gmi->group_id; + world.GetGroupManager()->RemoveGroupMember(group_id, client->GetPlayer()); + if (!world.GetGroupManager()->IsGroupIDValid(group_id)) { + // leader->Message(CHANNEL_COLOR_GROUP, "%s has left the group.", client->GetPlayer()->GetName()); + } + else { + world.GetGroupManager()->GroupMessage(group_id, "%s has left the group.", client->GetPlayer()->GetName()); + } + + client->SimpleMessage(CHANNEL_GROUP_CHAT, "You have left the group"); + } + + break; + } + case COMMAND_FRIEND_ADD:{ + string name = ""; + if(sep && sep->arg[0] && strlen(sep->arg[0]) > 1) + name = database.GetPlayerName(sep->arg[0]); + else if(cmdTarget && cmdTarget->IsPlayer()) + name = string(cmdTarget->GetName()); + if(name.length() > 0){ + if(strcmp(name.c_str(), client->GetPlayer()->GetName()) != 0){ + if(client->GetPlayer()->IsIgnored(name.c_str())){ + client->GetPlayer()->RemoveIgnore(name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Removed ignore: %s", name.c_str()); + client->SendChatRelationship(3, name.c_str()); + } + client->GetPlayer()->AddFriend(name.c_str(), true); + client->SendChatRelationship(0, name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Added friend: %s", name.c_str()); + if(zone_list.GetClientByCharName(name)) + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Friend: %s has logged in.", name.c_str()); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot be more friendly towards yourself!"); + } + else + client->SimpleMessage(CHANNEL_COLOR_CHAT_RELATIONSHIP, "That character does not exist."); + break; + } + case COMMAND_FRIEND_REMOVE:{ + string name = ""; + if(sep && sep->arg[0] && strlen(sep->arg[0]) > 1) + name = database.GetPlayerName(sep->arg[0]); + else if(cmdTarget && cmdTarget->IsPlayer()) + name = string(cmdTarget->GetName()); + if(name.length() > 0){ + client->GetPlayer()->RemoveFriend(name.c_str()); + client->SendChatRelationship(1, name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Removed friend: %s", name.c_str()); + } + else + client->SimpleMessage(CHANNEL_COLOR_CHAT_RELATIONSHIP, "That character does not exist."); + break; + } + case COMMAND_FRIENDS:{ + + break; + } + case COMMAND_IGNORE_ADD:{ + string name = ""; + if(sep && sep->arg[0] && strlen(sep->arg[0]) > 1) + name = database.GetPlayerName(sep->arg[0]); + else if(cmdTarget && cmdTarget->IsPlayer()) + name = string(cmdTarget->GetName()); + if(name.length() > 0){ + if(strcmp(name.c_str(), client->GetPlayer()->GetName()) != 0){ + if(client->GetPlayer()->IsFriend(name.c_str())){ + client->GetPlayer()->RemoveFriend(name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Removed friend: %s", name.c_str()); + client->SendChatRelationship(1, name.c_str()); + } + client->GetPlayer()->AddIgnore(name.c_str(), true); + client->SendChatRelationship(2, name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Added ignore: %s", name.c_str()); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot ignore yourself!"); + } + else + client->SimpleMessage(CHANNEL_COLOR_CHAT_RELATIONSHIP, "That character does not exist."); + break; + } + case COMMAND_IGNORE_REMOVE:{ + string name = ""; + if(sep && sep->arg[0] && strlen(sep->arg[0]) > 1) + name = database.GetPlayerName(sep->arg[0]); + else if(cmdTarget && cmdTarget->IsPlayer()) + name = string(cmdTarget->GetName()); + if(name.length() > 0){ + client->GetPlayer()->RemoveIgnore(name.c_str()); + client->SendChatRelationship(3, name.c_str()); + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Removed ignore: %s", name.c_str()); + } + break; + } + case COMMAND_IGNORES:{ + + break; + } + case COMMAND_GROUP_KICK:{ + Entity* kicked = 0; + Client* kicked_client = 0; + int32 group_id = 0; + + if (!client->GetPlayer()->GetGroupMemberInfo() || !client->GetPlayer()->GetGroupMemberInfo()->leader) + return; + + if (sep && sep->arg[0]) { + kicked_client = zone_list.GetClientByCharName(string(sep->arg[0])); + if (kicked_client) + kicked = kicked_client->GetPlayer(); + } + else if (cmdTarget && cmdTarget->IsEntity()) { + kicked = (Entity*)cmdTarget; + } + + group_id = client->GetPlayer()->GetGroupMemberInfo()->group_id; + + bool send_kicked_message = world.GetGroupManager()->GetGroupSize(group_id) > 2; + if (kicked) + world.GetGroupManager()->RemoveGroupMember(group_id, kicked); + + if(send_kicked_message) + world.GetGroupManager()->GroupMessage(group_id, "%s was kicked from the group.", kicked->GetName()); + else + client->Message(CHANNEL_GROUP_CHAT, "You kicked %s from the group", kicked->GetName()); + + if (kicked_client) + kicked_client->SimpleMessage(CHANNEL_GROUP_CHAT, "You were kicked from the group"); + + break; + } + case COMMAND_GROUP_MAKE_LEADER:{ + + if (!client->GetPlayer()->GetGroupMemberInfo() || !client->GetPlayer()->GetGroupMemberInfo()->leader) { + client->SimpleMessage(CHANNEL_COMMANDS, "You are not a group leader."); + return; + } + + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 1) { + Client* new_leader = zone_list.GetClientByCharName(sep->arg[0]); + if (new_leader && new_leader->GetPlayer()->GetGroupMemberInfo() && new_leader->GetPlayer()->GetGroupMemberInfo()->group_id == client->GetPlayer()->GetGroupMemberInfo()->group_id) { + if (client->GetPlayer()->IsGroupMember(new_leader->GetPlayer())) { + if(world.GetGroupManager()->MakeLeader(client->GetPlayer()->GetGroupMemberInfo()->group_id, new_leader->GetPlayer())) { + world.GetGroupManager()->GroupMessage(client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s is now leader of the group.", new_leader->GetPlayer()->GetName()); + } + } + } + else + client->SimpleMessage(CHANNEL_COMMANDS, "Unable to find the given player."); + } + else if (cmdTarget && cmdTarget->IsPlayer() && ((Player*)cmdTarget)->GetGroupMemberInfo() && ((Player*)cmdTarget)->GetGroupMemberInfo()->group_id == client->GetPlayer()->GetGroupMemberInfo()->group_id) { + if(client->GetPlayer()->IsGroupMember((Player*)cmdTarget) && world.GetGroupManager()->MakeLeader(client->GetPlayer()->GetGroupMemberInfo()->group_id, (Entity*)cmdTarget)) { + world.GetGroupManager()->GroupMessage(client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s is now leader of the group.", cmdTarget->GetName()); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to change group leader."); + + break; + } + case COMMAND_GROUP_ACCEPT_INVITE: { + if((sep && sep->arg[0] && strcmp(sep->arg[0], "group") == 0) || (!sep && client->GetVersion() <= 561)) { + int8 result = world.GetGroupManager()->AcceptInvite(client->GetPlayer()); + + if (result == 0) + client->SimpleMessage(CHANNEL_GROUP_CHAT, "You have joined the group."); + else if (result == 1) + client->SimpleMessage(CHANNEL_GROUP_CHAT, "You do not have a pending invite."); + else if (result == 2) + client->SimpleMessage(CHANNEL_GROUP_CHAT, "Unable to join group - could not find leader."); + else + client->SimpleMessage(CHANNEL_GROUP_CHAT, "Unable to join group - unknown error."); + } + break; + } + case COMMAND_GROUP_DECLINE_INVITE: { + if(sep && sep->arg[0] && strcmp(sep->arg[0], "group") == 0) { + world.GetGroupManager()->DeclineInvite(client->GetPlayer()); // TODO: Add message to leader + } + break; + } + case COMMAND_SUMMON:{ + char* search_name = 0; + if(sep && sep->arg[0][0]) + search_name = sep->arg[0]; + + if(!search_name && !cmdTarget){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /summon [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Summons a targeted spawn or a spawn by name to you. If more than one spawn matches name, it will summon closest."); + } + else{ + if(!client->Summon(search_name)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No matches found."); + } + break; + } + case COMMAND_GOTO:{ + const char* search_name = 0; + if(sep && sep->arg[0][0]) + search_name = sep->argplus[0]; + if(!search_name && !cmdTarget){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /goto [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Moves to targeted spawn or to a spawn by name. If more than one spawn matches name, you will move to closest."); + } + else{ + if(!client->GotoSpawn(search_name)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No matches found."); + } + break; + } + case COMMAND_MOVE:{ + bool success = false; + try{ + if(sep && sep->arg[2][0] && sep->IsNumber(0) && sep->IsNumber(1) && sep->IsNumber(2)){ + EQ2Packet* app = client->GetPlayer()->Move(atof(sep->arg[0]), atof(sep->arg[1]), atof(sep->arg[2]), client->GetVersion()); + if(app){ + client->QueuePacket(app); + success = true; + } + } + } + catch(...){} + if(!success){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /move x y z"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Moves the client to the given coordinates."); + } + break; + } + case COMMAND_SETTIME:{ + if(sep && sep->arg[0]){ + + world.MWorldTime.writelock(__FUNCTION__, __LINE__); + sscanf (sep->arg[0], "%d:%d", &world.GetWorldTimeStruct()->hour, &world.GetWorldTimeStruct()->minute); + if(sep->arg[1] && sep->IsNumber(1)) + world.GetWorldTimeStruct()->month = atoi(sep->arg[1]) - 1; //zero based indexes + if(sep->arg[2] && sep->IsNumber(2)) + world.GetWorldTimeStruct()->day = atoi(sep->arg[2]) - 1; //zero based indexes + if(sep->arg[3] && sep->IsNumber(3)) + world.GetWorldTimeStruct()->year = atoi(sep->arg[3]); + world.MWorldTime.releasewritelock(__FUNCTION__, __LINE__); + client->GetCurrentZone()->SendTimeUpdateToAllClients(); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /settime [hour:minute] (month) (day) (year)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Example: /settime 8:30"); + } + break; + } + case COMMAND_VERSION:{ + client->Message(CHANNEL_COLOR_YELLOW,"%s %s", EQ2EMU_MODULE, CURRENT_VERSION); + client->Message(CHANNEL_COLOR_YELLOW,"Last Compiled on %s %s", COMPILE_DATE, COMPILE_TIME); + auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - world.world_uptime)); + client->Message(CHANNEL_COLOR_YELLOW,"Uptime %u day(s), %u hour(s), %u minute(s), %u second(s).", days, hours, minutes, seconds); + break; + } + case COMMAND_ZONE:{ + int32 instanceID = 0; + string zone; + int32 zone_id = 0; + bool listSearch = false; + bool isInstance = false; + ZoneServer* zsZone = 0; + + if(sep && sep->arg[0][0]) + { + if(strncasecmp(sep->arg[0], "list", 4) == 0) + listSearch = true; + + else if(strncasecmp(sep->arg[0], "active", 6) == 0) + { + zone_list.SendZoneList(client); + break; + } + + else if(strncasecmp(sep->arg[0], "instance", 6) == 0) + { + if(sep->IsNumber(1)) + instanceID = atol(sep->arg[1]); + } + + else if(strncasecmp(sep->arg[0], "lock", 4) == 0) + { + PrintSep(sep, "ZONE LOCK"); + + if(sep->IsNumber(1)) + zsZone = zone_list.Get(atoul(sep->arg[1]), false, false, false); + else + zsZone = zone_list.Get(sep->arg[1], false, false, false); + + if( zsZone ) + { + zsZone->SetZoneLockState(true); + client->Message(CHANNEL_COLOR_YELLOW, "Zone %s (%u) is now locked.", zsZone->GetZoneName(), zsZone->GetZoneID()); + } + else + client->Message(CHANNEL_COLOR_RED, "Zone %s is not running and cannot be locked.", sep->arg[1]); + + break; + } + else if(strncasecmp(sep->arg[0], "unlock", 6) == 0) + { + PrintSep(sep, "ZONE UNLOCK"); + + if(sep->IsNumber(1)) + zsZone = zone_list.Get(atoul(sep->arg[1]), false, false, false); + else + zsZone = zone_list.Get(sep->arg[1], false, false, false); + + if( zsZone ) + { + zsZone->SetZoneLockState(false); + client->Message(CHANNEL_COLOR_YELLOW, "Zone %s (%u) is now unlocked.", zsZone->GetZoneName(), zsZone->GetZoneID()); + } + else + client->Message(CHANNEL_COLOR_RED, "Zone %s is not running and cannot be unlocked.", sep->arg[1]); + break; + } + else + { + if(sep->IsNumber(0)) + { + zone_id = atoul(sep->arg[0]); + string zonename = database.GetZoneName(zone_id); + + if(zonename.length() > 0) + zone = zonename; + } + else + zone = sep->arg[0]; + } + if(instanceID > 0) + { + ZoneServer* zsInstance = zone_list.GetByInstanceID(instanceID); + + if(zsInstance != NULL) + { + instanceID = zsInstance->GetInstanceID(); + zone = zsInstance->GetZoneName(); + zone_id = zsInstance->GetZoneID(); + isInstance = true; + } + } + } + + if(!listSearch) + { + if(zone.length() == 0) + client->Message(CHANNEL_COLOR_RED, "Error: Invalid Zone"); + else if(instanceID != client->GetCurrentZone()->GetInstanceID() || + zone_id != client->GetCurrentZone()->GetZoneID()) + { + const char* zonestr = zone.c_str(); + if(database.VerifyZone(zonestr)) + { + if(client->CheckZoneAccess(zonestr)) + { + client->Message(CHANNEL_COLOR_YELLOW,"Zoning to %s...", zonestr); + if(isInstance) + client->Zone(instanceID,true,true,false); + else + client->Zone(zonestr); + } + } + else + client->Message(CHANNEL_COLOR_RED, "Error: Zone '%s' not found. To get a list type /zone list", zonestr); + } + else + client->Message(CHANNEL_COLOR_RED, "Error: You are already in that zone!"); + } + else + { + const char* name = 0; + if(sep && sep->arg[1][0]) + { + map* zone_names; + name = sep->argplus[1]; + sint16 status = client->GetAdminStatus(); + if( status > 0 ) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "ADMIN MODE"); + zone_names = database.GetZoneList(name, true); + } + else + zone_names = database.GetZoneList(name); + + if(!zone_names) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No zones found."); + else + { + map::iterator itr; + client->SimpleMessage(CHANNEL_COLOR_YELLOW," ID Name: "); + for(itr = zone_names->begin(); itr != zone_names->end(); itr++) + client->Message(CHANNEL_COLOR_YELLOW,"%03lu %s", itr->first, itr->second.c_str()); + safe_delete(zone_names); + } + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /zone [zone name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /zone list [partial zone name]"); + } + } + break; + } + case COMMAND_USE: { + Spawn* target = cmdTarget; + if (target && target->IsWidget()) + ((Widget*)target)->HandleUse(client, "use"); + break; + } + case COMMAND_OPEN: { + Spawn* target = cmdTarget; + if (target && target->IsWidget()) + ((Widget*)target)->HandleUse(client, "Open", WIDGET_TYPE_DOOR); + break; + } + case COMMAND_CASTSPELL: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int8 tier = 1; + if (sep->arg[1] && sep->IsNumber(1)) + tier = atoul(sep->arg[1]); + + int8 self = 1; + if (sep->arg[1] && sep->IsNumber(2)) + { + self = atoul(sep->arg[2]); + if(self != 1 && !cmdTarget->IsEntity()) + { + client->Message(CHANNEL_COLOR_RED, "Target is not an entity required to cast non-self spell."); + self = 1; + } + } + + int32 spellid = atoul(sep->arg[0]); + Spell* spell = master_spell_list.GetSpell(spellid, tier); + + if (spell) + { + client->Message(CHANNEL_COLOR_RED, "Casting spell %u.", spellid); + SpellProcess* spellProcess = 0; + // Get the current zones spell process + spellProcess = client->GetCurrentZone()->GetSpellProcess(); + + spellProcess->CastInstant(spell, (!cmdTarget || self == 1) ? (Entity*)client->GetPlayer() : (Entity*)cmdTarget, (cmdTarget && cmdTarget->IsEntity()) ? (Entity*)cmdTarget : (Entity*)client->GetPlayer()); + } + else + { + client->Message(CHANNEL_COLOR_RED, "Could not find spell %u.",spellid); + } + } + else if (sep && sep->arg[0]) + { + database.FindSpell(client, (char*)sep->argplus[0]); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Syntax: /castspell [spellid] (tier=1) - Cast Spell with specified spell id, tier is optional, default of 1."); + client->Message(CHANNEL_COLOR_YELLOW, "Syntax: /castspell [spellname] - Find spells wildcard match with a partial spell name"); + } + break; + } + case COMMAND_KNOWLEDGEWINDOWSORT: { + + if (sep && (client->GetVersion() <= 561 && sep->GetArgNumber() == 3) || sep->GetArgNumber() == 4) + { + int32 book = atoul(sep->arg[0]); // 0 - spells, 1 - combat, 2 - abilities, 3 - tradeskill + + // cannot sort the ability book in this client it is greyed out + if (client->GetVersion() <= 561 && book == SPELL_BOOK_TYPE_ABILITY) + break; + + int32 sort_by = atoul(sep->arg[1]); // 0 - alpha, 1 - level, 2 - category + int32 order = atoul(sep->arg[2]); // 0 - ascending, 1 - descending + int32 pattern = atoul(sep->arg[3]); // 0 - zigzag, 1 - down, 2 - across + int32 maxlvlonly = 0; + + if(client->GetVersion() > 561 && sep->arg[4][0]) { + maxlvlonly = atoul(sep->arg[4]); // checkbox for newer clients + } + client->GetPlayer()->ResortSpellBook(sort_by, order, pattern, maxlvlonly, book); + ClientPacketFunctions::SendSkillSlotMappings(client); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Syntax: /knowledgewindow_sort [book] [sort_by] [order] [pattern]"); + } + break; + } + case COMMAND_MOVE_ITEM: + { + int32 id = 0; + + if (sep && sep->IsNumber(0)) + id = atoul(sep->arg[0]); + + Spawn* spawn = 0; + if (id == 0) + { + spawn = cmdTarget; + } + else + spawn = client->GetCurrentZone()->GetSpawnFromUniqueItemID(id); + + if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID()) + break; + + client->SendMoveObjectMode(spawn, 0); + break; + } + case COMMAND_PICKUP: + { + int32 id = 0; + if (sep && sep->IsNumber(0)) + id = atoul(sep->arg[0]); + + Spawn* spawn = 0; + if (id == 0) + { + spawn = cmdTarget; + } + else + spawn = client->GetCurrentZone()->GetSpawnFromUniqueItemID(id); + + if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID()) + break; + + if(client->AddItem(spawn->GetPickupItemID(), 1)) { + Query query; + query.RunQuery2(Q_INSERT, "delete from spawn_instance_data where spawn_id = %u and spawn_location_id = %u and pickup_item_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), spawn->GetPickupItemID()); + + if (database.RemoveSpawnFromSpawnLocation(spawn)) { + client->GetCurrentZone()->RemoveSpawn(spawn, true, true, true, true, true); + } + + // we had a UI Window displayed, update the house items + if ( id > 0 ) + client->GetCurrentZone()->SendHouseItems(client); + } + + break; + } + case COMMAND_HOUSE_DEPOSIT: + { + PrintSep(sep, "COMMAND_HOUSE_DEPOSIT"); + // arg0 = spawn_id for DoF, could also be house_id for newer clients + // arg1 = coin (in copper) + // arg2 = status? (not implemented yet) + PlayerHouse* ph = nullptr; + int32 spawn_id = 0; + if(sep && sep->arg[0]) { + spawn_id = atoul(sep->arg[0]); + ph = world.GetPlayerHouse(client, spawn_id, client->GetVersion() > 561 ? spawn_id : 0, nullptr); + } + + if(!ph) { + ph = world.GetPlayerHouseByInstanceID(client->GetCurrentZone()->GetInstanceID()); + } + if (ph && sep && sep->IsNumber(1)) + { + int64 outValCoin = strtoull(sep->arg[1], NULL, 0); + int32 outValStatus = 0; + + if(sep->IsNumber(2)) + { + outValStatus = strtoull(sep->arg[2], NULL, 0); + if(outValStatus > client->GetPlayer()->GetInfoStruct()->get_status_points()) + outValStatus = 0; // cheat check + } + + if ((!outValCoin && outValStatus) || client->GetPlayer()->RemoveCoins(outValCoin)) + { + if(outValStatus) + client->GetPlayer()->GetInfoStruct()->subtract_status_points(outValStatus); + char query[256]; + map::iterator itr = ph->depositsMap.find(string(client->GetPlayer()->GetName())); + if (itr != ph->depositsMap.end()) + { + snprintf(query, 256, "update character_house_deposits set timestamp = %u, amount = amount + %llu, last_amount = %llu, status = status + %u, last_status = %u where house_id = %u and instance_id = %u and name='%s'", Timer::GetUnixTimeStamp(), outValCoin, outValCoin, outValStatus, outValStatus, ph->house_id, ph->instance_id, client->GetPlayer()->GetName()); + } + else + snprintf(query, 256, "insert into character_house_deposits set timestamp = %u, house_id = %u, instance_id = %u, name='%s', amount = %llu, status = %u", Timer::GetUnixTimeStamp(), ph->house_id, ph->instance_id, client->GetPlayer()->GetName(), outValCoin, outValStatus); + + if (database.RunQuery(query, strnlen(query, 256))) + { + ph->escrow_coins += outValCoin; + ph->escrow_status += outValStatus; + database.UpdateHouseEscrow(ph->house_id, ph->instance_id, ph->escrow_coins, ph->escrow_status); + + database.LoadDeposits(ph); + client->PlaySound("coin_cha_ching"); + HouseZone* hz = world.GetHouseZone(ph->house_id); + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetVersion() <= 561 ? spawn_id : client->GetPlayer()->GetID()); + } + else + { + if(outValCoin) + client->GetPlayer()->AddCoins(outValCoin); + + if(outValStatus) + client->GetPlayer()->GetInfoStruct()->add_status_points(outValStatus); + client->SimpleMessage(CHANNEL_COLOR_RED, "Deposit failed!"); + } + } + } + break; + } + case COMMAND_HOUSE: + { + int32 spawn_id = 0; + if (sep && sep->IsNumber(0)) + { + PlayerHouse* ph = nullptr; + if(!ph && sep && sep->arg[0]) { + spawn_id = atoul(sep->arg[0]); + ph = world.GetPlayerHouse(client, spawn_id, spawn_id, nullptr); + } + + HouseZone* hz = 0; + + if (ph) + hz = world.GetHouseZone(ph->house_id); + // there is a arg[1] that is true/false, but not sure what it is for investigate more later + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetVersion() <= 561 ? spawn_id : client->GetPlayer()->GetID()); + } + else if (client->GetCurrentZone()->GetInstanceType() != 0) + { + // inside a house or something? Send the access window + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(client->GetCurrentZone()->GetInstanceID()); + + HouseZone* hz = 0; + if ( ph ) + hz = world.GetHouseZone(ph->house_id); + + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetVersion() <= 561 ? spawn_id : client->GetPlayer()->GetID()); + client->GetCurrentZone()->SendHouseItems(client); + } + break; + } + case COMMAND_HOUSE_UI: + { + if (sep && sep->IsNumber(0) && client->GetCurrentZone()->GetInstanceType() == Instance_Type::NONE) + { + int32 unique_id = atoi(sep->arg[0]); + PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); + HouseZone* hz = 0; + if ( ph ) + hz = world.GetHouseZone(ph->house_id); + // there is a arg[1] that is true/false, but not sure what it is for investigate more later + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, client->GetPlayer()->GetID()); + } + break; + } + case COMMAND_PLACE_HOUSE_ITEM: { + if (sep && sep->IsNumber(0)) + { + int32 uniqueid = atoi(sep->arg[0]); + + Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(uniqueid); + //Item* item = player->GetEquipmentList()->GetItem(slot); + if (item && item->IsHouseItem()) + { + if (!client->HasOwnerOrEditAccess()) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!"); + break; + } + else if (!item->generic_info.appearance_id) + { + client->Message(CHANNEL_COLOR_RED, "This item has not been configured in the database, %s (%u) needs an entry where ?? has the model type id, eg. insert into item_appearances set item_id=%u,equip_type=??;", item->name.c_str(), item->details.item_id, item->details.item_id); + break; + } + else if(item->CheckFlag2(HOUSE_LORE) && client->GetCurrentZone()->HouseItemSpawnExists(item->details.item_id)) { + client->Message(CHANNEL_COLOR_RED, "Item %s is house lore and you cannot place another.", item->name.c_str()); + break; + } + + if (client->GetTempPlacementSpawn()) + { + Spawn* tmp = client->GetTempPlacementSpawn(); + client->GetCurrentZone()->RemoveSpawn(tmp, true, true, true, true, true); + client->SetTempPlacementSpawn(nullptr); + } + + + Object* obj = new Object(); + Spawn* spawn = (Spawn*)obj; + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + strcpy(spawn->appearance.name, "temp"); + spawn->SetX(client->GetPlayer()->GetX()); + spawn->SetY(client->GetPlayer()->GetY()); + spawn->SetZ(client->GetPlayer()->GetZ()); + spawn->appearance.pos.collision_radius = 32; + spawn->SetHeading(client->GetPlayer()->GetHeading()); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + spawn->appearance.targetable = 1; + spawn->appearance.activity_status = ACTIVITY_STATUS_SOLID; + spawn->appearance.race = item && item->generic_info.appearance_id ? item->generic_info.appearance_id : 1472; + spawn->SetModelType(item && item->generic_info.appearance_id ? item->generic_info.appearance_id : 1472); + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->SetZone(client->GetCurrentZone()); + client->SetTempPlacementSpawn(spawn); + client->SetPlacementUniqueItemID(uniqueid); + client->GetCurrentZone()->AddSpawn(spawn); + } + } + break; + } + case COMMAND_GM: + { + if (sep && sep->arg[0] && sep->arg[1]) + { + bool onOff = (strcmp(sep->arg[1], "on") == 0); + if (strcmp(sep->arg[0], "vision") == 0) + { + client->GetPlayer()->SetGMVision(onOff); + #if defined(__GNUC__) + database.insertCharacterProperty(client, CHAR_PROPERTY_GMVISION, (onOff) ? (char*)"1" : (char*)"0"); + #else + database.insertCharacterProperty(client, CHAR_PROPERTY_GMVISION, (onOff) ? "1" : "0"); + #endif + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + + if (onOff) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Enabled!"); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Disabled!"); + } + else if (strcmp(sep->arg[0], "tag") == 0) + { + int32 value = 0; + int16 tag_icon = 0; + // groundspawn has special exception -1 argument so it needs to be handled here + if(sep->arg[2] && sep->arg[3] && ((tag_icon = atoul(sep->arg[3])) > 0) || (strncasecmp(sep->arg[1], "groundspawn", 11) == 0)) { + value = atoul(sep->arg[2]); + if(strncasecmp(sep->arg[1], "faction", 7) == 0){ + if(!value) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid faction id."); + break; + } + if(!tag_icon) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id."); + break; + } + client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_FACTION, value, "", tag_icon); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + client->Message(CHANNEL_COLOR_RED, "Adding faction id %u with tag icon %u.", value, tag_icon); + } + else if(strncasecmp(sep->arg[1], "spawngroup", 10) == 0){ + if(value>0) { + value = 1; + } + if(!tag_icon) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id."); + break; + } + client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, value, "", tag_icon); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + client->Message(CHANNEL_COLOR_RED, "Adding spawn group tag \"%s\" with tag icon %u.", (value == 1) ? "on" : "off", tag_icon); + } + else if(strncasecmp(sep->arg[1], "race", 4 == 0)){ + if(!value) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid race id."); + break; + } + if(!tag_icon) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id."); + break; + } + client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, value, "", tag_icon); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + client->Message(CHANNEL_COLOR_RED, "Adding race id %u with tag icon %u.", value, tag_icon); + } + else if(strncasecmp(sep->arg[1], "groundspawn", 11) == 0){ + // one less argument value field not tag_icon for this one + if(!value) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid tag icon id."); + break; + } + client->GetPlayer()->AddGMVisualFilter(GMTagFilterType::GMFILTERTYPE_GROUNDSPAWN, 1, "", value); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + client->Message(CHANNEL_COLOR_RED, "Adding groundspawns with tag icon %u.", value); + } + } + else { + if(strncasecmp(sep->arg[1], "clear", 5) == 0){ + client->GetPlayer()->ClearGMVisualFilters(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Tags Cleared!"); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "GM Tagging Missing Arguments:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/gm tag [type] [value] [visual_icon_display]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/gm tag clear - Clears all GM visual tags"); + + client->SimpleMessage(CHANNEL_COLOR_RED, "Visual Type Options:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: faction, value: faction id of spawn(s)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: spawngroup, value: 1 to show grouped, 0 to show not grouped"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: race, value: race id (either against base race or race id in spawn details)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "type: groundspawn, value: (not used)"); + + client->SimpleMessage(CHANNEL_COLOR_RED, "Visual Icon Options:"); + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "1 = skull, 2 = shield half dark blue / half light blue, 3 = purple? star, 4 = yellow sword, 5 = red X, 6 = green flame"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "7 = Number \"1\", 8 = Number \"2\", 9 = Number \"3\", 10 = Number \"4\", 11 = Number \"5\", 12 = Number \"6\""); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "25 = shield, 26 = green plus, 27 = crossed swords, 28 = bow with arrow in it, 29 = light blue lightning bolt"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "30 = bard instrument (hard to see), 31 = writ with shield, 32 = writ with green +, 33 = writ with crossed sword"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "34 = writ with bow, 35 = writ with light blue lightning bolt, 36 = same as 30, 37 = party with crossed sword, shield and lightning bolt"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "38 = shaking hands green background, 39 = shaking hands dark green background, unlocked keylock"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, + "40 = red aura icon with black shadow of person and big red aura, 41 = green aura icon with black shadow of person big green aura"); + } + } + } + else if (strcmp(sep->arg[0], "regiondebug") == 0) + { + client->SetRegionDebug(onOff); + #if defined(__GNUC__) + database.insertCharacterProperty(client, CHAR_PROPERTY_REGIONDEBUG, (onOff) ? (char*)"1" : (char*)"0"); + #else + database.insertCharacterProperty(client, CHAR_PROPERTY_REGIONDEBUG, (onOff) ? "1" : "0"); + #endif + + if (onOff) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Enabled!"); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Disabled!"); + } + else if (strcmp(sep->arg[0], "sight") == 0) + { + if(onOff && cmdTarget) { + client->SetPlayerPOVGhost(cmdTarget); + } + else + client->SetPlayerPOVGhost(nullptr); + } + else if (strcmp(sep->arg[0], "controleffects") == 0) + { + if(cmdTarget && cmdTarget->IsEntity()) { + ((Entity*)cmdTarget)->SendControlEffectDetailsToClient(client); + } + else { + client->GetPlayer()->SendControlEffectDetailsToClient(client); + } + } + else if (strcmp(sep->arg[0], "luadebug") == 0) + { + client->SetLuaDebugClient(onOff); +#if defined(__GNUC__) + database.insertCharacterProperty(client, CHAR_PROPERTY_LUADEBUG, (onOff) ? (char*)"1" : (char*)"0"); +#else + database.insertCharacterProperty(client, CHAR_PROPERTY_LUADEBUG, (onOff) ? "1" : "0"); +#endif + + if (onOff) + { + if (lua_interface) + lua_interface->UpdateDebugClients(client); + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will now receive LUA error messages."); + } + else + { + if (lua_interface) + lua_interface->RemoveDebugClients(client); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will no longer receive LUA error messages."); + } + } + } + break; + } + case COMMAND_ATTACK: + case COMMAND_AUTO_ATTACK:{ + Command_AutoAttack(client, sep); + break; + } + case COMMAND_DEPOP:{ + bool allow_respawns = false; + if(sep && sep->arg[0] && sep->IsNumber(0)){ + if(atoi(sep->arg[0]) == 1) + allow_respawns = true; + } + client->GetCurrentZone()->Depop(allow_respawns); + break; + } + case COMMAND_REPOP:{ + client->GetCurrentZone()->Depop(false, true); + break; + } + case COMMAND_BUYBACK:{ + if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ + // Item ID and Unique ID get combined in this command so we need to get the number as an int64 and break it into 2 int32's + int64 id = atoi64(sep->arg[0]); + // get the first int32, the item id + //int32 item_id = (int32)id; + // get the second int32, the unique id + int32 unique_id = (int32)(id>>32); + int8 quantity = atoi(sep->arg[1]); + if(quantity == 255) + quantity = 1; + client->BuyBack(unique_id, quantity); + } + break; + } + case COMMAND_MERCHANT_BUY:{ + if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ + int32 item_id = atoul(sep->arg[0]); + int8 quantity = atoi(sep->arg[1]); + client->BuyItem(item_id, quantity); + } + else + Command_SendMerchantWindow(client, sep); + break; + } + case COMMAND_MERCHANT_SELL:{ + if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ + // Item ID and Unique ID get combined in this command so we need to get the number as an int64 and break it into 2 int32's + int64 id = atoi64(sep->arg[0]); + // get the first int32, the item id + int32 item_id = (int32)id; + // get the second int32, the unique id + int32 unique_id = (int32)(id>>32); + + int8 quantity = atoi(sep->arg[1]); + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "Selling Item: Item Id = %ul, unique id = %ul, Quantity = %i", item_id, unique_id, quantity); + client->SellItem(item_id, quantity, unique_id); + } + break; + } + case COMMAND_MENDER_REPAIR: { + if (sep && sep->arg[0] && sep->IsNumber(0)) + client->RepairItem(atoul(sep->arg[0])); + break; + } + case COMMAND_MENDER_REPAIR_ALL: { + client->RepairAllItems(); + break; + } + case COMMAND_CANCEL_MERCHANT:{ + client->SetMerchantTransaction(0); + break; + } + case COMMAND_START_MERCHANT:{ + break; + } + case COMMAND_INVULNERABLE:{ + sint8 val = -1; + if(sep && sep->arg[0] && sep->IsNumber(0)){ + val = atoi(sep->arg[0]); + +//devn00b: Fix for linux builds +#if defined(__GNUC__) + database.insertCharacterProperty(client, CHAR_PROPERTY_INVUL, (val == 1) ? (char*) "1" : (char*) "0"); +#else + database.insertCharacterProperty(client, CHAR_PROPERTY_INVUL, (val == 1) ? "1" : "0"); +#endif +// database.insertCharacterProperty(client, CHAR_PROPERTY_INVUL, (val == 1) ? "1" : "0"); + + client->GetPlayer()->SetInvulnerable(val==1); + if(client->GetPlayer()->GetInvulnerable()) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are now invulnerable!"); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are no longer invulnerable!"); + } + if(val == -1) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /invulnerable [0/1]"); + break; + } + case COMMAND_REPAIR: { + Spawn* spawn = cmdTarget; + if (spawn && spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR) { + client->SendHailCommand(spawn); + client->SetMerchantTransaction(spawn); + client->SendRepairList(); + } + break; + } + case COMMAND_LOTTO: { + Spawn* spawn = cmdTarget; + if (spawn && spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) { + client->SetMerchantTransaction(spawn); + client->ShowLottoWindow(); + } + break; + } + case COMMAND_ACCEPT_REWARD:{ + int32 unknown = 0; + int32 selectable_item_id = 0; + //Quest *quest = 0; + /* no idea what the first argument is for (faction maybe?) + if the reward has a selectable item reward, it's sent as the second argument + if neither of these are included in the reward, there is no sep + since the regular item rewards are manditary to receive, we don't have to know what they are until we accept the collection or quest + only 1 quest or collection reward may be displayed at a time */ + if (sep && sep->arg[0] && sep->arg[1] && sep->IsNumber(0) && sep->IsNumber(1)) { + unknown = atoul(sep->arg[0]); + selectable_item_id = atoul(sep->arg[1]); + } + + /* this logic here may seem unexpected, but the quest queue response for GetPendingQuestAcceptance is only populated if it is the current reward displayed to the client based on a quest + ** Otherwise it will likely be a DoF client scenario (pending item rewards, selectable item rewards) which is specifying an item id + ** lastly it will be a collection which also supplies an item id and you can only have one pending collection turn in at a time (they queue against Client::HandInCollections + */ + int32 item_id = 0; + if(sep && sep->arg[0][0] && sep->IsNumber(0)) + item_id = atoul(sep->arg[0]); + + bool collectedItems = client->GetPlayer()->AcceptQuestReward(item_id, selectable_item_id); + + if (collectedItems) { + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Error in COMMAND_ACCEPT_REWARD. No pending quest or collection reward was found (unknown=%u).", unknown); + break; + } + case COMMAND_BUY_FROM_BROKER:{ + if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ + int32 item_id = atoul(sep->arg[0]); + int16 quantity = atoul(sep->arg[1]); + Item* item = master_item_list.GetItem(item_id); + if(item && item->generic_info.max_charges > 1) + quantity = item->generic_info.max_charges; + client->AddItem(item_id, quantity, AddItemType::BUY_FROM_BROKER); + } + break; + } + case COMMAND_SEARCH_STORES_PAGE:{ + if(sep && sep->arg[0][0] && sep->IsNumber(0)){ + int32 page = atoul(sep->arg[0]); + client->SearchStore(page); + } + break; + } + case COMMAND_SEARCH_STORES:{ + if(sep && sep->arg[0][0]){ + const char* values = sep->argplus[0]; + if(values){ + LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values); + + map str_values = TranslateBrokerRequest(values); + vector* items = master_item_list.GetItems(str_values, client); + if(items){ + client->SetItemSearch(items); + client->SearchStore(0); + } + } + } + break; + } + + case COMMAND_LUADEBUG:{ + if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "start") == 0){ + client->SetLuaDebugClient(true); + if(lua_interface) + lua_interface->UpdateDebugClients(client); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will now receive LUA error messages."); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "stop") == 0){ + client->SetLuaDebugClient(false); + if(lua_interface) + lua_interface->RemoveDebugClients(client); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will no longer receive LUA error messages."); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /luadebug {start | stop}"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will allow you to receive lua debug messages normally seen only in the console."); + } + break; + } + case COMMAND_SPAWN_GROUP: + { + Spawn* target = cmdTarget; + + if(target && target->IsPlayer() == false) + { + if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "create") == 0) + { + if(target->GetSpawnLocationPlacementID() > 0) + { + if(sep->arg[1] && !sep->IsNumber(1) && strlen(sep->arg[1]) > 0) + { + int32 id = database.CreateSpawnGroup(target, sep->arg[1]); + + if(id > 0) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully created new spawn group with id: %u", id); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error creating group, check console for details."); + + target->SetSpawnGroupID(id); + target->AddSpawnToGroup(target); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn group create [name]"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Could not use spawn to create a new group. Likely cause would be a newly spawned spawn that did not exist when /reload spawns was last used."); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "add") == 0) + { + if(sep->arg[1] && sep->IsNumber(1)) + { + int32 group_id = atoul(sep->arg[1]); + Spawn* leader = client->GetCurrentZone()->GetSpawnGroup(group_id); + + if(leader) + { + leader->AddSpawnToGroup(target); + target->SetSpawnGroupID(group_id); + + if(database.SpawnGroupAddSpawn(target, group_id)) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully added '%s' to group id: %u", target->GetName(), group_id); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error adding spawn to group, check console for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No spawns found in the current zone for that spawn group, be sure to use '/spawn group create' first!"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn group add [group id]"); + } + else if(target->GetSpawnGroupID() == 0) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "That spawn is not in a group!"); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "remove") == 0) + { + int32 group_id = target->GetSpawnGroupID(); + target->RemoveSpawnFromGroup(); + + if(database.SpawnGroupRemoveSpawn(target, group_id)) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully removed '%s' from group id: %u", target->GetName(), group_id); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error removing spawn from group, check console for details."); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "associate") == 0) + { + int32 group_id = target->GetSpawnGroupID(); + + if(sep->arg[1] && sep->IsNumber(1)) + { + if(database.SpawnGroupAddAssociation(group_id, atoul(sep->arg[1]))) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully associated group id %u with group id %u", group_id, atoul(sep->arg[1])); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error associating groups, check console for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Allows you to spawn only one associated group at once. Syntax: /spawn group associate [other group id]"); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "deassociate") == 0) + { + int32 group_id = target->GetSpawnGroupID(); + + if(sep->arg[1] && sep->IsNumber(1)) + { + if(database.SpawnGroupRemoveAssociation(group_id, atoul(sep->arg[1]))) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully removed association between group id %u and group id %u", group_id, atoul(sep->arg[1])); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error removing group associations, check console for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Removes previous group associations. Syntax: /spawn group deassociate [other group id]"); + } + else if(sep && sep->arg[0][0] && strcmp(sep->arg[0], "chance") == 0) + { + if(sep->arg[1] && sep->IsNumber(1)) + { + if(database.SetGroupSpawnChance(target->GetSpawnGroupID(), atof(sep->arg[1]))) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully set group spawn chance to %f for group id: %u", atof(sep->arg[1]), target->GetSpawnGroupID()); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Error setting group spawn chance, check console for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn group chance [group's spawn chance percentage]"); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command allows you to modify spawn groups."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn group [create/add/remove/chance/associate/deassociate]"); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must select a valid spawn to use this command."); + break; + } + case COMMAND_SPAWN_COMBINE:{ + Spawn* spawn = cmdTarget; + float radius = 0; + bool failed = true; + if(spawn && !spawn->IsPlayer() && sep && sep->arg[0] && sep->arg[0][0]){ + failed = false; + if(spawn->GetSpawnGroupID() > 0){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Can not combine this spawn as it is in a spawn group. Remove it from the group and try again."); + } + else if(sep->IsNumber(0)){ + radius = atof(sep->arg[0]); + client->CombineSpawns(radius, spawn); + } + else{ + if(strncasecmp(sep->arg[0], "add", 3) == 0){ + client->AddCombineSpawn(spawn); + } + else if(strncasecmp(sep->arg[0], "remove", 6) == 0){ + client->RemoveCombineSpawn(spawn); + } + else if(strncasecmp(sep->arg[0], "save", 4) == 0){ + const char* name = 0; + if(sep->arg[1] && sep->arg[1][0]) + name = sep->argplus[1]; + client->SaveCombineSpawns(name); + } + else + failed = true; + } + } + if(failed){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command combines several spawns into a single spawn group."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Syntax: /spawn combine [radius/add/remove/save]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Ex: /spawn combine 1, /spawn combine save (spawn group name)"); + } + break; + } + case COMMAND_SPAWN_CREATE:{ + Spawn* spawn = 0; + if(sep && sep->arg[4][0] && strncasecmp(sep->arg[0], "object", 6) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)){ + spawn = new Object(); + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + } + else if (sep && sep->arg[4][0] && strncasecmp(sep->arg[0], "groundspawn", 11) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)) { + spawn = new GroundSpawn(); + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + } + else if (sep && sep->arg[4][0] && strncasecmp(sep->arg[0], "sign", 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)) { + spawn = new Sign(); + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + } + else if(sep && sep->arg[4][0] && strncasecmp(sep->arg[0], "npc", 3) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3)){ + spawn = new NPC(); + memset(&spawn->appearance, 0, sizeof(spawn->appearance)); + spawn->appearance.pos.collision_radius = 32; + spawn->secondary_command_list_id = 0; + spawn->primary_command_list_id = 0; + spawn->appearance.display_name = 1; + spawn->appearance.show_level = 1; + spawn->appearance.attackable = 1; + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn create [spawn type] [race type] [class type] [level] [name] (difficulty) (size)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "All parameters are required except difficulty and size. Valid types are Object, NPC, Sign, or GroundSpawn"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /spawn create npc 203 1 50 'Lady Vox' 1 32"); + break; + } + spawn->SetID(Spawn::NextID()); + spawn->SetX(client->GetPlayer()->GetX()); + spawn->SetY(client->GetPlayer()->GetY()); + spawn->SetZ(client->GetPlayer()->GetZ()); + spawn->SetHeading(client->GetPlayer()->GetHeading()); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + + + if(spawn->IsSign()) + { + ((Sign*)spawn)->SetSignType(SIGN_TYPE_GENERIC); + ((Sign*)spawn)->SetSignDistance(20.0f); + ((Sign*)spawn)->SetIncludeLocation(1); + ((Sign*)spawn)->SetIncludeHeading(1); + ((Sign*)spawn)->SetInitialState(1); + ((Sign*)spawn)->SetSignTitle(sep->arg[4]); + ((Sign*)spawn)->SetActivityStatus(64); + spawn->appearance.race = 0; + spawn->SetLevel(0); + spawn->SetHP(0); + spawn->SetTotalHP(0); + spawn->SetPower(0); + spawn->SetTotalPower(0); + spawn->SetDifficulty(0); + spawn->SetTargetable(0); + spawn->SetSogaModelType(0); + spawn->SetCollisionRadius(19); + ((Sign*)spawn)->SetWidgetX(client->GetPlayer()->GetX()); + ((Sign*)spawn)->SetWidgetY(client->GetPlayer()->GetY()); + ((Sign*)spawn)->SetWidgetZ(client->GetPlayer()->GetZ()); + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->appearance.model_type = atoul(sep->arg[1]); + } + else + { + spawn->appearance.targetable = 1; + spawn->appearance.race = 255; + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->SetModelType(atoi(sep->arg[1])); + spawn->SetAdventureClass(atoi(sep->arg[2])); + spawn->SetLevel(atoi(sep->arg[3])); + spawn->SetName(sep->arg[4]); + if(sep->arg[5][0] && sep->IsNumber(5)) + spawn->SetDifficulty(atoi(sep->arg[5])); + if(sep->arg[6][0] && sep->IsNumber(6)) + spawn->size = atoi(sep->arg[6]); + if(spawn->GetTotalHP() == 0){ + spawn->SetTotalHP(25*spawn->GetLevel() + 1); + spawn->SetHP(25*spawn->GetLevel() + 1); + } + if(spawn->GetTotalPower() == 0){ + spawn->SetTotalPower(25*spawn->GetLevel() + 1); + spawn->SetPower(25*spawn->GetLevel() + 1); + } + } + + client->GetCurrentZone()->AddSpawn(spawn); + break; + } + case COMMAND_SPAWN_EQUIPMENT:{ + Spawn* spawn = cmdTarget; + if(spawn && sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1) && spawn->IsEntity()){ + int8 slot = atoi(sep->arg[0]); + int16 type = atoi(sep->arg[1]); + int8 red = 0; + int8 green = 0; + int8 blue = 0; + int8 h_red = 0; + int8 h_green = 0; + int8 h_blue = 0; + if(sep->arg[2]) + red = atoi(sep->arg[2]); + if(sep->arg[3]) + green = atoi(sep->arg[3]); + if(sep->arg[4]) + blue = atoi(sep->arg[4]); + if(sep->arg[5]) + h_red = atoi(sep->arg[5]); + if(sep->arg[6]) + h_green = atoi(sep->arg[6]); + if(sep->arg[7]) + h_blue = atoi(sep->arg[7]); + ((Entity*)spawn)->SetEquipment(slot, type, red, green, blue, h_red, h_green, h_blue); + database.SaveNPCAppearanceEquipment(spawn->GetDatabaseID() , slot, type, red, green, blue, h_red, h_green, h_blue); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn equipment [slot] [appearance id] (red) (green) (blue) (highlight red) (hgreen) (hblue)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will set the given spawn's equipment. "); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Slot is 0-24, Appearance ID from appearances table, Colors are 0-255"); + } + break; + } + case COMMAND_SPAWN_DETAILS: { + Spawn* spawn = cmdTarget; + if (sep && sep->arg[0][0]) { + if (cmdTarget) + { + if (ToLower(string(sep->arg[0])) == "los") + { + bool hasLOS = client->GetPlayer()->CheckLoS(cmdTarget); + if (hasLOS) + client->Message(CHANNEL_COLOR_YELLOW, "You have line of sight with %s", spawn->GetName()); + else + client->Message(CHANNEL_COLOR_RED, "You DO NOT have line of sight with %s", spawn->GetName()); + + break; + } + else if (ToLower(string(sep->arg[0])) == "bestz") + { + glm::vec3 targPos(cmdTarget->GetX(), cmdTarget->GetZ(), cmdTarget->GetY()); + + float bestZ = client->GetPlayer()->FindDestGroundZ(targPos, cmdTarget->GetYOffset()); + client->Message(CHANNEL_COLOR_YELLOW, "Best Z for %s is %f", spawn->GetName(), bestZ); + break; + } + else if (ToLower(string(sep->arg[0])) == "inwater") + { + if (cmdTarget->GetRegionMap() == nullptr) + client->SimpleMessage(CHANNEL_COLOR_RED, "No water map for zone."); + else + { + bool inWater = cmdTarget->InWater(); + client->Message(CHANNEL_COLOR_YELLOW, "%s is %s.", cmdTarget->GetName(), inWater ? "in water" : "out of water"); + } + break; + } + else if (ToLower(string(sep->arg[0])) == "inlava") + { + if (cmdTarget->GetRegionMap() == nullptr) + client->SimpleMessage(CHANNEL_COLOR_RED, "No region map for zone."); + else + { + bool inLava = cmdTarget->InLava(); + client->Message(CHANNEL_COLOR_YELLOW, "%s is %s.", cmdTarget->GetName(), inLava ? "in lava" : "out of lava"); + } + break; + } + else if (ToLower(string(sep->arg[0])) == "regions") + { + glm::vec3 targPos(cmdTarget->GetX(), cmdTarget->GetY(), cmdTarget->GetZ()); + + if (cmdTarget->GetRegionMap() == nullptr) + client->SimpleMessage(CHANNEL_COLOR_RED, "No region map for zone."); + else + { + cmdTarget->GetRegionMap()->IdentifyRegionsInGrid(client, targPos); + } + break; + } + else if (ToLower(string(sep->arg[0])) == "behind") + { + bool isBehind = client->GetPlayer()->BehindTarget(cmdTarget); + client->Message(CHANNEL_COLOR_YELLOW, "%s %s.", isBehind ? "YOU are behind" : "YOU are NOT behind", cmdTarget->GetName()); + break; + } + else if (ToLower(string(sep->arg[0])) == "infront") + { + bool isBehind = client->GetPlayer()->InFrontSpawn(cmdTarget, client->GetPlayer()->GetX(), client->GetPlayer()->GetZ()); + client->Message(CHANNEL_COLOR_YELLOW, "%s %s.", isBehind ? "YOU are infront of" : "YOU are NOT infront of", cmdTarget->GetName()); + break; + } + else if (ToLower(string(sep->arg[0])) == "flank") + { + bool isFlanking = client->GetPlayer()->FlankingTarget(cmdTarget); + client->Message(CHANNEL_COLOR_YELLOW, "%s is %s.", isFlanking ? "YOU are flanking" : "YOU are NOT flanking", cmdTarget->GetName()); + break; + } + else if (ToLower(string(sep->arg[0])) == "aggro") + { + if(cmdTarget->IsNPC() && ((NPC*)cmdTarget)->Brain()) { + ((NPC*)cmdTarget)->Brain()->SendEncounterList(client); + ((NPC*)cmdTarget)->Brain()->SendHateList(client); + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "/spawn details aggro is for NPCs only!"); + } + break; + } + else if (ToLower(string(sep->arg[0])) == "angle") + { + float spawnAngle = client->GetPlayer()->GetFaceTarget(cmdTarget->GetX(), cmdTarget->GetZ()); + + client->Message(CHANNEL_COLOR_YELLOW, "Angle %f between player %s and target %s", spawnAngle, client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget()->GetName() : client->GetPlayer()->GetName(), client->GetPlayer()->GetName()); + break; + } + } + if (sep->IsNumber(0)) + { + float radius = atof(sep->arg[0]); + if (!client->GetCurrentZone()->SendRadiusSpawnInfo(client, radius)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No results in the radius were found."); + break; + } + } + if (spawn) { + const char* type = "NPC"; + if (spawn->IsObject()) + type = "Object"; + else if (spawn->IsSign()) + type = "Sign"; + else if (spawn->IsWidget()) + type = "Widget"; + else if (spawn->IsGroundSpawn()) + type = "GroundSpawn"; + + int16 race_type = race_types_list.GetRaceType(spawn->GetModelType()); + int16 race_base_type = race_types_list.GetRaceBaseType(spawn->GetModelType()); + char* category = race_types_list.GetRaceTypeCategory(spawn->GetModelType()); + char* subcategory = race_types_list.GetRaceTypeSubCategory(spawn->GetModelType()); + char* modelname = race_types_list.GetRaceTypeModelName(spawn->GetModelType()); + + client->Message(CHANNEL_COLOR_YELLOW, "Name: %s, %s ID: %u", spawn->GetName(), type, spawn->GetDatabaseID()); + client->Message(CHANNEL_COLOR_YELLOW, "Last Name: %s, Sub-Title: %s, Prefix: %s, Suffix: %s", spawn->GetLastName(), spawn->GetSubTitle(), spawn->GetPrefixTitle(), spawn->GetSuffixTitle()); + client->Message(CHANNEL_COLOR_YELLOW, "Spawn Location ID: %u, Spawn Group ID: %u", spawn->GetSpawnLocationID(), spawn->GetSpawnGroupID()); + client->Message(CHANNEL_COLOR_YELLOW, "Faction ID: %u, Merchant ID: %u, Transporter ID: %u", spawn->GetFactionID(), spawn->GetMerchantID(), spawn->GetTransporterID()); + client->Message(CHANNEL_COLOR_YELLOW, "Grid ID: %u", spawn->GetLocation()); + client->Message(CHANNEL_COLOR_YELLOW, "Race: %i, Class: %i, Gender: %i", spawn->GetRace(), spawn->GetAdventureClass(), spawn->GetGender()); + client->Message(CHANNEL_COLOR_YELLOW, "Level: %i, HP: %u / %u, Power: %u / %u", spawn->GetLevel(), spawn->GetHP(), spawn->GetTotalHP(), spawn->GetPower(), spawn->GetTotalPower()); + client->Message(CHANNEL_COLOR_YELLOW, "Respawn Time: %u (sec), X: %f, Y: %f, Z: %f Heading: %f", spawn->GetRespawnTime(), spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetHeading()); + client->Message(CHANNEL_COLOR_YELLOW, "Collision Radius: %i, Size: %i, Difficulty: %i, Heroic: %i", spawn->GetCollisionRadius(), spawn->GetSize(), spawn->GetDifficulty(), spawn->GetHeroic()); + client->Message(CHANNEL_COLOR_YELLOW, "Targetable: %i, Show Name: %i, Attackable: %i, Show Level: %i", spawn->GetTargetable(), spawn->GetShowName(), spawn->GetAttackable(), spawn->GetShowLevel()); + client->Message(CHANNEL_COLOR_YELLOW, "Show Command Icon: %i, Display Hand Icon: %i", spawn->GetShowCommandIcon(), spawn->GetShowHandIcon()); + if (spawn->IsEntity()) { + client->Message(CHANNEL_COLOR_YELLOW, "Facial Hair Type: %i, Hair Type: %i, Chest Type: %i, Legs Type: %i", ((Entity*)spawn)->GetFacialHairType(), ((Entity*)spawn)->GetHairType(), ((Entity*)spawn)->GetChestType(), ((Entity*)spawn)->GetLegsType()); + client->Message(CHANNEL_COLOR_YELLOW, "Soga Facial Hair Type: %i, Soga Hair Type: %i, Wing Type: %i", ((Entity*)spawn)->GetSogaFacialHairType(), ((Entity*)spawn)->GetSogaHairType(), ((Entity*)spawn)->GetWingType()); + } + + client->Message(CHANNEL_COLOR_YELLOW, "Model Type: %i, Soga Race Type: %i, Race Type: %u, Race Base Type: %u, Race Category: %s (subcategory: %s), Model Name: %s.", spawn->GetModelType(), spawn->GetSogaModelType(), race_type, race_base_type, category, subcategory, modelname); + client->Message(CHANNEL_COLOR_YELLOW, "Primary Command Type: %u, Secondary Command Type: %u", spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID()); + client->Message(CHANNEL_COLOR_YELLOW, "Visual State: %i, Action State: %i, Mood State: %i, Initial State: %i, Activity Status: %i", spawn->GetVisualState(), spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(), spawn->GetActivityStatus()); + client->Message(CHANNEL_COLOR_YELLOW, "Emote State: %i, Pitch: %f, Roll: %f, Hide Hood: %i", spawn->GetEmoteState(), spawn->GetPitch(), spawn->GetRoll(), spawn->appearance.hide_hood); + if (spawn->IsNPC()) + client->Message(CHANNEL_COLOR_YELLOW, "Randomize: %u", ((NPC*)spawn)->GetRandomize()); + if (spawn->IsNPC()){ + client->Message(CHANNEL_COLOR_YELLOW, "Heat Resist/Base: %u/%u",((NPC*)spawn)->GetHeatResistance(), ((NPC*)spawn)->GetHeatResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Cold Resist/Base: %u/%u",((NPC*)spawn)->GetColdResistance(), ((NPC*)spawn)->GetColdResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Magic Resist/Base: %u/%u",((NPC*)spawn)->GetMagicResistance(), ((NPC*)spawn)->GetMagicResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Mental Resist/Base: %u/%u",((NPC*)spawn)->GetMentalResistance(), ((NPC*)spawn)->GetMentalResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Divine Resist/Base: %u/%u",((NPC*)spawn)->GetDivineResistance(), ((NPC*)spawn)->GetDivineResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Disease Resist/Base: %u/%u",((NPC*)spawn)->GetDiseaseResistance(), ((NPC*)spawn)->GetDiseaseResistanceBase()); + client->Message(CHANNEL_COLOR_YELLOW, "Poison Resist/Base: %u/%u",((NPC*)spawn)->GetPoisonResistance(), ((NPC*)spawn)->GetPoisonResistanceBase()); + } + + string details; + details += "\\#0000FFName: " + string(spawn->GetName()) + "\n"; + details += "Type: " + string(type) + "\n"; + details += "ID: " + to_string(spawn->GetDatabaseID()) + "\n"; + details += "Last Name: " + string(spawn->GetLastName()) + "\n"; + details += "Sub-Title: " + string(spawn->GetSubTitle()) + "\n"; + details += "Prefix: " + string(spawn->GetPrefixTitle()) + "\n"; + details += "Suffix: " + string(spawn->GetSuffixTitle()) + "\n"; + details += "Race: " + to_string(spawn->GetRace()) + "\n"; + details += "Class: " + to_string(spawn->GetAdventureClass()) + "\n"; + details += "Gender: " + to_string(spawn->GetGender()) + "\n"; + details += "Level: " + to_string(spawn->GetLevel()) + "\n"; + details += "HP: " + to_string(spawn->GetHP()) + " / " + to_string(spawn->GetTotalHP()) + "(" + to_string(spawn->GetIntHPRatio()) + "%)\n"; + details += "Power: " + to_string(spawn->GetPower()) + + " / " + to_string(spawn->GetTotalPower()) + "\n"; + details += "Difficulty: " + to_string(spawn->GetDifficulty()) + "\n"; + details += "Heroic: " + to_string(spawn->GetHeroic()) + "\n"; + details += "Group ID: " + to_string(spawn->GetSpawnGroupID()) + "\n"; + details += "Faction ID: " + to_string(spawn->GetFactionID()) + "\n"; + details += "Merchant ID: " + to_string(spawn->GetMerchantID()) + "\n"; + details += "Transport ID: " + to_string(spawn->GetTransporterID()) + "\n"; + details += "Location ID: " + to_string(spawn->GetSpawnLocationID()) + "\n"; + char x[16]; + sprintf(x, "%.2f", spawn->GetX()); + char y[16]; + sprintf(y, "%.2f", spawn->GetY()); + char z[16]; + sprintf(z, "%.2f", spawn->GetZ()); + details += "Location: " + string(x) + ", " + string(y) + ", " + string(z) + "\n"; + + string details2; + details2 += "Heading: " + to_string(spawn->GetHeading()) + "\n"; + details2 += "Grid ID: " + to_string(spawn->GetLocation()) + "\n"; + details2 += "Size: " + to_string(spawn->GetSize()) + "\n"; + details2 += "Collision Radius: " + to_string(spawn->GetCollisionRadius()) + "\n"; + details2 += "Respawn Time: " + to_string(spawn->GetRespawnTime()) + "\n"; + details2 += "Targetable: " + to_string(spawn->GetTargetable()) + "\n"; + details2 += "Show Name: " + to_string(spawn->GetShowName()) + "\n"; + details2 += "Attackable: " + to_string(spawn->GetAttackable()) + "\n"; + details2 += "Show Level: " + to_string(spawn->GetShowLevel()) + "\n"; + details2 += "Show Command Icon: " + to_string(spawn->GetShowCommandIcon()) + "\n"; + details2 += "Display Hand Icon: " + to_string(spawn->GetShowHandIcon()) + "\n"; + details2 += "Model Type: " + to_string(spawn->GetModelType()) + "\n"; + details2 += "Soga Race Type: " + to_string(spawn->GetSogaModelType()) + "\n"; + details2 += "Race Type: " + to_string(race_type) + "\n"; + details2 += "Race Base Type: " + to_string(race_base_type) + "\n"; + details2 += "Race Category (Sub Category): " + std::string(category) + " (" + std::string(subcategory) + ")\n"; + details2 += "Model Name: " + std::string(modelname) + "\n"; + details2 += "Primary Command ID: " + to_string(spawn->GetPrimaryCommandListID()) + "\n"; + details2 += "Secondary Cmd ID: " + to_string(spawn->GetSecondaryCommandListID()) + "\n"; + details2 += "Visual State: " + to_string(spawn->GetVisualState()) + "\n"; + details2 += "Action State: " + to_string(spawn->GetActionState()) + "\n"; + details2 += "Mood State: " + to_string(spawn->GetMoodState()) + "\n"; + details2 += "Initial State: " + to_string(spawn->GetInitialState()) + "\n"; + details2 += "Activity Status: " + to_string(spawn->GetActivityStatus()) + "\n"; + details2 += "Emote State: " + to_string(spawn->GetEmoteState()) + "\n"; + + string details3; + details3 += "Pitch: " + to_string(spawn->GetPitch()) + "\n"; + details3 += "Roll: " + to_string(spawn->GetRoll()) + "\n"; + details3 += "Hide Hood: " + to_string(spawn->appearance.hide_hood) + "\n"; + details3 += "Speed: " + to_string(spawn->GetSpeed()) + "\n"; + details3 += "BaseSpeed: " + to_string(spawn->GetBaseSpeed()) + "\n"; + + if(spawn->IsSign()) { + details3 += "Sign Language: " + to_string(((Sign*)spawn)->GetLanguage()) + "\n"; + } + + if(spawn->IsEntity()) + { + Entity* ent = (Entity*)spawn; + details3 += "STR / STRBase: " + to_string(ent->GetInfoStruct()->get_str()) + " / " + to_string(ent->GetInfoStruct()->get_str_base()) + "\n"; + details3 += "AGI / AGIBase: " + to_string(ent->GetInfoStruct()->get_agi()) + " / " + to_string(ent->GetInfoStruct()->get_agi_base()) + "\n"; + details3 += "STA / STABase: " + to_string(ent->GetInfoStruct()->get_sta()) + " / " + to_string(ent->GetInfoStruct()->get_sta_base()) + "\n"; + details3 += "INT / INTBase: " + to_string(ent->GetInfoStruct()->get_intel()) + " / " + to_string(ent->GetInfoStruct()->get_intel_base()) + "\n"; + details3 += "WIS / WISBase: " + to_string(ent->GetInfoStruct()->get_wis()) + " / " + to_string(ent->GetInfoStruct()->get_wis_base()) + "\n"; + } + + string details4; + if (spawn->IsEntity()) { + details4 += "Facial Hair Type: " + to_string(((Entity*)spawn)->GetFacialHairType()) + "\n"; + details4 += "Hair Type: " + to_string(((Entity*)spawn)->GetHairType()) + "\n"; + details4 += "Chest Type: " + to_string(((Entity*)spawn)->GetChestType()) + "\n"; + details4 += "Legs Type: " + to_string(((Entity*)spawn)->GetLegsType()) + "\n"; + details4 += "Soga Facial Hair Type: " + to_string(((Entity*)spawn)->GetSogaFacialHairType()) + "\n"; + details4 += "Soga Hair Type: " + to_string(((Entity*)spawn)->GetSogaHairType()) + "\n"; + details4 += "Wing Type: " + to_string(((Entity*)spawn)->GetWingType()) + "\n"; + if (spawn->IsNPC()) { + details4 += "\nRandomize: " + to_string(((NPC*)spawn)->GetRandomize()) + "\n"; + } + } + const char* spawnScriptMsg = (spawn->GetSpawnScript() && strlen(spawn->GetSpawnScript())>0) ? spawn->GetSpawnScript() : "Not Set"; + details4 += "\nSpawnScript: " + std::string(spawnScriptMsg) + "\n"; + + string title = string(spawn->GetName()) + "(" + to_string(spawn->GetDatabaseID()) + ")"; + client->SendShowBook(client->GetPlayer(), title, 0, 4, details, details2, details3, details4); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn details (radius)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will display information about the currently selected spawn, or in the case of specifying a numerical radius will show minor details about spawns in the players radius."); + } + break; + } + case COMMAND_SPAWN_TARGET:{ + if(sep && sep->arg[0][0] && sep->IsNumber(0)){ + int32 spawn_id = atoul(sep->arg[0]); + int16 response = client->GetCurrentZone()->SetSpawnTargetable(spawn_id); + client->Message(CHANNEL_COLOR_YELLOW, "%i spawn(s) in the current zone were reset to targetable.", response); + } + else if(sep && sep->arg[0][0] && sep->arg[1][0] && sep->IsNumber(1) && ToLower(string(sep->arg[0])) == "radius"){ + float distance = atof(sep->arg[1]); + int16 response = client->GetCurrentZone()->SetSpawnTargetable(client->GetPlayer(), distance); + client->Message(CHANNEL_COLOR_YELLOW, "%i spawn(s) in the current zone were reset to targetable.", response); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn target [spawn id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This will set the given spawn as targetable. Used to change a spawn if it was set to untargetable."); + } + break; + } + case COMMAND_SPAWN_SET: + { + Spawn* spawn = cmdTarget; + sint16 set_type = -1; + string type_str; + + if (spawn) + { + // check if parameters are (location or list or not player and not a 2nd param), and that there is at least 1 value + if(sep && ((sep->arg[0][0] && ToLower(string(sep->arg[0])) == "location") || (sep->arg[0][0] && ToLower(string(sep->arg[0])) == "list") || (spawn && spawn->IsPlayer() == false && sep->arg[1][0])) && spawn_set_values.count(ToLower(string(sep->arg[0]))) == 1) + { + // set the type, which will be 0 if location or list or invalid + set_type = spawn_set_values[ToLower(string(sep->arg[0]))]; + } + + if(set_type > 0) + { + // check if spawn set is NOT a char update, or not location, or isn't a number + if(!(set_type >= SPAWN_SET_VALUE_PREFIX) && !(set_type <= SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL) && set_type != SPAWN_SET_VALUE_NAME && ((set_type < SPAWN_SET_VALUE_SPAWN_SCRIPT) || (set_type > SPAWN_SET_VALUE_SUB_TITLE)) && set_type != SPAWN_SET_VALUE_LOCATION && sep->IsNumber(1) == false) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Invalid value for set command."); + } + else + { + string name = string(spawn->GetName()); + bool customSetSpawn = false; + if(set_type >= SPAWN_SET_CHEEK_TYPE && set_type <= SPAWN_SET_SOGA_NOSE_TYPE) + { + int8 index = sep->IsNumber(2) ? atoul(sep->arg[2]) : 0; + + // override the standard setspawncommand, we pass arguments different! + if(SetSpawnCommand(client, spawn, set_type, sep->arg[1], true, false, nullptr, index)) + customSetSpawn = true; + } + + if(customSetSpawn || SetSpawnCommand(client, spawn, set_type, sep->argplus[1])) + { + if (set_type == SPAWN_SET_VALUE_EXPANSION_FLAG || set_type == SPAWN_SET_VALUE_HOLIDAY_FLAG) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "A /reload spawns is required to properly update the spawns with the xpack/holiday flag."); + } + else if (set_type == SPAWN_SET_VALUE_NAME) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "New name will not be effective until zone reload."); + } + else if (set_type == SPAWN_SET_SKIN_COLOR || (set_type >= SPAWN_SET_HAIR_COLOR1 && set_type <= SPAWN_SET_SOGA_EYE_COLOR)) + { + client->Message(CHANNEL_COLOR_YELLOW, "Successfully set color field to R G B: %s.", sep->argplus[1]); + } + else if(set_type == SPAWN_SET_VALUE_LOCATION) + { + spawn->SetLocation(client->GetPlayer()->GetLocation()); + client->Message(CHANNEL_COLOR_YELLOW, "Successfully set '%s' to '%u' for spawn '%s' (DBID: %u)", sep->arg[0], client->GetPlayer()->GetLocation(), name.c_str(), spawn->GetDatabaseID()); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Successfully set '%s' to '%s' for spawn '%s' (DBID: %u)", sep->arg[0], sep->arg[1], name.c_str(), spawn->GetDatabaseID()); + } + + switch (set_type) + { + case SPAWN_SET_VALUE_EXPANSION_FLAG: + case SPAWN_SET_VALUE_HOLIDAY_FLAG: + case SPAWN_SET_VALUE_FACTION: + case SPAWN_SET_AAXP_REWARDS: + case SPAWN_SET_CHEEK_TYPE: + case SPAWN_SET_CHIN_TYPE: + case SPAWN_SET_EAR_TYPE: + case SPAWN_SET_EYE_BROW_TYPE: + case SPAWN_SET_EYE_TYPE: + case SPAWN_SET_LIP_TYPE: + case SPAWN_SET_NOSE_TYPE: + case SPAWN_SET_BODY_SIZE: + case SPAWN_SET_BODY_AGE: + case SPAWN_SET_SOGA_CHEEK_TYPE: + case SPAWN_SET_SOGA_CHIN_TYPE: + case SPAWN_SET_SOGA_EAR_TYPE: + case SPAWN_SET_SOGA_EYE_BROW_TYPE: + case SPAWN_SET_SOGA_EYE_TYPE: + case SPAWN_SET_SOGA_LIP_TYPE: + case SPAWN_SET_SOGA_NOSE_TYPE: + case SPAWN_SET_SOGA_BODY_SIZE: + case SPAWN_SET_SOGA_BODY_AGE: + case SPAWN_SET_ATTACK_TYPE: + case SPAWN_SET_RACE_TYPE: + case SPAWN_SET_LOOT_TIER: + case SPAWN_SET_LOOT_DROP_TYPE: + case SPAWN_SET_SCARED_STRONG_PLAYERS: + { + // not applicable already ran db command + break; + } + default: + { + client->GetCurrentZone()->ApplySetSpawnCommand(client, spawn, set_type, sep->argplus[1]); + break; + } + } + + if((set_type >= SPAWN_SET_VALUE_RESPAWN && set_type <=SPAWN_SET_VALUE_LOCATION) || (set_type >= SPAWN_SET_VALUE_EXPIRE && set_type <=SPAWN_SET_VALUE_Z_OFFSET) || (set_type == SPAWN_SET_VALUE_PITCH || set_type == SPAWN_SET_VALUE_ROLL)) + { + if(spawn->GetSpawnLocationID() > 0 && database.UpdateSpawnLocationSpawns(spawn)) + { + client->Message(CHANNEL_COLOR_YELLOW, "Successfully saved spawn information for spawn '%s' (DBID: %u)", name.c_str(), spawn->GetDatabaseID()); + } + else if(spawn->GetSpawnLocationID() > 0) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn information, see console window for details."); + } + } + else + { + if(spawn->GetDatabaseID() > 0 && database.SaveSpawnInfo(spawn)) + { + client->Message(CHANNEL_COLOR_YELLOW, "Successfully saved spawn for spawn '%s' (DBID: %u)", name.c_str(), spawn->GetDatabaseID()); + } + else if(spawn->GetDatabaseID() > 0) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn, see console window for details."); + } + } + } + } + } + else if(set_type == 0) + { + // /spawn set list - lists all possible attributes that can be changed with this command, 10 per line. + map::iterator itr; + int i=0; + string list; + for(itr = spawn_set_values.begin(); itr != spawn_set_values.end(); itr++, i++) + { + if(i==10) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, list.c_str()); + i = 0; + } + + if(i>0) + { + list.append(", ").append(itr->first); + } + else + { + list = itr->first; + } + } + + if(list.length() > 0) + { + // if 1 or more, display in client. + client->SimpleMessage(CHANNEL_COLOR_YELLOW, list.c_str()); + } + } + else + { + // syntax fail + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn set [type] [value]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command is used to change various settings for the targeted NPC or Object."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "For a list of changeable settings use /spawn set list"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Note: /spawn set location does not require a value. The client's current location is used."); + } + } + break; + } + case COMMAND_SPAWN_REMOVE:{ + Spawn* spawn = cmdTarget; + if(spawn && !spawn->IsPlayer()){ + if(spawn->GetSpawnLocationID() > 0){ + string name = string(spawn->GetName()); + int32 dbid = spawn->GetDatabaseID(); + if(database.RemoveSpawnFromSpawnLocation(spawn)){ + client->GetCurrentZone()->RemoveSpawn(spawn, true, false, true, true, true); + client->Message(CHANNEL_COLOR_YELLOW, "Successfully removed spawn from zone for spawn '%s' (DBID: %u)", name.c_str(), dbid); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error removing spawn, see console window for details."); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "This spawn does not have a spawn group associated with it"); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn remove"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command is used for removing the targeted NPC or Object from the zone."); + } + break; + } + case COMMAND_SPAWN_LIST:{ + if(sep && sep->arg[0][0]){ + vector* results = database.GetSpawnNameList(sep->argplus[0]); + vector::iterator itr; + if(results){ + for(itr=results->begin();itr!=results->end();itr++){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, (*itr).c_str()); + } + safe_delete(results); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No matches found. "); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn list [name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name can be a partial match."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /spawn list Qeynos Guard"); + } + break; + } + case COMMAND_SPAWN_ADD:{ + Spawn* spawn = cmdTarget; + if(spawn && sep && ((sep->arg[1][0] && sep->IsNumber(0)) || (sep->arg[0][0] && strncasecmp(sep->arg[0], "new", 3) == 0))){ + if(spawn->GetSpawnLocationID() > 0){ + client->Message(CHANNEL_COLOR_RED, "This spawn already has a spawn group id of %u, use /spawn remove to reassign it", spawn->GetSpawnLocationID()); + break; + } + if(spawn->GetDatabaseID() == 0){ + if(database.SaveSpawnInfo(spawn)) { + char spawn_type[32]; + memset(spawn_type, 0, sizeof(spawn_type)); + if (spawn->IsNPC()) + strncpy(spawn_type, "NPC", sizeof(spawn_type) - 1); + else if (spawn->IsObject()) + strncpy(spawn_type, "Object", sizeof(spawn_type) - 1); + else if (spawn->IsSign()) + strncpy(spawn_type, "Sign", sizeof(spawn_type) - 1); + else if (spawn->IsGroundSpawn()) + strncpy(spawn_type, "GroundSpawn", sizeof(spawn_type) - 1); + else + strncpy(spawn_type, "Unknown", sizeof(spawn_type) - 1); + client->Message(CHANNEL_COLOR_YELLOW, "Successfully saved spawn information with a %s id of %u", spawn_type, spawn->GetDatabaseID()); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error saving spawn information, see console window for details."); + } + int32 spawn_group_id = 0; + if(strncasecmp(sep->arg[0], "new", 3) == 0) + spawn_group_id = database.GetNextSpawnLocation(); + else + spawn_group_id = atol(sep->arg[0]); + int8 percent = 100; + if(sep->arg[2] && sep->IsNumber(2)) + percent = atoi(sep->arg[2]); + spawn->SetSpawnLocationID(spawn_group_id); + float x_offset = database.GetSpawnLocationPlacementOffsetX(spawn->GetSpawnLocationID()); + float y_offset = database.GetSpawnLocationPlacementOffsetY(spawn->GetSpawnLocationID()); + float z_offset = database.GetSpawnLocationPlacementOffsetZ(spawn->GetSpawnLocationID()); + if(database.SaveSpawnEntry(spawn, sep->arg[1], percent, x_offset, y_offset, z_offset)) + client->Message(CHANNEL_COLOR_YELLOW, "Successfully saved spawn location with a spawn group of %u", spawn->GetSpawnLocationID()); + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error saving spawn location, see console window for details."); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn add [spawn group id] [spawn group name] (percentage)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "This command is used for adding the targeted NPC or Object to the database."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can substitute new for [spawn group id] to create a new one."); + } + break; + } + case COMMAND_SPAWN:{ + int32 id = 0; + Spawn* spawn = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)){ + id = atol(sep->arg[0]); + spawn = client->GetCurrentZone()->GetSpawn(id); + } + if(id > 0 && spawn && spawn->appearance.name[0] == 0) + id = 0; + if(!id || !spawn){ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn [spawn id] (x) (y) (z) (heading) (location)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "All parameters are optional except the id. The spawn id must be in the database."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /spawn 1 0 0 0 0"); + safe_delete(sep); + return; + } + if(sep && sep->arg[1][0]){ + float x = atof(sep->arg[1]); + spawn->appearance.pos.X = x; + } + else + spawn->SetX(client->GetPlayer()->GetX(), false); + if(sep && sep->arg[2][0]){ + float y = atof(sep->arg[2]); + spawn->appearance.pos.Y = y; + } + else + spawn->SetY(client->GetPlayer()->GetY(), false); + if(sep && sep->arg[3][0]){ + float z = atof(sep->arg[3]); + spawn->appearance.pos.Z = z; + } + else + spawn->SetZ(client->GetPlayer()->GetZ(), false); + if(sep && sep->arg[4][0]){ + float heading = atof(sep->arg[4]); + spawn->SetHeading(heading); + } + else + spawn->SetHeading(client->GetPlayer()->GetHeading(), false); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + if(sep && sep->arg[5][0]) + spawn->SetLocation(atoul(sep->arg[5])); + else + spawn->SetLocation(client->GetPlayer()->GetLocation()); + + if(spawn->IsNPC() && spawn->GetTotalHP() == 0){ + spawn->SetTotalHP(spawn->GetLevel() * 15); + spawn->SetHP(spawn->GetTotalHP()); + } + if(spawn->GetTotalPower() == 0){ + spawn->SetTotalPower(spawn->GetLevel() * 15); + spawn->SetPower(spawn->GetTotalPower()); + } + const char* script = world.GetSpawnScript(id); + if(script && lua_interface && lua_interface->GetSpawnScript(script) != 0) + spawn->SetSpawnScript(string(script)); + + spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_PRESPAWN); + + client->GetCurrentZone()->AddSpawn(spawn); + if(spawn->IsNPC()) + spawn->GetZone()->AddLoot((NPC*)spawn); + spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + LogWrite(COMMAND__INFO, 0, "Command", "Received spawn command - Parms: %s", command_parms->data.c_str()); + break; + } + case COMMAND_ADMINFLAG: + { + if(sep && sep->arg[0]){ + sint16 tmp_status = database.GetCharacterAdminStatus(sep->arg[0]); + sint16 new_status = atoi(sep->arg[1]); + if(tmp_status == -10) + client->SimpleMessage(CHANNEL_ERROR,"Unable to flag character. Reason: Character does not exist."); + else if(tmp_status >= client->GetAdminStatus()) + client->SimpleMessage(CHANNEL_ERROR,"Unable to flag character. Reason: Character has same or higher level status."); + else if (new_status > client->GetAdminStatus()) + client->SimpleMessage(CHANNEL_ERROR, "Unable to flag character. Reason: New status is higher then your status."); + else{ + Client* client2 = client->GetCurrentZone()->GetClientByName(sep->arg[0]); + if (!client2) + client2 = zone_list.GetClientByCharName(sep->arg[0]); + + if(database.UpdateAdminStatus(sep->arg[0],new_status)) { + client->Message(CHANNEL_COLOR_YELLOW,"Character status updated to %i for %s.",new_status,sep->arg[0]); + if (client2) { + client2->SetAdminStatus(new_status); + client2->Message(CHANNEL_COLOR_YELLOW, "%s has set your admin status to %i.", client->GetPlayer()->GetName(), new_status); + } + } + else + client->SimpleMessage(CHANNEL_ERROR,"Unable to flag character. Unknown reason."); + } + }else{ + sint16 status = database.GetCharacterAdminStatus(client->GetPlayer()->GetName()); + if(status != client->GetAdminStatus()) + { + client->Message(CHANNEL_COLOR_YELLOW,"Flag status was changed from %i to %i.",status,client->GetAdminStatus()); + client->SetAdminStatus(status); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /flag {name} {new_status}"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW," Standard User: 0"); + client->Message(CHANNEL_COLOR_YELLOW," Admin User: %i", status); + } + } + break; + } + case COMMAND_CANNEDEMOTE:{ + client->GetCurrentZone()->HandleEmote(client->GetPlayer(), command_parms->data); + break; + } + case COMMAND_BROADCAST: { + if (sep && sep->arg[0]) + zone_list.HandleGlobalBroadcast(sep->argplus[0]); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /broadcast {message}"); + break; + } + case COMMAND_ANNOUNCE: { + if (sep && sep->arg[0]) + zone_list.HandleGlobalAnnouncement(sep->argplus[0]); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /announce {message}"); + break; + } + case COMMAND_RELOAD_ITEMS:{ + LogWrite(COMMAND__INFO, 0, "Command", "Reloading items.."); + + int32 item_id = (sep && sep->arg[0]) ? atoul(sep->arg[0]) : 0; + if(item_id > 0) { + client->Message(CHANNEL_COLOR_YELLOW, "Reloading item %u based on /reload items [item_id].", item_id); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Started Reloading items (this might take a few minutes...)"); + } + + database.ReloadItemList(item_id); + + if(!item_id) { + database.LoadMerchantInformation(); // we skip if there is only a reload of single item not all items + } + + if(item_id > 0) { + client->Message(CHANNEL_COLOR_YELLOW, "Reloaded item %u.", item_id); + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Finished Reloading items."); + } + break; + } + case COMMAND_ENABLE_ABILITY_QUE:{ + EQ2Packet* app = client->GetPlayer()->GetSpellBookUpdatePacket(client->GetVersion()); + if(app) + client->QueuePacket(app); + break; + } + case COMMAND_ITEMSEARCH: + case COMMAND_FROMBROKER:{ + PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer())); + //packet->setDataByName("unknown", 1); + packet->setDataByName("unknown2", 5, 0); + packet->setDataByName("unknown2", 20, 1); + packet->setDataByName("unknown2", 58, 3); + packet->setDataByName("unknown2", 40, 4); + client->QueuePacket(packet->serialize()); + PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion()); + if (packet2) { + packet2->setDataByName("char_id", client->GetCharacterID()); + client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data + safe_delete(packet2); + } + safe_delete(packet); + } + break; + } + case COMMAND_ANIMTEST:{ + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", client->GetVersion()); + if(command_packet){ + int32 id = client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()); + if (cmdTarget) + id = client->GetPlayer()->GetIDWithPlayerSpawn(cmdTarget); + command_packet->setDataByName ( "spawn_id" , id); + + int animID = 1; + + if(sep && sep->arg[0] && sep->IsNumber(0)) + animID = atoi(sep->arg[0]); + + VisualState* vs = NULL; + if(animID == 0) + { + vs = visual_states.FindVisualState(sep->arg[0]); + } + + + char msg[128]; + sprintf(msg,"Animation Test ID: %i",animID); + command_packet->setMediumStringByName ( "emote_msg" , msg ); + + if(vs != NULL) + command_packet->setDataByName ( "anim_type", vs->GetID ( ) ); + else + command_packet->setDataByName ( "anim_type", animID ); + + command_packet->setDataByName ( "unknown0", 0 ); + EQ2Packet* outapp = command_packet->serialize(); + client->QueuePacket(outapp); + safe_delete(command_packet); + } + break; + } + case COMMAND_KICK: + { + if( sep == 0 || sep->arg[0] == 0) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "/kick [name]"); + } + else + { + Client* kickClient = zone_list.GetClientByCharName(string(sep->arg[0])); + + if ( kickClient == client ) + { + client->Message(CHANNEL_COLOR_RED, "You can't kick yourself!"); + break; + } + else if(kickClient != NULL) + { + sint16 maxStatus = database.GetHighestCharacterAdminStatus(kickClient->GetAccountID()); + + if ( maxStatus >= client->GetAdminStatus( ) || kickClient->GetAdminStatus() >= client->GetAdminStatus() ) + { + client->Message(CHANNEL_COLOR_RED,"Don't even think about it..."); + break; + } + + client->Message(CHANNEL_COLOR_RED, "Kicking %s...",sep->arg[0]); + + kickClient->Disconnect(); + } + else + { + client->Message(CHANNEL_COLOR_RED, "Could not find %s.",sep->arg[0]); + } + } + + break; + } + case COMMAND_LOCK: + { + if( sep != NULL && sep->arg[0] != NULL && sep->IsNumber(0)){ + int worldLocked = atoi(sep->arg[0]); + net.world_locked = worldLocked; + if ( worldLocked ) + client->Message(CHANNEL_COLOR_YELLOW,"World server has been locked."); + else + client->Message(CHANNEL_COLOR_YELLOW,"World server has been unlocked."); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "/lock [0/1]"); + + break; + } + case COMMAND_BAN:{ + if( sep == 0 || sep->arg[0] == 0 || (sep->arg[1][0] != 0 && !sep->IsNumber(1) ) ) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "/ban [name] [permanent:0/1]"); + } + else + { + Client* kickClient = zone_list.GetClientByCharName(sep->arg[0]); + + if ( kickClient == client ) + { + client->Message(CHANNEL_COLOR_RED, "You can't ban yourself!"); + break; + } + else if(kickClient != NULL) + { + sint16 maxStatus = database.GetHighestCharacterAdminStatus(kickClient->GetAccountID()); + + if ( maxStatus > client->GetAdminStatus( ) || + client->GetAdminStatus ( ) > kickClient->GetAdminStatus ( ) ) + { + client->Message(CHANNEL_COLOR_RED,"Don't even think about it..."); + break; + } + + client->Message(CHANNEL_COLOR_RED, "Kicking & Banning %s...",sep->arg[0]); + + int perm = atol(sep->arg[1]); + if ( perm == 1 ) + database.UpdateAdminStatus(sep->arg[0],-2); + else + database.UpdateAdminStatus(sep->arg[0],-1); + kickClient->Disconnect(); + } + else + { + client->Message(CHANNEL_COLOR_RED, "Could not find %s.",sep->arg[0]); + } + } + break; + } + case COMMAND_SET_COMBAT_VOICE:{ + int32 value = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + value = atoi(sep->arg[0]); + client->GetPlayer()->SetCombatVoice(value); + break; + } + case COMMAND_SET_EMOTE_VOICE:{ + int32 value = 0; + if(sep && sep->arg[0] && sep->IsNumber(0)) + value = atoi(sep->arg[0]); + client->GetPlayer()->SetEmoteVoice(value); + break; + } + case COMMAND_GIVEITEM:{ + + if(sep && sep->arg[0][0] && sep->arg[0][1] && sep->IsNumber(1)){ + Client* itemToClient = zone_list.GetClientByCharName(sep->arg[0]); + + if ( itemToClient == NULL ) + client->Message(CHANNEL_COLOR_YELLOW,"Could not find %s.",sep->arg[0]); + else + { + int32 item_id = atol(sep->arg[1]); + client->Message(CHANNEL_COLOR_YELLOW,"Gave %s item id %i.",sep->arg[0],item_id); + itemToClient->AddItem(item_id); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /giveitem [name] [item_id]"); + + break; + } + + case COMMAND_REPORT_BUG : { Command_ReportBug(client, sep); break; } + case COMMAND_INVENTORY : { Command_Inventory(client, sep, command); break; } + case COMMAND_WEAPONSTATS : { Command_WeaponStats(client); break; } + case COMMAND_SKILL : + case COMMAND_SKILL_ADD : + case COMMAND_SKILL_REMOVE : + case COMMAND_SKILL_LIST : { Command_Skills(client, sep, command->handler); break; } + case COMMAND_ZONE_SET : { Command_ZoneSet(client, sep); break; } + case COMMAND_ZONE_DETAILS : { Command_ZoneDetails(client, sep); break; } + case COMMAND_ENTITYCOMMAND : + case COMMAND_ENTITYCOMMAND_LIST : { Command_EntityCommand(client, sep, command->handler); break; } + case COMMAND_MERCHANT : + case COMMAND_MERCHANT_LIST : { Command_Merchant(client, sep, command->handler); break; } + case COMMAND_APPEARANCE : + case COMMAND_APPEARANCE_LIST : { Command_Appearance(client, sep, command->handler); break; } + case COMMAND_TRACK : { Command_Track(client); break; } + case COMMAND_DISTANCE : { Command_Distance(client); break; } + case COMMAND_INSPECT_PLAYER : { Command_InspectPlayer(client, sep); break; } + case COMMAND_ZONE_SAFE : { Command_ZoneSafeCoords(client, sep); break; } + case COMMAND_GUILDSAY : { Command_GuildSay(client, sep); break; } + case COMMAND_OFFICERSAY : { Command_OfficerSay(client, sep); break; } + case COMMAND_SET_GUILD_MEMBER_NOTE : { Command_SetGuildMemberNote(client, sep); break; } + case COMMAND_SET_GUILD_OFFICER_NOTE : { Command_SetGuildOfficerNote(client, sep); break; } + case COMMAND_GUILD : { Command_Guild(client, sep); break; } + case COMMAND_CREATE_GUILD : { Command_CreateGuild(client); break; } + case COMMAND_GUILDS : { Command_Guilds(client); break; } + case COMMAND_GUILDS_ADD : { Command_GuildsAdd(client, sep); break; } + case COMMAND_GUILDS_CREATE : { Command_GuildsCreate(client, sep); break; } + case COMMAND_GUILDS_DELETE : { Command_GuildsDelete(client, sep); break; } + case COMMAND_GUILDS_LIST : { Command_GuildsList(client); break; } + case COMMAND_GUILDS_REMOVE : { Command_GuildsRemove(client, sep); break; } + case COMMAND_CLAIM : { Command_Claim(client, sep); break; } + case COMMAND_CLEAR_ALL_QUEUED : { Command_ClearAllQueued(client); break; } + case COMMAND_LOCATION : { Command_Location(client); break; } + case COMMAND_LOCATION_ADD : { Command_LocationAdd(client, sep); break; } + case COMMAND_LOCATION_CREATE : { Command_LocationCreate(client, sep); break; } + case COMMAND_LOCATION_DELETE : { Command_LocationDelete(client, sep); break; } + case COMMAND_LOCATION_LIST : { Command_LocationList(client, sep); break; } + case COMMAND_LOCATION_REMOVE : { Command_LocationRemove(client, sep); break; } + case COMMAND_GRID : { Command_Grid(client, sep); break; } + case COMMAND_TRY_ON : { Command_TryOn(client, sep); break; } + case COMMAND_RANDOMIZE : { Command_Randomize(client, sep); break; } + case COMMAND_AFK : { Command_AFK(client, sep); break; } + case COMMAND_SHOW_CLOAK : { Command_ShowCloak(client, sep); break; } + case COMMAND_SHOW_HELM : { Command_ShowHelm(client, sep); break; } + case COMMAND_SHOW_HOOD : { Command_ShowHood(client, sep); break; } + case COMMAND_SHOW_HOOD_OR_HELM : { Command_ShowHoodHelm(client, sep); break; } + case COMMAND_SHOW_RANGED : { Command_ShowRanged(client, sep); break; } + case COMMAND_STOP_DRINKING : { Command_StopDrinking(client); break; } + case COMMAND_STOP_EATING : { Command_StopEating(client); break; } + case COMMAND_TOGGLE_ANONYMOUS : { Command_Toggle_Anonymous(client); break; } + case COMMAND_TOGGLE_AUTOCONSUME : { Command_Toggle_AutoConsume(client, sep); break; } + case COMMAND_TOGGLE_BONUS_EXP : { Command_Toggle_BonusXP(client); break; } + case COMMAND_TOGGLE_COMBAT_EXP : { Command_Toggle_CombatXP(client); break; } + case COMMAND_TOGGLE_GM_HIDE : { Command_Toggle_GMHide(client); break; } + case COMMAND_TOGGLE_GM_VANISH : { Command_Toggle_GMVanish(client); break; } + case COMMAND_TOGGLE_ILLUSIONS : { Command_Toggle_Illusions(client, sep); break; } + case COMMAND_TOGGLE_LFG : { Command_Toggle_LFG(client); break; } + case COMMAND_TOGGLE_LFW : { Command_Toggle_LFW(client); break; } + case COMMAND_TOGGLE_QUEST_EXP : { Command_Toggle_QuestXP(client); break; } + case COMMAND_TOGGLE_ROLEPLAYING : { Command_Toggle_Roleplaying(client); break; } + case COMMAND_TOGGLE_DUELS : { Command_Toggle_Duels(client); break; } + case COMMAND_TOGGLE_TRADES : { Command_Toggle_Trades(client); break; } + case COMMAND_TOGGLE_GUILDS : { Command_Toggle_Guilds(client); break; } + case COMMAND_TOGGLE_GROUPS : { Command_Toggle_Groups(client); break; } + case COMMAND_TOGGLE_RAIDS : { Command_Toggle_Raids(client); break; } + case COMMAND_TOGGLE_LON : { Command_Toggle_LON(client); break; } + case COMMAND_TOGGLE_VCINVITE : { Command_Toggle_VoiceChat(client); break; } + case COMMAND_CANCEL_MAINTAINED : { Command_CancelMaintained(client, sep); break; } + case COMMAND_MOTD : { Command_MOTD(client); break; } + case COMMAND_RANDOM : { Command_Random(client, sep); break; } + case COMMAND_CREATE : { Command_Create(client, sep); break; } + case COMMAND_CREATEFROMRECIPE : { Command_CreateFromRecipe(client, sep); break; } + case COMMAND_TITLE : { Command_Title(client); break; } + case COMMAND_TITLE_LIST : { Command_TitleList(client); break; } + case COMMAND_TITLE_SETPREFIX : { Command_TitleSetPrefix(client, sep); break; } + case COMMAND_TITLE_SETSUFFIX : { Command_TitleSetSuffix(client, sep); break; } + case COMMAND_TITLE_FIX : { Command_TitleFix(client, sep); break; } + case COMMAND_LANGUAGES : { Command_Languages(client, sep); break; } + case COMMAND_SET_LANGUAGE : { Command_SetLanguage(client, sep); break; } + case COMMAND_FOLLOW : { Command_Follow(client, sep); break; } + case COMMAND_STOP_FOLLOW : { Command_StopFollow(client, sep); break; } + case COMMAND_LASTNAME : { Command_LastName(client, sep); break; } + case COMMAND_CONFIRMLASTNAME : { Command_ConfirmLastName(client, sep); break; } + case COMMAND_PET : { Command_Pet(client, sep); break; } + case COMMAND_PETNAME : { Command_PetName(client, sep); break; } + case COMMAND_NAME_PET : { Command_NamePet(client, sep); break; } + case COMMAND_RENAME : { Command_Rename(client, sep); break; } + case COMMAND_CONFIRMRENAME : { Command_ConfirmRename(client, sep); break; } + case COMMAND_PETOPTIONS : { Command_PetOptions(client, sep); break; } + case COMMAND_START_TRADE : { Command_TradeStart(client, sep); break; } + case COMMAND_ACCEPT_TRADE : { Command_TradeAccept(client, sep); break; } + case COMMAND_REJECT_TRADE : { Command_TradeReject(client, sep); break; } + case COMMAND_CANCEL_TRADE : { Command_TradeCancel(client, sep); break; } + case COMMAND_SET_TRADE_COIN : { Command_TradeSetCoin(client, sep); break; } + case COMMAND_ADD_TRADE_COPPER : { Command_TradeAddCoin(client, sep, COMMAND_ADD_TRADE_COPPER); break; } + case COMMAND_ADD_TRADE_SILVER : { Command_TradeAddCoin(client, sep, COMMAND_ADD_TRADE_SILVER); break; } + case COMMAND_ADD_TRADE_GOLD : { Command_TradeAddCoin(client, sep, COMMAND_ADD_TRADE_GOLD); break; } + case COMMAND_ADD_TRADE_PLAT : { Command_TradeAddCoin(client, sep, COMMAND_ADD_TRADE_PLAT); break; } + case COMMAND_REMOVE_TRADE_COPPER: { Command_TradeRemoveCoin(client, sep, COMMAND_REMOVE_TRADE_COPPER); break; } + case COMMAND_REMOVE_TRADE_SILVER: { Command_TradeRemoveCoin(client, sep, COMMAND_REMOVE_TRADE_SILVER); break; } + case COMMAND_REMOVE_TRADE_GOLD : { Command_TradeRemoveCoin(client, sep, COMMAND_REMOVE_TRADE_GOLD); break; } + case COMMAND_REMOVE_TRADE_PLAT : { Command_TradeRemoveCoin(client, sep, COMMAND_REMOVE_TRADE_PLAT); break; } + case COMMAND_ADD_TRADE_ITEM : { Command_TradeAddItem(client, sep); break; } + case COMMAND_REMOVE_TRADE_ITEM : { Command_TradeRemoveItem(client, sep); break; } + case COMMAND_ACCEPT_ADVANCEMENT : { Command_AcceptAdvancement(client, sep); break; } + case COMMAND_DUEL : { Command_Duel(client, sep); break; } + case COMMAND_DUELBET : { Command_DuelBet(client, sep); break; } + case COMMAND_DUEL_ACCEPT : { Command_DuelAccept(client, sep); break; } + case COMMAND_DUEL_DECLINE : { Command_DuelDecline(client, sep); break; } + case COMMAND_DUEL_SURRENDER : { Command_DuelSurrender(client, sep); break; } + case COMMAND_DUEL_TOGGLE : { Command_DuelToggle(client, sep); break; } + case COMMAND_SPAWN_TEMPLATE : { Command_SpawnTemplate(client, sep); break; } + //devn00b + case COMMAND_MOOD : { Command_Mood(client, sep); break;} + + case COMMAND_MODIFY : { Command_Modify(client); break; } + case COMMAND_MODIFY_CHARACTER : { Command_ModifyCharacter(client, sep); break; } + case COMMAND_MODIFY_QUEST : { Command_ModifyQuest(client, sep); break; } + case COMMAND_MODIFY_FACTION : { Command_ModifyFaction(client, sep); break; } + case COMMAND_MODIFY_GUILD : { Command_ModifyGuild(client, sep); break; } + case COMMAND_MODIFY_ITEM : { Command_ModifyItem(client, sep); break; } + case COMMAND_MODIFY_SKILL : { Command_ModifySkill(client, sep); break; } + case COMMAND_MODIFY_SPAWN : { Command_ModifySpawn(client, sep); break; } + case COMMAND_MODIFY_SPELL : { Command_ModifySpell(client, sep); break; } + case COMMAND_MODIFY_ZONE : { Command_ModifyZone(client, sep); break; } + + case COMMAND_JOIN_CHANNEL : { Command_JoinChannel(client, sep); break;} + case COMMAND_JOIN_CHANNEL_FROM_LOAD: { Command_JoinChannelFromLoad(client, sep); break;} + case COMMAND_TELL_CHANNEL : { Command_TellChannel(client, sep); break;} + case COMMAND_LEAVE_CHANNEL : { Command_LeaveChannel(client, sep); break;} + case COMMAND_WHO_CHANNEL : { Command_WhoChannel(client, sep); break;} + case COMMAND_RAIN : { Command_Rain(client, sep); break; } + case COMMAND_WIND : { Command_Wind(client, sep); break; } + case COMMAND_WEATHER : { Command_Weather(client, sep); break; } + case COMMAND_FROM_MERCHANT : { Command_SendMerchantWindow(client, sep); break; } + case COMMAND_TO_MERCHANT : { Command_SendMerchantWindow(client, sep, true); break; } + case COMMAND_SELECT : { Command_Select(client, sep); break; } + case COMMAND_SMP : { Command_StationMarketPlace(client, sep); break; } + case COMMAND_CONSUME_FOOD : { Command_ConsumeFood(client, sep); break; } + case COMMAND_SET_CONSUME_FOOD : { Command_ConsumeFood(client, sep); break; } + case COMMAND_AQUAMAN : { Command_Aquaman(client, sep); break; } + case COMMAND_ATTUNE_INV : { Command_Attune_Inv(client, sep); break; } + case COMMAND_PLAYER : { Command_Player(client, sep); break; } + case COMMAND_PLAYER_COINS : { Command_Player_Coins(client, sep); break; } + case COMMAND_RESET_ZONE_TIMER : { Command_Reset_Zone_Timer(client, sep); break; } + case COMMAND_ACHIEVEMENT_ADD : { Command_AchievementAdd(client, sep); break; } + case COMMAND_EDITOR : { Command_Editor(client, sep); break; } + case COMMAND_ACCEPT_RESURRECTION: { Command_AcceptResurrection(client, sep); break; } + case COMMAND_DECLINE_RESURRECTION:{ Command_DeclineResurrection(client, sep); break; } + case COMMAND_TEST : { Command_Test(client, command_parms); break; } + case COMMAND_SPEED : { Command_Speed(client, sep); break; } + + case COMMAND_BOT : { Command_Bot(client, sep); break; } + case COMMAND_BOT_CREATE : { Command_Bot_Create(client, sep); break; } + case COMMAND_BOT_CUSTOMIZE : { Command_Bot_Customize(client, sep); break; } + case COMMAND_BOT_SPAWN : { Command_Bot_Spawn(client, sep); break; } + case COMMAND_BOT_LIST : { Command_Bot_List(client, sep); break; } + case COMMAND_BOT_INV : { Command_Bot_Inv(client, sep); break; } + case COMMAND_BOT_SETTINGS : { Command_Bot_Settings(client, sep); break; } + case COMMAND_BOT_HELP : { Command_Bot_Help(client, sep); break; } + case GET_AA_XML : { Get_AA_Xml(client, sep); break; } + case ADD_AA : { Add_AA(client, sep); break; } + case COMMIT_AA_PROFILE : { Commit_AA_Profile(client, sep); break; } + case BEGIN_AA_PROFILE : { Begin_AA_Profile(client, sep); break; } + case BACK_AA : { Back_AA(client, sep); break; } + case REMOVE_AA : { Remove_AA(client, sep); break; } + case SWITCH_AA_PROFILE : { Switch_AA_Profile(client, sep); break; } + case CANCEL_AA_PROFILE : { Cancel_AA_Profile(client, sep); break; } + case SAVE_AA_PROFILE : { Save_AA_Profile(client, sep); break; } + case COMMAND_TARGETITEM : { Command_TargetItem(client, sep); break; } + case COMMAND_FINDSPAWN: { Command_FindSpawn(client, sep); break; } + case COMMAND_MOVECHARACTER: { Command_MoveCharacter(client, sep); break; } + case COMMAND_CRAFTITEM: { + Item* item = 0; + if (sep && sep->IsNumber(0)) { + int32 item_id = atol(sep->arg[0]); + int32 quantity = 1; + + if (sep->arg[1] && sep->IsNumber(1)) + quantity = atoi(sep->arg[1]); + item = new Item(master_item_list.GetItem(item_id)); + if (!item) { + LogWrite(TRADESKILL__ERROR, 0, "CraftItem", "Item (%u) not found.", item_id); + } + else { + item->details.count = quantity; + // use CHANNEL_COLOR_CHAT_RELATIONSHIP as that is the same value (4) as it is in a log for this message + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You created %s.", item->CreateItemLink(client->GetVersion()).c_str()); + bool itemDeleted = false; + client->AddItem(item, &itemDeleted); + //Check for crafting quest updates + int8 update_amt = 0; + if (!itemDeleted && item->stack_count > 1) + update_amt = 1; + else + update_amt = quantity; + + if(!itemDeleted) + client->GetPlayer()->CheckQuestsCraftUpdate(item, update_amt); + } + + } + + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /craftitem {item_id} [quantity] "); + } + break; + } + case COMMAND_UNMENTOR: + case COMMAND_MENTOR: { + client->GetPlayer()->MentorTarget(); + break; + } + case COMMAND_CANCEL_EFFECT: { Command_CancelEffect(client, sep); break; } + case COMMAND_CUREPLAYER: { Command_CurePlayer(client, sep); break; } + case COMMAND_SHARE_QUEST: { Command_ShareQuest(client, sep); break; } + case COMMAND_YELL: { Command_Yell(client, sep); break; } + case COMMAND_SETAUTOLOOTMODE: { Command_SetAutoLootMode(client, sep); break; } + case COMMAND_ASSIST: { Command_Assist(client, sep); break; } + case COMMAND_TARGET: { Command_Target(client, sep); break; } + case COMMAND_TARGET_PET: { Command_Target_Pet(client, sep); break; } + default: + { + LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str()); + break; + } + + } + safe_delete(sep); +} + + +/******************** New COMMAND Handler Functions ********************/ +/* + Started breaking apart the huge switch() for commands into sepErate + functions so it is easier to locate the blocks of code by command + -- JA 2012.03.03 +*/ + +// sample function header +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +//void Commands::Command() +//{ +//} + + +/* + Function: Command_AcceptAdvancement() + Purpose : Player accepts a new advancement option + Params : Spell ID + Dev : Jabantiz +*/ +void Commands::Command_AcceptAdvancement(Client* client, Seperator* sep) +{ + Player *player = client->GetPlayer(); + if (sep && sep->IsSet(0)) { + int32 trait_id = atoul(sep->arg[0]); + TraitData* trait = nullptr; + if(client->GetVersion() <= 561) { + trait = master_trait_list.GetTraitByItemID(trait_id); + } + else { + trait = master_trait_list.GetTrait(trait_id); + } + + if(!trait) { + LogWrite(COMMAND__ERROR, 0, "Command", "Invalid accept advancement of trait %u, no trait found.", trait_id); + return; // not valid lets not crash! + } + + if(!master_trait_list.IsPlayerAllowedTrait(client, trait)) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Not enough trait points to accept trait."); + return; + } + // Check to see if this is a trait or grandmaster training (traits are always new spells, training is always upgrades) + if (!player->HasSpell(trait->spellID, 0, true)) + { + Spell* spell = master_spell_list.GetSpell(trait->spellID, trait->tier); + if(spell) { + player->AddSpellBookEntry(trait->spellID, trait->tier, player->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + } + else { + client->Message(CHANNEL_COLOR_RED, "ERROR! Trait is not a valid spell id %u may be disabled.", trait->spellID); + } + } + else + { + Spell* spell = master_spell_list.GetSpell(trait->spellID, trait->tier); + if(spell) { + int8 old_slot = player->GetSpellSlot(spell->GetSpellID()); + player->RemoveSpellBookEntry(spell->GetSpellID()); + player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + player->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + else { + client->Message(CHANNEL_COLOR_RED, "ERROR! Trait is not a valid spell id %u may be disabled.", trait->spellID); + } + } + + // Spell book update + client->QueuePacket(player->GetSpellBookUpdatePacket(client->GetVersion())); + client->QueuePacket(master_trait_list.GetTraitListPacket(client)); + + if(client->GetVersion() <= 561) { + master_trait_list.ChooseNextTrait(client); + } + } +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_AFK(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_AFK); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s afk.", client->GetPlayer()->get_character_flag(CF_AFK)?"now":"no longer"); + + if (player->get_character_flag(CF_AFK)) + { + if (sep && sep->argplus[0]) + player->SetAwayMessage("I am away from the keyboard, " + string(sep->argplus[0])); + else + player->SetAwayMessage("Sorry, I am A.F.K. (Away From Keyboard)"); + + string message = string(player->GetName()) + " is going afk."; + Spawn* target = player->GetTarget(); + + if (target && target != player) + { + message = string(player->GetName()) + " tells " + string(target->GetName()) + " that "; + player->GetGender() == 1 ? message += "he" : message += "she"; + message += " is going afk."; + } + + player->GetZone()->SimpleMessage(CHANNEL_COLOR_YELLOW, message.c_str(), player, 30); + } + + if (player->get_character_flag(CF_AFK)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_AFK); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_AFK); +} + +/* + Function: Command_Appearance() + Purpose : Handles /appearance commands + Params : list + Dev : Scatman + Example : /appearance list +*/ +void Commands::Command_Appearance(Client* client, Seperator* sep, int handler) +{ + if( handler == COMMAND_APPEARANCE ) + { + // /appearance command by itself shows help (to be extended) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /appearance list [appearance name]"); + return; + } + else if( handler == COMMAND_APPEARANCE_LIST ) + { + // /appearance list command expects "name" param + if (sep && sep->arg[0]) + { + const char* appearance_name = sep->argplus[0]; + client->Message(CHANNEL_COLOR_YELLOW, "Listing appearances like '%s':", appearance_name); + vector* appearances = database.GetAppearanceIDsLikeName(string(appearance_name)); + + if (appearances) + { + vector::iterator itr; + for (itr = appearances->begin(); itr != appearances->end(); itr++) + { + int16 id = *itr; + string name = database.GetAppearanceName(id); + + if (ToLower(name).find(ToLower(string(appearance_name))) < 0xFFFFFFFF) + client->Message(CHANNEL_COLOR_YELLOW, "%s (%u)", name.c_str(), id); + } + safe_delete(appearances); + } + } + else // no param + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /appearance list [appearance name]"); + } + +} + +/* + Function: Command_Claim() + Purpose : Summon veteran rewards + Params : nothing = show claim window, any number claims that item. + Dev : devn00b + Example : /claim 0 (claims the 1st item added to the list claim[0]) +*/ +void Commands::Command_Claim(Client* client, Seperator* sep) +{ + //if we were passed a claim id + if (sep && sep->argplus[0] && sep->IsNumber(0)) + { + int32 char_id = client->GetCharacterID(); + int8 my_claim_id = atoi(sep->argplus[0]); + vector claim = database.LoadCharacterClaimItems(char_id); + Item* item = master_item_list.GetItem(claim[my_claim_id].item_id); + database.ClaimItem(char_id, item->details.item_id, client); + return; + } + else { + //if no arg just send the window. + client->ShowClaimWindow(); + return; + } + return; +} + +/* + Function: Command_ClearAllQueued() + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_ClearAllQueued(Client* client) +{ + ZoneServer* zone = client->GetPlayer()->GetZone(); + if (zone && zone->GetSpellProcess()) + zone->GetSpellProcess()->RemoveSpellFromQueue(client->GetPlayer()); +} + +/* + Function: Command_CancelMaintained() + Purpose : Cancels maintained spells + Params : Maintained Spell Index + Dev : Zcoretri + Example : /cancel_maintained 1 - would cancel the spell in slot 1 of Maintained Spells list +*/ +void Commands::Command_CancelMaintained(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 spell_index = atoul(sep->arg[0]); + if(spell_index > 29) + return; + + client->GetPlayer()->MMaintainedSpells.readlock(__FUNCTION__, __LINE__); + MaintainedEffects mEffects = client->GetPlayer()->GetInfoStruct()->maintained_effects[spell_index]; + client->GetPlayer()->MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__); + + if (!client->GetPlayer()->GetZone()->GetSpellProcess()->DeleteCasterSpell(mEffects.spell, "canceled", false)) + client->Message(CHANNEL_COLOR_RED, "The maintained spell could not be cancelled."); + } +} + +/* + Function: Command_Create() + Purpose : Handler for starting Tradeskilling table + Params : + Dev : Zcoretri + Example : +*/ +void Commands::Command_Create(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_CREATE"); + client->ShowRecipeBook(); +} + +void Commands::Command_CreateFromRecipe(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_CREATEFROMRECIPE"); + if (sep && sep->arg[0] && sep->IsNumber(0)) + ClientPacketFunctions::SendCreateFromRecipe(client, atoul(sep->arg[0])); +} + +/* + Function: Command_Distance() + Purpose : Displays distance from targeted spawn + Params : + Dev : Scatman + Example : /distance +*/ +void Commands::Command_Distance(Client* client) +{ + Spawn* target = client->GetPlayer()->GetTarget(); + + if (target) + client->Message(CHANNEL_COLOR_YELLOW, "Your distance from %s is %f", target->GetName(), client->GetPlayer()->GetDistance(target)); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must have a spawn targeted to use /distance"); +} + +/* + Function: Command_Duel() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_Duel(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Duel Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: Command_DuelBet() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelBet(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUELBET"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Duel Bet Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: Command_DuelAccept() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelAccept(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL_ACCEPT"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Accept Duel Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: Command_DuelDecline() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelDecline(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL_DECLINE"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Decline Duel Request Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: Command_DuelSurrender() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelSurrender(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL_SURRENDER"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Surrender Duel Command"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); + + // JA, just messin around ;) + char surrender[64]; + sprintf(surrender, "%s surrendered like a cowardly dog!", client->GetPlayer()->GetName()); + zone_list.HandleGlobalAnnouncement(surrender); +} + +/* + Function: Command_DuelToggle() + Purpose : Handle the /duel commands - not yet implemented + Params : unknown + Dev : + Example : +*/ +void Commands::Command_DuelToggle(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_DUEL"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Duel Commands"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot duel other players (Not Implemented)"); +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_EntityCommand(Client* client, Seperator* sep, int handler) +{ + if( handler == COMMAND_ENTITYCOMMAND ) + { + // /entitycommand command by itself shows help (to be extended) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /entity_command list [entity command name]"); + return; + } + else if( handler == COMMAND_ENTITYCOMMAND_LIST ) + { + // /entitycommand list command expects "name" param + if (sep && sep->arg[0]) + { + const char* entity_command_name = sep->argplus[0]; + client->Message(CHANNEL_COLOR_YELLOW, "Listing entity commands like '%s':", entity_command_name); + map*>* entity_command_list_all = client->GetCurrentZone()->GetEntityCommandListAll(); + map*>::iterator itr; + + for (itr = entity_command_list_all->begin(); itr != entity_command_list_all->end(); itr++) + { + vector* entity_command_list = itr->second; + vector::iterator itr2; + + for (itr2 = entity_command_list->begin(); itr2 != entity_command_list->end(); itr2++) + { + EntityCommand* entity_command = *itr2; + + if (ToLower(entity_command->name).find(ToLower(string(entity_command_name))) < 0xFFFFFFFF) + client->Message(CHANNEL_COLOR_YELLOW, "Command Text: %s, Command List ID: %u, Distance: %f\n", entity_command->name.c_str(), itr->first, entity_command->distance); + } + } + } + else // no command name + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /entity_command list [entity command name]"); + } +} + + +/* + Function: Command_Follow() + Purpose : Handle the /follow command - not yet implemented + Params : + Dev : + Example : +*/ +void Commands::Command_Follow(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_FOLLOW"); + // flag to toggle if the players target is in the players group + bool targetInGroup = false; + // get a pointer to the players group + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + // If the player has a group and has a target + if (gmi && client->GetPlayer()->GetTarget()) { + deque::iterator itr; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + // Loop through the group members + for (itr = members->begin(); itr != members->end(); itr++) { + // If a group member matches a target + if ((*itr)->member && (*itr)->member == client->GetPlayer()->GetTarget()) { + // toggle the flag and break the loop + targetInGroup = true; + break; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + if (targetInGroup) { + // CHANNEL_COLOR_CHAT_RELATIONSHIP = 4, which matches the value in logs + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You start to follow %s.", client->GetPlayer()->GetTarget()->GetName()); + client->GetPlayer()->SetFollowTarget(client->GetPlayer()->GetTarget()); + client->GetPlayer()->info_changed = true; + client->GetPlayer()->changed = true; + } + else + client->Message(CHANNEL_NARRATIVE, "You must first select a group member to follow."); +} + +/* + Function: Command_StopFollow() + Purpose : Handle the /stop_follow command - not yet implemented + Params : + Dev : + Example : +*/ +void Commands::Command_StopFollow(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_STOP_FOLLOW"); + if (client->GetPlayer()->GetFollowTarget()) { + // CHANNEL_COLOR_CHAT_RELATIONSHIP = 4, which matches the value in logs + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You are no longer following %s", client->GetPlayer()->GetFollowTarget()->GetName()); + client->GetPlayer()->SetFollowTarget(0); + client->GetPlayer()->info_changed = true; + client->GetPlayer()->changed = true; + } +} + +/* + Function: Command_Grid() + Purpose : Show player's current Grid ID + Params : + Dev : Scatman + Example : /grid +*/ +void Commands::Command_Grid(Client* client, Seperator* sep) +{ + if (client->GetPlayer()->GetMap() != nullptr) { + if(sep && sep->arg[0][0] && strncasecmp("spawns", sep->arg[0], 6) == 0) { + int32 grid = client->GetPlayer()->GetLocation(); + + if(sep->IsNumber(1)) + grid = atoul(sep->arg[1]); + + client->GetCurrentZone()->SendClientSpawnListInGrid(client, grid); + } + if(sep && sep->arg[0][0] && strncasecmp("boundary", sep->arg[0], 8) == 0) { + int32 grid = client->GetPlayer()->GetLocation(); + + if(sep->IsNumber(1)) + grid = atoul(sep->arg[1]); + + if(!client->GetPlayer()->GetMap()) { + client->Message(CHANNEL_COLOR_RED, "No map to check grid!"); + return; + } + GridMapBorder* gmb = client->GetPlayer()->GetMap()->GetMapGridBorder(grid, false); + if(gmb) { + client->Message(CHANNEL_COLOR_YELLOW, "Grid %u border MinX %f MaxX %f, MinY %f MaxY %f, MinZ %f MaxZ %f", grid, gmb->m_MinX, gmb->m_MaxX, gmb->m_MinY, gmb->m_MaxY, gmb->m_MinZ, gmb->m_MaxZ); + } + else { + client->Message(CHANNEL_COLOR_RED, "Grid %u has no grid map border", grid); + } + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "Your Grid ID is %u", client->GetPlayer()->GetLocation()); + auto loc = glm::vec3(client->GetPlayer()->GetX(), client->GetPlayer()->GetZ(), client->GetPlayer()->GetY()); + uint32 GridID = 0; + uint32 WidgetID = 0; + float new_z = client->GetPlayer()->FindBestZ(loc, nullptr, &GridID, &WidgetID); + float minY = client->GetPlayer()->GetMap()->GetMinY(); + float maxY = client->GetPlayer()->GetMap()->GetMaxY(); + float minZ = client->GetPlayer()->GetMap()->GetMinZ(); + float maxZ = client->GetPlayer()->GetMap()->GetMaxZ(); + int32 grid_spawn_count = client->GetPlayer()->GetZone()->GetSpawnCountInGrid(GridID); + client->Message(CHANNEL_COLOR_YELLOW, "Grid result is %u, at EQ2 Y coordinate %f. Spawns on grid: %u. Min/Max Y %f/%f Z %f/%f. Widget ID: %u", GridID, new_z, grid_spawn_count, minY, maxY, minZ, maxZ, WidgetID); + } + } +} + +/* + Function: Command_Guild() + Purpose : Handler for all UI-related guild commands + Dev : Scatman +*/ +void Commands::Command_Guild(Client* client, Seperator* sep) +{ + Guild* guild = client->GetPlayer()->GetGuild(); + if (sep && sep->GetMaxArgNum() > 0 && sep->arg[0]) + { + const char* command = sep->arg[0]; + int32 length = strlen(command); + + LogWrite(COMMAND__DEBUG, 0, "Command", "Guild Command: %s", command); + + if (strncmp(command, "rank_name", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && guild) + guild->SetRankName(atoi(sep->arg[1]), sep->argplus[2]); + else if (strncmp(command, "rank_permission", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && guild) + guild->SetPermission(atoi(sep->arg[1]), atoi(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); + else if (strncmp(command, "filter_event", length) == 0 && sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && guild) + guild->SetEventFilter(atoi(sep->arg[1]), atoi(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); + else if (strncmp(command, "kick", length) == 0 && sep->arg[1] && guild) + guild->KickGuildMember(client, sep->arg[1]); + else if (strncmp(command, "demote", length) == 0 && sep->arg[1] && guild) + guild->DemoteGuildMember(client, sep->arg[1]); + else if (strncmp(command, "promote", length) == 0 && sep->arg[1] && guild) + guild->PromoteGuildMember(client, sep->arg[1]); + else if (strncmp(command, "points", length) == 0 && guild) + { + if (sep->arg[1] && strncmp(sep->arg[1], "add", length) == 0) + { + if (sep->arg[2] && sep->IsNumber(2) && sep->arg[3]) + { + float points = atof(sep->arg[2]); + const char* option = sep->arg[3]; + const char* comment = sep->argplus[4]; + + if (strncmp(option, "all", strlen(option)) == 0) + guild->AddPointsToAll(client, points, comment); + else if (strncmp(option, "online", strlen(option)) == 0) + guild->AddPointsToAllOnline(client, points, comment); + else if (strncmp(option, "group", strlen(option)) == 0) + guild->AddPointsToGroup(client, points, comment); + else if (strncmp(option, "raid", strlen(option)) == 0) + guild->AddPointsToRaid(client, points, comment); + else + guild->AddPointsToGuildMember(client, points, option, comment); + } + } + else if (sep->arg[1] && strncmp(sep->arg[1], "view", strlen(sep->arg[1])) == 0 && sep->arg[2]) + guild->ViewGuildMemberPoints(client, sep->arg[2]); + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild points command: %s", sep->argplus[0]); + } + else if (strncmp(command, "motd", length) == 0 && sep->arg[1] && guild) + guild->SetMOTD(sep->argplus[1]); + else if (strncmp(command, "recruiting", length) == 0 && guild) + { + if (sep->arg[1]) + { + const char* option = sep->arg[1]; + + if (strncmp(option, "short_text", strlen(option)) == 0 && sep->arg[2]) + guild->SetRecruitingShortDesc(sep->argplus[2]); + else if (strncmp(option, "long_text", strlen(option)) == 0 && sep->arg[2]) + guild->SetRecruitingFullDesc(sep->argplus[2]); + else if (strncmp(option, "flag", strlen(option)) == 0 && sep->arg[2] && sep->IsNumber(2) && sep->arg[3]) + guild->SetRecruitingFlag(atoi(sep->arg[2]), strncmp(sep->arg[3], "true", 4) == 0 ? 1 : 0); + else if (strncmp(option, "min_level", strlen(option)) == 0 && sep->arg[2] && sep->IsNumber(2)) + guild->SetRecruitingMinLevel(atoi(sep->arg[2])); + else if (strncmp(option, "playstyle", strlen(option)) == 0 && sep->arg[2] && sep->IsNumber(2)) + guild->SetRecruitingPlayStyle(atoi(sep->arg[2])); + else if (strncmp(option, "tag", strlen(option)) == 0 && sep->arg[2] && sep->IsNumber(2) && sep->arg[3] && sep->IsNumber(3)) + guild->SetRecruitingDescTag(atoi(sep->arg[2]), atoi(sep->arg[3])); + else if (strncmp(command, "recruiting", strlen(option)) == 0) + guild->ChangeMemberFlag(client, GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD, strncmp(sep->arg[1], "true", 4) == 0 ? 1 : 0); + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild recruiting command: %s", sep->argplus[0]); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild recruiting command: %s", sep->argplus[0]); + } + else if (strncmp(command, "notify_online", length) == 0 && sep->arg[1] && guild) + guild->ChangeMemberFlag(client, GUILD_MEMBER_FLAGS_NOTIFY_LOGINS, strncmp(sep->arg[1], "true", 4) == 0 ? 1 : 0); + else if (strncmp(command, "event_privacy", length) == 0 && sep->arg[1] && guild) + guild->ChangeMemberFlag(client, GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS, strncmp(sep->arg[1], "true", 4) == 0 ? 1 : 0); + else if (strncmp(command, "recruiter", length) == 0 && sep->arg[1] && sep->arg[2] && guild) + guild->SetGuildRecruiter(client, sep->arg[1], strncmp(sep->arg[2], "true", 4) == 0 ? true : false); + else if (strncmp(command, "recruiter_description", length) == 0 && sep->arg[1] && guild) + guild->SetGuildRecruiterDescription(client, sep->argplus[1]); + else if (strncmp(command, "lock_event", length) == 0 && sep->arg[1] && sep->arg[2] && guild) + guild->LockGuildEvent(atoul(sep->arg[1]), strncmp(sep->arg[2], "true", 4) == 0 ? true : false); + else if (strncmp(command, "delete_event", length) == 0 && sep->arg[1] && guild) + guild->DeleteGuildEvent(atoul(sep->arg[1])); + else if (strncmp(command, "invite", length) == 0 && guild) + { + if (sep->arg[1] && strlen(sep->arg[1]) > 0) + guild->InvitePlayer(client, sep->arg[1]); + else + { + Spawn* target = client->GetPlayer()->GetTarget(); + if (target) + { + if (target->IsPlayer()) + guild->InvitePlayer(client, target->GetName()); + else + client->Message(CHANNEL_NARRATIVE, "%s is not a player.", target->GetName()); + } + } + } + else if (strncmp(command, "accept", length) == 0) + { + PendingGuildInvite* pgi = client->GetPendingGuildInvite(); + + if (pgi && pgi->guild && pgi->invited_by) + pgi->guild->AddNewGuildMember(client, pgi->invited_by->GetName()); + client->SetPendingGuildInvite(0); + } + else if (strncmp(command, "decline", length) == 0) + { + PendingGuildInvite* pgi = client->GetPendingGuildInvite(); + + if (pgi && pgi->guild && pgi->invited_by && pgi->invited_by->IsPlayer()) + { + Client* client_inviter = ((Player*)pgi->invited_by)->GetClient(); + + if (client_inviter) { + client_inviter->Message(CHANNEL_NARRATIVE, "%s has declined your invitation to join %s.", client->GetPlayer()->GetName(), pgi->guild->GetName()); + } + } + client->SetPendingGuildInvite(0); + } + else if (strncmp(command, "create", length) == 0 && sep->arg[1]) + { + const char* guild_name = sep->argplus[1]; + + if (!guild_list.GetGuild(guild_name)) + world.CreateGuild(guild_name, client, client->GetPlayer()->GetGroupMemberInfo() ? client->GetPlayer()->GetGroupMemberInfo()->group_id : 0); + else + client->SimpleMessage(CHANNEL_NARRATIVE, "A guild with that name already exists."); + } + else if (strncmp(command, "search", length) == 0) + client->ShowGuildSearchWindow(); + else if (strncmp(command, "recruiting_details", length) == 0 && sep->arg[1] && sep->IsNumber(1)) + { + Guild* to_guild = guild_list.GetGuild(atoul(sep->arg[1])); + + if (to_guild) + to_guild->SendGuildRecruitingDetails(client); + } + else if (strncmp(command, "recruiting_image", length) == 0 && sep->arg[1] && sep->IsNumber(1)) + { + Guild* to_guild = guild_list.GetGuild(atoul(sep->arg[1])); + + if (to_guild) + to_guild->SendGuildRecruitingImages(client); + } + else if (strncmp(command, "recruiter_adventure_class", length) == 0) + { + if (sep->arg[1]) + { + const char* option = sep->arg[1]; + + if (strncmp(option, "toggle", strlen(option)) == 0) + guild->ToggleGuildRecruiterAdventureClass(client); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild recruiter_adventure_class command: '%s'", sep->argplus[0]); + } + else if (strncmp(command, "display_heraldry", length) == 0) + { + // JA: not sure this is right... + client->GetPlayer()->toggle_character_flag(CF_SHOW_GUILD_HERALDRY); + client->GetPlayer()->SetCharSheetChanged(true); + } + else + LogWrite(COMMAND__ERROR, 0, "Command", "Unhandled guild command: '%s'", sep->argplus[0]); + } +} + +/* + Function: Command_GuildCreate() + Purpose : Display's in-game Guild Creation window + Dev : Scatman +*/ +void Commands::Command_CreateGuild(Client* client) +{ + client->SendGuildCreateWindow(); +} + +/* + Function: Command_SetGuildOfficerNote() + Purpose : + Dev : Scatman +*/ +void Commands::Command_SetGuildOfficerNote(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->arg[1]) + { + Guild* guild = client->GetPlayer()->GetGuild(); + + if (guild) + guild->SetGuildOfficerNote(sep->arg[0], sep->argplus[1]); + } +} + +/* + Function: Command_SetGuildMemberNote() + Purpose : + Dev : Scatman +*/ +void Commands::Command_SetGuildMemberNote(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->arg[1]) + { + Guild* guild = client->GetPlayer()->GetGuild(); + + if (guild) + guild->SetGuildMemberNote(sep->arg[0], sep->argplus[1]); + } +} + +/* + Function: Command_GuildSay() + Purpose : + Dev : Scatman +*/ +void Commands::Command_GuildSay(Client* client, Seperator* sep) +{ + Guild* guild = client->GetPlayer()->GetGuild(); + + if (guild) + { + if (sep && sep->arg[0]) + guild->HandleGuildSay(client, sep->argplus[0]); + } + else + client->SimpleMessage(CHANNEL_NARRATIVE, "You are not a member of a guild"); +} + +/* + Function: Command_OfficerSay() + Purpose : + Dev : Scatman +*/ +void Commands::Command_OfficerSay(Client* client, Seperator* sep) +{ + Guild* guild = client->GetPlayer()->GetGuild(); + + if (guild) + { + if (sep && sep->arg[0]) + guild->HandleOfficerSay(client, sep->argplus[0]); + } + else + client->SimpleMessage(CHANNEL_NARRATIVE, "You are not a member of a guild"); +} + +/* + Function: Command_Guilds() + Purpose : Shows help for /guild command + Params : + Dev : Scatman + Example : +*/ +void Commands::Command_Guilds(Client* client) +{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds [create|delete|add|remove|list]"); +} + +/* + Function: Command_GuildsAdd() + Purpose : Add's players to a guild + Params : guild_name|guild_id, player name + Dev : Scatman + Example : /guilds add 1 Admin = adds player Admin to guild_id 1 +*/ +void Commands::Command_GuildsAdd(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 0) + { + Guild* guild = 0; + bool found = true; + + if (sep->IsNumber(0)) + { + guild = guild_list.GetGuild(atoul(sep->arg[0])); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild with ID %u does not exist.", atoul(sep->arg[0])); + found = false; + } + } + else + { + guild = guild_list.GetGuild(sep->arg[0]); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' does not exist.", sep->arg[0]); + found = false; + } + } + if (found) + { + Client* to_client = 0; + + if (sep->arg[1] && strlen(sep->arg[1]) > 0) + to_client = zone_list.GetClientByCharName(string(sep->arg[1])); + else if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) + to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); + + if (to_client) + guild->InvitePlayer(client, to_client->GetPlayer()->GetName()); + else + client->Message(CHANNEL_COLOR_YELLOW, "Could not find player '%s' to invite to the guild.", sep->arg[1]); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds add [guild name|guild id] (player name)."); +} + +/* + Function: Command_GuildsCreate() + Purpose : Creates a guild + Params : guild_name, player_name (optional) + Dev : Scatman + Example : /guilds create [guild name] (player name) +*/ +void Commands::Command_GuildsCreate(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + { + const char* guild_name = sep->arg[0]; + + if (!guild_list.GetGuild(guild_name)) + { + bool ret = false; + + if (sep->arg[1] && strlen(sep->arg[1]) > 0) + { + Client* to_client = zone_list.GetClientByCharName(string(sep->arg[1])); + + if (to_client) + { + world.CreateGuild(guild_name, to_client); + ret = true; + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Could not find player with name '%s'", sep->arg[1]); + } + else if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) + { + Client* to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); + + if (to_client) + { + world.CreateGuild(guild_name, to_client); + ret = true; + } + } + else + { + world.CreateGuild(guild_name); + ret = true; + } + + if (ret) + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' was successfully created.", guild_name); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There was an error creating the guild."); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' already exists.", guild_name); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds create [guild name] (player name). If no player is specified, the player's target will be used. If the player has no target, a guild with no members will be created."); +} + +/* + Function: Command_GuildsDelete() + Purpose : Delete's a guild + Params : guild name + Dev : Scatman + Example : /guilds delete Test +*/ +void Commands::Command_GuildsDelete(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + { + Guild* guild = 0; + bool found = true; + + if (sep->IsNumber(0)) + { + guild = guild_list.GetGuild(atoul(sep->arg[0])); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild with ID %u does not exist.", atoul(sep->arg[0])); + found = false; + } + } + else + { + guild = guild_list.GetGuild(sep->arg[0]); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' does not exist.", sep->arg[0]); + found = false; + } + } + + if (found) + { + guild->RemoveAllGuildMembers(); + database.DeleteGuild(guild); + + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' was successfully deleted.", guild->GetName()); + guild_list.RemoveGuild(guild, true); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds delete [guild name]."); +} + +/* + Function: Command_GuildsList() + Purpose : Lists current guilds on server + Params : + Dev : Scatman + Example : /guilds list +*/ +void Commands::Command_GuildsList(Client* client) +{ + MutexMap* guilds = guild_list.GetGuilds(); + MutexMap::iterator itr = guilds->begin(); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Guild List:"); + if (guilds->size() == 0) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "None."); + else { + while (itr.Next()) { + Guild* guild = itr.second; + client->Message(CHANNEL_COLOR_YELLOW, "%u) %s", guild->GetID(), guild->GetName()); + } + } +} + +/* + Function: Command_GuildsRemove() + Purpose : Removes a player from a guild + Params : guild name|guild id, player name + Dev : Scatman + Example : /guilds remove 1 Admin = removes Admin from guild 1 +*/ +void Commands::Command_GuildsRemove(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 0) + { + Guild* guild = 0; + bool found = true; + + if (sep->IsNumber(0)) + { + guild = guild_list.GetGuild(atoul(sep->arg[0])); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild with ID %u does not exist.", atoul(sep->arg[0])); + found = false; + } + } + else + { + guild = guild_list.GetGuild(sep->arg[0]); + + if (!guild) + { + client->Message(CHANNEL_COLOR_YELLOW, "Guild '%s' does not exist.", sep->arg[0]); + found = false; + } + } + + if (found) + { + Client* to_client = 0; + + if (sep->arg[1] && strlen(sep->arg[1]) > 0) + to_client = zone_list.GetClientByCharName(string(sep->arg[1])); + else if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) + to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); + + if (to_client) + { + Player* to_player = to_client->GetPlayer(); + if (to_player->GetGuild()) + { + if (to_player->GetGuild() == guild) + { + guild->KickGuildMember(client, to_player->GetName()); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "%s is not in the guild '%s'.", to_player->GetName(), guild->GetName()); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "%s is not in a guild.", to_player->GetName()); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Could not find player '%s' to invite to the guild.", sep->arg[1]); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /guilds remove [guild name|guild id] (player name)."); +} + +/* + Function: Command_InspectPlayer() + Purpose : Handle the Inspect functions + Params : Client to inspect + Dev : Scatman + Example : /inspect Scatman +*/ +void Commands::Command_InspectPlayer(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + { + Client* inspect_client = zone_list.GetClientByCharName(string(sep->arg[0])); + + if (inspect_client) + client->InspectPlayer(inspect_client->GetPlayer()); + } + else + { + Spawn* target = client->GetPlayer()->GetTarget(); + + if (target && target->IsPlayer()) + client->InspectPlayer((Player*)target); + } +} + +/* + Function: Command_Inventory() + Purpose : Handle changes in player inventory + Params : + Dev : All + Example : /inventory delete item_id +*/ +void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteCommandString* command) +{ + PrintSep(sep, "Command_Inventory"); // temp to figure out the params + + Player* player = client->GetPlayer(); + + if(sep && sep->arg[0][0]) + { + LogWrite(COMMAND__INFO, 0, "Command", "command: %s", sep->argplus[0]); + + if(!client->GetPlayer()->Alive()) + client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now."); + else if(sep->arg[1][0] && strncasecmp("destroy", sep->arg[0], 6) == 0 && sep->IsNumber(1)) + { + int16 index = atoi(sep->arg[1]); + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index); + + if(item) + { + if(item->details.item_locked) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use."); + return; + } + else if(item->IsBag() && item->details.equip_slot_id) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item, it is being used as quiver."); + return; + } + else if(item->CheckFlag(NO_DESTROY)) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You can't destroy this item."); + return; + } + if(item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer()); + + //reobtain item make sure it wasn't removed + item = player->item_list.GetItemFromIndex(index); + int32 bag_id = 0; + if(item){ + bag_id = item->details.inv_slot_id; + database.DeleteItem(client->GetCharacterID(), item, 0); + } + client->GetPlayer()->item_list.DestroyItem(index); + client->GetPlayer()->UpdateInventory(bag_id); + client->GetPlayer()->CalculateApplyWeight(); + } + } + else if(sep->arg[4][0] && strncasecmp("move", sep->arg[0], 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4)) + { + int16 from_index = atoi(sep->arg[1]); + sint16 to_slot = atoi(sep->arg[2]); // don't convert slot since this is inventory not equipment + sint32 bag_id = atol(sep->arg[3]); + int8 charges = atoi(sep->arg[4]); + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index); + + if(!item) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You have no item."); + return; + } + + if(to_slot == item->details.slot_id && (bag_id < 0 || bag_id == item->details.inv_slot_id)) { + return; + } + if(item->details.item_locked) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use."); + return; + } + if(bag_id == -4 && !client->GetPlayer()->item_list.SharedBankAddAllowed(item)) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "That item (or an item inside) cannot be shared."); + return; + } + + sint32 old_inventory_id = 0; + + if(item) + old_inventory_id = item->details.inv_slot_id; + + //autobank + if (bag_id == -3 && to_slot == -1) + { + if (player->HasFreeBankSlot()) + to_slot = player->FindFreeBankSlot(); + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not have any free bank slots."); + return; + } + } + + //auto inventory + if (bag_id == 0 && to_slot == -1) + { + if (!player->item_list.GetFirstFreeSlot(&bag_id, &to_slot)) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not have any free slots."); + return; + } + } + + bool item_deleted = false; + EQ2Packet* outapp = client->GetPlayer()->MoveInventoryItem(bag_id, from_index, (int8)to_slot, charges, 0, &item_deleted, client->GetVersion()); + client->QueuePacket(outapp); + + if(item_deleted) + item = nullptr; + + //removed from bag send update + if(old_inventory_id > 0 && item && item->details.inv_slot_id != old_inventory_id) + { + outapp = client->GetPlayer()->SendBagUpdate(old_inventory_id, client->GetVersion()); + if(outapp) + client->QueuePacket(outapp); + } + + if(item && item->details.inv_slot_id > 0 && item->details.inv_slot_id != old_inventory_id) + { + outapp = client->GetPlayer()->SendBagUpdate(item->details.inv_slot_id, client->GetVersion()); + + if(outapp) + client->QueuePacket(outapp); + } + } + else if(sep->arg[1][0] && strncasecmp("equip", sep->arg[0], 5) == 0 && sep->IsNumber(1)) + { + int16 index = atoi(sep->arg[1]); + int8 slot_id = 255; + int8 unk3 = 0; + int8 appearance_equip = 0; + + if(sep->arg[2][0] && sep->IsNumber(2)) + slot_id = player->ConvertSlotFromClient(atoi(sep->arg[2]), client->GetVersion()); + if(sep->arg[3][0] && sep->IsNumber(3)) + unk3 = atoul(sep->arg[3]); + if(sep->arg[4][0] && sep->IsNumber(4)) + appearance_equip = atoul(sep->arg[4]); + + vector packets = client->GetPlayer()->EquipItem(index, client->GetVersion(), appearance_equip, slot_id); + EQ2Packet* outapp = 0; + + for(int32 i=0;iQueuePacket(outapp); + } + + client->GetPlayer()->UpdateWeapons(); + EQ2Packet* characterSheetPackets = client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion()); + client->QueuePacket(characterSheetPackets); + } + else if (sep->arg[1][0] && strncasecmp("unpack", sep->arg[0], 6) == 0 && sep->IsNumber(1)) + { + if (client->GetPlayer()->EngagedInCombat()) + client->SimpleMessage(CHANNEL_COLOR_RED, "You may not unpack items while in combat."); + else { + int16 index = atoi(sep->arg[1]); + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index); + if (item) { + if(item->details.item_locked) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use."); + return; + } + // client->GetPlayer()->item_list.DestroyItem(index); + if (item->item_sets.size() > 0) { + for (int32 i = 0; i < item->item_sets.size(); i++) { + ItemSet* set = item->item_sets[i]; + if (set->item_stack_size == 0) + set->item_stack_size += 1; + client->AddItem(set->item_id, set->item_stack_size); + } + } + + } + client->RemoveItem(item, 1); + + } + + } + else if(sep->arg[1][0] && strncasecmp("unequip", sep->arg[0], 7) == 0 && sep->IsNumber(1)) + { + int16 index = player->ConvertSlotFromClient(atoi(sep->arg[1]), client->GetVersion()); + sint32 bag_id = -999; + int8 to_slot = 255; + + if(sep->arg[3][0]) + { + if(sep->IsNumber(2)) + bag_id = atol(sep->arg[2]); + + if(sep->IsNumber(3)) + to_slot = atoi(sep->arg[3]); + } + + sint8 unk4 = 0; + int8 appearance_equip = 0; + if(sep->arg[4][0] && sep->IsNumber(4)) + unk4 = atoi(sep->arg[4]); + if(sep->arg[5][0] && sep->IsNumber(5)) + appearance_equip = atoul(sep->arg[5]); + + vector packets = client->GetPlayer()->UnequipItem(index, bag_id, to_slot, client->GetVersion(), appearance_equip); + EQ2Packet* outapp = 0; + + for(int32 i=0;iQueuePacket(outapp); + } + + client->UnequipItem(index, bag_id, to_slot, appearance_equip); + } + else if(sep->arg[2][0] && strncasecmp("swap_equip", sep->arg[0], 10) == 0 && sep->IsNumber(1) && sep->IsNumber(2)) + { + if(client->GetPlayer()->EngagedInCombat() && rule_manager.GetGlobalRule(R_Player, AllowPlayerEquipCombat)->GetInt8() == 0) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You may not swap items while in combat."); + } + else { + int16 index1 = client->GetPlayer()->ConvertSlotFromClient(atoi(sep->arg[1]), client->GetVersion()); + int16 index2 = client->GetPlayer()->ConvertSlotFromClient(atoi(sep->arg[2]), client->GetVersion()); + int8 type = 0; + if(sep->IsNumber(3)) + type = atoul(sep->arg[3]); // type 0 is combat, 3 = appearance + + EQ2Packet* outapp = client->GetPlayer()->SwapEquippedItems(index1, index2, client->GetVersion(), type); + + if(outapp) + client->QueuePacket(outapp); + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to swap items"); + return; + } + } + } + else if (sep->arg[2][0] && strncasecmp("pop", sep->arg[0], 3) == 0 && sep->IsNumber(1) && sep->IsNumber(2)) + { + sint16 to_slot = atoi(sep->arg[1]); + sint32 bag_id = atoi(sep->arg[2]); + Item* item = client->GetPlayer()->item_list.GetOverflowItem(); + if (item) { + //auto inventory + if (bag_id == 0 && to_slot == -1) + { + if (!player->item_list.GetFirstFreeSlot(&bag_id, &to_slot)) + { + client->SimpleMessage(CHANNEL_ERROR, "You do not have any free slots."); + return; + } + // Set the slot for the item + item->details.inv_slot_id = bag_id; + item->details.slot_id = to_slot; + // Flag the item so it gets saved in its new location + item->save_needed = true; + + // Add the item to its new location + if(player->item_list.AddItem(item)) { + // Remove the item from the overflow list + player->item_list.RemoveOverflowItem(item); + } + + // Send the inventory update packet + client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); + return; + } + else if (bag_id == -3 && to_slot == -1) { + // Auto Bank + if (!player->item_list.GetFirstFreeBankSlot(&bag_id, &to_slot)) { + client->SimpleMessage(CHANNEL_STATUS, "You do not have any free bank slots."); + return; + } + item->details.inv_slot_id = bag_id; + item->details.slot_id = to_slot; + item->save_needed = true; + if(player->item_list.AddItem(item)) { + player->item_list.RemoveOverflowItem(item); + } + client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); + } + else if (bag_id == -4) { + // Shared Bank + if (!player->item_list.SharedBankAddAllowed(item)) { + client->SimpleMessage(CHANNEL_STATUS, "That item (or an item inside) cannot be shared."); + return; + } + Item* tmp_item = player->item_list.GetItem(-4, to_slot); + if (tmp_item) { + client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot"); + return; + } + else { + item->details.inv_slot_id = bag_id; + item->details.slot_id = to_slot; + item->save_needed = true; + if(player->item_list.AddItem(item)) { + player->item_list.RemoveOverflowItem(item); + } + client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); + return; + } + } + else { + // Try to get an item from the given bag id and slot id + Item* tmp_item = player->item_list.GetItem(bag_id, to_slot); + // Check to see if we got an item, if we do send an error, + // if we don't put the overflow item into this slot + if (tmp_item) { + client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot"); + } + else { + item->details.inv_slot_id = bag_id; + item->details.slot_id = to_slot; + item->save_needed = true; + if(player->item_list.AddItem(item)) { + player->item_list.RemoveOverflowItem(item); + } + client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); + return; + } + } + } + } + else if(sep->arg[2][0] && strncasecmp("nosale", sep->arg[0], 6) == 0 && sep->IsNumber(1) && sep->IsNumber(2)) + { + sint64 data = strtoull(sep->arg[1], NULL, 0); + + int32 character_item_id = (int32) (data >> 32); + int32 item_id = (int32) (data & 0xffffffffL); + + int8 sale_setting = atoi(sep->arg[2]); + Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(character_item_id); + if(item) + { + item->no_sale = sale_setting; + item->save_needed = true; + client->SendSellMerchantList(); + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /inventory {destroy|move|equip|unequip|swap_equip|pop} {item_id} [to_slot] [bag_id]"); + +} + +/* + Function: Command_Languages() + Purpose : Show's languages the player knows + Params : + Dev : Zcoretri + Example : +*/ +void Commands::Command_Languages(Client* client, Seperator* sep) +{ + list* languages = client->GetPlayer()->GetPlayerLanguages()->GetAllLanguages(); + list::iterator itr; + Language* language; + client->Message(CHANNEL_NARRATIVE, "You know the following languages:"); + + for(itr = languages->begin(); itr != languages->end(); itr++) + { + language = *itr; + client->Message(CHANNEL_NARRATIVE, "%s", language->GetName()); + } +} + +/* + Function: Command_SetLanguage() + Purpose : Handles language commands + Params : Language ID + Dev : Zcoretri + Example : +*/ +void Commands::Command_SetLanguage(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + { + if(!sep->IsNumber(0)) + { + //String passed in + const char* value = sep->arg[0]; + + if(strncasecmp(value, "Common", strlen(value)) == 0) + { + database.SaveCharacterCurrentLang(0, client->GetCharacterID(), client); + client->SendLanguagesUpdate(0); + client->Message(CHANNEL_NARRATIVE, "You are now speaking %s", value); + } + else + { + if(player->HasLanguage(value)) + { + Language* language = player->GetPlayerLanguages()->GetLanguageByName(value); + database.SaveCharacterCurrentLang(language->GetID(), client->GetCharacterID(), client); + client->SendLanguagesUpdate(language->GetID()); + client->Message(CHANNEL_NARRATIVE, "You are now speaking %s", language->GetName()); + } + else + client->Message(CHANNEL_NARRATIVE, "You do not know how to speak %s", value); + } + } + else + { + //Number passed in + int32 id = atoul(sep->arg[0]); + + if(player->HasLanguage(id)) + { + Language* language = player->GetPlayerLanguages()->GetLanguage(id); + database.SaveCharacterCurrentLang(id, client->GetCharacterID(), client); + client->SendLanguagesUpdate(id); + client->Message(CHANNEL_NARRATIVE, "You are now speaking %s", language->GetName()); + } + else + { + Language* language = master_languages_list.GetLanguage(id); + if(language) + client->Message(CHANNEL_NARRATIVE, "You do not know how to speak %s", language->GetName()); + } + } + } + else + { + //No value was passed in + int32 id = database.GetCharacterCurrentLang(client->GetCharacterID(), player); + + if(id > 0) + { + Language* language = player->GetPlayerLanguages()->GetLanguage(id); + client->Message(CHANNEL_NARRATIVE, "You are currently speaking %s ", language->GetName()); + } + else + client->Message(CHANNEL_NARRATIVE, "You are currently speaking Common"); + } +} + +/* + Function: Command_LastName() + Purpose : Sets player surname + Params : Name text + Dev : theFoof + Example : +*/ +void Commands::Command_LastName(Client* client, Seperator* sep) +{ + if (!client) + return; + + if (sep && sep->arg[0]) + { + if (!client->GetPlayer()->get_character_flag(CF_ENABLE_CHANGE_LASTNAME)){ + client->Message(CHANNEL_COLOR_YELLOW, "You must be atleast level %i to change your last name.", rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8()); + return; + } + client->RemovePendingLastName(); + + uchar* checkname = (uchar*)sep->arg[0]; + bool valid_name = true; + for (int32 i = 0; i < strlen(sep->arg[0]); i++) { + if (!alpha_check(checkname[i])) { + valid_name = false; + break; + } + } + + if (!valid_name) { + client->Message(CHANNEL_COLOR_YELLOW, "Your last name can only contain letters.", rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8()); + return; + } + + string last_name = (string)sep->arg[0]; + int8 max_length = rule_manager.GetGlobalRule(R_Player, MaxLastNameLength)->GetInt8(); + int8 min_length = rule_manager.GetGlobalRule(R_Player, MinLastNameLength)->GetInt8(); + if (last_name.length() <= max_length && last_name.length() >= min_length){ + client->SetPendingLastName(last_name); + client->SendLastNameConfirmation(); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Your last name must be between %i and %i characters long.", min_length, max_length); + } +} + +/* + Function: Command_ConfirmLastName() + Purpose : Confirms setting of player surname + Params : Name text + Dev : theFoof + Example : +*/ +void Commands::Command_ConfirmLastName(Client* client, Seperator* sep) +{ + if (!client) + return; + + string* name = client->GetPendingLastName(); + if (name){ + Player* player = client->GetPlayer(); + player->SetLastName(name->c_str(), false); + client->SendTitleUpdate(); + player->SetCharSheetChanged(true); + client->RemovePendingLastName(); + } +} + +/* + Function: Command_Location() + Purpose : Display's Help for /location commands + Params : + Dev : Scatman + Example : /location = show's help for command +*/ +void Commands::Command_Location(Client* client) +{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Valid /location commands are:"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location create [name] (include y). Include y defaults to false"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location add [location id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location remove [location point id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location delete [location id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/location list [locations|points] [location id if points used]"); +} + +/* + Function: Command_LocationAdd() + Purpose : Add's a location to an existing location config + Params : location_id + Dev : Scatman + Example : /location add {location_id} +*/ +void Commands::Command_LocationAdd(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 location_id = atoul(sep->arg[0]); + float x = client->GetPlayer()->GetX(); + float y = client->GetPlayer()->GetY(); + float z = client->GetPlayer()->GetZ(); + + if (database.AddLocationPoint(location_id, x, y, z)) + client->Message(CHANNEL_COLOR_YELLOW, "Point (%f, %f, %f) was successfully added to location %u", x, y, z, location_id); + else + client->Message(CHANNEL_COLOR_YELLOW, "A location with ID %u does not exist. Use /location create to create one", location_id); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /location add [location id]"); +} + +/* + Function: Command_LocationCreate() + Purpose : Creates a new location config + Params : location name, 0/1 for include_y (optional) + Dev : Scatman + Example : /location create Test 1 = creates a new location named Test with include_y True +*/ +void Commands::Command_LocationCreate(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && strlen(sep->arg[0]) > 0) + { + const char* name = sep->arg[0]; + bool include_y = false; + + if (sep->arg[1] && sep->IsNumber(1) && atoi(sep->arg[1]) > 0) + include_y = true; + + int32 location_id = database.CreateLocation(client->GetPlayer()->GetZone()->GetZoneID(), client->GetPlayer()->GetLocation(), name, include_y); + + if (location_id > 0) + client->Message(CHANNEL_COLOR_YELLOW, "Location '%s' was successfully created with location id %u", name, location_id); + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There was an error creating the requested location"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /location create [name] (include_y). Include y defaults to false"); +} + +/* + Function: Command_LocationDelete() + Purpose : Delete's a location config and all it's location points + Params : location_id + Dev : Scatman + Example : /location delete {location_id} +*/ +void Commands::Command_LocationDelete(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 location_id = atoul(sep->arg[0]); + + if (database.DeleteLocation(location_id)) + client->Message(CHANNEL_COLOR_YELLOW, "Location id %u and all its points were successfully deleted", location_id); + else + client->Message(CHANNEL_COLOR_YELLOW, "A location with ID %u does not exist", location_id); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /location delete [location id]"); +} + +/* + Function: Command_LocationList() + Purpose : Display's a list of location points + Params : location_id + Dev : Scatman + Example : /location list {location_id} +*/ +void Commands::Command_LocationList(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + { + const char* option = sep->arg[0]; + + if (strncmp(option, "locations", strlen(option)) == 0) + database.ListLocations(client); + else if (strncmp(option, "points", strlen(option)) == 0 && sep->arg[1] && sep->IsNumber(1)) + { + int32 location_id = atoul(sep->arg[1]); + database.ListLocationPoints(client, location_id); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Useage: /location list [locations|points] [location ID if points used]"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Useage: /location list [locations|points] [location ID if points used]"); +} + +/* + Function: Command_LocationRemove() + Purpose : Removes a single location point from a location config + Params : location_point_id (gotten from /location list {id}) + Dev : Scatman + Example : /location remove 1 = will remove location_point_id 1 +*/ +void Commands::Command_LocationRemove(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 location_point_id = atoul(sep->arg[0]); + + if (database.DeleteLocationPoint(location_point_id)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Location point was successfully deleted"); + else + client->Message(CHANNEL_COLOR_YELLOW, "Location point with ID %u does not exist", location_point_id); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /location remove [location point id]"); +} + +/* + Function: Command_Merchant() + Purpose : Handles Merchant commands + Params : list + Dev : Scatman + Example : /merchant list +*/ +void Commands::Command_Merchant(Client* client, Seperator* sep, int handler) +{ + if( handler == COMMAND_MERCHANT ) + { + // /merchant command by itself shows help (to be extended) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /merchant list [merchant description]"); + return; + } + else if( handler == COMMAND_MERCHANT_LIST ) + { + // /merchant list command expects "description" param + if (sep && sep->arg[0]) + { + const char* merchant_description = sep->argplus[0]; + client->Message(CHANNEL_COLOR_YELLOW, "Listing merchants like '%s':", merchant_description); + map* merchant_info = world.GetMerchantInfo(); + map::iterator itr; + + for (itr = merchant_info->begin(); itr != merchant_info->end(); itr++) + { + string description = database.GetMerchantDescription(itr->first); + + if (ToLower(description).find(ToLower(string(merchant_description))) < 0xFFFFFFFF) + client->Message(CHANNEL_COLOR_YELLOW, "Merchant ID: %u, Description: %s", itr->first, description.c_str()); + } + } + else // no description + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /merchant list [merchant description]"); + } +} + +/* + Function: Command_Modify() + Purpose : to replace our other "set" commands + Params : System = the system to modify, Action = the action to perform, Target = what to change (target, or index) + Dev : John Adams + Example : /modify spell set name "Aegolism III" +*/ +void Commands::Command_Modify(Client* client) +{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /modify [system] [action] [field] [value] {target|id}"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Systems: character, faction, guild, item, skill, spawn, spell, zone"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Actions: set, create, delete, add, remove"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Value : field name in the table being modified"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target : modify current target, or specified {system} ID"); +} + + +/* + Function: Command_ModifySpawn() + Purpose : replace "spawn set" commands + Params : Action : the action to perform (or special handler) + : Field : the DB field to change + : Value : what to set the value to + : Target : what object to change (my target, or spawn_id) + Dev : John Adams + Example : /modify spawn set name "Lady Vox" + : + Note : Special Handlers, like "zoneto" for Signs + : /modify spawn zoneto + : Will set a sign's zone x/y/z to my current coords +*/ +void Commands::Command_ModifySpawn(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + // JA: just a quick implementation because I need it :) + if (strcmp(sep->arg[0], "zoneto") == 0) + { + if( sep->IsNumber(1) ) + { + int32 spawn_id = atoul(sep->arg[1]); + float x_coord = client->GetPlayer()->GetX(); + float y_coord = client->GetPlayer()->GetY(); + float z_coord = client->GetPlayer()->GetZ(); + float h_coord = client->GetPlayer()->GetHeading(); + + database.SaveSignZoneToCoords(spawn_id, x_coord, y_coord, z_coord, h_coord); + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Usage: /modify spawn zoneto spawn_id - sets spawn_id to your coords"); + } + } + else + Command_Modify(client); +} + +/* + Function: Command_ModifyCharacter() + Purpose : to replace our other "set" commands + Params : Action : Add, Remove + : Field : copper, silver, gold, plat + : Value : min 1, max unlimited + : Target : Self, Player + Dev : Cynnar + Example : /modify character add gold 50 + : /modify character remove silver 25 +*/ +void Commands::Command_ModifyCharacter(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + int64 value = 0; + + Player* player = client->GetPlayer(); + Client* targetClient = client; + if (player->HasTarget() && player->GetTarget()->IsPlayer()) { + player = (Player*)player->GetTarget(); + targetClient = player->GetClient(); + } + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + + { + if (strcmp(sep->arg[0], "add") == 0) + + { + if (strcmp(sep->arg[1], "copper") == 0) + { + value = atoi64(sep->arg[2]); + player->AddCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu copper coin%s", value, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu copper coin%s", player->GetName(), value, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu copper coin%s", client->GetPlayer()->GetName(), value, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "silver") == 0) + { + value = atoi64(sep->arg[2]) * 100; + player->AddCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu silver coin%s", value / 100, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu silver coin%s", player->GetName(), value / 100, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu silver coin%s", client->GetPlayer()->GetName(), value / 100, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "gold") == 0) + { + value = atoi64(sep->arg[2]) * 10000; + player->AddCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu gold coin%s", value / 10000, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu gold coin%s", player->GetName(), value / 10000, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu gold coin%s", client->GetPlayer()->GetName(), value / 10000, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "plat") == 0) + { + value = atoi64(sep->arg[2]) * 1000000; + player->AddCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu platinum coin%s", value / 1000000, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu platinum coin%s", player->GetName(), value / 1000000, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu platinum coin%s", client->GetPlayer()->GetName(), value / 1000000, (value > 1 ? "s" : "")); + } + } + } + + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Usage: /modify character [action] [field] [value]"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Actions: add, remove"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Value : copper, silver, gold, plat"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify character add copper 20"); + } + } + + else if (strcmp(sep->arg[0], "remove") == 0) + { + if (strcmp(sep->arg[1], "copper") == 0) + { + value = atoi64(sep->arg[2]); + player->RemoveCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu copper coin%s from yourself", value, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu copper coin%s from %s", value, (value > 1 ? "s" : ""), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s takes %llu copper coin%s from you", client->GetPlayer()->GetName(), value, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "silver") == 0) + { + value = atoi64(sep->arg[2]) * 100; + player->RemoveCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu silver coin%s from yourself", value / 100, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu silver coin%s from %s", value / 100, (value > 1 ? "s" : ""), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s takes %llu silver coin%s from you", client->GetPlayer()->GetName(), value / 100, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "gold") == 0) + { + value = atoi64(sep->arg[2]) * 10000; + player->RemoveCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu gold coin%s from yourself", value / 10000, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu gold coin%s from %s", value / 10000, (value > 1 ? "s" : ""), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s takes %llu gold coin%s from you", client->GetPlayer()->GetName(), value / 10000, (value > 1 ? "s" : "")); + } + } + } + + else if (strcmp(sep->arg[1], "plat") == 0) + { + value = atoi64(sep->arg[2]) * 1000000; + player->RemoveCoins(value); + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu platinum coin%s from yourself", value / 1000000, (value > 1 ? "s" : "")); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You take %llu platinum coin%s from %s", value / 1000000, (value > 1 ? "s" : ""), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s takes %llu platinum coin%s from you", client->GetPlayer()->GetName(), value / 1000000, (value > 1 ? "s" : "")); + } + } + } + + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify character remove gold 15"); + + } + } + + else if (strcmp(sep->arg[0], "set") == 0) { + + if (strcmp(sep->arg[1], "tslevel") == 0) { + int8 level = atoi(sep->arg[2]); + + if (level > 0 && level < 256) { + if (player) { + if (client->GetPlayer() == player) + client->ChangeTSLevel(player->GetTSLevel(), level); + else if(targetClient) + targetClient->ChangeTSLevel(player->GetTSLevel(), level); + } + } + else + client->SimpleMessage(CHANNEL_ERROR, "Level must be between 1 - 255"); + } + + else if (strcmp(sep->arg[1], "tsclass") == 0) { + int8 tsclass = atoi(sep->arg[2]); + + player->SetTradeskillClass(tsclass); + player->GetInfoStruct()->set_tradeskill_class1(classes.GetTSBaseClass(player->GetTradeskillClass())); + player->GetInfoStruct()->set_tradeskill_class2(classes.GetSecondaryTSBaseClass(player->GetTradeskillClass())); + player->GetInfoStruct()->set_tradeskill_class3(player->GetTradeskillClass()); + player->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[1], "gender") == 0) { + int8 gender = atoi(sep->arg[2]); + client->GetPlayer()->GetInfoStruct()->set_gender(gender); + client->GetPlayer()->SetCharSheetChanged(true); + client->UpdateTimeStampFlag ( GENDER_UPDATE_FLAG ); + } + } + } + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Usage: /modify character [action] [field] [value]"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Actions: add, remove"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Value : copper, silver, gold, plat"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify character remove gold 15"); + } + +} + + +void Commands::Command_ModifyFaction(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + + +void Commands::Command_ModifyGuild(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + + +void Commands::Command_ModifyItem(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + +/* + Function: Command_ModifyQuest() + Purpose : to list players quest and completed quests + Params : Action : list, completed + : Target : Self, Player + Dev : Cynnar + Example : /modify quest list + : /modify quest completed +*/ + +void Commands::Command_ModifyQuest(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + int64 value = 0; + + Player* player = client->GetPlayer(); + Client* targetClient = client; + if (player->HasTarget() && player->GetTarget()->IsPlayer()) { + player = (Player*)player->GetTarget(); + targetClient = player->GetClient(); + } + + // need at least 2 args for a valid command + + if (sep && sep->arg[1]) + { + + if (strcmp(sep->arg[0], "list") == 0) + { + map* quests = player->GetPlayerQuests(); + map::iterator itr; + client->Message(CHANNEL_COLOR_YELLOW, "%s's Quest List:.", client->GetPlayer()->GetName()); + if (quests->size() == 0) + client->Message(CHANNEL_COLOR_YELLOW, "%s has no quests.", client->GetPlayer()->GetName()); + else + { + for (itr = quests->begin(); itr != quests->end(); itr++) + { + Quest* quest = itr->second; + if(quest) { + client->Message(CHANNEL_COLOR_YELLOW, "%u) %s", itr->first, quest->GetName()); + } + } + } + + } + + else if (strcmp(sep->arg[0], "completed") == 0) + { + + map* quests = player->GetCompletedPlayerQuests(); + map::iterator itr; + client->Message(CHANNEL_COLOR_YELLOW, "%s's Completed Quest List:.", client->GetPlayer()->GetName()); + if (quests->size() == 0) + client->Message(CHANNEL_COLOR_YELLOW, "%s has no completed quests.", client->GetPlayer()->GetName()); + else + { + for (itr = quests->begin(); itr != quests->end(); itr++) + { + Quest* quest = itr->second; + if(quest) { + client->Message(CHANNEL_COLOR_YELLOW, "%u) %s", itr->first, quest->GetName()); + } + } + } + + } + // Add in a progress step, and a LogWrite() for tracking GM Commands. + // LogWrite(LUA__DEBUG, 0, "LUA", "Quest: %s, function: %s", quest->GetName(), function); + else if (strcmp(sep->arg[0], "remove") == 0) + { + int32 quest_id = 0; + + if (sep && sep->arg[1] && sep->IsNumber(1)) + quest_id = atoul(sep->arg[1]); + + if (quest_id > 0) + { + if (lua_interface && client->GetPlayer()->player_quests.count(quest_id) > 0) + { + Quest* quest = client->GetPlayer()->player_quests[quest_id]; + if (quest) + if (client->GetPlayer() == player) + { + client->Message(CHANNEL_COLOR_YELLOW, "The quest %s has been removed from your journal", quest->GetName()); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "You have removed the quest %s from %s's journal", quest->GetName(), player->GetName()); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s has removed the quest %s from your journal", client->GetPlayer()->GetName(), quest->GetName()); + } + } + LogWrite(COMMAND__INFO, 0, "GM Command", "%s removed the quest %s from %s", client->GetPlayer()->GetName(), quest->GetName(), player->GetName()); + lua_interface->CallQuestFunction(quest, "Deleted", client->GetPlayer()); + } + client->RemovePlayerQuest(quest_id); + client->GetCurrentZone()->SendQuestUpdates(client); + } + } + + else if (strcmp(sep->arg[0], "advance") == 0) + { + int32 quest_id = 0; + int32 step = 0; + + if (sep && sep->arg[1] && sep->IsNumber(1)) + { + quest_id = atoul(sep->arg[1]); + Quest* quest = client->GetPlayer()->player_quests[quest_id]; + + if(!quest) { + client->Message(CHANNEL_COLOR_RED, "Quest not found!"); + return; + } + if (sep && sep->arg[2] && sep->IsNumber(1)) + { + step = atoul(sep->arg[2]); + + if (quest_id > 0 && step > 0) + { + if (player && player->IsPlayer() && quest_id > 0 && step > 0 && (player->player_quests.count(quest_id) > 0)) + { + if (client) + { + client->AddPendingQuestUpdate(quest_id, step); + client->Message(CHANNEL_COLOR_YELLOW, "The quest %s has been advanced one step.", quest->GetName()); + LogWrite(COMMAND__INFO, 0, "GM Command", "%s advanced the quest %s one step", client->GetPlayer()->GetName(), quest->GetName()); + } + } + } + else + { + client->Message(CHANNEL_COLOR_RED, "Quest ID and Step Number must be greater than 0!"); + } + } + else + { + client->Message(CHANNEL_COLOR_RED, "Step Number must be a number!"); + } + } + else + { + client->Message(CHANNEL_COLOR_RED, "Quest ID must be a number!"); + } + } + + else + { + Command_Modify(client); + } + } + + else + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Usage: /modify quest [action] [quest id] [step number]"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Actions: list, completed, remove, advance"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify quest list"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify quest remove 156"); + client->SimpleMessage(CHANNEL_COLOR_RED, "Example: /modify quest advance 50 1"); + } + +} + + +void Commands::Command_ModifySkill(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + if (strcmp(sep->arg[0], "add") == 0) { + const char* skill_name = sep->argplus[1]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + + if (skill) { + Player* player = 0; + Client* to_client = 0; + + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) { + player = (Player*)client->GetPlayer()->GetTarget(); + to_client = player->GetClient(); + } + else { + player = client->GetPlayer(); + to_client = client; + } + + if (player) + { + if (!player->GetSkills()->HasSkill(skill->skill_id)) + { + player->AddSkill(skill->skill_id, 1, player->GetLevel() * 5, true); + if (to_client == client) { + client->Message(CHANNEL_STATUS, "Added skill '%s'.", skill_name); + } + else { + client->Message(CHANNEL_STATUS, "You gave skill '%s' to player '%s'.", skill_name, player->GetName()); + to_client->Message(CHANNEL_STATUS, "%s gave you skill '%s'.", client->GetPlayer()->GetName(), skill_name); + } + } + else + client->Message(CHANNEL_STATUS, "%s already has the skill '%s'.", to_client == client ? "You" : player->GetName(), skill_name); + } + } + else + client->Message(CHANNEL_STATUS, "The skill '%s' does not exist.", skill_name); + } + else if (strcmp(sep->arg[0], "remove") == 0) { + const char* skill_name = sep->argplus[1]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + + if (skill) { + Player* player = 0; + Client* to_client = 0; + + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) { + player = (Player*)client->GetPlayer()->GetTarget(); + to_client = player->GetClient(); + } + else { + player = client->GetPlayer(); + to_client = client; + } + + if (player) + { + if (player->GetSkills()->HasSkill(skill->skill_id)) + { + player->RemoveSkillFromDB(skill, true); + if (client == to_client) { + client->Message(CHANNEL_STATUS, "Removed skill '%s'.", skill_name); + } + else { + client->Message(CHANNEL_STATUS, "Removed skill '%s' from player %s.", skill_name, player->GetName()); + if(to_client) { + to_client->Message(CHANNEL_STATUS, "%s has removed skill '%s' from you.", client->GetPlayer()->GetName(), skill_name); + } + } + } + else { + if (client == to_client) + client->Message(CHANNEL_STATUS, "You do not have the skill '%s'.", skill_name); + else + client->Message(CHANNEL_STATUS, "Player '%s' does not have the skill '%s'.", player->GetName(), skill_name); + } + } + } + else + client->Message(CHANNEL_STATUS, "The skill '%s' does not exist.", skill_name); + } + else if (strcmp(sep->arg[0], "set") == 0) { + if (!sep->IsNumber(2)) { + client->Message(CHANNEL_STATUS, "The last parameter must be a number."); + return; + } + + const char* skill_name = sep->arg[1]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + if (skill) { + int16 val = atoi(sep->arg[2]); + Player* player = 0; + Client* to_client = 0; + + if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) { + player = (Player*)client->GetPlayer()->GetTarget(); + to_client = player->GetClient(); + } + else { + player = client->GetPlayer(); + to_client = client; + } + + if (player) + { + if (player->GetSkills()->HasSkill(skill->skill_id)) { + player->GetSkills()->SetSkill(skill->skill_id, val); + if (client != to_client) + client->Message(CHANNEL_STATUS, "You set %s's '%s' skill to %i.", player->GetName(), skill_name, val); + if(to_client) { + to_client->Message(CHANNEL_STATUS, "Your '%s' skill has been set to %i.", skill_name, val); + } + } + else { + client->Message(CHANNEL_STATUS, "Target does not have the skill '%s'.", skill_name); + } + } + } + else { + client->Message(CHANNEL_STATUS, "The skill '%s' does not exist.", skill_name); + } + } + else { + client->SimpleMessage(CHANNEL_STATUS, "Usage: /modify skill [action] [skill]"); + client->SimpleMessage(CHANNEL_STATUS, "Actions: add, remove, set"); + client->SimpleMessage(CHANNEL_STATUS, "Example: /modify skill add parry"); + } + } + else { + client->SimpleMessage(CHANNEL_STATUS, "Usage: /modify skill [action] [skill]"); + client->SimpleMessage(CHANNEL_STATUS, "Actions: add, remove, set"); + client->SimpleMessage(CHANNEL_STATUS, "Example: /modify skill add parry"); + } +} + + +void Commands::Command_ModifySpell(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + + +void Commands::Command_ModifyZone(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_MODIFY"); + + // need at least 2 args for a valid command + if( sep && sep->arg[1] ) + { + } + else + Command_Modify(client); +} + + +/* + Function: Command_MOTD() + Purpose : Displays server MOTD + Params : + Dev : LethalEncounter + Example : /motd +*/ +void Commands::Command_MOTD(Client* client) +{ + if (client) + ClientPacketFunctions::SendMOTD(client); +} + +/* + Function: Command_Pet() + Purpose : Handle Pet {Command} commands + Params : attack, backoff, preserve_master, preserve_self, follow, stay, getlost + Dev : + Example : /pet preserve_master +*/ +void Commands::Command_Pet(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_PET"); + //LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Pet Commands"); + //client->Message(CHANNEL_COLOR_YELLOW, "Pets are not yet implemented."); + + + if (!sep || !sep->arg[0]) + return; // should have sep->arg[0] filled + + if (strcmp(sep->arg[0], "hide") == 0) { + // doing /pet hide will toggle the hide status on all the pets that can be hidden + Entity* pet = client->GetPlayer()->GetDeityPet(); + if (pet) { + if (pet->IsPrivateSpawn()) + client->GetPlayer()->HideDeityPet(false); + else + client->GetPlayer()->HideDeityPet(true); + } + + pet = client->GetPlayer()->GetCosmeticPet(); + if (pet) { + if (pet->IsPrivateSpawn()) + client->GetPlayer()->HideCosmeticPet(false); + else + client->GetPlayer()->HideCosmeticPet(true); + } + + return; + } + + // below is for all combat pets + if (!client->GetPlayer()->HasPet()) { + client->Message(CHANNEL_COLOR_YELLOW, "You do not have a pet."); + return; + } + + if (strcmp(sep->arg[0], "stay") == 0 || strcmp(sep->arg[0], "stayhere") == 0) { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to stay."); + client->GetPlayer()->GetInfoStruct()->set_pet_movement(1); + client->GetPlayer()->SetCharSheetChanged(true); + if (client->GetPlayer()->GetPet()) + client->GetPlayer()->GetPet()->following = false; + if (client->GetPlayer()->GetCharmedPet()) + client->GetPlayer()->GetCharmedPet()->following = false; + } + else if (strcmp(sep->arg[0], "follow") == 0 || strcmp(sep->arg[0], "followme") == 0) { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to follow."); + client->GetPlayer()->GetInfoStruct()->set_pet_movement(2); + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[0], "preserve_master") == 0 || strcmp(sep->arg[0], "guardme") == 0) { + if (client->GetPlayer()->GetInfoStruct()->get_pet_behavior() & 1) { + client->Message(CHANNEL_COLOR_YELLOW, "Your pet will no longer protect you."); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(client->GetPlayer()->GetInfoStruct()->get_pet_behavior()-1); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to protect you."); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(client->GetPlayer()->GetInfoStruct()->get_pet_behavior()+1); + } + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[0], "preserve_self") == 0 || strcmp(sep->arg[0], "guardhere") == 0) { // guardhere might be accurate, diff logic + if (client->GetPlayer()->GetInfoStruct()->get_pet_behavior() & 2) { + client->Message(CHANNEL_COLOR_YELLOW, "Your pet will no longer protect itself."); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(client->GetPlayer()->GetInfoStruct()->get_pet_behavior()-2); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to protect itself."); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(client->GetPlayer()->GetInfoStruct()->get_pet_behavior()+2); + } + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[0], "backoff") == 0) { + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to back down."); + if (client->GetPlayer()->GetPet()) + ((NPC*)client->GetPlayer()->GetPet())->Brain()->ClearHate(); + if (client->GetPlayer()->GetCharmedPet()) + ((NPC*)client->GetPlayer()->GetCharmedPet())->Brain()->ClearHate(); + client->GetPlayer()->GetInfoStruct()->set_pet_behavior(0); + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (strcmp(sep->arg[0], "attack") == 0) { + if (client->GetPlayer()->HasTarget() && client->GetPlayer()->GetTarget()->IsEntity()) { + if (client->GetPlayer()->AttackAllowed((Entity*)client->GetPlayer()->GetTarget())){ + client->Message(CHANNEL_COLOR_YELLOW, "You command your pet to attack your target."); + if (client->GetPlayer()->GetPet()) + client->GetPlayer()->GetPet()->AddHate((Entity*)client->GetPlayer()->GetTarget(), 1); + if (client->GetPlayer()->GetCharmedPet()) + client->GetPlayer()->GetCharmedPet()->AddHate((Entity*)client->GetPlayer()->GetTarget(), 1); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You can not attack that."); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You do not have a target."); + + } + else if (strcmp(sep->arg[0], "getlost") == 0) { + client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetPet()); + client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetCharmedPet()); + + client->Message(CHANNEL_COLOR_YELLOW, "You tell your pet to get lost."); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Unknown pet command %s.", sep->arg[0]); + +} + +/* + Function: Command_PetName() + Purpose : Pet your name (???) + Params : + Dev : + Example : +*/ +void Commands::Command_PetName(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) { + const char* pet_name = sep->argplus[0]; + client->SetPetName(pet_name); + } + else { + client->GetPlayer()->GetInfoStruct()->set_pet_name(""); + } +} + +/* + Function: Command_NamePet() + Purpose : Name your pet + Params : + Dev : + Example : +*/ +void Commands::Command_NamePet(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_NAME_PET"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Name Pet Command"); + client->Message(CHANNEL_COLOR_YELLOW, "Pets are not yet implemented."); +} + +/* + Function: Command_Rename() + Purpose : Renames existing pet + Params : + Dev : + Example : +*/ +void Commands::Command_Rename(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_RENAME"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Rename Pet Command"); + client->Message(CHANNEL_COLOR_YELLOW, "Pets are not yet implemented."); +} + +/* + Function: Command_ConfirmRename() + Purpose : Confirms renaming your pet + Params : + Dev : + Example : +*/ +void Commands::Command_ConfirmRename(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_CONFIRMRENAME"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Confirm Rename Pet Command"); + client->Message(CHANNEL_COLOR_YELLOW, "Pets are not yet implemented."); +} + +/* + Function: Command_PetOptions() + Purpose : Sets various pet options + Params : + Dev : + Example : +*/ +void Commands::Command_PetOptions(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_PETOPTIONS"); + + Player* player = client->GetPlayer(); + Spawn* target = player->GetTarget(); + if (!target) + return; + + if (target == player->GetPet()) + client->SendPetOptionsWindow(player->GetPet()->GetName()); + else if (target == player->GetCharmedPet()) + client->SendPetOptionsWindow(player->GetCharmedPet()->GetName()); + else if (target == player->GetCosmeticPet()) + client->SendPetOptionsWindow(player->GetCosmeticPet()->GetName(), 0); + else if (target == player->GetDeityPet()) + client->SendPetOptionsWindow(player->GetDeityPet()->GetName(), 0); +} + +/* + Function: Command_Random() + Purpose : Handles /random dice roll in-game + Params : 1-100 + Dev : Scatman + Example : /randon 1 100 +*/ +void Commands::Command_Random(Client* client, Seperator* sep) +{ + char message[256] = {0}; + + if (sep) + { + if (sep->GetArgNumber() == 0 && sep->IsNumber(0)) + sprintf(message, "Random: %s rolls 1 to %i on the magic dice...and scores a %i!", client->GetPlayer()->GetName(), atoi(sep->arg[0]), MakeRandomInt(1, atoi(sep->arg[0]))); + else if (sep->GetArgNumber() > 0 && sep->IsNumber(0) && sep->IsNumber(1)) + sprintf(message, "Random: %s rolls from %i to %i on the magic dice...and scores a %i!", client->GetPlayer()->GetName(), atoi(sep->arg[0]), atoi(sep->arg[1]), MakeRandomInt(atoi(sep->arg[0]), atoi(sep->arg[1]))); + else + sprintf(message, "Random: %s rolls from 1 to 100 on the magic dice...and scores a %i!", client->GetPlayer()->GetName(), MakeRandomInt(1, 100)); + } + else + sprintf(message, "Random: %s rolls from 1 to 100 on the magic dice...and scores a %i!", client->GetPlayer()->GetName(), MakeRandomInt(1, 100)); + + client->GetPlayer()->GetZone()->HandleChatMessage(0, 0, CHANNEL_EMOTE, message); +} + +/* + Function: Command_Randomize() + Purpose : Sets randomize (appearance) values for NPCs + Params : Attrib Name + Dev : Scatman + Example : /randomize gender 1 -- will randomize the NPCs gender (male/female) +*/ +void Commands::Command_Randomize(Client* client, Seperator* sep) +{ + NPC* target = (NPC*)client->GetPlayer()->GetTarget(); + if (target) + { + if (target->IsNPC()) + { + if (sep && sep->arg[0] && !sep->IsNumber(0)) + { + const char* value = sep->arg[0]; + if (strncasecmp(value, "show", strlen(value)) == 0) + { + client->DisplayRandomizeFeatures(target->GetRandomize()); + } + else + { + if (sep->arg[1] && sep->IsNumber(1)) + { + int8 option = atoi(sep->arg[1]); + if (option == 0 || option == 1) + { + int32 feature = 0; + char feature_text[32] = {0}; + if (strncasecmp(value, "gender", strlen(value)) == 0) { + feature = RANDOMIZE_GENDER; + strncpy(feature_text, "gender", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "race", strlen(value)) == 0) { + feature = RANDOMIZE_RACE; + strncpy(feature_text, "race", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "model", strlen(value)) == 0) { + feature = RANDOMIZE_MODEL_TYPE; + strncpy(feature_text, "model", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "facial_hair", strlen(value)) == 0) { + feature = RANDOMIZE_FACIAL_HAIR_TYPE; + strncpy(feature_text, "facial hair", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_TYPE; + strncpy(feature_text, "hair", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "wing", strlen(value)) == 0) { + feature = RANDOMIZE_WING_TYPE; + strncpy(feature_text, "wings", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "cheek", strlen(value)) == 0) { + feature = RANDOMIZE_CHEEK_TYPE; + strncpy(feature_text, "cheeks", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "chin", strlen(value)) == 0) { + feature = RANDOMIZE_CHIN_TYPE; + strncpy(feature_text, "chin", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "ear", strlen(value)) == 0) { + feature = RANDOMIZE_EAR_TYPE; + strncpy(feature_text, "ears", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "eye_brow", strlen(value)) == 0) { + feature = RANDOMIZE_EYE_BROW_TYPE; + strncpy(feature_text, "eye brows", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "eye", strlen(value)) == 0) { + feature = RANDOMIZE_EYE_TYPE; + strncpy(feature_text, "eyes", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "lip", strlen(value)) == 0) { + feature = RANDOMIZE_LIP_TYPE; + strncpy(feature_text, "lips", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "nose", strlen(value)) == 0) { + feature = RANDOMIZE_NOSE_TYPE; + strncpy(feature_text, "nose", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "eye_color", strlen(value)) == 0) { + feature = RANDOMIZE_EYE_COLOR; + strncpy(feature_text, "eye color", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_color", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_TYPE_COLOR; + strncpy(feature_text, "hair color", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_color1", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_COLOR1; + strncpy(feature_text, "hair color1", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_color2", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_COLOR2; + strncpy(feature_text, "hair color2", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_highlight", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_HIGHLIGHT; + strncpy(feature_text, "hair highlights", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "facial_hair_color", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_FACE_COLOR; + strncpy(feature_text, "facial hair color", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "facial_hair_color_highlight", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR; + strncpy(feature_text, "facial hair color highlights", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "hair_color_highlight", strlen(value)) == 0) { + feature = RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR; + strncpy(feature_text, "hair color highlights", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "skin_color", strlen(value)) == 0) { + feature = RANDOMIZE_SKIN_COLOR; + strncpy(feature_text, "skin color", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "wing_color1", strlen(value)) == 0) { + feature = RANDOMIZE_WING_COLOR1; + strncpy(feature_text, "wing color1", sizeof(feature_text) - 1); + } + else if (strncasecmp(value, "wing_color2", strlen(value)) == 0) { + feature = RANDOMIZE_WING_COLOR2; + strncpy(feature_text, "wing color2", sizeof(feature_text) - 1); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "'%s' is not a valid feature to randomize.", value); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Valid features are: 'gender', 'race', 'model', 'hair', 'facial_hair', 'legs', 'wings', 'cheek', 'chin', 'ear', 'eye', 'eye_brow', 'lip', 'nose', 'eye_color', 'hair_color1', 'hair_color2', 'hair_highlight', 'facial_hair_color', 'facial_hair_color_highlight', 'hair_color', 'hair_color_highlight', 'skin_color', 'wing_color1', 'wing_color2'."); + } + + if (feature > 0) { + if (option == 1) { + if (target->GetRandomize() & feature) + client->Message(CHANNEL_COLOR_YELLOW, "'%s' is already set to randomly generate their %s.", target->GetName(), feature_text); + else { + target->AddRandomize(feature); + ((NPC*)client->GetCurrentZone()->GetSpawn(target->GetDatabaseID()))->AddRandomize(feature); + database.UpdateRandomize(target->GetDatabaseID(), feature); + client->Message(CHANNEL_COLOR_YELLOW, "'%s' will now generate their %s randomly.", target->GetName(), feature_text); + } + } + else { + if (target->GetRandomize() & feature) { + target->AddRandomize(-feature); + ((NPC*)client->GetCurrentZone()->GetSpawn(target->GetDatabaseID()))->AddRandomize(-feature); + database.UpdateRandomize(target->GetDatabaseID(), -feature); + client->Message(CHANNEL_COLOR_YELLOW, "'%s' will no longer generate their %s randomly.", target->GetName(), feature_text); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "'%s' already does not randomly generate their %s.", target->GetName(), feature_text); + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must specify either a 1(on) or 0(off) as the second parameter."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must specify either a 1(on) or 0(off) as the second parameter."); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /randomize [show | [feature [1|0]]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "'show' will display current configured features"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Valid features are: 'gender', 'race', 'model', 'hair', 'facial_hair', 'legs', 'wings', 'cheek', 'chin', 'ear', 'eye', 'eye_brow', 'lip', 'nose', 'eye_color', 'hair_color1', 'hair_color2', 'hair_highlight', 'facial_hair_color', 'facial_hair_color_highlight', 'hair_color', 'hair_color_highlight', 'skin_color', 'wing_color1', 'wing_color2'."); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "The target you wish to randomize must be an NPC."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must select the target you wish to randomize."); +} + +/* + Function: Command_ShowCloak() + Purpose : Character Settings combo box for Show Cloak + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowCloak(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + { + const char* value = sep->arg[0]; + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->set_character_flag(CF_SHOW_CLOAK); + player->reset_character_flag(CF2_SHOW_RANGED); + } + else if (strncasecmp(value, "false", strlen(value)) == 0) + player->reset_character_flag(CF_SHOW_CLOAK); + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: Command_ShowHelm() + Purpose : Character Settings combo box for Show Helm + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowHelm(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if(client->GetVersion() <= 561) { + return; // not allowed/supported + } + + if (sep && sep->arg[0]) + { + PrintSep(sep, "Command_ShowHelm"); + const char* value = sep->arg[0]; + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->toggle_character_flag(CF_HIDE_HELM); + player->toggle_character_flag(CF_HIDE_HOOD); + } + else if (strncasecmp(value, "false", strlen(value)) == 0) + player->toggle_character_flag(CF_HIDE_HELM); + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: Command_ShowHood() + Purpose : Character Settings combo box for Show Hood + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowHood(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + { + PrintSep(sep, "Command_ShowHood"); + const char* value = sep->arg[0]; + + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->toggle_character_flag(CF_HIDE_HOOD); + if(client->GetVersion() > 561) { // no hide helm support in DoF + player->toggle_character_flag(CF_HIDE_HELM); + } + } + else if (strncasecmp(value, "false", strlen(value)) == 0) { + player->toggle_character_flag(CF_HIDE_HOOD); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: Command_ShowHoodHelm() + Purpose : Character Settings combo box for Show Helm or Hood + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowHoodHelm(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if(client->GetVersion() <= 561) { + return; // not allowed/supported + } + + if (sep && sep->arg[0]) + { + PrintSep(sep, "Command_ShowHoodHelm"); + const char* value = sep->arg[0]; + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->toggle_character_flag(CF_HIDE_HOOD); + player->toggle_character_flag(CF_HIDE_HELM); + } + else if (strncasecmp(value, "false", strlen(value)) == 0) + { + // don't think we ever wind up in here... + player->toggle_character_flag(CF_HIDE_HOOD); + player->toggle_character_flag(CF_HIDE_HELM); + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: Command_ShowRanged() + Purpose : Character Settings combo box for Show Ranged weapon + Params : true/false + Dev : John Adams + Example : +*/ +void Commands::Command_ShowRanged(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + { + const char* value = sep->arg[0]; + if (strncasecmp(value, "true", strlen(value)) == 0) + { + player->set_character_flag(CF2_SHOW_RANGED); + player->reset_character_flag(CF_SHOW_CLOAK); + } + else if (strncasecmp(value, "false", strlen(value)) == 0) + player->reset_character_flag(CF2_SHOW_RANGED); + else + { + client->Message(CHANNEL_COLOR_YELLOW, "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + LogWrite(COMMAND__WARNING, 0, "Command", "Not supposed to be here! Please /bug this: Error in %s (%u)", __FUNCTION__, __LINE__); + } + } +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_Skills(Client* client, Seperator* sep, int handler) +{ + Player* player = 0; + Client* to_client = 0; + + if(client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsPlayer()) + to_client = ((Player*)client->GetPlayer()->GetTarget())->GetClient(); + + switch(handler) + { + case COMMAND_SKILL: + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /skill [add|remove|list] [skill name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target player to add/remove skills for that player."); + break; + } + case COMMAND_SKILL_ADD: + { + if (sep && sep->arg[0]) + { + const char* skill_name = sep->argplus[0]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + + if (skill && to_client && !(client->GetPlayer() == to_client->GetPlayer())) // add skill to your target, if target is not you + { + player = to_client->GetPlayer(); + + if (player) + { + if (!player->GetSkills()->HasSkill(skill->skill_id)) + { + player->AddSkill(skill->skill_id, 1, player->GetLevel() * 5, true); + client->Message(CHANNEL_COLOR_YELLOW, "You gave skill '%s' to player '%s'.", skill_name, player->GetName()); + to_client->Message(CHANNEL_COLOR_YELLOW, "%s gave you skill '%s'.", client->GetPlayer()->GetName(), skill_name); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "%s already has the skill '%s'.", player->GetName(), skill_name); + } + } + else if (skill) // add skill to yourself + { + player = client->GetPlayer(); + + if (player) + { + if (!player->GetSkills()->HasSkill(skill->skill_id)) + { + player->AddSkill(skill->skill_id, 1, player->GetLevel() * 5, true); + client->Message(CHANNEL_COLOR_YELLOW, "Added skill '%s'.", skill_name); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You already have the skill '%s'.", skill_name); + } + } + else + client->Message(CHANNEL_COLOR_YELLOW, "The skill '%s' does not exist.", skill_name); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /skill add [skill name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target player to give skill to that player."); + } + break; + } + case COMMAND_SKILL_REMOVE: + { + if (sep && sep->arg[0]) + { + const char* skill_name = sep->argplus[0]; + Skill* skill = master_skill_list.GetSkillByName(skill_name); + + if (skill && to_client && !(client->GetPlayer() == to_client->GetPlayer())) // remove skill from your target, if target is not you + { + player = to_client->GetPlayer(); + + if (player) + { + if (player->GetSkills()->HasSkill(skill->skill_id)) + { + player->RemoveSkillFromDB(skill, true); + client->Message(CHANNEL_COLOR_YELLOW, "Removed skill '%s' from player %s.", skill_name, player->GetName()); + to_client->Message(CHANNEL_COLOR_YELLOW, "%s has removed skill '%s' from you.", client->GetPlayer()->GetName(), skill_name); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "Player '%s' does not have the skill '%s'.", player->GetName(), skill_name); + } + } + else if(skill) // remove skill from yourself + { + Player* player = client->GetPlayer(); + + if (player) + { + if (player->GetSkills()->HasSkill(skill->skill_id)) + { + player->RemoveSkillFromDB(skill, true); + client->Message(CHANNEL_COLOR_YELLOW, "Removed skill '%s'.", skill_name); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "You do not have the skill '%s'.", skill_name); + } + } + else + client->Message(CHANNEL_COLOR_YELLOW, "The skill '%s' does not exist.", skill_name); + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /skill remove [skill name]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target player to give skill to that player."); + } + break; + } + case COMMAND_SKILL_LIST: + { + if (sep && sep->arg[0]) + { + const char* skill_name = sep->argplus[0]; + client->Message(CHANNEL_COLOR_YELLOW, "Listing skills like '%s':", skill_name); + map* skills = master_skill_list.GetAllSkills(); + map::iterator itr; + + if (skills && skills->size() > 0) + { + for (itr = skills->begin(); itr != skills->end(); itr++) + { + Skill* skill = itr->second; + string current_skill_name = ::ToLower(string(skill->name.data.c_str())); + + if (current_skill_name.find(::ToLower(string(skill_name))) < 0xFFFFFFFF) + client->Message(CHANNEL_COLOR_YELLOW, "%s (%u)", skill->name.data.c_str(), skill->skill_id); + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /skill list [skill name]"); + break; + } + } +} + +/* + Function: Command_SpawnTemplate() + Purpose : Create or Use defined Spawn Templates + Params : create, save, rempve, list + Dev : John Adams + Example : /spawn template create "Test" +*/ +void Commands::Command_SpawnTemplate(Client* client, Seperator* sep) +{ + if (sep == NULL || sep->arg[0] == NULL) + { + client->Message(CHANNEL_COLOR_YELLOW, "Examples:\n/spawn template save Test - saves a template of the targetted spawn as the name Test\n/spawn template list - shows a list of current templates\n/spawn template spawn [id|name] creates a new spawn based on template [id|name] at your current location."); + return; + } + + // got params, continue + const char * template_cmd = sep->arg[0]; + + if (strncasecmp(template_cmd, "list", strlen(template_cmd)) == 0) + { + if (!sep->IsSet(1)) + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Usage: /spawn template list [name|location_id]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW,"Examples:\n/spawn template list [name] - will show all templates containing [name].\n/spawn template list [location_id] - will show all templates that [location_id] is assigned to."); + return; + } + + map* template_names = 0; + if (sep->IsNumber(1)) + { + int32 location_id = 0; + location_id = atoi(sep->arg[1]); + template_names = database.GetSpawnTemplateListByID(location_id); + } + else + { + const char* name = 0; + name = sep->argplus[1]; + template_names = database.GetSpawnTemplateListByName(name); + } + + if(!template_names) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "No templates found."); + else + { + map::iterator itr; + client->SimpleMessage(CHANNEL_COLOR_YELLOW," ID Name: "); + for(itr = template_names->begin(); itr != template_names->end(); itr++) + client->Message(CHANNEL_COLOR_YELLOW,"%03lu %s", itr->first, itr->second.c_str()); + safe_delete(template_names); + } + } + + else if (strncasecmp(template_cmd, "save", strlen(template_cmd)) == 0) + { + NPC* target = (NPC*)client->GetPlayer()->GetTarget(); + if ( target && (target->IsNPC() || target->IsObject() || target->IsSign() || target->IsWidget() || target->IsGroundSpawn()) ) + { + if (sep && sep->arg[1][0] && !sep->IsNumber(1)) + { + // first, lookup to see if template name already exists + const char* name = 0; + name = sep->argplus[1]; + map* template_names = database.GetSpawnTemplateListByName(name); + if(!template_names) + { + int32 new_template_id = database.SaveSpawnTemplate(target->GetSpawnLocationID(), name); + if( new_template_id > 0 ) + client->Message(CHANNEL_COLOR_YELLOW, "Spawn template '%s' added for spawn '%s' (%u) as TemplateID: %u", name, target->GetName(), target->GetSpawnLocationID(), new_template_id); + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to add new template '%s' for spawn '%s' (%u).", name, target->GetName(), target->GetSpawnLocationID()); + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to add new template '%s' - Already exists!", name); + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Saving a new spawn template requires a valid template name!"); + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: You must target the spawn you wish to save as a template!"); + } + + else if (strncasecmp(template_cmd, "remove", strlen(template_cmd)) == 0) + { + if (sep && sep->arg[1][0] && sep->IsNumber(1)) + { + int32 template_id = 0; + template_id = atoi(sep->arg[1]); + if (database.RemoveSpawnTemplate(template_id)) + client->Message(CHANNEL_COLOR_YELLOW, "Spawn template ID: %u successfully removed.", template_id); + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to remove spawn template ID: %u", template_id); + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Removing a spawn template requires a valid template ID!"); + } + + // Renamed create to spawn + else if (strncasecmp(template_cmd, "spawn", strlen(template_cmd)) == 0) + { + if (sep && sep->arg[1][0]) + { + int32 new_location = 0; + + if (sep->IsNumber(1)) + { + int32 template_id = 0; + template_id = atoi(sep->arg[1]); + + new_location = database.CreateSpawnFromTemplateByID(client, template_id); + if( new_location > 0 ) + client->Message(CHANNEL_COLOR_YELLOW, "New spawn location %u created from template ID: %u", new_location, template_id); + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to spawn the new spawn location from template ID: %u", template_id); + } + else + { + const char* name = 0; + name = sep->argplus[1]; + + new_location = database.CreateSpawnFromTemplateByName(client, name); + if( new_location > 0 ) + client->Message(CHANNEL_COLOR_YELLOW, "New spawn location %u created from template: '%s'", new_location, name); + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Failed to spawn the new spawn location from template: '%s'", name); + } + } + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Spawning a new spawn location requires a valid template name or ID!"); + } + + else + client->Message(CHANNEL_COLOR_RED, "ERROR: Unknown /spawn template command."); +} + +void Commands::Command_Speed(Client* client, Seperator* sep) { + if(sep && sep->arg[0][0] && sep->IsNumber(0)){ + float new_speed = atof(sep->arg[0]); + if (new_speed > 0.0f) + { + client->GetPlayer()->SetSpeed(new_speed, true); + client->GetPlayer()->SetCharSheetChanged(true); + database.insertCharacterProperty(client, CHAR_PROPERTY_SPEED, sep->arg[0]); + client->Message(CHANNEL_STATUS, "Setting speed to %.2f.", new_speed); + } + else + client->Message(CHANNEL_STATUS, "Invalid speed provided %s.", sep->arg[0]); + } + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /speed {new speed value}"); + } + +} + +/* + Function: Command_StationMarketPlace() + Purpose : just trying to eat the console spam for now + Params : + Dev : John +*/ +void Commands::Command_StationMarketPlace(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_SMP"); + // This will reduce the spam from once every 10 sec to once every 30 sec, + // can't seem to reduce it any more get rid of it completely... + if (sep && strcmp(sep->arg[0], "gkw") == 0) { + PacketStruct* packet = configReader.getStruct("WS_MarketFundsUpdate", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("character_id", client->GetCharacterID()); + packet->setDataByName("unknown1", 1, 5); + packet->setDataByName("unknown1", 26, 6); + packet->setDataByName("unknown2", 0xFFFFFFFF); + packet->setDataByName("unknown3", 248, 1); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } +} + +/* + Function: Command_StopDrinking() + Purpose : Stops player from auto-consuming drink + Params : + Dev : Zcoretri + Example : /stopdrinking +*/ +void Commands::Command_StopDrinking(Client* client) +{ + client->GetPlayer()->reset_character_flag(CF_DRINK_AUTO_CONSUME); + client->GetPlayer()->SetActiveDrinkUniqueID(0); + client->Message(CHANNEL_COLOR_YELLOW,"You stop drinking your current drink."); +} + +/* + Function: Command_StopEating() + Purpose : Stops player from auto-consuming food + Params : + Dev : Zcoretri + Example : /stopeating +*/ +void Commands::Command_StopEating(Client* client) +{ + client->GetPlayer()->reset_character_flag(CF_FOOD_AUTO_CONSUME); + client->GetPlayer()->SetActiveFoodUniqueID(0); + client->Message(CHANNEL_COLOR_YELLOW,"You stop eating your current food."); +} + +/* + Function: Command_Title() + Purpose : Help for /title command + Params : n/a + Dev : Zcoretri + Example : /title +*/ +void Commands::Command_Title(Client* client) +{ + client->Message(CHANNEL_COLOR_YELLOW, "Available subcommands: list, setprefix , setsuffix , fix"); +} + +/* + Function: Command_TitleList() + Purpose : List available titles for player + Params : n/a + Dev : Zcoretri + Example : /title list +*/ +void Commands::Command_TitleList(Client* client) +{ + // must call release read lock before leaving function on GetPlayerTitles + vector* titles = client->GetPlayer()->GetPlayerTitles()->GetAllTitles(); + vector::iterator itr; + Title* title; + sint32 i = 0; + + client->Message(CHANNEL_NARRATIVE, "Listing available titles:"); + for(itr = titles->begin(); itr != titles->end(); itr++) + { + title = *itr; + client->Message(CHANNEL_NARRATIVE, "%i: type=[%s] title=[%s]", i, title->GetPrefix() ? "Prefix":"Suffix", title->GetName()); + i++; + } + + client->GetPlayer()->GetPlayerTitles()->ReleaseReadLock(); +} + +/* + Function: Command_TitleSetPrefix() + Purpose : Set Prefix title for player + Params : Title ID + Dev : Zcoretri + Example : /title setprefix 1 +*/ +void Commands::Command_TitleSetPrefix(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + sint32 index = atoul(sep->arg[0]); + + if(index > -1) + { + Title* title = client->GetPlayer()->GetPlayerTitles()->GetTitle(index); + if(!title) + { + client->Message(CHANNEL_COLOR_RED, "Missing index %i to set title", index); + return; + } + else if(!title->GetPrefix()) + { + client->Message(CHANNEL_COLOR_RED, "%s is not a prefix.", title->GetName()); + return; + } + } + else // make sure client doesn't pass some bogus negative index + index = -1; + + database.SaveCharPrefixIndex(index, client->GetCharacterID()); + client->SendTitleUpdate(); + } +} + +/* + Function: Command_TitleSetSuffix() + Purpose : Set Suffix title for player + Params : Title ID + Dev : Zcoretri + Example : /title setsuffix 1 +*/ +void Commands::Command_TitleSetSuffix(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + sint32 index = atoul(sep->arg[0]); + if(index > -1) + { + Title* title = client->GetPlayer()->GetPlayerTitles()->GetTitle(index); + if(!title) + { + client->Message(CHANNEL_COLOR_RED, "Missing index %i to set title", index); + return; + } + else if(title->GetPrefix()) + { + client->Message(CHANNEL_COLOR_RED, "%s is not a suffix.", title->GetName()); + return; + } + } + else // make sure client doesn't pass some bogus negative index + index = -1; + + database.SaveCharSuffixIndex(index, client->GetCharacterID()); + client->SendTitleUpdate(); + } +} + +/* + Function: Command_TitleFix() + Purpose : Fix title for player (???) + Params : + Dev : Zcoretri + Example : +*/ +void Commands::Command_TitleFix(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_TITLE_FIX"); + LogWrite(MISC__TODO, 1, "Titles", "TODO-Command: TITLE_FIX"); +} + + +/* + Function: Command_Toggle_Anonymous() + Purpose : Toggles player Anonymous + Params : + Dev : paulgh + Example : /anon +*/ +void Commands::Command_Toggle_Anonymous(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ANONYMOUS); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s anonymous.", player->get_character_flag(CF_ANONYMOUS)?"now":"no longer"); + + if (player->get_character_flag(CF_ANONYMOUS)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_ANONYMOUS); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_ANONYMOUS); +} + +/* + Function: Command_Toggle_AutoConsume() + Purpose : Toggles player food/drink auto consume + Params : unknown + Dev : paulgh + Example : /set_auto_consume +*/ +void Commands::Command_Toggle_AutoConsume(Client* client, Seperator* sep) +{ + Player* player = client->GetPlayer(); + + if (sep && sep->arg[0]) + PrintSep(sep, "COMMAND_SET_AUTO_CONSUME"); + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int8 slot = atoi(sep->arg[0]); + int8 flag = atoi(sep->arg[1]); + if (client->GetVersion() <= 373) { + slot += 4; + } + else if (client->GetVersion() <= 561) { + slot += 2; + } + if (slot == EQ2_FOOD_SLOT) + { + player->toggle_character_flag(CF_FOOD_AUTO_CONSUME); + player->SetCharSheetChanged(true); + client->QueuePacket(player->GetEquipmentList()->serialize(client->GetVersion(), player)); + if (flag == 1) + client->Message(CHANNEL_NARRATIVE, "You decide to eat immediately whenever you become hungry."); + else + { + client->Message(CHANNEL_NARRATIVE, "You decide to ignore the hunger."); + return; + } + } + else + { + player->toggle_character_flag(CF_DRINK_AUTO_CONSUME); + player->SetCharSheetChanged(true); + client->QueuePacket(player->GetEquipmentList()->serialize(client->GetVersion(), player)); + if (flag == 1) + client->Message(CHANNEL_NARRATIVE, "You decide to drink immediately whenever you become thirsty."); + else + { + client->Message(CHANNEL_NARRATIVE, "You decide to ignore the thirst."); + return; + } + } + + if(!client->CheckConsumptionAllowed(slot, false)) + return; + + Item* item = player->GetEquipmentList()->GetItem(slot); + if(item) + client->ConsumeFoodDrink(item, slot); + } +} + +/* + Function: Command_Toggle_BonusXP() + Purpose : Toggles player Bonus XP + Params : + Dev : John Adams + Example : /disable_char_bonus_exp +*/ +void Commands::Command_Toggle_BonusXP(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF2_CHARACTER_BONUS_EXPERIENCE_ENABLED); + player->SetCharSheetChanged(true); +} + +/* + Function: Command_Toggle_CombatXP() + Purpose : Toggles player Adventure XP + Params : + Dev : John Adams + Example : /disable_combat_exp +*/ +void Commands::Command_Toggle_CombatXP(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_COMBAT_EXPERIENCE_ENABLED); + player->SetCharSheetChanged(true); +} + +/* + Function: Command_Toggle_GMHide() + Purpose : Toggles hiding player GM status + Params : + Dev : Scatman + Example : /gm_hide +*/ +void Commands::Command_Toggle_GMHide(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_HIDE_STATUS); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s hiding your GM status.", player->get_character_flag(CF_HIDE_STATUS)?"now":"no longer"); +} + +/* + Function: Command_Toggle_GMVanish() + Purpose : Toggles hiding GM players from /who searches + Params : + Dev : Scatman + Example : /gm_vanish +*/ +void Commands::Command_Toggle_GMVanish(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_GM_HIDDEN); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s invisible to who queries.", player->get_character_flag(CF_GM_HIDDEN)?"now":"no longer"); +} + +/* + Function: Command_Toggle_Illusions() + Purpose : Toggles player illusion form + Params : not sure sep is needed, testing + Dev : paulgh + Example : /hide_illusions +*/ +void Commands::Command_Toggle_Illusions(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0]) + PrintSep(sep, "COMMAND_TOGGLE_ILLUSIONS"); + client->GetPlayer()->toggle_character_flag(CF_SHOW_ILLUSION); +} + +/* + Function: Command_Toggle_LFG() + Purpose : Toggles player LFG Flag + Params : + Dev : paulgh + Example : /lfg +*/ +void Commands::Command_Toggle_LFG(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_LFG); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s LFG.", player->get_character_flag(CF_LFG)?"now":"no longer"); + + if (player->get_character_flag(CF_LFG)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_LFG); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_LFG); +} + +/* + Function: Command_Toggle_LFW() + Purpose : Toggles player LFW Flag + Params : + Dev : paulgh + Example : /lfw +*/ +void Commands::Command_Toggle_LFW(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_LFW); + client->Message(CHANNEL_COLOR_YELLOW,"You %s looking for work.", player->get_character_flag(CF_LFW)?"let others know you are":"stop"); + + if (player->get_character_flag(CF_LFW)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_LFW); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_LFW); +} + +/* + Function: Command_Toggle_QuestXP() + Purpose : Toggles player Quest XP + Params : + Dev : John Adams + Example : /disable_quest_exp +*/ +void Commands::Command_Toggle_QuestXP(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_QUEST_EXPERIENCE_ENABLED); + player->SetCharSheetChanged(true); +} + +/* + Function: Command_Toggle_Roleplaying() + Purpose : Toggles player Roleplaying flag + Params : + Dev : paulgh + Example : /role +*/ +void Commands::Command_Toggle_Roleplaying(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ROLEPLAYING); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s roleplaying.", player->get_character_flag(CF_ROLEPLAYING)?"now":"no longer"); + + if (player->get_character_flag(CF_ROLEPLAYING)) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_ROLEPLAYING); + else + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_ROLEPLAYING); +} + +void Commands::Command_Toggle_Duels(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_DUEL_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting duel invites.", player->get_character_flag(CF_ALLOW_DUEL_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_Trades(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_TRADE_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting trade invites.", player->get_character_flag(CF_ALLOW_TRADE_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_Guilds(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_GUILD_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting guild invites.", player->get_character_flag(CF_ALLOW_GUILD_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_Groups(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_GROUP_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting group invites.", player->get_character_flag(CF_ALLOW_GROUP_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_Raids(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF_ALLOW_RAID_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting raid invites.", player->get_character_flag(CF_ALLOW_RAID_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_LON(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF2_ALLOW_LON_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting LoN invites.", player->get_character_flag(CF2_ALLOW_LON_INVITES)?"now":"no longer"); +} + +void Commands::Command_Toggle_VoiceChat(Client* client) +{ + Player* player = client->GetPlayer(); + + player->toggle_character_flag(CF2_ALLOW_VOICE_INVITES); + client->Message(CHANNEL_COLOR_YELLOW,"You are %s accepting voice chat invites.", player->get_character_flag(CF2_ALLOW_VOICE_INVITES)?"now":"no longer"); +} + +/* + Function: Command_TradeStart() + Purpose : Starts item/coin trade between players + Params : + Dev : + Example : +*/ +#include "../Trade.h" +void Commands::Command_TradeStart(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_START_TRADE"); + + Entity* trader = client->GetPlayer(); + Entity* trader2 = 0; + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + Spawn* spawn = client->GetPlayer()->GetSpawnWithPlayerID(atoi(sep->arg[0])); + if (spawn) { + if (spawn->IsEntity()) + trader2 = (Entity*)spawn; + } + } + else if (client->GetPlayer()->GetTarget()) { + if (client->GetPlayer()->GetTarget()->IsEntity()) + trader2 = (Entity*)client->GetPlayer()->GetTarget(); + } + + // can only trade with player or bots + if (trader && trader2) { + if (trader == trader2) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can't trade with yourself."); + } + else if (trader2->IsPlayer() || trader2->IsBot()) { + LogWrite(PLAYER__ERROR, 0, "Trade", "creating trade"); + if (trader->trade) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already trading."); + else if (trader2->trade) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Your target is already trading."); + else { + Trade* trade = new Trade(trader, trader2); + trader->trade = trade; + trader2->trade = trade; + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only trade with another player or a bot."); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find target"); +} + +/* + Function: Command_Track() + Purpose : Starts/Stops Tracking for a player + Params : + Dev : Scatman + Example : /track +*/ +void Commands::Command_Track(Client* client) +{ + if (!client->GetPlayer()->GetIsTracking()) + client->GetPlayer()->GetZone()->AddPlayerTracking(client->GetPlayer()); + else + client->GetPlayer()->GetZone()->RemovePlayerTracking(client->GetPlayer(), TRACKING_STOP); +} + +/* + Function: Command_TradeAccept() + Purpose : Accepts item/coin trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeAccept(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_ACCEPT_TRADE"); + Trade* trade = client->GetPlayer()->trade; + if (trade) { + bool trade_complete = trade->SetTradeAccepted(client->GetPlayer()); + if (trade_complete) + safe_delete(trade); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +/* + Function: Command_TradeReject() + Purpose : Rejects item/coin trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeReject(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_REJECT_TRADE"); + Command_TradeCancel(client, sep); +} + +/* + Function: Command_TradeCancel() + Purpose : Cancels item/coin trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeCancel(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_CANCEL_TRADE"); + Trade* trade = client->GetPlayer()->trade; + if (trade) { + trade->CancelTrade(client->GetPlayer()); + safe_delete(trade); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +/* + Function: Command_TradeSetCoin() + Purpose : Sets coin trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeSetCoin(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_SET_TRADE_COIN"); + LogWrite(MISC__TODO, 1, "Command", "TODO-Command: Set Trade Coin"); + client->Message(CHANNEL_COLOR_YELLOW, "You cannot trade with other players (Not Implemented)"); +} + +/* + Function: Command_TradeAddCoin() + Purpose : Adds coin to trade between players + Params : Passes "handler" through so we can process copper, silver, gold and plat in the same function + Dev : + Example : +*/ +void Commands::Command_TradeAddCoin(Client* client, Seperator* sep, int handler) +{ + PrintSep(sep, "COMMAND_ADD_TRADE_{coin type}"); + Trade* trade = client->GetPlayer()->trade; + if (trade) { + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + int32 amount = atoi(sep->arg[0]); + int64 val = 0; + switch (handler) { + case COMMAND_ADD_TRADE_COPPER: + { + val = amount; + trade->AddCoinToTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_ADD_TRADE_SILVER: + { + val = amount * 100; + trade->AddCoinToTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_ADD_TRADE_GOLD: + { + val = amount * 10000; + trade->AddCoinToTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_ADD_TRADE_PLAT: + { + val = amount * 1000000; + trade->AddCoinToTrade(client->GetPlayer(), val); + break; + } + + default: + { + LogWrite(COMMAND__ERROR, 0, "Command", "No coin type specified in func: '%s', line %u", __FUNCTION__, __LINE__); + break; + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid coin amount."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +/* + Function: Command_TradeRemoveCoin() + Purpose : Removes coin from trade between players + Params : Passes "handler" through so we can process copper, silver, gold and plat in the same function + Dev : + Example : +*/ +void Commands::Command_TradeRemoveCoin(Client* client, Seperator* sep, int handler) +{ + PrintSep(sep, "COMMAND_REMOVE_TRADE_{coin type}"); + + Trade* trade = client->GetPlayer()->trade; + if (trade) { + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + int32 amount = atoi(sep->arg[0]); + int64 val = 0; + switch (handler) { + case COMMAND_REMOVE_TRADE_COPPER: + { + val = amount; + trade->RemoveCoinFromTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_REMOVE_TRADE_SILVER: + { + val = amount * 100; + trade->RemoveCoinFromTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_REMOVE_TRADE_GOLD: + { + val = amount * 10000; + trade->RemoveCoinFromTrade(client->GetPlayer(), val); + break; + } + + case COMMAND_REMOVE_TRADE_PLAT: + { + val = amount * 1000000; + trade->RemoveCoinFromTrade(client->GetPlayer(), val); + break; + } + + default: + { + LogWrite(COMMAND__ERROR, 0, "Command", "No coin type specified in func: '%s', line %u", __FUNCTION__, __LINE__); + break; + } + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid coin amount"); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +/* + Function: Command_TradeAddItem() + Purpose : Adds item to trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeAddItem(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_ADD_TRADE_ITEM"); + /* + arg[0] = item index + arg[1] = slot + arg[2] = quantity + */ + if (!client->GetPlayer()->trade) { + LogWrite(PLAYER__ERROR, 0, "Trade", "Player is not currently trading."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); + return; + } + + if (sep && sep->IsSet(0) && sep->IsNumber(0) && sep->IsSet(1) && sep->IsNumber(1) && sep->IsSet(2) && sep->IsNumber(2)) { + Item* item = 0; + int32 index = atoi(sep->arg[0]); + item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(index); + if (item) { + int8 result = client->GetPlayer()->trade->AddItemToTrade(client->GetPlayer(), item, atoi(sep->arg[2]), atoi(sep->arg[1])); + if (result == 1) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Item is already being traded."); + else if (result == 2) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can't trade NO-TRADE items."); + else if (result == 3) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can't trade HEIRLOOM items."); + else if (result == 254) + client->Message(CHANNEL_COLOR_YELLOW, "You are trading with an older client with a %u trade slot restriction...", client->GetPlayer()->trade->MaxSlots()); + else if (result == 255) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unknown error trying to add the item to the trade..."); + } + else { + LogWrite(PLAYER__ERROR, 0, "Trade", "Unable to get an item for the player (%s) from the index (%u)", client->GetPlayer()->GetName(), index); + client->Message(CHANNEL_ERROR, "Unable to find item at index %u", index); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid item."); +} + +/* + Function: Command_TradeRemoveItem() + Purpose : Removes item from trade between players + Params : + Dev : + Example : +*/ +void Commands::Command_TradeRemoveItem(Client* client, Seperator* sep) +{ + PrintSep(sep, "COMMAND_REMOVE_TRADE_ITEM"); + /* + arg[0] = trade window slot + */ + + Trade* trade = client->GetPlayer()->trade; + if (trade) { + if (sep && sep->IsSet(0) && sep->IsNumber(0)) { + trade->RemoveItemFromTrade(client->GetPlayer(), atoi(sep->arg[0])); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid item."); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not currently trading."); +} + +void Commands::Command_TryOn(Client* client, Seperator* sep) +{ + Item *item = 0; + sint32 crc; + + if (sep) { + // 1096+ sends more info so need to do a version check so older clients (SF) still work + if (client->GetVersion() < 1096) { + if (sep->arg[1] && sep->IsNumber(1) && sep->arg[2] && sep->IsNumber(2)) + { + item = master_item_list.GetItem(atoul(sep->arg[1])); + crc = atol(sep->arg[2]); + } + } + else { + // From the broker and links in chat + if (strcmp(sep->arg[0], "crc") == 0) { + if (sep->IsNumber(2) && sep->IsNumber(3)) { + item = master_item_list.GetItem(atoul(sep->arg[2])); + crc = atol(sep->arg[3]); + } + } + // From inventory + if (strcmp(sep->arg[0], "dbid") == 0) { + if (sep->IsNumber(2) && sep->IsNumber(4)) { + item = master_item_list.GetItem(atoul(sep->arg[2])); + crc = atol(sep->arg[4]); + } + } + } + if (item) + client->ShowDressingRoom(item, crc); + } +} + +void Commands::Command_JoinChannel(Client * client, Seperator *sep) { + const char *channel_name, *password = NULL; + + if (sep == NULL || !sep->IsSet(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /joinchannel [password]"); + return; + } + + channel_name = sep->arg[0]; + if (sep->IsSet(1)) + password = sep->arg[1]; + + if (!chat.ChannelExists(channel_name) && !chat.CreateChannel(channel_name, password)) { + client->Message(CHANNEL_COLOR_RED, "Unable to create channel '%s'.", channel_name); + return; + } + + if (chat.IsInChannel(client, channel_name)) { + client->Message(CHANNEL_NARRATIVE, "You are already in '%s'.", channel_name); + return; + } + + if (chat.HasPassword(channel_name)) { + if (password == NULL) { + client->Message(CHANNEL_NARRATIVE, "Unable to join '%s': That channel is password protected.", channel_name); + return; + } + if (!chat.PasswordMatches(channel_name, password)) { + client->Message(CHANNEL_NARRATIVE, "Unable to join '%s': The password is not correc.t", channel_name); + return; + } + } + + if (!chat.JoinChannel(client, channel_name)) + client->Message(CHANNEL_COLOR_RED, "There was an internal error preventing you from joining '%s'.", channel_name); +} + +void Commands::Command_JoinChannelFromLoad(Client * client, Seperator *sep) { + printf("ScatDebug: Received 'joinfromchannel', using the same function as 'joinchannel' (not sure what the difference is)\n"); + Command_JoinChannel(client, sep); +} + +void Commands::Command_TellChannel(Client *client, Seperator *sep) { + if (sep == NULL || !sep->IsSet(0) || !sep->IsSet(1)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /tellchannel "); + PrintSep(sep, "tellchannel"); + return; + } + + chat.TellChannel(client, sep->arg[0], sep->argplus[1]); +} + +void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) { + Seperator* sep = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + if (sep->IsSet(0)) { + if (atoi(sep->arg[0]) == 1) { + PacketStruct* packet2 = configReader.getStruct("WS_SpellGainedMsg", client->GetVersion()); + if (packet2) { + packet2->setDataByName("spell_type", 2); + packet2->setDataByName("spell_id", 8308); + packet2->setDataByName("spell_name", "Sprint"); + packet2->setDataByName("add_silently", 0); + packet2->setDataByName("tier", 1); + packet2->setDataByName("blah1", 1); + packet2->setDataByName("blah2", 1); + EQ2Packet* outapp = packet2->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 2) { + PacketStruct* packet2 = configReader.getStruct("WS_GuildUpdate", client->GetVersion()); + if (packet2) { + packet2->setDataByName("guild_name", "Test"); + packet2->setDataByName("guild_motd", "Test MOTD"); + packet2->setDataByName("guild_id", 1234); + packet2->setDataByName("guild_level", 1); + packet2->setDataByName("unknown", 2); + packet2->setDataByName("unknown2", 3); + packet2->setDataByName("exp_current", 1); + packet2->setDataByName("exp_to_next_level", 4); + EQ2Packet* outapp = packet2->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 3) { + PacketStruct* packet2 = configReader.getStruct("WS_JoinGuildNotify", client->GetVersion()); + if (packet2) { + packet2->setDataByName("guild_id", 1234); + packet2->setDataByName("character_id", 1); + packet2->setDataByName("account_id", 2); + packet2->setDataByName("guild_level", 1); + packet2->setDataByName("name", "Test"); + packet2->setDataByName("unknown2", 0); + packet2->setDataByName("unknown3", 1); + packet2->setDataByName("adventure_class", 6); + packet2->setDataByName("adventure_level", 7); + packet2->setDataByName("tradeskill_class", 4); + packet2->setDataByName("tradeskill_level", 5); + packet2->setDataByName("rank", 0); + packet2->setDataByName("member_flags", 2); + packet2->setDataByName("join_date", 1591112273); + packet2->setDataByName("guild_status", 2); + packet2->setDataByName("last_login", 1591132273); + packet2->setDataByName("recruiter_id", 1); + packet2->setDataByName("points", 2345); + packet2->setDataByName("note", "note"); + packet2->setMediumStringByName("officer_note", "O note"); + packet2->setMediumStringByName("zone", "Blah"); + + EQ2Packet* outapp = packet2->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 5) { + int16 offset = atoi(sep->arg[1]); + int32 value1 = atol(sep->arg[2]); + EQ2Packet* outapp = client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion(), offset, value1); + client->QueuePacket(outapp); + } + else if (atoi(sep->arg[0]) == 6) { + Spawn* spawn = client->GetPlayer()->GetTarget(); + if (!spawn) + spawn = client->GetPlayer(); + else if (spawn->IsEntity()) + ((Entity*)spawn)->SetSpeed(atof(sep->arg[4])); + spawn->RunToLocation(atof(sep->arg[1]), atof(sep->arg[2]), atof(sep->arg[3])); + } + else if (atoi(sep->arg[0]) == 7) { + int32 id = 0; + Spawn* spawn = client->GetCurrentZone()->GetSpawn(7720001); + if (spawn) { + spawn->SetX(client->GetPlayer()->GetX() + .5, false); + spawn->SetY(client->GetPlayer()->GetY(), false); + spawn->SetZ(client->GetPlayer()->GetZ() + .5, false); + float heading = client->GetPlayer()->GetHeading() + 180; + if (heading > 360) + heading -= 360; + spawn->SetLevel(5); + spawn->SetAdventureClass(4); + spawn->SetHeading(heading, false); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->appearance.targetable = 1; + if (spawn->IsNPC() && spawn->GetTotalHP() == 0) { + spawn->SetTotalHP(spawn->GetLevel() * 15); + spawn->SetHP(spawn->GetTotalHP()); + } + if (spawn->GetTotalPower() == 0) { + spawn->SetTotalPower(spawn->GetLevel() * 15); + spawn->SetPower(spawn->GetTotalPower()); + } + int16 offset = atoi(sep->arg[1]); + int32 value1 = atol(sep->arg[2]); + sprintf(spawn->appearance.name, "Offset %i", offset); + EQ2Packet* ret = spawn->spawn_serialize(client->GetPlayer(), client->GetVersion(), offset, value1); + DumpPacket(ret); + client->QueuePacket(ret); + } + } + else if (atoi(sep->arg[0]) == 8) { + int32 id = atoi(sep->arg[1]); + Spawn* spawn = client->GetCurrentZone()->GetSpawn(id); + if (spawn) { + spawn->SetX(client->GetPlayer()->GetX() + .5, false); + spawn->SetY(client->GetPlayer()->GetY(), false); + spawn->SetZ(client->GetPlayer()->GetZ() + .5, false); + float heading = client->GetPlayer()->GetHeading() + 180; + if (heading > 360) + heading -= 360; + spawn->SetLevel(5); + spawn->SetAdventureClass(4); + spawn->SetHeading(heading, false); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + spawn->SetLocation(client->GetPlayer()->GetLocation()); + spawn->appearance.targetable = 1; + if (spawn->IsNPC() && spawn->GetTotalHP() == 0) { + spawn->SetTotalHP(spawn->GetLevel() * 15); + spawn->SetHP(spawn->GetTotalHP()); + } + if (spawn->GetTotalPower() == 0) { + spawn->SetTotalPower(spawn->GetLevel() * 15); + spawn->SetPower(spawn->GetTotalPower()); + } + int16 offset = atoi(sep->arg[2]); + int16 offset2 = atoi(sep->arg[3]); + int32 value1 = atol(sep->arg[4]); + int16 offset3 = 0; + int16 offset4 = 0; + int32 value2 = 0; + if (sep->IsSet(7)) { + offset3 = atoi(sep->arg[5]); + offset4 = atoi(sep->arg[6]); + value2 = atol(sep->arg[7]); + } + sprintf(spawn->appearance.name, "Offset %i to %i", offset, offset2); + spawn->AddPrimaryEntityCommand("attack", 10000, "attack","", 0, 0); + EQ2Packet* ret = spawn->spawn_serialize(client->GetPlayer(), client->GetVersion(), offset, value1, offset2, offset3, offset4, value2); + DumpPacket(ret); + client->QueuePacket(ret); + } + } + else if (atoi(sep->arg[0]) == 9) { + PacketStruct* packet2 = configReader.getStruct("WS_DeathWindow", client->GetVersion()); + if (packet2) { + packet2->setArrayLengthByName("location_count", 1); + packet2->setArrayDataByName("location_ida", 1234); + packet2->setArrayDataByName("unknown2a", 3); + packet2->setArrayDataByName("zone_name", "Queen's Colony"); + packet2->setArrayDataByName("location_name", "Myrrin's Tower"); + packet2->setArrayDataByName("distance", 134); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int8 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int8 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 1); + ptr2 += 1; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 10) { + PacketStruct* packet2 = configReader.getStruct("WS_QuestJournalUpdate", client->GetVersion()); + if (packet2) { + packet2->setArrayLengthByName("num_quests", 1); + packet2->setArrayDataByName("active", 1); + packet2->setArrayDataByName("name", "Tasks aboard the Far Journey"); + packet2->setArrayDataByName("quest_type", "Hallmark"); + packet2->setArrayDataByName("quest_zone", "Hallmark"); + packet2->setArrayDataByName("journal_updated", 1); + packet2->setArrayDataByName("quest_id", 524); + packet2->setArrayDataByName("day", 19); + packet2->setArrayDataByName("month", 6); + packet2->setArrayDataByName("year", 20); + packet2->setArrayDataByName("level", 2); + packet2->setArrayDataByName("encounter_level", 4); + packet2->setArrayDataByName("difficulty", 3); + packet2->setArrayDataByName("visible", 1); + packet2->setDataByName("visible_quest_id", 524); + packet2->setDataByName("player_crc", 2900677088); + packet2->setDataByName("player_name", "LethalEncounter"); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int8 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int8 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 1); + ptr2 += 1; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 11) { + PacketStruct* packet2 = configReader.getStruct("WS_QuestJournalReply", client->GetVersion()); + if (packet2) { + packet2->setDataByName("quest_id", 524); + packet2->setDataByName("player_crc", 2900677088); + packet2->setDataByName("name", "Tasks aboard the Far Journey"); + packet2->setDataByName("description", "I completed all the tasks assigned to me by Captain Varlos aboard the Far Journey"); + packet2->setDataByName("type", "Hallmark"); + packet2->setDataByName("complete_header", "To complete this quest, I must do the following tasks:"); + packet2->setDataByName("day", 19); + packet2->setDataByName("month", 6); + packet2->setDataByName("year", 20); + packet2->setDataByName("level", 1); + packet2->setDataByName("encounter_level", 1); + packet2->setDataByName("difficulty", 1); + packet2->setDataByName("time_obtained", Timer::GetUnixTimeStamp()); + //packet2->setDataByName("timer_start", Timer::GetUnixTimeStamp()); + //packet2->setDataByName("timer_duration", 300); + //packet2->setDataByName("timer_running", 1); + packet2->setArrayLengthByName("task_groups_completed", 9); + packet2->setArrayLengthByName("num_task_groups", 10); + packet2->setArrayDataByName("task_group", "I spoke to Waulon as Captain Varlos had asked of me."); + packet2->setArrayDataByName("task_group", "I found Waulon's hat in one of the boxes.", 1); + packet2->setArrayDataByName("task_group", "I returned Waulon's hat.", 2); + packet2->setArrayDataByName("task_group", "I have spoken to Ingrid.", 3); + packet2->setArrayDataByName("task_group", "I purchased a Shard of Luclin.", 4); + packet2->setArrayDataByName("task_group", "I gave the Shard of Luclin to Ingrid.", 5); + packet2->setArrayDataByName("task_group", "I have spoken to Captain Varlos.", 6); + packet2->setArrayDataByName("task_group", "I killed the rats that Captain Varlos requested.", 7); + packet2->setArrayDataByName("task_group", "Captain Varlos has ordered you to kill the escaped goblin.", 8); + packet2->setArrayDataByName("task_group", "I killed the escaped goblin.", 9); + /*packet2->setSubArrayLengthByName("num_tasks", 1); + packet2->setSubArrayDataByName("task", "I need to talk to Garven Tralk"); + packet2->setSubArrayLengthByName("num_updates", 1); + packet2->setSubArrayDataByName("update_currentval", 0); + packet2->setSubArrayDataByName("update_maxval", 1); + packet2->setSubArrayDataByName("icon", 11); + */ + packet2->setArrayDataByName("waypoint", 0xFFFFFFFF); + packet2->setDataByName("journal_updated", 1); + packet2->setDataByName("bullets", 1); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int16 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 2); + ptr2 += 2; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 12) { + PacketStruct* packet2 = configReader.getStruct("WS_QuestJournalReply", client->GetVersion()); + if (packet2) { + packet2->setDataByName("quest_id", 5725); + packet2->setDataByName("player_crc", 2900677088); + packet2->setDataByName("name", "Archetype Selection"); + packet2->setDataByName("description", "I have reported my profession to Garven Tralk."); + packet2->setDataByName("type", "Hallmark"); + packet2->setDataByName("complete_header", "To complete this quest, I must do the following tasks:"); + packet2->setDataByName("day", 19); + packet2->setDataByName("month", 6); + packet2->setDataByName("year", 20); + packet2->setDataByName("level", 2); + packet2->setDataByName("encounter_level", 4); + packet2->setDataByName("difficulty", 3); + packet2->setDataByName("time_obtained", Timer::GetUnixTimeStamp()); + packet2->setDataByName("timer_start", Timer::GetUnixTimeStamp()); + packet2->setDataByName("timer_duration", 300); + packet2->setDataByName("timer_running", 1); + packet2->setArrayLengthByName("task_groups_completed", 0); + packet2->setArrayLengthByName("num_task_groups", 1); + packet2->setArrayDataByName("task_group", "I need to talk to Garven Tralk"); + packet2->setSubArrayLengthByName("num_tasks", 1); + packet2->setSubArrayDataByName("task", "I need to talk to Garven Tralk"); + packet2->setSubArrayLengthByName("num_updates", 1); + packet2->setSubArrayDataByName("update_currentval", 0); + packet2->setSubArrayDataByName("update_maxval", 1); + packet2->setSubArrayDataByName("icon", 11); + + packet2->setArrayDataByName("waypoint", 0xFFFFFFFF); + packet2->setDataByName("journal_updated", 1); + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!"); + packet2->setSubstructDataByName("reward_data", "unknown2", 0x3f); + packet2->setSubstructDataByName("reward_data", "coin", 150); + packet2->setSubstructDataByName("reward_data", "status_points", 5); + packet2->setSubstructDataByName("reward_data", "exp_bonus", 10); + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", 1); + packet2->setSubstructArrayDataByName("reward_data", "reward_id", 123); + Item* item = master_item_list.GetItem(152755); + packet2->setArrayDataByName("reward_id", item->details.item_id); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, client->GetClientItemPacketOffset()); + /*packet2->setSubstructDataByName("item", "unique_id", 567); + packet2->setSubstructDataByName("item", "broker_item_id", 0xFFFFFFFFFFFFFFFF); + packet2->setSubstructDataByName("item", "icon", 0xe7); + packet2->setSubstructDataByName("item", "tier", 4); + packet2->setSubstructDataByName("item", "flags", 0x60); + packet2->setSubstructArrayLengthByName("item", "stat_count", 1); + packet2->setSubstructDataByName("item", "stat_type", 1); + packet2->setSubstructDataByName("item", "stat_subtype", 2); + packet2->setSubstructDataByName("item", "value", 3); + packet2->setSubstructDataByName("item", "condition", 100); + packet2->setSubstructDataByName("item", "weight", 1); + packet2->setSubstructDataByName("item", "skill_req1", 0xacafa99e); + packet2->setSubstructDataByName("item", "skill_req2", 0xacafa99e); + packet2->setSubstructDataByName("item", "skill_min", 1); + packet2->setSubstructArrayLengthByName("item", "class_count", 3); + packet2->setSubstructDataByName("item", "adventure_class", 1); + packet2->setSubstructDataByName("item", "adventure_class", 11, 0, 1); + packet2->setSubstructDataByName("item", "adventure_class", 0x1f, 0, 2); + packet2->setSubstructDataByName("item", "tradeskill_class", 255); + packet2->setSubstructDataByName("item", "tradeskill_class", 255, 0, 1); + packet2->setSubstructDataByName("item", "tradeskill_class", 255, 0, 2); + packet2->setSubstructDataByName("item", "level", 0x1e); + packet2->setSubstructDataByName("item", "level", 100, 0, 1); + packet2->setSubstructDataByName("item", "level", 100, 0, 2); + packet2->setSubstructDataByName("item_footer", "name", "Footman Gloves");*/ + //packet2->PrintPacket(); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int16 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 2); + ptr2 += 2; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 13) { + PacketStruct* packet2 = configReader.getStruct("WS_OnScreenMsg", client->GetVersion()); + if (packet2 && sep->IsSet(7)) { + packet2->setDataByName("unknown", atoi(sep->arg[1])); + char blah[128]; + sprintf(blah, "\\#6EFF6EYou get better at \12\\#C8FFC8%s\\#6EFF6E! (7/15)", sep->arg[2]); + packet2->setDataByName("text", blah); + packet2->setDataByName("message_type", sep->arg[3]); + packet2->setDataByName("size", atof(sep->arg[4])); + packet2->setDataByName("red", atoi(sep->arg[5])); + packet2->setDataByName("green", atoi(sep->arg[6])); + packet2->setDataByName("blue", atoi(sep->arg[7])); + EQ2Packet* app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 14) { + PacketStruct* packet2 = configReader.getStruct("WS_InstructionWindow", client->GetVersion()); + if (packet2 && sep->IsSet(3)) { + packet2->setDataByName("open_seconds_min", atof(sep->arg[1])); + packet2->setDataByName("open_seconds_max", atof(sep->arg[2])); + packet2->setDataByName("voice_sync", atoi(sep->arg[3])); + packet2->setDataByName("text", "Welcome to Norrath, the world of EverQuest II. Left click on the help button at any time for more detailed help and information."); + packet2->setDataByName("voice", "voiceover/english/narrator/boat_06p_tutorial02/narrator_001_63779ca0.mp3"); + packet2->setArrayLengthByName("num_goals", 1); + //packet2->setArrayDataByName("goal_text", ) + packet2->setSubArrayLengthByName("num_tasks", 1); + packet2->setSubArrayDataByName("task_text", "continue"); + packet2->setDataByName("complete_sound", "click"); + packet2->setDataByName("signal", "introduction"); + packet2->setDataByName("voice_key1", 0xcda65173); + packet2->setDataByName("voice_key2", 0x984bfc6d); + EQ2Packet* app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + packet2 = configReader.getStruct("WS_ShowWindow", client->GetVersion()); + packet2->setDataByName("window", "MainHUD.StartMenu"); + packet2->setDataByName("show", 1); + app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + + packet2 = configReader.getStruct("WS_FlashWindow", client->GetVersion()); + packet2->setDataByName("window", "MainHUD.StartMenu.help"); + packet2->setDataByName("flash_seconds", 10); + app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 15) { + PacketStruct* packet2 = configReader.getStruct("WS_UpdateLoot", client->GetVersion()); + if (packet2) { + packet2->setArrayLengthByName("loot_count", 1); + packet2->setArrayDataByName("name", "Test"); + packet2->setArrayDataByName("item_id", 1234); + packet2->setArrayDataByName("count", 1); + packet2->setArrayDataByName("icon", 258); + packet2->setArrayDataByName("ability_id", 0xFFFFFFFF); + Spawn* spawn = client->GetPlayer()->GetTarget(); + if (spawn) + packet2->setDataByName("object_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet2->setDataByName("display", 1); + packet2->setDataByName("loot_type", 1); + packet2->setDataByName("lotto_timeout", 60); + EQ2Packet* app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 16 && sep->IsNumber(1)) { + char blah[32]; + sprintf(blah, "Testing: %i", atoi(sep->arg[1])); + client->SimpleMessage(atoi(sep->arg[1]), blah); + } + else if (atoi(sep->arg[0]) == 17 && sep->IsNumber(2)) { + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + client->QueuePacket(client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion(), offset, value1)); + cout << "Sent" << endl; + } + } + } + else if (atoi(sep->arg[0]) == 18) { + PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion()); + if (packet2) { + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!"); + packet2->setSubstructDataByName("reward_data", "max_coin", 0); + packet2->setSubstructDataByName("reward_data", "text", "Some custom text to mess things up?"); + packet2->setSubstructDataByName("reward_data", "status_points", 5); + //packet2->setSubstructDataByName("reward_data", "exp_bonus", 10); + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", 1); + Item* item = new Item(master_item_list.GetItem(9357)); + packet2->setArrayDataByName("reward_id", item->details.item_id); + //item->stack_count = 20; + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, client->GetClientItemPacketOffset()); + safe_delete(item); + /*item = new Item(master_item_list.GetItem(36685)); + item->stack_count = 20; + packet2->setArrayDataByName("reward_id", item->details.item_id); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 1, 0, -1); + safe_delete(item); + item = master_item_list.GetItem(1414); + packet2->setArrayDataByName("reward_id", item->details.item_id); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 2, 0, -1); + item = master_item_list.GetItem(75057); + packet2->setArrayDataByName("reward_id", item->details.item_id); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 3, 0, -1);*/ + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int16 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 2); + ptr2 += 2; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 19) { + PacketStruct* packet2 = configReader.getStruct("WS_UpdateLoot", client->GetVersion()); + Spawn* spawn = client->GetPlayer()->GetTarget(); + if (packet2 && spawn) { + Item* item = master_item_list.GetItem(130053); + packet2->setArrayLengthByName("loot_count", 1); + packet2->setArrayDataByName("loot_id", item->details.item_id); + packet2->setArrayDataByName("unknown2", 1); + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, 2, true); + packet2->setDataByName("display", 1); + packet2->setDataByName("unknown2b", 1); + packet2->setDataByName("unknown3", 0x3c); + packet2->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet2->PrintPacket(); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + int16 offset2 = 0; + int32 value1 = 0; + if (sep->IsSet(3)) { + offset2 = atoi(sep->arg[2]); + value1 = atol(sep->arg[3]); + } + else + value1 = atol(sep->arg[2]); + int16 offset3 = 0; + int16 offset4 = 0; + int32 value2 = 0; + if (sep->IsSet(6)) { + offset3 = atoi(sep->arg[4]); + offset4 = atoi(sep->arg[5]); + value2 = atol(sep->arg[6]); + } + offset--; + if (offset2 > 0 && offset2 >= offset) { + offset2--; + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + for (int i = offset; i <= offset2; i++) { + if (value1 > 0xFFFF) { + memcpy(ptr2, (uchar*)&value1, 4); + i += 3; + ptr2 += 3; + } + else if (value1 > 0xFF) { + memcpy(ptr2, (uchar*)&value1, 2); + i++; + ptr2++; + } + else + memcpy(ptr2, (uchar*)&value1, 1); + ptr2++; + } + } + if (offset4 > 0 && offset4 >= offset3) { + offset3--; + offset4--; + uchar* ptr2 = app->pBuffer; + ptr2 += offset3; + for (int i = offset3; i <= offset4; i++) { + if (value2 > 0xFFFF) { + memcpy(ptr2, (uchar*)&value2, 4); + i += 3; + ptr2 += 3; + } + else if (value2 > 0xFF) { + memcpy(ptr2, (uchar*)&value2, 2); + i++; + ptr2++; + } + else + memcpy(ptr2, (uchar*)&value2, 1); + ptr2++; + } + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 21) { + PacketStruct* packet2 = configReader.getStruct("WS_OfferQuest", client->GetVersion()); + if (packet2) { + packet2->setDataByName("unknown0", 255); + packet2->setDataByName("reward", "New Quest"); + packet2->setDataByName("title", "Title"); + packet2->setDataByName("description", "description"); + packet2->setDataByName("quest_difficulty", 3); + packet2->setDataByName("unknown1", 5); + packet2->setDataByName("level", 3); + packet2->setDataByName("coin", 150); + packet2->setDataByName("status_points", 5); + packet2->setDataByName("exp_bonus", 10); + packet2->setArrayLengthByName("num_rewards", 1); + Item* item = new Item(master_item_list.GetItem(36212)); + packet2->setArrayDataByName("reward_id", item->details.item_id); + item->stack_count = 20; + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, client->GetClientItemPacketOffset()); + safe_delete(item); + char accept[35] = { 0 }; + char decline[35] = { 0 }; + sprintf(accept, "q_accept_pending_quest %u", 0); + sprintf(decline, "q_deny_pending_quest %u", 0); + packet2->setDataByName("accept_command", accept); + packet2->setDataByName("decline_command", decline); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + } + else if (atoi(sep->arg[0]) == 22) { //same as 21, but 8bit string + PacketStruct* packet2 = configReader.getStruct("WS_OfferQuest", client->GetVersion()); + if (packet2) { + packet2->setDataByName("unknown0", 255); + packet2->setDataByName("reward", "New Quest"); + packet2->setDataByName("title", "Title"); + packet2->setDataByName("description", "description"); + packet2->setDataByName("quest_difficulty", 3); + packet2->setDataByName("unknown1", 5); + packet2->setDataByName("level", 3); + packet2->setDataByName("coin", 150); + packet2->setDataByName("status_points", 5); + packet2->setDataByName("exp_bonus", 10); + packet2->setArrayLengthByName("num_rewards", 1); + Item* item = new Item(master_item_list.GetItem(36212)); + packet2->setArrayDataByName("reward_id", item->details.item_id); + item->stack_count = 20; + packet2->setItemArrayDataByName("item", item, client->GetPlayer(), 0, 0, client->GetClientItemPacketOffset()); + safe_delete(item); + char accept[35] = { 0 }; + char decline[35] = { 0 }; + sprintf(accept, "q_accept_pending_quest %u", 0); + sprintf(decline, "q_deny_pending_quest %u", 0); + packet2->setDataByName("accept_command", accept); + packet2->setDataByName("decline_command", decline); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int8 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 1); + ptr2 += 1; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + else if (atoi(sep->arg[0]) == 23) { + if (client->GetPlayer()->GetTarget()) { + PacketStruct* packet2 = configReader.getStruct("WS_UpdateMerchant", client->GetVersion()); + if (packet2) { + Spawn* target = client->GetPlayer()->GetTarget(); + packet2->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(target)); + Item* item = new Item(master_item_list.GetItem(12565)); + int8 i = 0; + packet2->setArrayLengthByName("num_items", 1); + packet2->setArrayDataByName("item_name", item->name.c_str(), i); + packet2->setArrayDataByName("item_id", item->details.item_id, i); + packet2->setArrayDataByName("stack_size", item->stack_count, i); + packet2->setArrayDataByName("icon", item->GetIcon(client->GetVersion()), i); + int8 tmp_level = 0; + if (item->generic_info.adventure_default_level > 0) + tmp_level = item->generic_info.adventure_default_level; + else + tmp_level = item->generic_info.tradeskill_default_level; + packet2->setArrayDataByName("level", tmp_level, i); + packet2->setArrayDataByName("tier", item->details.tier, i); + packet2->setArrayDataByName("item_id2", item->details.item_id, i); + int8 item_difficulty = client->GetPlayer()->GetArrowColor(tmp_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + packet2->setArrayDataByName("item_difficulty", item_difficulty, i); + packet2->setArrayDataByName("quantity", 2, i); + packet2->setArrayDataByName("unknown5", 255, i); + packet2->setArrayDataByName("stack_size2", item->stack_count, i); + packet2->setArrayDataByName("description", item->description.c_str(), i); + packet2->setDataByName("type", 2); + EQ2Packet* app = packet2->serialize(); + if (sep->IsSet(2)) { + int16 offset = atoi(sep->arg[1]); + uchar* ptr2 = app->pBuffer; + ptr2 += offset; + if (sep->IsNumber(2)) { + int32 value1 = atol(sep->arg[2]); + if (value1 > 0xFFFF) + memcpy(ptr2, (uchar*)&value1, 4); + else if (value1 > 0xFF) + memcpy(ptr2, (uchar*)&value1, 2); + else + memcpy(ptr2, (uchar*)&value1, 1); + } + else { + int8 len = strlen(sep->arg[2]); + memcpy(ptr2, (uchar*)&len, 1); + ptr2 += 1; + memcpy(ptr2, sep->arg[2], len); + } + } + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + } + else if (atoi(sep->arg[0]) == 24) { + PacketStruct* packet2 = configReader.getStruct("WS_EnableGameEvent", client->GetVersion()); + if (packet2) { + if (sep->IsSet(2)) { + packet2->setDataByName("event_name", sep->arg[1]); + packet2->setDataByName("enabled", atoi(sep->arg[2])); + EQ2Packet* app = packet2->serialize(); + DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet2); + } + } + } + else if (atoi(sep->arg[0]) == 25) { + if (sep->IsSet(1)) { + Widget* new_spawn = new Widget(); + int32 id = atoul(sep->arg[1]); + new_spawn->SetWidgetID(id); + EQ2Packet* ret = new_spawn->serialize(client->GetPlayer(), client->GetVersion()); + client->QueuePacket(ret); + int8 index = client->GetPlayer()->GetIndexForSpawn(new_spawn); + PacketStruct* packet2 = configReader.getStruct("WS_DestroyGhostCmd", client->GetVersion()); + if (packet2) { + packet2->setDataByName("spawn_index", index); + packet2->setDataByName("delete", 1); + EQ2Packet* app = packet2->serialize(); + client->QueuePacket(app); + safe_delete(packet2); + } + } + } + else if (atoi(sep->arg[0]) == 26) { + if (sep->IsSet(4)) { + Widget* new_spawn = new Widget(); + int32 id = atoul(sep->arg[1]); + new_spawn->SetWidgetID(id); + float x = atof(sep->arg[2]); + float y = atof(sep->arg[3]); + float z = atof(sep->arg[4]); + new_spawn->SetLocation(client->GetPlayer()->GetLocation()); + new_spawn->SetWidgetX(x); + new_spawn->SetWidgetY(y); + new_spawn->SetWidgetZ(z); + new_spawn->SetX(x); + new_spawn->SetY(y); + new_spawn->SetZ(z); + EQ2Packet* ret = new_spawn->serialize(client->GetPlayer(), client->GetVersion()); + client->QueuePacket(ret); + } + } + else if (atoi(sep->arg[0]) == 27) { + Spawn* target = client->GetPlayer()->GetTarget(); + PacketStruct* packet2 = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion()); + if (packet2 && target && sep->IsSet(4)) { + client->GetPlayer()->GetZone()->SendDamagePacket(client->GetPlayer(), target, atoul(sep->arg[1]), atoul(sep->arg[2]), atoul(sep->arg[3]), atoul(sep->arg[4]), sep->arg[5] != nullptr ? sep->arg[5] : ""); + } + } + else if (atoi(sep->arg[0]) == 28 && sep->IsNumber(1)) { + World::newValue = strtoull(sep->arg[1], NULL, 0); + } + else if (atoi(sep->arg[0]) == 29 && sep->IsNumber(1)) { + client->SendHearCast(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(), + strtoull(sep->arg[1], NULL, 0), atoul(sep->arg[2])); + } + else if (atoi(sep->arg[0]) == 30) { + PacketStruct* packet = configReader.getStruct("WS_UpdateSkillBook", client->GetVersion()); + if (packet) { + packet->setDataByName("unknown", World::newValue); + EQ2Packet* outapp = packet->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } + } + else if (atoi(sep->arg[0]) == 31) { + client->SendRecipeList(); + } + else if (atoi(sep->arg[0]) == 32 && sep->IsNumber(1) && sep->IsNumber(2)) { + if(client->GetVersion() <= 561) { + int32 param = atoul(sep->arg[1]); + int32 paramvalue = atoul(sep->arg[2]); + client->Message(CHANNEL_COLOR_YELLOW, "Send control flag param %u param value %u", param, paramvalue); + ClientPacketFunctions::SendServerControlFlagsClassic(client, param, paramvalue); + } + else if(sep->IsNumber(3)) { + int8 param1 = atoul(sep->arg[1]); + int8 param2 = atoul(sep->arg[2]); + int8 paramval = atoul(sep->arg[3]); + client->Message(CHANNEL_COLOR_YELLOW, "Send control flag param1 %u param2 %u param value %u", param1, param2, paramval); + ClientPacketFunctions::SendServerControlFlags(client, param1, param2, paramval); + } + } + else if (atoi(sep->arg[0]) == 33 && sep->IsNumber(1) && sep->IsNumber(2)) { + client->GetCurrentZone()->SendHealPacket(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(), + atoul(sep->arg[1]), atoul(sep->arg[2]), "TestSpell"); + } + else if(atoi(sep->arg[0]) == 34 && sep->IsNumber(1) && sep->IsNumber(2)) { + PacketStruct* packet = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion()); + packet->setSubstructDataByName("reward_data", "unknown1", atoi(sep->arg[1])); + Item* item = master_item_list.GetItem(atoul(sep->arg[2])); + if(item) { + packet->setSubstructArrayLengthByName("reward_data", "num_select_rewards", 1); + packet->setArrayDataByName("select_reward_id", item->details.item_id, 0); + packet->setItemArrayDataByName("select_item", item, client->GetPlayer(), 0, 0, World::newValue - 2); + } + + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + else { + PacketStruct* packet2 = configReader.getStruct("WS_ExaminePartialSpellInfo", client->GetVersion()); + if (packet2) { + packet2->setSubstructDataByName("info_header", "show_name", 1); + packet2->setSubstructDataByName("info_header", "unknown", 7); + packet2->setSubstructDataByName("info_header", "unknown2", 1); + packet2->setSubstructDataByName("info_header", "unknown3", 1); + packet2->setSubstructDataByName("info_header", "unknown4", 1); + packet2->setSubstructDataByName("info_header", "unknown5", 1); + packet2->setSubstructDataByName("info_header", "unknown6", "Testing"); + packet2->setSubstructDataByName("info_header", "unknown7", "Testing"); + packet2->setSubstructDataByName("info_header", "unknown8", 66); + packet2->setSubstructDataByName("info_header", "title", "Blah Title Blah"); + packet2->setSubstructDataByName("info_header", "title_text", "Blah Blah"); + packet2->setSubstructDataByName("info_header", "title_text2", "Blah Blah2"); + packet2->setSubstructDataByName("info_header", "show_popup", 1); + packet2->setSubstructDataByName("info_header", "packettype", 3); + packet2->setSubstructDataByName("spell_info", "skill_name", "Testing"); + packet2->setSubstructDataByName("spell_info", "level", 19); + packet2->setSubstructDataByName("spell_info", "tier", 1); + packet2->setSubstructDataByName("spell_info", "health_cost", 5); + packet2->setSubstructDataByName("spell_info", "min_class_skill_req", 3); + packet2->setSubstructDataByName("spell_info", "mana_cost", 6); + packet2->setSubstructDataByName("spell_info", "req_concentration", 7); + packet2->setSubstructDataByName("spell_info", "cast_time", 200); + packet2->setSubstructDataByName("spell_info", "recovery", 220); + packet2->setSubstructDataByName("spell_info", "recast", 280); + packet2->setSubstructDataByName("spell_info", "beneficial", 1); + packet2->setSubstructDataByName("spell_info", "maintained", 1); + packet2->setSubstructDataByName("spell_info", "spell_book_type", 1); + packet2->setSubstructDataByName("spell_info", "quality", 3); + + packet2->setSubstructDataByName("spell_info", "test_1a", 1); + packet2->setSubstructDataByName("spell_info", "test_1b", 1); + packet2->setSubstructDataByName("spell_info", "test_1c", 1); + packet2->setSubstructDataByName("spell_info", "test_1d", 1); + packet2->setSubstructDataByName("spell_info", "test_2a", 2); + packet2->setSubstructDataByName("spell_info", "test_2b", 2); + packet2->setSubstructDataByName("spell_info", "test_2c", 2); + packet2->setSubstructDataByName("spell_info", "test_2d", 2); + packet2->setSubstructDataByName("spell_info", "test_3", 3); + packet2->setSubstructDataByName("spell_info", "test_4", 4); + packet2->setSubstructDataByName("spell_info", "test_5", 5); + packet2->setSubstructDataByName("spell_info", "test_6", 6); + packet2->setSubstructDataByName("spell_info", "min_class_skill_req", 1); + packet2->setSubstructDataByName("spell_info", "min_class_skill_rec", 30123); + packet2->setSubstructDataByName("spell_info", "num_reagents", 1); + packet2->setSubstructArrayLengthByName("spell_info", "num_reagents", 1); + packet2->setArrayDataByName("reagent", "Alcohol"); + packet2->setArrayDataByName("consumed", "123"); + packet2->setSubstructDataByName("spell_info", "class_skill", 52); + packet2->setSubstructDataByName("spell_info", "id", 8308); + packet2->setSubstructDataByName("spell_info", "icon", 303); + packet2->setSubstructDataByName("spell_info", "icon2", 0xFFFF); + packet2->setSubstructDataByName("spell_info", "icontype", 317); + packet2->setSubstructDataByName("spell_info", "type", 2); + packet2->setSubstructDataByName("spell_info", "spell_text_color", 255); + packet2->setSubstructDataByName("spell_info", "duration1", 600); + packet2->setSubstructDataByName("spell_info", "duration2", 600); + packet2->setSubstructDataByName("spell_info", "name", "Sprint"); + packet2->setSubstructDataByName("spell_info", "description", "Test description"); + EQ2Packet* outapp = packet2->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet2); + } + } + return; + PacketStruct* p = configReader.getStruct("WS_EqTargetItemCmd", client->GetVersion()); + if (!p) return; + + //Seperator* sep2 = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + //client->GetCurrentZone()->SendSpellFailedPacket(client, atoi(sep2->arg[0])); + //} + + + + + + + + + + + + + + /*Seperator* sep2 = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + if (sep2 && sep2->arg[0] && sep2->IsNumber(0)) { + client->SetPendingFlightPath(atoi(sep2->arg[0])); + PacketStruct* packet = configReader.getStruct("WS_ReadyForTakeOff", client->GetVersion()); + if (packet) { + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + }*/ + + /*if (client->GetCurrentZone()->Grid != nullptr) { + int32 numFaces = 0; + int32 numGrids = client->GetPlayer()->Cell_Info.CurrentCell->FaceList.size(); + client->Message(CHANNEL_COLOR_YELLOW, "Num grids in cell: %u", numGrids); + + map >::iterator itr; + for (itr = client->GetPlayer()->Cell_Info.CurrentCell->FaceList.begin(); itr != client->GetPlayer()->Cell_Info.CurrentCell->FaceList.end(); itr++) + numFaces += (*itr).second.size(); + + client->Message(CHANNEL_COLOR_YELLOW, "Num faces in cell: %u", numFaces); + }*/ + + //uchar blah[] = { + // 1208 - OP_EQUpdateStoreCmd + // /*0x00,0x3A,*/0x2B,0x00,0x00,0x00,0xFF,0x78,0x02,0x53,0x2C,0x33,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + + // /*0x58,*/0x47,0x35,0xD3,0x45,0x42,0x42,0x51,0x00,0x40,0xD1,0xE7,0x57,0xB1,0x51,0xB1,0xBB,0xBB,0xB1,0x3B,0xB0,0xBB,0xB0,0x3B,0x59,0x85,0x5B,0x47,0x1D,0x9C,0x3B,0x3A,0x1B,0xB8,0xD9,0x64,0x14,0x85,0x9F,0x4C,0xC8,0x09,0x21,0xA4,0xB3,0x7F,0x45,0x90,0x0B,0x79,0x90,0x0F,0x31,0x28,0x80,0x42,0x28,0x82,0x62,0x28,0x81,0x52,0x28,0x83,0x38,0x94,0x43,0x05,0x54,0x42,0x02,0xAA,0xA0,0x1A,0x6A,0xA0,0x16,0xEA,0xA0,0x1E,0x1A,0xA0,0x11,0x9A,0xA0,0x19,0x5A,0xA0,0x15,0xDA,0xA0,0x1D,0x3A,0xA0,0x13,0xBA,0xA0,0x1B,0x7A,0xA0,0x17,0xFA,0xA0,0x1F,0x06,0x60,0x10,0x86,0x60,0x18,0x46,0x60,0x14,0xC6,0x60,0x1C,0x26,0x20,0x09,0x93,0x30,0x05,0xD3,0x30,0x03,0xB3,0x30,0x07,0xF3,0xB0,0x00,0x8B,0xB0,0x04,0xCB,0xB0,0x02,0xAB,0xB0,0x06,0xEB,0xB0,0x01,0x29,0xD8,0x84,0x2D,0xD8,0x86,0x1D,0xD8,0x85,0x3D,0xD8,0x87,0x03,0x38,0x84,0x23,0x38,0x86,0x13,0x38,0x85,0x33,0x38,0x87,0x0B,0xB8,0x84,0x34,0x5C,0xC1,0x35,0xDC,0xC0,0x2D,0xDC,0xC1,0x3D,0x3C,0xC0,0x23,0x3C,0xC1,0x33,0xBC,0xC0,0x2B,0xBC,0xC1,0x3B,0x7C,0xC0,0x27,0x7C,0xC1,0x37,0x64,0xE0,0xFF,0xD3,0xF0,0x0B,0x29,0x4A,0xD8,0x68 + + // 1193 - -- OP_ClientCmdMsg::OP_EqUpdateMerchantCmd -- + // /*0x00,0x3A,*/0x38,0x00,0x00,0x00,0xFF,0x77,0x02,0xA6,0xAA,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3F,0x00,0x00,0x00,0x00,0xA8,0x55,0xEC,0x8F,0x7F,0x70,0x31,0x08,0x40,0x71,0x3A,0x8B,0xB4,0x55,0xEC,0x8F,0x00 + /*}; + + Seperator* sep2 = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + int8 val = 0; + int16 pos = 0; + int idx = 0; + while(sep2 && sep2->arg[idx+1] && sep2->IsNumber(idx) && sep2->IsNumber(idx+1)){ + pos = atoi(sep2->arg[idx]); + val = atoi(sep2->arg[idx+1]); + memset(blah+pos, val, 1); + idx+=2; + } + + DumpPacket(blah, sizeof(blah)); + client->QueuePacket(new EQ2Packet(OP_GroupCreatedMsg , blah, sizeof(blah)));*/ + + if (client->GetPlayer()->GetTarget()) { + PacketStruct* packet = configReader.getStruct("WS_SetControlGhost", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetTarget())); + packet->setDataByName("size", 0); + packet->setDataByName("unknown2", 0); + EQ2Packet* app = packet->serialize(); + client->QueuePacket(app); + safe_delete(packet); + } + + PacketStruct* set_pov = configReader.getStruct("WS_SetPOVGhostCmd", client->GetVersion()); + if (set_pov) { + set_pov->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetTarget())); + EQ2Packet* app_pov = set_pov->serialize(); + client->QueuePacket(app_pov); + safe_delete(set_pov); + } + } + + Seperator* sep2 = new Seperator(command_parms->data.c_str(), ' ', 50, 500, true); + + PacketStruct* packet = configReader.getStruct("WS_OpenCharCust", client->GetVersion()); + if (packet) { + + + if (sep2 && sep2->arg[0] && sep2->IsNumber(0)) + packet->setDataByName("Type", atoi(sep2->arg[0])); + else + packet->setDataByName("Type", 2); + + if (sep2 && sep2->arg[1] && sep2->IsNumber(1)) + packet->setDataByName("race_id", atoi(sep2->arg[1])); + else + packet->setDataByName("race_id", 0); + + if (sep2 && sep2->arg[2] && sep2->IsNumber(2)) + packet->setDataByName("gender", atoi(sep2->arg[2])); + else + packet->setDataByName("gender", 2); + + if (sep2 && sep2->arg[3] && sep2->IsNumber(3)) + packet->setDataByName("unknown", atoi(sep2->arg[3]), 0); + else + packet->setDataByName("unknown", 0, 0); + + if (sep2 && sep2->arg[4] && sep2->IsNumber(4)) + packet->setDataByName("unknown", atoi(sep2->arg[4]), 1); + else + packet->setDataByName("unknown", 0, 1); + + if (sep2 && sep2->arg[5] && sep2->IsNumber(5)) + packet->setDataByName("unknown", atoi(sep2->arg[5]), 2); + else + packet->setDataByName("unknown", 0, 2); + + + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + safe_delete(sep2); +} + +void Commands::Command_LeaveChannel(Client *client, Seperator *sep) { + const char *channel_name; + + if (sep == NULL || !sep->IsSet(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /leavechat "); + PrintSep(sep); + return; + } + + channel_name = sep->arg[0]; + + if (!chat.IsInChannel(client, channel_name)) + client->Message(CHANNEL_NARRATIVE, "Unable to leave '%s': You are not in the channel.", channel_name); + else if (!chat.LeaveChannel(client, channel_name)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There was an internal error preventing you from leaving that channel MUAHAHA"); +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_WeaponStats(Client* client) +{ + Player* player = client->GetPlayer(); + Spawn* target = player->GetTarget(); + + Item* primary = player->GetEquipmentList()->GetItem(EQ2_PRIMARY_SLOT); + Item* secondary = player->GetEquipmentList()->GetItem(EQ2_SECONDARY_SLOT); + Item* ranged = player->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); + const char* charName = player->GetName(); + if(target && target->IsEntity()) { + primary = ((Entity*)target)->GetEquipmentList()->GetItem(EQ2_PRIMARY_SLOT); + secondary = ((Entity*)target)->GetEquipmentList()->GetItem(EQ2_SECONDARY_SLOT); + ranged = ((Entity*)target)->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); + charName = target->GetName(); + } + else { + target = nullptr; + } + client->Message(0, "WeaponStats for %s", charName); + client->SimpleMessage(0, "Primary:"); + if (primary) { + client->Message(0, "Name: %s", primary->name.c_str()); + client->Message(0, "Base Damage: %u - %u", primary->weapon_info->damage_low3, primary->weapon_info->damage_high3); + client->Message(0, "Mastery Damage: %u - %u", primary->weapon_info->damage_low2, primary->weapon_info->damage_high2); + client->Message(0, "Damage: %u - %u", primary->weapon_info->damage_low1, primary->weapon_info->damage_high1); + client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetPrimaryWeaponMinDamage() : player->GetPrimaryWeaponMinDamage(), + target ? ((Entity*)target)->GetPrimaryWeaponMaxDamage() : player->GetPrimaryWeaponMaxDamage()); + client->Message(0, "Actual Delay: %u", target ? ((Entity*)target)->GetPrimaryWeaponDelay() : player->GetPrimaryWeaponDelay()); + client->Message(0, "Proc Percent: %d%%", 0); + client->Message(0, "Procs Per Minute: %d", 0); + } + else { + client->SimpleMessage(0, "Name: fist"); + client->Message(0, "Base Damage: %u - %u", target ? ((Entity*)target)->GetPrimaryWeaponMinDamage() : player->GetPrimaryWeaponMinDamage(), + target ? ((Entity*)target)->GetPrimaryWeaponMaxDamage() : player->GetPrimaryWeaponMaxDamage()); + client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetPrimaryWeaponMinDamage() : player->GetPrimaryWeaponMinDamage(), + target ? ((Entity*)target)->GetPrimaryWeaponMaxDamage() : player->GetPrimaryWeaponMaxDamage()); + client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetPrimaryWeaponDelay() : player->GetPrimaryWeaponDelay() * 0.1); + client->Message(0, "Proc Percent: %d%%", 0); + client->Message(0, "Procs Per Minute: %d", 0); + } + client->SimpleMessage(0, " "); + client->SimpleMessage(0, " "); + if (secondary) { + client->SimpleMessage(0, "Secondary:"); + client->Message(0, "Name: %s", secondary->name.c_str()); + client->Message(0, "Base Damage: %u - %u", secondary->weapon_info->damage_low3, secondary->weapon_info->damage_high3); + client->Message(0, "Mastery Damage: %u - %u", secondary->weapon_info->damage_low2, secondary->weapon_info->damage_high2); + client->Message(0, "Damage: %u - %u", secondary->weapon_info->damage_low1, secondary->weapon_info->damage_high1); + client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetSecondaryWeaponMinDamage() : player->GetSecondaryWeaponMinDamage(), + target ? ((Entity*)target)->GetSecondaryWeaponMaxDamage() : player->GetSecondaryWeaponMaxDamage()); + client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetSecondaryWeaponDelay() : player->GetSecondaryWeaponDelay() * 0.1); + client->Message(0, "Proc Percent: %d%%", 0); + client->Message(0, "Procs Per Minute: %d", 0); + client->SimpleMessage(0, " "); + client->SimpleMessage(0, " "); + } + client->SimpleMessage(0, "Ranged:"); + if (ranged) { + client->Message(0, "Name: %s", ranged->name.c_str()); + client->Message(0, "Base Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low3, ranged->ranged_info->weapon_info.damage_high3); + client->Message(0, "Mastery Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low2, ranged->ranged_info->weapon_info.damage_high2); + client->Message(0, "Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low1, ranged->ranged_info->weapon_info.damage_high1); + client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetRangedWeaponMinDamage() : player->GetRangedWeaponMinDamage(), + target ? ((Entity*)target)->GetRangedWeaponMaxDamage() : player->GetRangedWeaponMaxDamage()); + client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetRangeWeaponDelay() : player->GetRangeWeaponDelay() * 0.1); + client->Message(0, "Proc Percent: %d%%", 0); + client->Message(0, "Procs Per Minute: %d", 0); + } + else + client->SimpleMessage(0, "None"); + +} + +void Commands::Command_WhoChannel(Client *client, Seperator *sep) { + const char *channel_name; + + if (sep == NULL || !sep->IsSet(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /chatwho "); + return; + } + + channel_name = sep->arg[0]; + + if (!chat.ChannelExists(channel_name)) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "That channel does not exist!"); + else + chat.SendChannelUserList(client, channel_name); +} + +void Commands::Command_ZoneSafeCoords(Client *client, Seperator *sep) +{ + ZoneServer* zone = 0; + int32 zone_id = client->GetPlayer()->GetZone()->GetZoneID(); + + if (zone_id > 0) + { + zone = zone_list.Get(zone_id, false, false, false); + if (zone) + { + zone->SetSafeX(client->GetPlayer()->GetX()); + zone->SetSafeY(client->GetPlayer()->GetY()); + zone->SetSafeZ(client->GetPlayer()->GetZ()); + zone->SetSafeHeading(client->GetPlayer()->GetHeading()); + if( database.SaveZoneSafeCoords(zone_id, zone->GetSafeX(), zone->GetSafeY(), zone->GetSafeZ(), zone->GetSafeHeading()) ) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone safe coordinates updated!"); + else + client->SimpleMessage(CHANNEL_COLOR_RED, "FAILED to update zone safe coordinates!"); + } + } +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_ZoneDetails(Client* client, Seperator* sep) +{ + ZoneInfo* zone_info = new ZoneInfo; + + if (sep && sep->arg[0]) + { + if (sep->IsNumber(0)) + zone_info->id = atoi(sep->arg[0]); + else + zone_info->id = database.GetZoneID(sep->arg[0]); + + if (zone_info->id > 0) + { + database.LoadZoneInfo(zone_info); + client->Message(CHANNEL_COLOR_YELLOW, "id: %u, name: %s, file: %s, description: %s", zone_info->id, zone_info->name, zone_info->file, zone_info->description); + client->Message(CHANNEL_COLOR_YELLOW, "safe_x: %f, safe_y: %f, safe_z: %f, underworld: %f", zone_info->safe_x, zone_info->safe_y, zone_info->safe_z, zone_info->underworld); + client->Message(CHANNEL_COLOR_YELLOW, "min_status: %u, min_level: %u, max_level: %u, xp_modifier: %u", zone_info->min_status, zone_info->min_level, zone_info->max_level, zone_info->xp_modifier); + client->Message(CHANNEL_COLOR_YELLOW, "instance_type: %u, shutdown_timer: %u, ruleset_id: %u", zone_info->instance_type, zone_info->shutdown_timer, zone_info->ruleset_id); + client->Message(CHANNEL_COLOR_YELLOW, "default_reenter_time: %u, default_reset_time: %u, default_lockout_time: %u", zone_info->default_reenter_time, zone_info->default_reenter_time, zone_info->default_lockout_time); + client->Message(CHANNEL_COLOR_YELLOW, "force_group_to_zone: %u, expansion_id: %u, min_version: %u", zone_info->force_group_to_zone, zone_info->expansion_id, zone_info->min_version); + client->Message(CHANNEL_COLOR_YELLOW, "always_loaded: %u, city_zone: %u, start_zone: %u, weather_allowed: %u", zone_info->always_loaded, zone_info->city_zone, zone_info->start_zone, zone_info->weather_allowed); + client->Message(CHANNEL_COLOR_YELLOW, "zone_type: %s, sky_file: %s", zone_info->zone_type, zone_info->sky_file); + client->Message(CHANNEL_COLOR_YELLOW, "lua_script: %s", zone_info->lua_script); + client->Message(CHANNEL_COLOR_YELLOW, "zone_motd: %s", zone_info->zone_motd); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "The zone name or ID '%s' does not exist.", sep->arg[0]); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /zone details [zone id|zone name]"); + + safe_delete(zone_info); +} + +/* + Function: + Purpose : + Params : + Dev : + Example : +*/ +void Commands::Command_ZoneSet(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->arg[1] && sep->arg[2]) + { + ZoneServer* zone = 0; + int32 zone_id = 0; + + if (sep->IsNumber(0) && atoi(sep->arg[0]) > 0) + { + zone_id = atoi(sep->arg[0]); + zone = zone_list.Get(atoi(sep->arg[0]), false, false, false); + } + else + { + zone_id = database.GetZoneID(sep->arg[0]); + + if (zone_id > 0) + zone = zone_list.Get(sep->arg[0], false, false, false); + } + + if (zone_id > 0) + { + if (zone_set_values.count(string(sep->arg[1])) > 0) + SetZoneCommand(client, zone_id, zone, zone_set_values[sep->arg[1]], sep->arg[2]); + else + client->Message(CHANNEL_COLOR_YELLOW, "The attribute '%s' is not valid.", sep->arg[1]); + } + else + client->Message(CHANNEL_COLOR_YELLOW, "The zone name or ID '%s' does not exist.", sep->arg[0]); + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /zone set [zone id|zone name] [attribute] [value]"); +} + +void Commands::Command_Rain(Client* client, Seperator* sep) { + if (sep == NULL || !sep->IsSet(0) || !sep->IsNumber(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /rain "); + return; + } + + client->Message(CHANNEL_COLOR_YELLOW,"Setting rain to %.2f", atof(sep->arg[0])); + client->GetCurrentZone()->SetRain(atof(sep->arg[0])); + client->GetCurrentZone()->SetCurrentWeather(atof(sep->arg[0])); +} + +void Commands::Command_Wind(Client* client, Seperator* sep) { + if (sep == NULL || !sep->IsSet(0) || !sep->IsNumber(0)) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /wind "); + return; + } + + client->Message(CHANNEL_COLOR_YELLOW, "Setting wind to %.2f", atof(sep->arg[0])); + client->GetCurrentZone()->SetWind(atof(sep->arg[0])); +} + + +void Commands::Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell) { + Spawn* spawn = client->GetPlayer()->GetTarget(); + if(client->GetVersion() < 561) { + sell = false; // doesn't support in the same way as AoM just open the normal buy/sell window + } + if(spawn) { + client->SetMerchantTransaction(spawn); + if (spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(client)){ + client->SendHailCommand(spawn); + //MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(spawn->GetMerchantID()); + //if(!multiplier || (multiplier && client->GetPlayer()->GetFactions()->GetFactionValue(multiplier->faction_id) >= multiplier->faction_min)){ + client->SendBuyMerchantList(sell); + if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) + client->SendSellMerchantList(sell); + if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) + client->SendBuyBackList(sell); + + if(client->GetVersion() > 561) { + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + packet->setDataByName("type", 16); + EQ2Packet* outapp = packet->serialize(); + if (outapp) + client->QueuePacket(outapp); + safe_delete(packet); + } + } + } + if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR) + client->SendRepairList(); + } + // client->SimpleMessage(CHANNEL_COLOR_RED, "Your faction is too low to use this merchant."); +} + + +void Commands::Command_Weather(Client* client, Seperator* sep) +{ + //PrintSep(sep, "Weather"); + + if( sep && sep->arg[0] ) + { + ZoneServer* zsZone = client->GetCurrentZone(); + const char* value = sep->arg[0]; + + // process single-param commands first + if( strncasecmp(value, "details", strlen(value)) == 0 ) + { + client->Message(CHANNEL_COLOR_YELLOW, "Weather Details for zone %s: ", zsZone->GetZoneName()); + client->Message(CHANNEL_COLOR_YELLOW, "enabled: %i, allowed: %i, type: %i, frequency: %u", zsZone->isWeatherEnabled(), zsZone->isWeatherAllowed(), zsZone->GetWeatherType(), zsZone->GetWeatherFrequency()); + client->Message(CHANNEL_COLOR_YELLOW, "severity: %.2f = %.2f, current: %.2f", zsZone->GetWeatherMinSeverity(), zsZone->GetWeatherMaxSeverity(), zsZone->GetCurrentWeather()); + client->Message(CHANNEL_COLOR_YELLOW, "pattern: %i, chance: %i, amount: %.2f, offset: %.2f", zsZone->GetWeatherPattern(), zsZone->GetWeatherChance(), zsZone->GetWeatherChangeAmount(), zsZone->GetWeatherDynamicOffset()); + } + else if( strncasecmp(value, "process", strlen(value)) == 0 ) + { + zsZone->SetWeatherLastChangedTime(Timer::GetUnixTimeStamp() - zsZone->GetWeatherFrequency()); + zsZone->ProcessWeather(); + } + else if( strncasecmp(value, "reset", strlen(value)) == 0 ) + { + zsZone->SetWeatherType(rule_manager.GetGlobalRule(R_Zone, WeatherType)->GetInt8()); + zsZone->SetWeatherFrequency(rule_manager.GetGlobalRule(R_Zone, WeatherChangeFrequency)->GetInt32()); + zsZone->SetWeatherMinSeverity(rule_manager.GetGlobalRule(R_Zone, MinWeatherSeverity)->GetFloat()); + zsZone->SetWeatherMaxSeverity(rule_manager.GetGlobalRule(R_Zone, MaxWeatherSeverity)->GetFloat()); + zsZone->SetCurrentWeather(zsZone->GetWeatherMinSeverity()); + zsZone->SetWeatherPattern(1); + zsZone->SetWeatherChance(rule_manager.GetGlobalRule(R_Zone, WeatherChangeChance)->GetInt8()); + zsZone->SetWeatherChangeAmount(rule_manager.GetGlobalRule(R_Zone, WeatherChangePerInterval)->GetFloat()); + zsZone->SetWeatherDynamicOffset(rule_manager.GetGlobalRule(R_Zone, WeatherDynamicMaxOffset)->GetFloat()); + zsZone->SetWeatherLastChangedTime(Timer::GetUnixTimeStamp() - zsZone->GetWeatherFrequency()); + zsZone->ProcessWeather(); + } + + // process commands with params + if( sep->arg[1] ) + { + if( strncasecmp(value, "enable", strlen(value)) == 0 && sep->IsNumber(1) ) + zsZone->SetWeatherEnabled( ( atoi(sep->arg[1]) == 1 ) ? true : false ); + else if( strncasecmp(value, "type", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 3) ) + zsZone->SetWeatherType(atoi(sep->arg[1])); + else if( strncasecmp(value, "frequency", strlen(value)) == 0 && sep->IsNumber(1)) + zsZone->SetWeatherFrequency( atoul(sep->arg[1]) ); + else if( strncasecmp(value, "range", strlen(value)) == 0 && sep->IsNumber(1) && sep->IsNumber(2) ) { + zsZone->SetWeatherMinSeverity(atof(sep->arg[1])); + zsZone->SetWeatherMaxSeverity(atof(sep->arg[2])); + zsZone->SetRain(zsZone->GetWeatherMinSeverity()); + } + else if( strncasecmp(value, "current", strlen(value)) == 0 && sep->IsNumber(1) ) { + zsZone->SetCurrentWeather(atof(sep->arg[1])); + zsZone->SetRain(zsZone->GetCurrentWeather()); + } + else if( strncasecmp(value, "pattern", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 2) ) + zsZone->SetWeatherPattern( atoi(sep->arg[1]) ); + else if( strncasecmp(value, "chance", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 100) ) + zsZone->SetWeatherChance( atoi(sep->arg[1]) ); + else if( strncasecmp(value, "amount", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 1) ) + zsZone->SetWeatherChangeAmount( atof(sep->arg[1]) ); + else if( strncasecmp(value, "offset", strlen(value)) == 0 && sep->IsNumber(1) && (atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 1) ) + zsZone->SetWeatherDynamicOffset( atof(sep->arg[1]) ); + } + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /weather [command] [param]"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Commands: enable (0|1), type (0-3), frequency (sec), range|current (0.0 - 1.0)"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Commands: details, process, pattern (0|1), chance (%), amount (float), offset (float)"); + return; + } + +} + +void Commands::Command_Select(Client* client, Seperator* sep) { + if (sep && sep->arg[1]) { + Spawn* spawn = client->GetPlayer()->GetTarget(); + if (spawn && strlen(sep->arg[1]) > 0) + client->GetCurrentZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_CUSTOM, client->GetPlayer(), sep->arg[1]); + } +} + +/* + Function: Command_ConsumeFood() + Purpose : Consume Food/Drink and apply mods + Params : Slot ID - EQ2_FOOD_SLOT 22, EQ2_DRINK_SLOT 23 + Dev : Zcoretri + Example : /consume_food 22 +*/ +void Commands::Command_ConsumeFood(Client* client, Seperator* sep) { + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + Player* player = client->GetPlayer(); + int32 slot = atoul(sep->arg[0]); + if (client->GetVersion() <= 373) { + if(client->GetVersion() <= 561) { + if(slot <= 255) { + slot = 255 - slot; + } + else { + if(slot == 256) { // first "new" item to inventory is assigned index 256 by client + slot = client->GetPlayer()->item_list.GetFirstNewItem(); + } + else { + // otherwise the slot has to be mapped out depending on the amount of new items + index sent in + slot = client->GetPlayer()->item_list.GetNewItemByIndex((int16)slot - 255); + } + } + } + + Item* item = client->GetPlayer()->item_list.GetItemFromIndex(slot); + if(item && item->IsFood()) { + if(client->CheckConsumptionAllowed(slot)) { + if(item->IsFoodFood()) + slot = EQ2_FOOD_SLOT; + else if(item->IsFoodDrink()) + slot = EQ2_DRINK_SLOT; + else + return; // not valid item + + client->ConsumeFoodDrink(item, slot); + } + } + } + else { + if (client->GetVersion() > 373 && client->GetVersion() <= 561) { + slot += 2; + } + Item* item = player->GetEquipmentList()->GetItem(slot); + + if(client->CheckConsumptionAllowed(slot)) { + client->ConsumeFoodDrink(item, slot); + } + } + } +} + +void Commands::Command_Aquaman(Client* client, Seperator* sep) { + if (sep && sep->arg[0] && sep->IsNumber(0)) { + if (atoi(sep->arg[0]) == 1) { + client->GetPlayer()->GetInfoStruct()->set_vision(4); + client->GetPlayer()->GetInfoStruct()->set_breathe_underwater(1); + client->GetPlayer()->SetCharSheetChanged(true); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Maybe you ought to stick to the shallow end until you know how to swim."); + } + else { + client->GetPlayer()->GetInfoStruct()->set_vision(0); + client->GetPlayer()->GetInfoStruct()->set_breathe_underwater(0); + client->GetPlayer()->SetCharSheetChanged(true); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Aquaman mode turned off."); + } + } + else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /aquaman [0|1]"); +} + +void Commands::Command_ReportBug(Client* client, Seperator* sep) +{ + if(sep) + { + string data; + + if(sep->arg[0]){ + data = string(sep->arg[0]); + } + + for(int i=1;iGetMaxArgNum();i++){ + if(sep->arg[i]) + data.append(" ").append(sep->arg[i]); + } + + if(!sep->IsSet(7)){ + data.append(" ").append(std::to_string(client->GetVersion())).append("\a"); + } + + const char* target_name = 0; + int32 spawn_id = 0; + + if(client->GetPlayer()->GetTarget()) + { + target_name = client->GetPlayer()->GetTarget()->GetName(); + spawn_id = client->GetPlayer()->GetTarget()->GetDatabaseID(); + } + else + target_name = "N/A"; + + LogWrite(COMMAND__DEBUG, 1, "Command", "%s", data.c_str()); + + if(world.ReportBug(data, client->GetPlayer()->GetName(), client->GetAccountID(), target_name, spawn_id, client->GetCurrentZone()->GetZoneID())) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully submitted bug."); + else + client->SimpleMessage(CHANNEL_COLOR_RED, "Error submitting bug."); + } +} + +void Commands::Command_Attune_Inv(Client* client, Seperator* sep) { + PrintSep(sep, "Command_Attune_Inv"); + if (sep && sep->arg[0] && sep->IsNumber(0)) { + // Get the item index from the first parameter + int16 index = atoi(sep->arg[0]); + // Check to see if this is a valid item index for the player, if not exit out of the function + if (client->GetPlayer()->item_list.indexed_items.count(index) == 0) { + LogWrite(ITEM__DEBUG, 0, "Items", "%s has no item with an index of %i", client->GetPlayer()->GetName(), index); + return; + } + + // Get the item + Item* item = client->GetPlayer()->item_list.indexed_items[index]; + if(item) { + // Valid item lets check to make sure this item is attunable, if not return out + if (!item->CheckFlag(ATTUNEABLE)) { + LogWrite(ITEM__DEBUG, 0, "Items", "attune_inv called for an item that is not attunable (%s)", item->name.c_str()); + return; + } + + // Remove the attunable flag + item->generic_info.item_flags -= ATTUNEABLE; + // Set the attuned flag + item->generic_info.item_flags += ATTUNED; + // Flag this item for saving + item->save_needed = true; + + client->QueuePacket(item->serialize(client->GetVersion(), false, client->GetPlayer())); + + vector packets = client->GetPlayer()->EquipItem(index, client->GetVersion(), 0, -1); // appearance type?? + EQ2Packet* outapp = 0; + + for (int32 i=0;iQueuePacket(outapp); + } + } + + } +} + +void Commands::Command_Reset_Zone_Timer(Client* client, Seperator* sep) { + PrintSep(sep, "Command_Reset_Zone_Timer"); + /*if (sep && sep->arg[0] && sep->IsNumber(0)) { + int32 db_id = atoul(sep->arg[0]); + InstanceData* data = client->GetPlayer()->GetCharacterInstances().FindInstanceByDBID(db_id); + if (data) { + // TODO: add a check to timers to ensure it can be reset + + // Delete the character from the instance + database.DeleteCharacterFromInstance(client->GetPlayer()->GetCharacterID(), data->instance_id); + data->instance_id = 0; + + // Update the success time and set to 0 so the player can enter it again + database.UpdateCharacterInstance(client->GetPlayer()->GetCharacterID(), data->zone_name, 0, 1, 0); + data->last_success_timestamp = 0; + } + }*/ +} + +void Commands::Command_Player(Client* client, Seperator* sep) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, " -- /player syntax --"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins"); +} + +void Commands::Command_Player_Coins(Client* client, Seperator* sep) { + // /player coins add 10 + // /player coins add plat 10 + Player* player = client->GetPlayer(); + Client* targetClient = client; + if (player->HasTarget() && player->GetTarget()->IsPlayer()) { + player = (Player*)player->GetTarget(); + targetClient = player->GetClient(); + } + + if (sep && sep->arg[0] && sep->arg[1]) { + const char* action = sep->arg[0]; + int64 value = 0; + + if (strncasecmp(action, "add", strlen(action)) == 0) { + if (sep->IsNumber(1)) { + value = atoi64(sep->arg[1]); + player->AddCoins(value); + + if (client->GetPlayer() == player) + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu coin%s", value, (value > 1 ? "s" : "")); + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu coin%s", player->GetName(), value, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu coin%s", client->GetPlayer()->GetName(), value, (value > 1 ? "s" : "")); + } + } + + return; + } + else if (sep->arg[2] && sep->IsNumber(2)) { + const char* type = sep->arg[1]; + if (strncasecmp(type, "copper", strlen(type)) == 0) { + value = atoi64(sep->arg[2]); + } + else if (strncasecmp(type, "silver", strlen(type)) == 0) { + value = atoi64(sep->arg[2]) * 100; + } + else if (strncasecmp(type, "gold", strlen(type)) == 0) { + value = atoi64(sep->arg[2]) * 10000; + } + else if (strncasecmp(type, "plat", strlen(type)) == 0) { + value = atoi64(sep->arg[2]) * 1000000; + } + player->AddCoins(value); + + if (client->GetPlayer() == player) + client->Message(CHANNEL_COLOR_YELLOW, "You give yourself %llu coin%s", value, (value > 1 ? "s" : "")); + else { + client->Message(CHANNEL_COLOR_YELLOW, "You give %s %llu coin%s", player->GetName(), value, (value > 1 ? "s" : "")); + if(targetClient) { + targetClient->Message(CHANNEL_COLOR_YELLOW, "%s gave you %llu coin%s", client->GetPlayer()->GetName(), value, (value > 1 ? "s" : "")); + } + } + return; + } + } + } + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, " -- /player coins syntax --"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add [value] - adds the given number of coins to the player"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add copper [value] - adds the given amount of copper to the player"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add silver [value] - adds the given amount of silver to the player"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add gold [value] - adds the given amount of gold to the player"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/player coins add plat [value] - adds the given amount of platinum to the player"); +} + +void Commands::Command_AchievementAdd(Client* client, Seperator* sep) { + PrintSep(sep, "ACHIEVEMENT_ADD"); + if (sep && sep->IsSet(0)) { + int32 spell_id = atoul(sep->arg[1]); + int8 spell_tier = 0; + spell_tier = client->GetPlayer()->GetSpellTier(spell_id); + AltAdvanceData* data = master_aa_list.GetAltAdvancement(spell_id); + // addspellbookentry here + if (spell_tier >= data->maxRank) { + return; + } + if (!spell_tier) { + spell_tier = 1; + } + if (!client->GetPlayer()->HasSpell(spell_id, 0, true)) + { + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + client->GetPlayer()->AddSpellBookEntry(spell_id, 1, client->GetPlayer()->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + else + { + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier + 1); + int8 old_slot = client->GetPlayer()->GetSpellSlot(spell->GetSpellID()); + client->GetPlayer()->RemoveSpellBookEntry(spell->GetSpellID()); + client->GetPlayer()->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + + + // cast spell here + if (!spell_tier) + spell_tier = 1; + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + if (spell) { + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /useability {spell_id} [spell_tier]"); + } + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, 0, 0); +} + +void Commands::Command_Editor(Client* client, Seperator* sep) { + PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", client->GetVersion()); + if (packet) { + string url = string(rule_manager.GetGlobalRule(R_World, EditorURL)->GetString()); + + if (rule_manager.GetGlobalRule(R_World, EditorOfficialServer)->GetBool()) { + char command[255]; + url = "browser " + url; + int32 spawn_id = 0; + int32 zone_id = 0; + string type; + + if (client->GetCurrentZone()) + zone_id = client->GetCurrentZone()->GetZoneID(); + + if (client->GetPlayer()->GetTarget()) { + Spawn* target = client->GetPlayer()->GetTarget(); + if (target->IsWidget()) + type = "widgets"; + else if (target->IsSign()) + type = "signs"; + else if (target->IsObject()) + type = "objects"; + else if (target->IsNPC()) + type = "npcs"; + else if (target->IsGroundSpawn()) + type = "ground"; + else + type = "spawn"; + + spawn_id = client->GetPlayer()->GetTarget()->GetDatabaseID(); + } + + sprintf(command, url.c_str(), zone_id, type.c_str(), spawn_id); + + packet->setDataByName("accept_command", command); + } + else if (rule_manager.GetGlobalRule(R_World, EditorIncludeID)->GetBool()) { + char command[255]; + url = "browser " + url; + if (client->GetPlayer()->GetTarget()) + sprintf(command, url.c_str(), client->GetPlayer()->GetTarget()->GetDatabaseID()); + + packet->setDataByName("accept_command", command); + } + else { + string command = "browser " + url; + packet->setDataByName("accept_command", command.c_str()); + } + + packet->setDataByName("text", "Open the web editor?"); + packet->setDataByName("accept_text", "Open"); + + packet->setDataByName("cancel_text", "Cancel"); + // No clue if we even need the following 2 unknowns, just added them so the packet matches what live sends + packet->setDataByName("unknown2", 50); + packet->setDataByName("unknown4", 1); + + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void Commands::Command_AcceptResurrection(Client* client, Seperator* sep) { + if(!client || !sep || client->GetPlayer()->GetID() != atoul(sep->arg[0])) + return; + client->GetResurrectMutex()->writelock(__FUNCTION__, __LINE__); + if(client->GetCurrentRez()->active) + client->AcceptResurrection(); + client->GetResurrectMutex()->releasewritelock(__FUNCTION__, __LINE__); +} + +void Commands::Command_DeclineResurrection(Client* client, Seperator* sep) { + if(!client || !sep || client->GetPlayer()->GetID() != atoul(sep->arg[0])) + return; + client->GetResurrectMutex()->writelock(__FUNCTION__, __LINE__); + if(client->GetCurrentRez()->active) + client->GetCurrentRez()->should_delete = true; + client->GetResurrectMutex()->releasewritelock(__FUNCTION__, __LINE__); +} +void Commands::Switch_AA_Profile(Client* client, Seperator* sep) { + PrintSep(sep, "Switch_AA_Profile"); + if (sep && sep->IsSet(0)) { + string type = sep->arg[0]; + int8 newtemplate = atoul(sep->arg[1]); + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, newtemplate, 1); + } +} +void Commands::Get_AA_Xml(Client* client, Seperator* sep) { + PrintSep(sep, "Get_AA_Xml"); + if (sep && sep->IsSet(0)) { + string tabnum = sep->arg[0]; + string spellid = sep->arg[1]; + + + + } +} +void Commands::Add_AA(Client* client, Seperator* sep) { + PrintSep(sep, "Add_AA"); + if (sep && sep->IsSet(0)) { + int32 spell_id = atoul(sep->arg[1]); + int8 spell_tier = 0; + spell_tier = client->GetPlayer()->GetSpellTier(spell_id); + AltAdvanceData* data = master_aa_list.GetAltAdvancement(spell_id); + // addspellbookentry here + if(!data) { + LogWrite(COMMAND__ERROR, 0, "Command", "Error in Add_AA no data for spell_id %u spell_tier %u", spell_id, spell_tier); + return; + } + if (spell_tier >= data->maxRank) { + LogWrite(COMMAND__ERROR, 0, "Command", "Error in Add_AA spell_tier %u >= maxRank %u", spell_tier, data->maxRank); + return; + } + if (!spell_tier) { + spell_tier = 1; + } + if (!client->GetPlayer()->HasSpell(spell_id, 0, true)) + { + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + if(spell) + { + client->GetPlayer()->AddSpellBookEntry(spell_id, 1, client->GetPlayer()->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + } + else + { + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier + 1 ); + if(spell) + { + int8 old_slot = client->GetPlayer()->GetSpellSlot(spell->GetSpellID()); + client->GetPlayer()->RemoveSpellBookEntry(spell->GetSpellID()); + client->GetPlayer()->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell); + } + } + + + // cast spell here + if (!spell_tier) + spell_tier = 1; + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + if (spell) { + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()); + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Usage: /useability {spell_id} [spell_tier]"); + } + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, 0, 0); +} +void Commands::Commit_AA_Profile(Client* client, Seperator* sep) { + PrintSep(sep, "Commit_AA_Profile"); + if (sep && sep->IsSet(0)) { + + + } +} +void Commands::Begin_AA_Profile(Client* client, Seperator* sep) { + PrintSep(sep, "Begin_AA_Profile"); + if (sep && sep->IsSet(0)) { + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, 100, 2); + } +} +void Commands::Back_AA(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + PrintSep(sep, "Back_AA"); + + } +} +void Commands::Remove_AA(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + PrintSep(sep, "Remove_AA"); + + } +} + +void Commands::Cancel_AA_Profile(Client* client, Seperator* sep) { + MasterAAList master_aa_list; + PrintSep(sep, "Cancel_AA_Profile"); + if(client->GetVersion() > 561) + master_aa_list.DisplayAA(client, 0, 0); + +} +void Commands::Save_AA_Profile(Client* client, Seperator* sep) { + if (sep && sep->IsSet(0)) { + PrintSep(sep, "Save_AA_Profile"); + + } +} + +void Commands::Command_TargetItem(Client* client, Seperator* sep) { + if (!sep || sep->GetArgNumber() < 1 || !client->GetPlayer()) return; + + if (!sep->IsNumber(0)) return; + + int32 request_id = atoul(sep->arg[0]); + + if (!sep->IsNumber(1)) return; + + sint32 item_id = atoi(sep->arg[1]); + + if (client->IsCurrentTransmuteID(request_id)) { + Transmute::HandleItemResponse(client, client->GetPlayer(), request_id, reinterpret_cast(item_id)); + } + else if (client->IsCurrentTransmuteID(item_id)) { + if (!sep->IsSet(2)) return; + + if (sep->IsNumber(2) && atoi(sep->arg[2]) == 1) { + Transmute::HandleConfirmResponse(client, client->GetPlayer(), reinterpret_cast(item_id)); + } + } +} + +void Commands::Command_FindSpawn(Client* client, Seperator* sep) { + if(sep) + client->GetCurrentZone()->FindSpawn(client, (char*)sep->argplus[0]); +} + +void Commands::Command_MoveCharacter(Client* client, Seperator* sep) { + if(sep && sep->arg[0][0] && sep->arg[1][0]) + { + char* name = sep->arg[0]; + char* zoneName = sep->arg[1]; + + char query[256]; + snprintf(query, 256, "UPDATE characters c, zones z set c.x = z.safe_x, c.y = z.safe_y, c.z = z.safe_z, c.heading = z.safe_heading, c.current_zone_id = z.id where c.name = '%s' and z.name='%s'", name, zoneName); + if (database.RunQuery(query, strnlen(query, 256))) + { + client->Message(CHANNEL_COLOR_YELLOW, "Ran query:%s", query); + } + else + client->Message(CHANNEL_COLOR_RED, "Query FAILED to run: %s", query); + } +} + +void Commands::Command_Mood(Client* client, Seperator* sep) { +Player* player = client->GetPlayer(); + + if( sep && sep->arg[0] ) + { + const char* value = sep->arg[0]; + InfoStruct* info = player->GetInfoStruct(); + int32 cid = client->GetCharacterID(); + char* characterName = database.GetCharacterName(cid); + char tmp[1024]; // our emote string "xyz appears zyx" + //char properties vals + char* pname = "mood"; + char* pval; // mood value + bool pt; //used to verify return from DB. + + //This should never be seen. + sprintf(tmp, " "); + if( strncasecmp(value, "angry", strlen(value)) == 0 ) + { + sprintf(tmp, "%s appears angry", characterName); + pval = "11852"; + player->SetMoodState(11852, 1); + info->set_mood(11852); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "afraid", strlen(value)) == 0 ) + { + sprintf(tmp, "%s appears afraid", characterName); + pval = "11851"; + player->SetMoodState(11851, 1); + info->set_mood(11851); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "happy", strlen(value)) == 0 ) + { + sprintf(tmp, "%s appears happy", characterName); + pval = "11854"; + player->SetMoodState(11854, 1); + info->set_mood(11854); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "sad", strlen(value)) == 0 ) { + sprintf(tmp, "%s appears sad", characterName); + pval = "11856"; + player->SetMoodState(11856, 1); + info->set_mood(11856); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "tired", strlen(value)) == 0 ) + { + sprintf(tmp, "%s appears tired", characterName); + pval = "11857"; + player->SetMoodState(11857, 1); + info->set_mood(11857); + pt = database.insertCharacterProperty(client, pname, pval); + } + else if( strncasecmp(value, "none", strlen(value)) == 0 ) + { + //using 11855 mood_idle for none, I assume thats what its for? + pval = "11855"; + player->SetMoodState(11855, 1); + info->set_mood(11855); + pt = database.insertCharacterProperty(client, pname, pval); + //return since we have nothing left to do. No emote for none. + return; + }else{ + client->SimpleMessage(CHANNEL_NARRATIVE, "Listing Available Moods:"); + client->SimpleMessage(CHANNEL_NARRATIVE, "none"); + client->SimpleMessage(CHANNEL_NARRATIVE, "afraid"); + client->SimpleMessage(CHANNEL_NARRATIVE, "angry"); + client->SimpleMessage(CHANNEL_NARRATIVE, "happy"); + client->SimpleMessage(CHANNEL_NARRATIVE, "sad"); + client->SimpleMessage(CHANNEL_NARRATIVE, "tired"); + return; + } + + client->GetPlayer()->GetZone()->HandleChatMessage(0, 0, CHANNEL_EMOTE, tmp); + return; + } + + client->SimpleMessage(CHANNEL_NARRATIVE, "Listing Available Moods:"); + client->SimpleMessage(CHANNEL_NARRATIVE, "none"); + client->SimpleMessage(CHANNEL_NARRATIVE, "afraid"); + client->SimpleMessage(CHANNEL_NARRATIVE, "angry"); + client->SimpleMessage(CHANNEL_NARRATIVE, "happy"); + client->SimpleMessage(CHANNEL_NARRATIVE, "sad"); + client->SimpleMessage(CHANNEL_NARRATIVE, "tired"); + return; +} + +/* + Function: Command_CancelEffect() + Purpose : Cancels (good) effect spells + Example : /cancel_effect spell_id - would cancel the spell with the value in spell effects list +*/ +void Commands::Command_CancelEffect(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) + { + int32 spell_id = atoul(sep->arg[0]); + + SpellEffects* effect = client->GetPlayer()->GetSpellEffect(spell_id); + if(!effect || effect->spell->spell->GetSpellData()->det_type) { + return; + } + + MaintainedEffects* meffect = effect->caster->GetMaintainedSpell(spell_id); + + if (!meffect || !meffect->spell || !meffect->spell->caster || !meffect->spell->caster->GetZone() || + !meffect->spell->caster->GetZone()->GetSpellProcess()->DeleteCasterSpell(meffect->spell, "canceled", false, client->GetPlayer())) + client->Message(CHANNEL_COLOR_RED, "The spell effect could not be cancelled."); + } +} + + +/* + Function: Command_CurePlayer() + Purpose : Identifies spell to cast for cure based on type + Example : /cureplayer ?? + https://eq2.fandom.com/wiki/Update:58 + New command /cureplayer [playername|group or raid position][trauma|arcane|noxious|elemental|curse] optional [spell|potion] + + Example: /cureplayer g0 noxious spell + Will attempt to cure yourself of a noxious detriment with only spells and without using potions (even if you have them). + Example: /cureplayer r4 noxious + Will attempt to cure the character in raid slot 4 of a noxious detriment using a spell or potion (whichever is available). +*/ +void Commands::Command_CurePlayer(Client* client, Seperator* sep) +{ + Entity* target = nullptr; + bool use_spells = true; + bool use_potions = true; + if (sep && sep->arg[0] && sep->arg[1]) { + if(sep->arg[2]) { + std::string type(sep->arg[2]); + boost::algorithm::to_lower(type); + if(type == "potion") + use_spells = false; + else if(type == "spell") + use_potions = false; + } + + if(strlen(sep->arg[0]) > 1 && isdigit(sep->arg[0][1])) { + int32 mapped_position = (int32)(sep->arg[0][1]) - 48; + + // TODO: RAID Support ('r' argument) + if(sep->arg[0][0] == 'g' && !mapped_position) { + target = (Entity*)client->GetPlayer(); + } + else { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if(group) { + target = group->GetGroupMemberByPosition(client->GetPlayer(), mapped_position); + } + } + } + } + else { + Client* target_client = zone_list.GetClientByCharName(sep->arg[0]); + if(target_client && target_client->GetPlayer() && target_client->GetPlayer()->GetZone() == client->GetPlayer()->GetZone()) { + target = (Entity*)target_client->GetPlayer(); + } + } + + ItemEffectType type = EFFECT_CURE_TYPE_ALL; + bool successful_spell = false; + bool successful_potion = false; + if(target) { + SpellBookEntry* entry = nullptr; + std::string str(sep->arg[1]); + boost::algorithm::to_lower(str); + if(str == "arcane") { + type = EFFECT_CURE_TYPE_ARCANE; + // cure arcane spell missing in DB? + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureArcaneSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureArcaneSpellID)->GetInt32()); // cure noxious + } + } + else if(str == "trauma") { + type = EFFECT_CURE_TYPE_TRAUMA; + // cure trauma spell missing in DB? + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureTraumaSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureTraumaSpellID)->GetInt32()); // cure noxious + } + } + else if(str == "noxious") { + type = EFFECT_CURE_TYPE_NOXIOUS; + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureNoxiousSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureNoxiousSpellID)->GetInt32()); // cure noxious + } + } + else if(str == "curse") { + type = EFFECT_CURE_TYPE_CURSE; + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureCurseSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureCurseSpellID)->GetInt32()); // cure curse + } + } + else if(str == "magic") { + type = EFFECT_CURE_TYPE_MAGIC; + if(use_spells && rule_manager.GetGlobalRule(R_Spells, CureMagicSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureMagicSpellID)->GetInt32()); // cure magic + } + } + + if(use_spells) { + // check if any of the specific cure types are available, if not then check the base cures + if(!entry && rule_manager.GetGlobalRule(R_Spells, CureSpellID)->GetInt32()) { + entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureSpellID)->GetInt32()); // cure + } + + if(entry && entry->spell_id) { + Spell* spell = master_spell_list.GetSpell(entry->spell_id, entry->tier); + target->GetZone()->ProcessSpell(spell, (Entity*)client->GetPlayer(), target, true, false); + successful_spell = true; + } + } + } + + if(!successful_spell && use_potions && target && type != NO_EFFECT_TYPE) { + vector* potential_items = client->GetItemsByEffectType(type, EFFECT_CURE_TYPE_ALL); + if (potential_items && potential_items->size() > 0) { + vector::iterator itr; + for (itr = potential_items->begin(); itr != potential_items->end(); itr++) + { + Item* item = *itr; + if(client->UseItem(item, target)) { + successful_potion = true; + break; + } + } + } + safe_delete(potential_items); + } + } +} + + +/* + Function: Command_ShareQuest() + Purpose : Share quest with the group + Example : /share_quest [quest_id] +*/ +void Commands::Command_ShareQuest(Client* client, Seperator* sep) +{ + if (sep && sep->arg[0] && sep->IsNumber(0)) { + int32 quest_id = atoul(sep->arg[0]); + + bool hasQuest = client->GetPlayer()->HasAnyQuest(quest_id); + if(hasQuest) { + Quest* quest = master_quest_list.GetQuest(quest_id, false); + if(quest) { + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) { + group->ShareQuestWithGroup(client, quest); + } + } + } + } + else { + client->SimpleMessage(CHANNEL_COLOR_RED, "Cannot find quest."); + } + } +} + + + +/* + Function: Command_Yell() + Purpose : Yell to break an encounter + Example : /yell + * Uses self target, encounter target or no target +*/ +void Commands::Command_Yell(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + Spawn* res = nullptr; + Spawn* prev = nullptr; + bool cycleAll = false; + if (player->GetTarget() == player) { + cycleAll = true; // self target breaks all encounters + } + else if (player->GetTarget()) { + res = player->GetTarget(); // selected target other than self only dis-engages that encounter + } + + if (res && !client->GetPlayer()->IsEngagedBySpawnID(res->GetID())) + return; + + bool groupPermissionYell = true; + + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + // If the player has a group and has a target + if (gmi) { + deque::iterator itr; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group && !group->GetGroupOptions()->default_yell && !gmi->leader) { // default_yell_method = 0 means leader only + groupPermissionYell = false; + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + + if (!groupPermissionYell) { + LogWrite(COMMAND__ERROR, 0, "Command", "%s permission to yell denied due to group yell method set to leader only", client->GetPlayer()->GetName()); + return; + } + + bool alreadyYelled = false; + do { + if (!res && player->IsEngagedInEncounter(&res)) { // no target is set, dis-engage top of hated by list + + } + if (!res || prev == res) { + return; + } + + if (res->IsNPC() && ((NPC*)res)->Brain()) { + if (!alreadyYelled) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You yell for help!"); + string yellMsg = std::string(client->GetPlayer()->GetName()) + " yelled for help!"; + client->GetPlayer()->GetZone()->SimpleMessage(CHANNEL_COLOR_RED, yellMsg.c_str(), client->GetPlayer(), 35.0f, false); + client->GetPlayer()->GetZone()->SendYellPacket(client->GetPlayer()); + } + alreadyYelled = true; + + NPC* npc = (NPC*)res; + npc->Brain()->ClearEncounter(); + npc->SetLockedNoLoot(ENCOUNTER_STATE_BROKEN); + npc->UpdateEncounterState(ENCOUNTER_STATE_BROKEN); + } + prev = res; + res = nullptr; + } while (cycleAll); + + if (!player->IsEngagedInEncounter()) { + if (player->GetInfoStruct()->get_engaged_encounter()) { + player->GetInfoStruct()->set_engaged_encounter(0); + player->SetRegenValues((player->GetInfoStruct()->get_effective_level() > 0) ? player->GetInfoStruct()->get_effective_level() : player->GetLevel()); + client->GetPlayer()->SetCharSheetChanged(true); + player->info_changed = true; + } + } +} + + +/* + Function: Command_SetAutoLootMode() + Purpose : Set player auto loot mode (0 = disabled, 1 = need/lotto, 2 = decline). + Example : /setautolootmode [mode] +*/ +void Commands::Command_SetAutoLootMode(Client* client, Seperator* sep) { + if (sep && sep->IsNumber(0)) { + int8 mode = atoul(sep->arg[0]); + switch (mode) { + case AutoLootMode::METHOD_DISABLED: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Disabled auto loot mode"); + break; + } + case AutoLootMode::METHOD_ACCEPT: { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Enabled auto loot mode for need and lotto."); + break; + } + default: { + mode = AutoLootMode::METHOD_DECLINE; + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Enabled auto loot mode to decline need and lotto."); + break; + } + } + client->GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(mode); + database.insertCharacterProperty(client, CHAR_PROPERTY_AUTOLOOTMETHOD, (char*)std::to_string(mode).c_str()); + client->SendDefaultGroupOptions(); + } +} + +/* + Function: Command_AutoAttack() + Purpose : Attack / Auto Attack + Example : /attack, /autoattack type +*/ +void Commands::Command_AutoAttack(Client* client, Seperator* sep) { + int8 type = 1; + bool update = false; + Player* player = client->GetPlayer(); + if(!player) + return; + bool incombat = player->EngagedInCombat(); + if(sep && sep->arg[0] && sep->IsNumber(0)) + type = atoi(sep->arg[0]); + if(!client->GetPlayer()->Alive()){ + client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now."); + return; + } + if(type == 0){ + if(incombat) + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting."); + player->StopCombat(type); + update = true; + } + else { + if(type == 2){ + player->InCombat(false); + if(incombat && player->GetRangeAttack()){ + player->StopCombat(type); + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting."); + update = true; + } + else{ + player->SetRangeAttack(true); + player->InCombat(true, true); + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting."); + update = true; + } + } + else { + player->InCombat(false, true); + player->SetRangeAttack(false); + player->InCombat(true); + if(!incombat) { + client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting."); + update = true; + } + } + /*else + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You cannot attack that!");*/ + } + + if(update) { + player->SetCharSheetChanged(true); + } +} + +/* + Function: Command_Assist() + Purpose : Assist target + Example : /assist [name] + * Uses target or character name +*/ +void Commands::Command_Assist(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + Spawn* res = nullptr; + if(sep && sep->arg[0]) { + if(!stricmp(sep->arg[0], "on")) { + database.insertCharacterProperty(client, CHAR_PROPERTY_ASSISTAUTOATTACK, "1"); + return; + } + else if(!stricmp(sep->arg[0], "off")) { + database.insertCharacterProperty(client, CHAR_PROPERTY_ASSISTAUTOATTACK, "0"); + return; + } + Client* otherClient = client->GetPlayer()->GetZone()->GetClientByName(sep->arg[0]); + if(otherClient) { + res = otherClient->GetPlayer(); + } + } + + if (player->GetTarget()) { + res = player->GetTarget(); // selected target other than self only dis-engages that encounter + } + if(res && res->GetTarget()) { + res = res->GetTarget(); + } + + if(res) { + client->TargetSpawn(res); + + if(client->GetPlayer()->GetInfoStruct()->get_assist_auto_attack() && !player->EngagedInCombat()) { + Command_AutoAttack(client, nullptr); + } + } +} + +/* + Function: Command_Target() + Purpose : Target spawn/player + Example : /target [name] + * Uses target or character name +*/ +void Commands::Command_Target(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + Spawn* res = nullptr; + if(sep && sep->arg[0] && player->GetZone()) { + float max_distance = rule_manager.GetGlobalRule(R_Player, MaxTargetCommandDistance)->GetFloat(); + + if(max_distance < 1.0f) { + max_distance = 10.0f; + } + + player->GetZone()->SetPlayerTargetByName(client, (char*)sep->argplus[0], max_distance); + } +} + + +/* + Function: Command_Target_Pet() + Purpose : Target pet + Example : /target_pet + * Use to target pet +*/ +void Commands::Command_Target_Pet(Client* client, Seperator* sep) { + Entity* player = (Entity*)client->GetPlayer(); + Spawn* res = client->GetPlayer()->GetPet(); + + if(res) { + client->TargetSpawn(res); + } +} + diff --git a/source/WorldServer/Commands/Commands.h b/source/WorldServer/Commands/Commands.h new file mode 100644 index 0000000..bea8f47 --- /dev/null +++ b/source/WorldServer/Commands/Commands.h @@ -0,0 +1,976 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_COMMANDS__ +#define __EQ2_COMMANDS__ +#include "../../common/DataBuffer.h" +#include "../../common/MiscFunctions.h" +#include "../../common/types.h" +#include "../../common/opcodemgr.h" +#include +#include +#include +#include "../../common/debug.h" +using namespace std; + +class Client; +class Spawn; +class ZoneServer; +extern mapEQOpcodeManager; + +#define CHANNEL_COLOR_RED 3 +#define CHANNEL_COLOR_CHAT_RELATIONSHIP 4 +#define CHANNEL_COLOR_YELLOW 5 +#define CHANNEL_COLOR_NEW_LOOT 84 +#define CHANNEL_COLOR_NEWEST_LOOT 89 + +#define UPDATE_COLOR_WHITE 254 // For UpdateText + +#define CHANNEL_ALL_TEXT 0 +#define CHANNEL_GAME_TEXT 1 +#define CHANNEL_DEFAULT 2 +#define CHANNEL_ERROR 3 +#define CHANNEL_STATUS 4 +#define CHANNEL_MOTD 5 +#define CHANNEL_CHAT_TEXT 6 +#define CHANNEL_NEARBY_CHAT 7 +#define CHANNEL_SAY 8 +#define CHANNEL_SHOUT 9 +#define CHANNEL_EMOTE 10 +#define CHANNEL_YELL 11 +#define CHANNEL_NARRATIVE 12 //white +#define CHANNEL_NONPLAYER_SAY 13 +#define CHANNEL_GROUP_CHAT 14 +#define CHANNEL_GROUP_SAY 15 // Use this for group chat +#define CHANNEL_RAID_SAY 16 +#define CHANNEL_GUILD_CHAT 17 +#define CHANNEL_GUILD_SAY 18 // Use this for guild chat +#define CHANNEL_OFFICER_SAY 19 +#define CHANNEL_GUILD_MOTD 20 +#define CHANNEL_GUILD_MEMBER_ONLINE 21 +#define CHANNEL_GUILD_EVENT 22 +#define CHANNEL_GUILD_RECRUITING_PAGE 23 +#define CHANNEL_GUILD_RECRUITING_PAGE_OTHER 24 +#define CHANNEL_PRIVATE_CHAT 25 +#define CHANNEL_NONPLAYER_TELL 26 +#define CHANNEL_OBJECT_TEXT 27 +#define CHANNEL_PRIVATE_TELL 28 +#define CHANNEL_TELL_FROM_CS 29 +#define CHANNEL_ARENA 30 +#define CHANNEL_CHAT_CHANNEL_TEXT 31 +#define CHANNEL_OUT_OF_CHARACTER 32 +#define CHANNEL_AUCTION 33 +#define CHANNEL_CUSTOM_CHANNEL 34 // 34 is nothing, message with 34 as type will not show on client +#define CHANNEL_CHARACTER_TEXT 35 +#define CHANNEL_REWARD 36 +#define CHANNEL_DEATH 37 +#define CHANNEL_PET_CHAT 38 +#define CHANNEL_SKILL 39 +#define CHANNEL_FACTION 40 +// Combat related chat channels start here +#define CHANNEL_SPELLS 41 +#define CHANNEL_YOU_CAST 42 +#define CHANNEL_YOU_FAIL 43 +#define CHANNEL_CRITICAL_CAST 44 +#define CHANNEL_FRIENDLY_CAST 45 +#define CHANNEL_FRIENDLY_FAIL 46 +#define CHANNEL_OTHER_CAST 47 +#define CHANNEL_OTHER_FAIL 48 +#define CHANNEL_HOSTILE_CAST 49 +#define CHANNEL_HOSTILE_FAIL 50 +#define CHANNEL_WORN_OFF 51 +#define CHANNEL_SPELLS_OTHER 52 +#define CHANNEL_HEAL_SPELLS 53 +#define CHANNEL_HEALS 54 +#define CHANNEL_FRIENDLY_HEALS 55 +#define CHANNEL_OTHER_HEALS 56 +#define CHANNEL_HOSTILE_HEALS 57 +#define CHANNEL_CRITICAL_HEALS 58 +#define CHANNEL_COMBAT 59 +#define CHANNEL_GENERAL_COMBAT 60 +#define CHANNEL_HEROIC_OPPORTUNITY 61 +#define CHANNEL_NON_MELEE_DAMAGE 62 +#define CHANNEL_DAMAGE_SHIELD 63 +#define CHANNEL_WARD 64 +#define CHANNEL_DAMAGE_INTERCEPT 65 +#define CHANNEL_MELEE_COMBAT 66 +#define CHANNEL_WARNINGS 67 +#define CHANNEL_YOU_HIT 68 +#define CHANNEL_YOU_MISS 69 +#define CHANNEL_ATTACKER_HITS 70 +#define CHANNEL_ATTACKER_MISSES 71 +#define CHANNEL_YOUR_PET_HITS 72 +#define CHANNEL_YOUR_PET_MISSES 73 +#define CHANNEL_ATTACKER_HITS_PET 74 +#define CHANNEL_ATTACKER_MISSES_PET 75 +#define CHANNEL_OTHER_HIT 76 +#define CHANNEL_OTHER_MISSES 77 +#define CHANNEL_CRITICAL_HIT 78 +#define CHANNEL_HATE_ADJUSTMENTS 79 +#define CHANNEL_YOUR_HATE 80 +#define CHANNEL_OTHERS_HATE 81 +#define CHANNEL_DISPELS_AND_CURES 82 +#define CHANNEL_DISPEL_YOU 83 +#define CHANNEL_DISPEL_OTHER 84 +#define CHANNEL_CURE_YOU 85 +#define CHANNEL_CURE_OTHER 86 +// End of combat chat channels +#define CHANNEL_OTHER 87 +#define CHANNEL_MONEY_SPLIT 88 +#define CHANNEL_LOOT 89 +#define CHANNEL_LOOT_ROLLS 90 +#define CHANNEL_COMMAND_TEXT 91 +#define CHANNEL_BROADCAST 92 // Goes to all chat windows no matter what +#define CHANNEL_WHO 93 +#define CHANNEL_COMMANDS 94 +#define CHANNEL_MERCHANT 95 +#define CHANNEL_MERCHANT_BUY_SELL 96 +#define CHANNEL_CONSIDER_MESSAGE 97 +#define CHANNEL_CON_MINUS_2 98 +#define CHANNEL_CON_MINUS_1 99 +#define CHANNEL_CON_0 100 +#define CHANNEL_CON_1 101 +#define CHANNEL_CON_2 102 +#define CHANNEL_TRADESKILLS 103 +#define CHANNEL_HARVESTING 104 +#define CHANNEL_HARVESTING_WARNINGS 105 +// 106 is nothing, message sent with this channel won't display in the client +#define CHANNEL_VOICE_CHAT 107 +// 108+ will crash the client DO NOT USE + +/* Using this in the /zone details command so that we do not have to store a whole zone in memory while changing zone attributes. Also, + ran into a problem when deleting a zone pointer (for zones that were not running), it would try to shut down a zone which was not + running, causing world to crash. */ +struct ZoneInfo { + int32 id; + int8 expansion_id; + char name[64]; + char file[64]; + char description[256]; + float safe_x; + float safe_y; + float safe_z; + float underworld; + int8 min_recommended; + int8 max_recommended; + char zone_type[64]; + bool always_loaded; + bool city_zone; + sint16 min_status; + int16 min_level; + int16 max_level; + int8 start_zone; + int8 instance_type; + int32 default_reenter_time; + int32 default_reset_time; + int32 default_lockout_time; + int8 force_group_to_zone; + char lua_script[256]; + int32 shutdown_timer; + char zone_motd[256]; + float xp_modifier; + int16 min_version; + bool weather_allowed; + int32 ruleset_id; + char sky_file[64]; +}; + +class EQ2_CommandString : public DataBuffer{ +public: + EQ2_CommandString(){ handler = 0; } + EQ2_CommandString(uchar* buffer, int32 size){ + InitializeLoadData(buffer, size); + LoadData(handler); + LoadDataString(command); + } + EQ2_16BitString command; + int16 handler; +}; +class EQ2_RemoteCommandString : public DataBuffer{ +public: + EQ2_RemoteCommandString(){ handler = 0; } + EQ2_RemoteCommandString(char* name, int32 in_handler, sint16 in_status){ + command.data = string(name); + command.size = command.data.length(); + handler = in_handler; + required_status = in_status; + } + EQ2_RemoteCommandString(uchar* buffer, int32 size){ + required_status = 0; + InitializeLoadData(buffer, size); + LoadData(handler); + LoadDataString(command); + } + + EQ2_8BitString command; + int16 handler; + sint16 required_status; +}; +class RemoteCommands { +public: + RemoteCommands(){ num_commands = 0; buffer.clear(); } + int16 num_commands; + vector commands; + void addCommand(EQ2_RemoteCommandString add){ commands.push_back(add); num_commands++;} + void AddSubCommand(string command, EQ2_RemoteCommandString subcommand){ + subcommands[command][subcommand.command.data] = subcommand; + } + bool validSubCommand(string command, string subcommand){ + if(subcommands.count(command) > 0 && subcommands[command].count(subcommand) > 0) + return true; + return false; + } + void addZero(){ + num_commands++; + EQ2_RemoteCommandString add; + add.handler = 0; + add.required_status = 300; + add.command.size = 0; + commands.push_back(add); + } + void CheckAddSubCommand(string command, EQ2_RemoteCommandString subcommand){ + vector::iterator itr; + for(itr = commands.begin(); itr != commands.end();itr++){ + if((*itr).command.data == command){ + AddSubCommand(command, subcommand); + return; + } + } + // TODO: cannot seem to use LogWrite in this .h file! + printf("Unable to find parent command '%s' for subcommand: '%s'\n\tEvery subcommand must have a parent command!", command.c_str(), subcommand.command.data.c_str()); + } + void AddDataCommand(EQ2_RemoteCommandString* command){ + buffer.append((char*)&command->command.size, sizeof(command->command.size)); + if(command->command.size>0) + buffer.append(command->command.data); + } + int32 GetCommandHandler(const char* name){ + if(!name) + return 0xFFFFFFFF; + int8 name_size = strlen(name); + for(int32 i = 0; i < commands.size(); i++){ + if(commands[i].command.size > 0){ + if(strncasecmp(commands[i].command.data.c_str(), name, name_size) == 0) + return commands[i].handler; + } + } + return 0xFFFFFFFF; + } + string buffer; + EQ2Packet* serialize(int16 version = 0); + map > subcommands; +}; +class Commands{ +public: + Commands(); + ~Commands(); + bool SetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value, bool send_update = true, bool temporary = false, string* temp_value = 0, int8 index = 0); + void UpdateDatabaseAppearance(Client* client, Spawn* target, string fieldName, sint8 r, sint8 g, sint8 b); + bool SetZoneCommand(Client* client, int32 zone_id, ZoneServer* zone, int8 type, const char* value); + RemoteCommands* GetRemoteCommands() { return remote_commands; } + void Process(int32 index, EQ2_16BitString* command_parms, Client* client, Spawn* targetOverride=NULL); + int32 GetCommandHandler(const char* name){ + return remote_commands->GetCommandHandler(name); + } + int32 GetSpawnSetType(string val); + + // JA: New Command handlers + void Command_AcceptAdvancement(Client* client, Seperator* sep); + void Command_AFK(Client* client, Seperator* sep); + void Command_Appearance(Client* client, Seperator* sep, int handler); + void Command_CancelMaintained(Client* client, Seperator* sep); + void Command_Claim(Client* client, Seperator* sep); + void Command_ClearAllQueued(Client* client); + void Command_Create(Client* client, Seperator* sep); + void Command_CreateFromRecipe(Client* client, Seperator* sep); + void Command_Distance(Client* client); + void Command_Duel(Client* client, Seperator* sep); + void Command_DuelBet(Client* client, Seperator* sep); + void Command_DuelAccept(Client* client, Seperator* sep); + void Command_DuelDecline(Client* client, Seperator* sep); + void Command_DuelSurrender(Client* client, Seperator* sep); + void Command_DuelToggle(Client* client, Seperator* sep); + void Command_EntityCommand(Client* client, Seperator* sep, int handler); + void Command_Follow(Client* client, Seperator* sep); + void Command_StopFollow(Client* client, Seperator* sep); + void Command_Grid(Client* client, Seperator* sep); + void Command_Guild(Client* client, Seperator* sep); + void Command_CreateGuild(Client* client); + void Command_SetGuildOfficerNote(Client* client, Seperator* sep); + void Command_SetGuildMemberNote(Client* client, Seperator* sep); + void Command_OfficerSay(Client* client, Seperator* sep); + void Command_GuildSay(Client* client, Seperator* sep); + void Command_Guilds(Client* client); + void Command_GuildsAdd(Client* client, Seperator* sep); + void Command_GuildsCreate(Client* client, Seperator* sep); + void Command_GuildsDelete(Client* client, Seperator* sep); + void Command_GuildsList(Client* client); + void Command_GuildsRemove(Client* client, Seperator* sep); + void Command_InspectPlayer(Client* client, Seperator* sep); + void Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteCommandString* command); + void Command_Languages(Client* client, Seperator* sep); + void Command_SetLanguage(Client* client, Seperator* sep); + void Command_LastName(Client* client, Seperator* sep); + void Command_ConfirmLastName(Client* client, Seperator* sep); + void Command_Location(Client* client); + void Command_LocationAdd(Client* client, Seperator* sep); + void Command_LocationCreate(Client* client, Seperator* sep); + void Command_LocationDelete(Client* client, Seperator* sep); + void Command_LocationList(Client* client, Seperator* sep); + void Command_LocationRemove(Client* client, Seperator* sep); + void Command_Merchant(Client* client, Seperator* sep, int handler); + //devn00b + void Command_Mood(Client* client, Seperator* sep); + + void Command_Modify(Client* client); // usage function + void Command_ModifyCharacter(Client* client, Seperator* sep); + void Command_ModifyFaction(Client* client, Seperator* sep); + void Command_ModifyGuild(Client* client, Seperator* sep); + void Command_ModifyItem(Client* client, Seperator* sep); + void Command_ModifyQuest(Client* client, Seperator* sep); + void Command_ModifySkill(Client* client, Seperator* sep); + void Command_ModifySpawn(Client* client, Seperator* sep); + void Command_ModifySpell(Client* client, Seperator* sep); + void Command_ModifyZone(Client* client, Seperator* sep); + + void Command_MOTD(Client* client); + void Command_Pet(Client* client, Seperator* sep); + void Command_PetName(Client* client, Seperator* sep); + void Command_NamePet(Client* client, Seperator* sep); + void Command_Rename(Client* client, Seperator* sep); + void Command_ConfirmRename(Client* client, Seperator* sep); + void Command_PetOptions(Client* client, Seperator* sep); + void Command_Random(Client* client, Seperator* sep); + void Command_Randomize(Client* client, Seperator* sep); + void Command_ReportBug(Client* client, Seperator* sep); + void Command_ShowCloak(Client* client, Seperator* sep); + void Command_ShowHelm(Client* client, Seperator* sep); + void Command_ShowHood(Client* client, Seperator* sep); + void Command_ShowHoodHelm(Client* client, Seperator* sep); + void Command_ShowRanged(Client* client, Seperator* sep); + void Command_Skills(Client* client, Seperator* sep, int handler); + void Command_SpawnTemplate(Client* client, Seperator* sep); + void Command_Speed(Client* client, Seperator* sep); + void Command_StationMarketPlace(Client* client, Seperator* sep); + void Command_StopEating(Client* client); + void Command_StopDrinking(Client* client); + void Command_Test(Client* client, EQ2_16BitString* command_parms); + void Command_Title(Client* client); + void Command_TitleList(Client* client); + void Command_TitleSetPrefix(Client* client, Seperator* sep); + void Command_TitleSetSuffix(Client* client, Seperator* sep); + void Command_TitleFix(Client* client, Seperator* sep); + void Command_Toggle_Anonymous(Client* client); + void Command_Toggle_AutoConsume(Client* client, Seperator* sep); + void Command_Toggle_BonusXP(Client* client); + void Command_Toggle_CombatXP(Client* client); + void Command_Toggle_GMHide(Client* client); + void Command_Toggle_GMVanish(Client* client); + void Command_Toggle_Illusions(Client* client, Seperator* sep); + void Command_Toggle_LFG(Client* client); + void Command_Toggle_LFW(Client* client); + void Command_Toggle_QuestXP(Client* client); + void Command_Toggle_Roleplaying(Client* client); + void Command_Toggle_Duels(Client* client); + void Command_Toggle_Trades(Client* client); + void Command_Toggle_Guilds(Client* client); + void Command_Toggle_Groups(Client* client); + void Command_Toggle_Raids(Client* client); + void Command_Toggle_LON(Client* client); + void Command_Toggle_VoiceChat(Client* client); + void Command_Track(Client* client); + void Command_TradeStart(Client* client, Seperator* sep); + void Command_TradeAccept(Client* client, Seperator* sep); + void Command_TradeReject(Client* client, Seperator* sep); + void Command_TradeCancel(Client* client, Seperator* sep); + void Command_TradeSetCoin(Client* client, Seperator* sep); + void Command_TradeAddCoin(Client* client, Seperator* sep, int handler); + void Command_TradeRemoveCoin(Client* client, Seperator* sep, int handler); + void Command_TradeAddItem(Client* client, Seperator* sep); + void Command_TradeRemoveItem(Client* client, Seperator* sep); + void Command_TryOn(Client* client, Seperator* sep); + void Command_JoinChannel(Client *client, Seperator *sep); + void Command_JoinChannelFromLoad(Client *client, Seperator *sep); + void Command_TellChannel(Client *client, Seperator *sep); + void Command_LeaveChannel(Client *client, Seperator *sep); + void Command_WeaponStats(Client *client); + void Command_WhoChannel(Client *client, Seperator *sep); + void Command_ZoneSafeCoords(Client *client, Seperator *sep); + void Command_ZoneDetails(Client *client, Seperator *sep); + void Command_ZoneSet(Client *client, Seperator *sep); + void Command_Rain(Client* client, Seperator* sep); + void Command_Wind(Client* client, Seperator* sep); + void Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell = false); + void Command_Weather(Client* client, Seperator* sep); + void Command_Select(Client* client, Seperator* sep); + void Command_ConsumeFood(Client* client, Seperator* sep); + void Command_Aquaman(Client* client, Seperator* sep); + void Command_Attune_Inv(Client* client, Seperator* sep); + void Command_Player(Client* client, Seperator* sep); + void Command_Player_Coins(Client* client, Seperator* sep); + void Command_Reset_Zone_Timer(Client* client, Seperator* sep); + void Command_AchievementAdd(Client* client, Seperator* sep); + void Command_Editor(Client* client, Seperator* sep); + void Command_AcceptResurrection(Client* client, Seperator* sep); + void Command_DeclineResurrection(Client* client, Seperator* set); + void Command_TargetItem(Client* client, Seperator* set); + + void Command_FindSpawn(Client* client, Seperator* set); + + void Command_MoveCharacter(Client* client, Seperator* set); + + // Bot Commands + void Command_Bot(Client* client, Seperator* sep); + void Command_Bot_Create(Client* client, Seperator* sep); + void Command_Bot_Customize(Client* client, Seperator* sep); + void Command_Bot_Spawn(Client* client, Seperator* sep); + void Command_Bot_List(Client* client, Seperator* sep); + void Command_Bot_Inv(Client* client, Seperator* sep); + void Command_Bot_Settings(Client* client, Seperator* sep); + void Command_Bot_Help(Client* client, Seperator* sep); + + void Command_CancelEffect(Client* client, Seperator* sep); + void Command_CurePlayer(Client* client, Seperator* sep); + void Command_ShareQuest(Client* client, Seperator* sep); + void Command_Yell(Client* client, Seperator* sep); + void Command_SetAutoLootMode(Client* client, Seperator* sep); + void Command_AutoAttack(Client* client, Seperator* sep); + void Command_Assist(Client* client, Seperator* sep); + void Command_Target(Client* client, Seperator* sep); + void Command_Target_Pet(Client* client, Seperator* sep); + + // AA Commands + void Get_AA_Xml(Client* client, Seperator* sep); + void Add_AA(Client* client, Seperator* sep); + void Commit_AA_Profile(Client* client, Seperator* sep); + void Begin_AA_Profile(Client* client, Seperator* sep); + void Back_AA(Client* client, Seperator* sep); + void Remove_AA(Client* client, Seperator* sep); + void Switch_AA_Profile(Client* client, Seperator* sep); + void Cancel_AA_Profile(Client* client, Seperator* sep); + void Save_AA_Profile(Client* client, Seperator* sep); +private: + RemoteCommands* remote_commands; + map spawn_set_values; + map zone_set_values; +}; +#define SPAWN_SET_VALUE_LIST 0 +#define SPAWN_SET_VALUE_NAME 1 +#define SPAWN_SET_VALUE_LEVEL 2 +#define SPAWN_SET_VALUE_DIFFICULTY 3 +#define SPAWN_SET_VALUE_MODEL_TYPE 4 +#define SPAWN_SET_VALUE_CLASS 5 +#define SPAWN_SET_VALUE_GENDER 6 +#define SPAWN_SET_VALUE_SHOW_NAME 7 +#define SPAWN_SET_VALUE_ATTACKABLE 8 +#define SPAWN_SET_VALUE_SHOW_LEVEL 9 +#define SPAWN_SET_VALUE_TARGETABLE 10 +#define SPAWN_SET_VALUE_SHOW_COMMAND_ICON 11 +#define SPAWN_SET_VALUE_HAND_ICON 12 +#define SPAWN_SET_VALUE_HAIR_TYPE 13 +#define SPAWN_SET_VALUE_FACIAL_HAIR_TYPE 14 +#define SPAWN_SET_VALUE_WING_TYPE 15 +#define SPAWN_SET_VALUE_CHEST_TYPE 16 +#define SPAWN_SET_VALUE_LEGS_TYPE 17 +#define SPAWN_SET_VALUE_SOGA_HAIR_TYPE 18 +#define SPAWN_SET_VALUE_SOGA_FACIAL_HAIR_TYPE 19 +#define SPAWN_SET_VALUE_SOGA_MODEL_TYPE 20 +#define SPAWN_SET_VALUE_SIZE 21 +#define SPAWN_SET_VALUE_HP 22 +#define SPAWN_SET_VALUE_POWER 23 +#define SPAWN_SET_VALUE_HEROIC 24 +#define SPAWN_SET_VALUE_RESPAWN 25 +#define SPAWN_SET_VALUE_X 26 +#define SPAWN_SET_VALUE_Y 27 +#define SPAWN_SET_VALUE_Z 28 +#define SPAWN_SET_VALUE_HEADING 29 +#define SPAWN_SET_VALUE_LOCATION 30 +#define SPAWN_SET_VALUE_COMMAND_PRIMARY 31 +#define SPAWN_SET_VALUE_COMMAND_SECONDARY 32 +#define SPAWN_SET_VALUE_VISUAL_STATE 33 +#define SPAWN_SET_VALUE_ACTION_STATE 34 +#define SPAWN_SET_VALUE_MOOD_STATE 35 +#define SPAWN_SET_VALUE_INITIAL_STATE 36 +#define SPAWN_SET_VALUE_ACTIVITY_STATE 37 +#define SPAWN_SET_VALUE_COLLISION_RADIUS 38 +#define SPAWN_SET_VALUE_FACTION 39 +#define SPAWN_SET_VALUE_SPAWN_SCRIPT 40 +#define SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT 41 +#define SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT 42 +#define SPAWN_SET_VALUE_SUB_TITLE 43 +#define SPAWN_SET_VALUE_EXPIRE 45 +#define SPAWN_SET_VALUE_EXPIRE_OFFSET 46 +#define SPAWN_SET_VALUE_X_OFFSET 47 +#define SPAWN_SET_VALUE_Y_OFFSET 48 +#define SPAWN_SET_VALUE_Z_OFFSET 49 +#define SPAWN_SET_VALUE_DEVICE_ID 50 +#define SPAWN_SET_VALUE_PITCH 51 +#define SPAWN_SET_VALUE_ROLL 52 +#define SPAWN_SET_VALUE_HIDE_HOOD 53 +#define SPAWN_SET_VALUE_EMOTE_STATE 54 +#define SPAWN_SET_VALUE_ICON 55 +#define SPAWN_SET_VALUE_PREFIX 56 +#define SPAWN_SET_VALUE_SUFFIX 57 +#define SPAWN_SET_VALUE_LASTNAME 58 +#define SPAWN_SET_VALUE_EXPANSION_FLAG 59 +#define SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL 60 +#define SPAWN_SET_VALUE_MERCHANT_MAX_LEVEL 61 +#define SPAWN_SET_VALUE_HOLIDAY_FLAG 62 +#define SPAWN_SET_SKIN_COLOR 63 +#define SPAWN_SET_AAXP_REWARDS 64 + +#define SPAWN_SET_HAIR_COLOR1 65 +#define SPAWN_SET_HAIR_COLOR2 66 +#define SPAWN_SET_HAIR_TYPE_COLOR 67 +#define SPAWN_SET_HAIR_FACE_COLOR 68 +#define SPAWN_SET_HAIR_TYPE_HIGHLIGHT_COLOR 69 +#define SPAWN_SET_HAIR_FACE_HIGHLIGHT_COLOR 70 +#define SPAWN_SET_HAIR_HIGHLIGHT 71 +#define SPAWN_SET_MODEL_COLOR 72 +#define SPAWN_SET_EYE_COLOR 73 +#define SPAWN_SET_SOGA_SKIN_COLOR 74 +#define SPAWN_SET_SOGA_HAIR_COLOR1 75 +#define SPAWN_SET_SOGA_HAIR_COLOR2 76 +#define SPAWN_SET_SOGA_HAIR_TYPE_COLOR 77 +#define SPAWN_SET_SOGA_HAIR_FACE_COLOR 78 +#define SPAWN_SET_SOGA_HAIR_TYPE_HIGHLIGHT_COLOR 79 +#define SPAWN_SET_SOGA_HAIR_FACE_HIGHLIGHT_COLOR 80 +#define SPAWN_SET_SOGA_HAIR_HIGHLIGHT 81 +#define SPAWN_SET_SOGA_MODEL_COLOR 82 +#define SPAWN_SET_SOGA_EYE_COLOR 83 + +#define SPAWN_SET_CHEEK_TYPE 84 +#define SPAWN_SET_CHIN_TYPE 85 +#define SPAWN_SET_EAR_TYPE 86 +#define SPAWN_SET_EYE_BROW_TYPE 87 +#define SPAWN_SET_EYE_TYPE 88 +#define SPAWN_SET_LIP_TYPE 89 +#define SPAWN_SET_NOSE_TYPE 90 +#define SPAWN_SET_BODY_SIZE 91 +#define SPAWN_SET_BODY_AGE 92 +#define SPAWN_SET_SOGA_CHEEK_TYPE 93 +#define SPAWN_SET_SOGA_CHIN_TYPE 94 +#define SPAWN_SET_SOGA_EAR_TYPE 95 +#define SPAWN_SET_SOGA_EYE_BROW_TYPE 96 +#define SPAWN_SET_SOGA_EYE_TYPE 97 +#define SPAWN_SET_SOGA_LIP_TYPE 98 +#define SPAWN_SET_SOGA_NOSE_TYPE 99 +#define SPAWN_SET_SOGA_BODY_SIZE 100 +#define SPAWN_SET_SOGA_BODY_AGE 101 + +#define SPAWN_SET_ATTACK_TYPE 102 +#define SPAWN_SET_RACE_TYPE 103 +#define SPAWN_SET_LOOT_TIER 104 +#define SPAWN_SET_LOOT_DROP_TYPE 105 +#define SPAWN_SET_SCARED_STRONG_PLAYERS 106 + +#define ZONE_SET_VALUE_EXPANSION_ID 0 +#define ZONE_SET_VALUE_NAME 1 +#define ZONE_SET_VALUE_FILE 2 +#define ZONE_SET_VALUE_DESCRIPTION 3 +#define ZONE_SET_VALUE_SAFE_X 4 +#define ZONE_SET_VALUE_SAFE_Y 5 +#define ZONE_SET_VALUE_SAFE_Z 6 +#define ZONE_SET_VALUE_UNDERWORLD 7 +#define ZONE_SET_VALUE_MIN_RECOMMENDED 8 +#define ZONE_SET_VALUE_MAX_RECOMMENDED 9 +#define ZONE_SET_VALUE_ZONE_TYPE 10 +#define ZONE_SET_VALUE_ALWAYS_LOADED 11 +#define ZONE_SET_VALUE_CITY_ZONE 12 +#define ZONE_SET_VALUE_MIN_STATUS 13 +#define ZONE_SET_VALUE_MIN_LEVEL 14 +#define ZONE_SET_VALUE_START_ZONE 15 +#define ZONE_SET_VALUE_INSTANCE_TYPE 16 +#define ZONE_SET_VALUE_DEFAULT_REENTER_TIME 17 +#define ZONE_SET_VALUE_DEFAULT_RESET_TIME 18 +#define ZONE_SET_VALUE_DEFAULT_LOCKOUT_TIME 19 +#define ZONE_SET_VALUE_FORCE_GROUP_TO_ZONE 20 +#define ZONE_SET_VALUE_LUA_SCRIPT 21 +#define ZONE_SET_VALUE_SHUTDOWN_TIMER 22 +#define ZONE_SET_VALUE_ZONE_MOTD 23 +#define ZONE_SET_VALUE_MAX_LEVEL 24 +#define ZONE_SET_VALUE_WEATHER_ALLOWED 25 + +#define COMMAND_SPAWN 1 +#define COMMAND_RACE 2 +#define COMMAND_LEVEL 3 +#define COMMAND_CLASS 4 +#define COMMAND_NAME 6 +#define COMMAND_SAY 7 +#define COMMAND_TELL 8 +#define COMMAND_YELL 9 +#define COMMAND_SHOUT 10 +#define COMMAND_AUCTION 11 +#define COMMAND_OOC 12 +#define COMMAND_EMOTE 13 +#define COMMAND_GROUPSAY 14 +#define COMMAND_GROUPINVITE 15 +#define COMMAND_GROUPDISBAND 16 +#define COMMAND_GROUP 17 +#define COMMAND_CLAIM 18 +#define COMMAND_HAIL 19 +#define COMMAND_ZONE 20 +#define COMMAND_ADMINFLAG 21 +#define COMMAND_KICK 22 +#define COMMAND_BAN 23 +#define COMMAND_INVENTORY 24 +#define COMMAND_SUMMONITEM 25 +#define COMMAND_FOLLOW 26 +#define COMMAND_STOP_FOLLOW 27 +#define COMMAND_LASTNAME 28 +#define COMMAND_CONFIRMLASTNAME 29 +#define COMMAND_COLLECTION_ADDITEM 30 +#define COMMAND_COLLECTION_FILTER_MATCHITEM 31 +#define COMMAND_MOVE 32 +#define COMMAND_INFO 33 +#define COMMAND_USEABILITY 34 +#define COMMAND_ENABLE_ABILITY_QUE 35 +#define COMMAND_RELOAD_ITEMS 36 +#define COMMAND_AUTO_ATTACK 37 +#define COMMAND_WEATHER 38 +#define COMMAND_SPEED 39 +#define COMMAND_SPAWN_MOVE 40 +#define COMMAND_WHO 41 +#define COMMAND_VERSION 42 +#define COMMAND_SPAWN_ADD 43 +#define COMMAND_SPAWN_CREATE 44 +#define COMMAND_SPAWN_SET 45 +#define COMMAND_SPAWN_REMOVE 46 +#define COMMAND_SPAWN_LIST 47 +#define COMMAND_SIT 48 +#define COMMAND_STAND 49 +#define COMMAND_SPAWN_TARGET 50 +#define COMMAND_SPAWN_EQUIPMENT 51 +#define COMMAND_SPAWN_DETAILS 52 +#define COMMAND_SELECT_JUNCTION 53 +#define COMMAND_KILL 54 +#define COMMAND_SUMMON 55 +#define COMMAND_GOTO 56 +#define COMMAND_FLYMODE 57 +#define COMMAND_SETTIME 58 +#define COMMAND_RELOAD_SPELLS 59 +#define COMMAND_LOOT 60 +#define COMMAND_USE 61 +#define COMMAND_RELOADSPAWNSCRIPTS 62 +#define COMMAND_RELOADLUASYSTEM 63 +#define COMMAND_RELOADSTRUCTS 64 +#define COMMAND_RELOAD 65 +#define COMMAND_LOOT_LIST 66 +#define COMMAND_LOOT_SETCOIN 67 +#define COMMAND_LOOT_ADDITEM 68 +#define COMMAND_LOOT_REMOVEITEM 69 +#define COMMAND_BANK 70 +#define COMMAND_BANK_DEPOSIT 71 +#define COMMAND_BANK_WITHDRAWAL 72 +#define COMMAND_BANK_CANCEL 73 +#define COMMAND_ATTACK 74 +#define COMMAND_REPORT_BUG 75 +#define COMMAND_ACCEPT_QUEST 76 +#define COMMAND_DECLINE_QUEST 77 +#define COMMAND_DELETE_QUEST 78 +#define COMMAND_RELOAD_QUESTS 79 +#define COMMAND_SPAWN_COMBINE 80 +#define COMMAND_DEPOP 81 +#define COMMAND_REPOP 82 +#define COMMAND_LUADEBUG 83 +#define COMMAND_TEST 84 +#define COMMAND_ACCEPT_REWARD 85 +#define COMMAND_FROM_MERCHANT 86 +#define COMMAND_MERCHANT_BUY 87 +#define COMMAND_MERCHANT_SELL 88 +#define COMMAND_CANCEL_MERCHANT 89 +#define COMMAND_START_MERCHANT 90 +#define COMMAND_BUYBACK 91 +#define COMMAND_SEARCH_STORES 92 +#define COMMAND_INVULNERABLE 93 +#define COMMAND_SEARCH_STORES_PAGE 94 +#define COMMAND_BUY_FROM_BROKER 95 +#define COMMAND_GROUP_ACCEPT_INVITE 96 +#define COMMAND_GROUP_DECLINE_INVITE 97 +#define COMMAND_RELOAD_GROUNDSPAWNS 98 +#define COMMAND_RELOAD_SPAWNS 99 +#define COMMAND_LOCK 100 +#define COMMAND_GIVEITEM 101 +#define COMMAND_SET_COMBAT_VOICE 102 +#define COMMAND_SET_EMOTE_VOICE 103 +#define COMMAND_RELOAD_ZONESCRIPTS 104 +#define COMMAND_GROUP_LEAVE 105 +#define COMMAND_GROUP_MAKE_LEADER 106 +#define COMMAND_GROUP_KICK 107 +#define COMMAND_FRIEND_ADD 108 +#define COMMAND_FRIEND_REMOVE 109 +#define COMMAND_FRIENDS 110 +#define COMMAND_IGNORE_ADD 111 +#define COMMAND_IGNORE_REMOVE 112 +#define COMMAND_IGNORES 113 +#define COMMAND_MENDER_REPAIR 114 +#define COMMAND_MENDER_REPAIR_ALL 115 +#define COMMAND_REPAIR 116 +#define COMMAND_USE_ITEM 117 +#define COMMAND_WEAPONSTATS 118 +#define COMMAND_START_MAIL 119 +#define COMMAND_GET_MAIL_MESSAGE 120 +#define COMMAND_TAKE_MAIL_ATTACHMENTS 121 +#define COMMAND_REPORT_SPAM 122 +#define COMMAND_CANCEL_MAIL 123 +#define COMMAND_ADD_MAIL_PLAT 124 +#define COMMAND_ADD_MAIL_GOLD 125 +#define COMMAND_ADD_MAIL_SILVER 126 +#define COMMAND_ADD_MAIL_COPPER 127 +#define COMMAND_SET_MAIL_ITEM 128 +#define COMMAND_CANCEL_SEND_MAIL 129 +#define COMMAND_REMOVE_MAIL_PLAT 130 +#define COMMAND_REMOVE_MAIL_GOLD 131 +#define COMMAND_REMOVE_MAIL_SILVER 132 +#define COMMAND_REMOVE_MAIL_COPPER 133 +#define COMMAND_DELETE_MAIL_MESSAGE 134 +#define COMMAND_TRACK 135 +#define COMMAND_INSPECT_PLAYER 136 +#define COMMAND_PET 137 +#define COMMAND_PETNAME 138 +#define COMMAND_NAME_PET 139 +#define COMMAND_RENAME 140 +#define COMMAND_CONFIRMRENAME 141 +#define COMMAND_PETOPTIONS 142 +#define COMMAND_SPAWN_TEMPLATE 143 // JA: new /spawn template command +#define COMMAND_CANNEDEMOTE 144 +#define COMMAND_BROADCAST 145 +#define COMMAND_ANNOUNCE 146 +#define COMMAND_AFK 147 +#define COMMAND_TOGGLE_ANONYMOUS 148 +#define COMMAND_TOGGLE_LFW 149 +#define COMMAND_TOGGLE_LFG 150 +#define COMMAND_SHOW_RANGED 151 +#define COMMAND_TOGGLE_AUTOCONSUME 152 +#define COMMAND_SHOW_HELM 153 +#define COMMAND_SHOW_HOOD_OR_HELM 154 +#define COMMAND_SHOW_CLOAK 155 +#define COMMAND_STOP_EATING 156 +#define COMMAND_STOP_DRINKING 157 +#define COMMAND_TOGGLE_ILLUSIONS 158 +#define COMMAND_SHOW_HOOD 159 +#define COMMAND_TOGGLE_DUELS 160 +#define COMMAND_TOGGLE_TRADES 161 +#define COMMAND_TOGGLE_GUILDS 162 +#define COMMAND_TOGGLE_GROUPS 163 +#define COMMAND_TOGGLE_RAIDS 164 +#define COMMAND_TOGGLE_LON 165 + +#define COMMAND_TOGGLE_GM_HIDE 167 +#define COMMAND_TOGGLE_GM_VANISH 168 +#define COMMAND_SPAWN_GROUP 169 +#define COMMAND_TOGGLE_ROLEPLAYING 170 +#define COMMAND_TOGGLE_VCINVITE 171 +#define COMMAND_START_TRADE 172 +#define COMMAND_ACCEPT_TRADE 173 +#define COMMAND_REJECT_TRADE 174 +#define COMMAND_CANCEL_TRADE 175 +#define COMMAND_SET_TRADE_COIN 176 +#define COMMAND_ADD_TRADE_COPPER 177 +#define COMMAND_ADD_TRADE_SILVER 178 +#define COMMAND_ADD_TRADE_GOLD 179 +#define COMMAND_ADD_TRADE_PLAT 180 +#define COMMAND_REMOVE_TRADE_COPPER 181 +#define COMMAND_REMOVE_TRADE_SILVER 182 +#define COMMAND_REMOVE_TRADE_GOLD 183 +#define COMMAND_REMOVE_TRADE_PLAT 184 +#define COMMAND_ADD_TRADE_ITEM 185 +#define COMMAND_REMOVE_TRADE_ITEM 186 +#define COMMAND_TOGGLE_COMBAT_EXP 187 +#define COMMAND_TOGGLE_QUEST_EXP 188 +#define COMMAND_TOGGLE_BONUS_EXP 189 +#define COMMAND_ZONE_SHUTDOWN 190 +#define COMMAND_ZONE_SAFE 191 +#define COMMAND_ZONE_REVIVE 192 +#define COMMAND_RELOAD_ZONES 193 + +#define COMMAND_DUEL 200 +#define COMMAND_DUELBET 201 +#define COMMAND_DUEL_ACCEPT 202 +#define COMMAND_DUEL_DECLINE 203 +#define COMMAND_DUEL_SURRENDER 204 +#define COMMAND_DUEL_TOGGLE 205 + +#define COMMAND_ANIMTEST 211 +#define COMMAND_ITEMSEARCH 212 + +#define COMMAND_ACTION 232 // JA: What is this? Exists nowhere else... +#define COMMAND_SKILL_ADD 233 +#define COMMAND_SKILL_REMOVE 234 +#define COMMAND_SKILL_LIST 235 +#define COMMAND_SKILL 236 +#define COMMAND_ZONE_SET 237 +#define COMMAND_ZONE_DETAILS 238 +#define COMMAND_RANDOMIZE 239 +#define COMMAND_RELOAD_ENTITYCOMMANDS 240 +#define COMMAND_ENTITYCOMMAND 241 +#define COMMAND_ENTITYCOMMAND_LIST 242 +#define COMMAND_RELOAD_FACTIONS 243 +#define COMMAND_MERCHANT 244 +#define COMMAND_MERCHANT_LIST 245 +#define COMMAND_APPEARANCE 246 +#define COMMAND_APPEARANCE_LIST 247 +#define COMMAND_RELOAD_MAIL 248 +#define COMMAND_DISTANCE 249 +#define COMMAND_GUILDSAY 250 +#define COMMAND_OFFICERSAY 251 +#define COMMAND_GUILD 252 +#define COMMAND_SET_GUILD_MEMBER_NOTE 253 +#define COMMAND_SET_GUILD_OFFICER_NOTE 254 +#define COMMAND_RELOAD_GUILDS 255 +#define COMMAND_CREATE 256 +#define COMMAND_CREATE_GUILD 257 +#define COMMAND_GUILDS 258 +#define COMMAND_GUILDS_CREATE 259 +#define COMMAND_GUILDS_DELETE 260 +#define COMMAND_GUILDS_ADD 261 +#define COMMAND_GUILDS_REMOVE 262 +#define COMMAND_GUILDS_LIST 263 +#define COMMAND_LOTTO 264 +#define COMMAND_CLEAR_ALL_QUEUED 265 +#define COMMAND_SCRIBE_SCROLL_ITEM 266 +#define COMMAND_RELOAD_LOCATIONS 267 +#define COMMAND_LOCATION 268 +#define COMMAND_LOCATION_CREATE 269 +#define COMMAND_LOCATION_ADD 270 +#define COMMAND_GRID 271 +#define COMMAND_LOCATION_REMOVE 272 +#define COMMAND_LOCATION_DELETE 273 +#define COMMAND_LOCATION_LIST 274 +#define COMMAND_USE_EQUIPPED_ITEM 275 +#define COMMAND_CANCEL_MAINTAINED 276 +#define COMMAND_LOOT_CORPSE 277 +#define COMMAND_MOTD 278 +#define COMMAND_RANDOM 279 +#define COMMAND_TRY_ON 280 +#define COMMAND_TITLE 281 +#define COMMAND_GUILD_BANK 282 +#define COMMAND_GUILD_BANK_DEPOSIT 283 +#define COMMAND_GUILD_BANK_WITHDRAWAL 284 +#define COMMAND_GUILD_BANK_CANCEL 285 +#define COMMAND_TITLE_LIST 286 +#define COMMAND_TITLE_SETPREFIX 287 +#define COMMAND_TITLE_SETSUFFIX 288 +#define COMMAND_TITLE_FIX 289 +#define COMMAND_LANGUAGES 290 +#define COMMAND_SET_LANGUAGE 291 +#define COMMAND_ACCEPT_ADVANCEMENT 293 + +#define COMMAND_JOIN_CHANNEL 294 +#define COMMAND_JOIN_CHANNEL_FROM_LOAD 295 +#define COMMAND_TELL_CHANNEL 296 +#define COMMAND_LEAVE_CHANNEL 297 +#define COMMAND_WHO_CHANNEL 298 + +#define COMMAND_CREATEFROMRECIPE 299 +#define COMMAND_RAIN 300 +#define COMMAND_TO_MERCHANT 301 +#define COMMAND_SELECT 302 +#define COMMAND_SMP 303 +#define COMMAND_CONSUME_FOOD 304 +#define COMMAND_AQUAMAN 305 +#define COMMAND_ATTUNE_INV 306 +#define COMMAND_PLAYER 307 +#define COMMAND_PLAYER_COINS 308 +#define COMMAND_RESET_ZONE_TIMER 309 +#define COMMAND_ACHIEVEMENT_ADD 310 +#define COMMAND_EDITOR 311 +#define COMMAND_ACCEPT_RESURRECTION 312 +#define COMMAND_DECLINE_RESURRECTION 313 +#define COMMAND_WIND 314 +#define COMMAND_TARGETITEM 315 +#define COMMAND_READ 463 + +#define COMMAND_BOT 500 +#define COMMAND_BOT_CREATE 501 +#define COMMAND_BOT_CUSTOMIZE 502 +#define COMMAND_BOT_SPAWN 503 +#define COMMAND_BOT_LIST 504 +#define COMMAND_BOT_INV 505 +#define COMMAND_BOT_SETTINGS 506 +#define COMMAND_BOT_HELP 507 + +#define COMMAND_OPEN 508 +#define COMMAND_CASTSPELL 509 +#define COMMAND_DISARM 510 +#define COMMAND_KNOWLEDGEWINDOWSORT 511 +#define COMMAND_PLACE_HOUSE_ITEM 512 +#define COMMAND_GM 513 +#define COMMAND_HOUSE_UI 514 +#define COMMAND_HOUSE 515 +#define COMMAND_MOVE_ITEM 516 +#define COMMAND_PICKUP 517 +#define COMMAND_HOUSE_DEPOSIT 518 + +#define COMMAND_RELOAD_RULES 519 +#define COMMAND_RELOAD_TRANSPORTERS 520 +#define COMMAND_FINDSPAWN 521 +#define COMMAND_RELOAD_STARTABILITIES 522 + +#define COMMAND_WAYPOINT 523 + +#define COMMAND_RELOADREGIONSCRIPTS 524 + +#define COMMAND_MOVECHARACTER 525 + +#define COMMAND_CRAFTITEM 526 + +#define COMMAND_FROMBROKER 527 + +#define COMMAND_MENTOR 528 +#define COMMAND_UNMENTOR 529 + +#define COMMAND_CANCEL_EFFECT 530 +#define COMMAND_CUREPLAYER 531 + +#define COMMAND_RELOAD_VOICEOVERS 532 +#define COMMAND_SHARE_QUEST 533 + +#define COMMAND_SETAUTOLOOTMODE 534 +#define COMMAND_ASSIST 535 +#define COMMAND_TARGET 536 +#define COMMAND_TARGET_PET 537 + +#define COMMAND_SET_CONSUME_FOOD 538 + +#define GET_AA_XML 750 +#define ADD_AA 751 +#define COMMIT_AA_PROFILE 752 +#define BEGIN_AA_PROFILE 753 +#define BACK_AA 754 +#define REMOVE_AA 755 +#define SWITCH_AA_PROFILE 756 +#define CANCEL_AA_PROFILE 757 +#define SAVE_AA_PROFILE 758 + +#define COMMAND_MOOD 800 + + +#define COMMAND_MODIFY 1000 // INSERT INTO `commands`(`id`,`type`,`command`,`subcommand`,`handler`,`required_status`) VALUES ( NULL,'1','modify','','1000','200'); +#define COMMAND_MODIFY_CHARACTER 1001 +#define COMMAND_MODIFY_FACTION 1002 +#define COMMAND_MODIFY_GUILD 1003 +#define COMMAND_MODIFY_ITEM 1004 +#define COMMAND_MODIFY_QUEST 1005 +#define COMMAND_MODIFY_SKILL 1006 +#define COMMAND_MODIFY_SPAWN 1007 +#define COMMAND_MODIFY_SPELL 1008 +#define COMMAND_MODIFY_ZONE 1009 + +#endif diff --git a/source/WorldServer/Commands/CommandsDB.cpp b/source/WorldServer/Commands/CommandsDB.cpp new file mode 100644 index 0000000..a5c8d01 --- /dev/null +++ b/source/WorldServer/Commands/CommandsDB.cpp @@ -0,0 +1,316 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Commands.h" +#include "ConsoleCommands.h" + + + +map* WorldDatabase::GetSpawnTemplateListByName(const char* name) +{ + LogWrite(COMMAND__DEBUG, 0, "Command", "Player listing spawn templates by template name ('%s')...", name); + + map* ret = 0; + string template_name = ""; + + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name FROM spawn_templates WHERE name RLIKE '%s' LIMIT 0,10", getSafeEscapeString(name).c_str()); + if(result && mysql_num_rows(result) > 0) + { + ret = new map; + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))) + { + template_name = string(row[1]); + (*ret)[atoul(row[0])] = template_name; + LogWrite(COMMAND__DEBUG, 5, "Command", "\t%u. '%s'", atoul(row[0]), template_name.c_str()); + } + } + return ret; +} + +map* WorldDatabase::GetSpawnTemplateListByID(int32 location_id) +{ + LogWrite(COMMAND__DEBUG, 0, "Command", "Player listing spawn templates by LocaionID: %u...", location_id); + + map* ret = 0; + string template_name = ""; + + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name FROM spawn_templates WHERE spawn_location_id = %u", location_id); + if(result && mysql_num_rows(result) > 0) + { + ret = new map; + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))) + { + template_name = string(row[1]); + (*ret)[atoul(row[0])] = template_name; + LogWrite(COMMAND__DEBUG, 5, "Command", "\t%u. '%s'", atoul(row[0]), template_name.c_str()); + } + } + return ret; +} + + +int32 WorldDatabase::SaveSpawnTemplate(int32 placement_id, const char* template_name) +{ + Query query; + + string str_name = getSafeEscapeString(template_name).c_str(); + LogWrite(COMMAND__DEBUG, 0, "Command", "Player saving spawn template '%s' for placement_id %u...", str_name.c_str(), placement_id); + + query.RunQuery2(Q_INSERT, "INSERT INTO spawn_templates (name, spawn_location_id) VALUES ('%s', %u)", str_name.c_str(), placement_id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in SaveSpawnTemplate query '%s': %s", query.GetQuery(), query.GetError()); + return 0; + } + int32 ret = query.GetLastInsertedID(); + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Returning TemplateID: %u...", ret); + return ret; +} + +bool WorldDatabase::RemoveSpawnTemplate(int32 template_id) +{ + Query query; + + LogWrite(COMMAND__DEBUG, 0, "Command", "Player removing spawn template ID %u...", template_id); + + query.RunQuery2(Q_DELETE, "DELETE FROM spawn_templates WHERE id = %u", template_id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in RemoveSpawnTemplate query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + if (query.GetAffectedRows() > 0 ) + { + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Removed spawn template ID %u...", template_id); + return true; + } + + return false; +} + + +int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_id) +{ + Query query, query2, query3, query4, query5, query6; + MYSQL_ROW row; + int32 spawn_location_id = 0; + float new_x = client->GetPlayer()->GetX(); + float new_y = client->GetPlayer()->GetY(); + float new_z = client->GetPlayer()->GetZ(); + float new_heading = client->GetPlayer()->GetHeading(); + + LogWrite(COMMAND__DEBUG, 0, "Command", "Creating spawn point from templateID %u...", template_id); + LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z); + + // find the spawn_location_id in the template we plan to duplicate + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_location_id FROM spawn_templates WHERE id = %u", template_id); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + spawn_location_id = atoi(row[0]); + } + + if( spawn_location_id > 0 ) + { + LogWrite(COMMAND__DEBUG, 5, "Command", "\tUsing LocationID: %u...", spawn_location_id); + + // insert a new spawn_location_name record + string name = "TemplateGenerated"; + query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError()); + return 0; + } + int32 new_location_id = query2.GetLastInsertedID(); + LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id); + + // get all spawn_location_entries that match the templates spawn_location_id value and insert as new + LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id); + MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id); + if(result2 && mysql_num_rows(result2) > 0){ + MYSQL_ROW row2; + while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0]) + { + query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)", + atoul(row2[0]), new_location_id, atoi(row2[1])); + if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError()); + return 0; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1])); + } + } + + // get all spawn_location_placements that match the templates spawn_location_id value and insert as new + // Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands) + LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id); + MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id); + if(result3 && mysql_num_rows(result3) > 0){ + MYSQL_ROW row3; + while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0]) + { + query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)", + atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7])); + if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError()); + return 0; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id); + } + } + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! New spawn(s) from TemplateID %u created from location %u", template_id, spawn_location_id); + return new_location_id; + } + + return 0; +} + + +int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* template_name) +{ + Query query, query1, query2, query3, query4, query5, query6; + MYSQL_ROW row; + int32 template_id = 0; + int32 spawn_location_id = 0; + float new_x = client->GetPlayer()->GetX(); + float new_y = client->GetPlayer()->GetY(); + float new_z = client->GetPlayer()->GetZ(); + float new_heading = client->GetPlayer()->GetHeading(); + + LogWrite(COMMAND__DEBUG, 0, "Command", "Creating spawn point from template '%s'...", template_name); + LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z); + + // find the spawn_location_id in the template we plan to duplicate + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, spawn_location_id FROM spawn_templates WHERE name = '%s'", template_name); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + { + template_id = atoul(row[0]); + spawn_location_id = atoi(row[1]); + } + } + + if( spawn_location_id > 0 ) + { + LogWrite(COMMAND__DEBUG, 5, "Command", "\tUsing LocationID: %u...", spawn_location_id); + + // insert a new spawn_location_name record + string name = "TemplateGenerated"; + query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError()); + return 0; + } + int32 new_location_id = query2.GetLastInsertedID(); + LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id); + + // get all spawn_location_entries that match the templates spawn_location_id value and insert as new + LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id); + MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id); + if(result2 && mysql_num_rows(result2) > 0){ + MYSQL_ROW row2; + while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0]) + { + query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)", + atoul(row2[0]), new_location_id, atoi(row2[1])); + if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError()); + return 0; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1])); + } + } + + // get all spawn_location_placements that match the templates spawn_location_id value and insert as new + // Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands) + LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id); + MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id); + if(result3 && mysql_num_rows(result3) > 0){ + MYSQL_ROW row3; + while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0]) + { + query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)", + atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7])); + if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError()); + return 0; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id); + } + } + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! New spawn(s) from TemplateID %u created from location %u", template_id, spawn_location_id); + return new_location_id; + } + + return 0; +} + +bool WorldDatabase::SaveZoneSafeCoords(int32 zone_id, float x, float y, float z, float heading) +{ + Query query; + + LogWrite(COMMAND__DEBUG, 0, "Command", "Setting safe coords for zone %u (X: %.2f, Y: %.2f, Z: %.2f, H: %.2f)", zone_id, x, y, z, heading); + + query.RunQuery2(Q_UPDATE, "UPDATE zones SET safe_x = %f, safe_y = %f, safe_z = %f, safe_heading = %f WHERE id = %u", x, y, z, heading, zone_id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(DATABASE__ERROR, 0, "DBCore", "Error in SaveZoneSafeCoords query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + if (query.GetAffectedRows() > 0 ) + { + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Set new safe coordinates in zone ID %u...", zone_id); + return true; + } + + LogWrite(COMMAND__ERROR, 0, "Command", "FAILED! Set new safe coordinates in zone ID %u...", zone_id); + return false; +} + +bool WorldDatabase::SaveSignZoneToCoords(int32 spawn_id, float x, float y, float z, float heading) +{ + Query query; + + LogWrite(COMMAND__DEBUG, 0, "Command", "Setting Zone-To coords for Spawn ID %u (X: %.2f, Y: %.2f, Z: %.2f, H: %.2f)", spawn_id, x, y, z, heading); + + query.RunQuery2(Q_UPDATE, "UPDATE spawn_signs SET zone_x = %f, zone_y = %f, zone_z = %f, zone_heading = %f WHERE spawn_id = %u", x, y, z, heading, spawn_id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(DATABASE__ERROR, 0, "DBCore", "Error in SaveSignZoneToCoords query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + if (query.GetAffectedRows() > 0 ) + { + LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Set new Zone-To coordinates in zone ID %u...", spawn_id); + return true; + } + + LogWrite(COMMAND__ERROR, 0, "Command", "FAILED! Set new Zone-To coordinates in zone ID %u...", spawn_id); + return false; +} diff --git a/source/WorldServer/Commands/ConsoleCommands.cpp b/source/WorldServer/Commands/ConsoleCommands.cpp new file mode 100644 index 0000000..648ec93 --- /dev/null +++ b/source/WorldServer/Commands/ConsoleCommands.cpp @@ -0,0 +1,549 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include +using namespace std; +#include +#include +#include + +#include "../../common/debug.h" +#include "../../common/Log.h" +#include "../../common/seperator.h" +#include "ConsoleCommands.h" +#include "../World.h" +#include "../Rules/Rules.h" +#include "../WorldDatabase.h" + +extern volatile bool RunLoops; +bool ContinueLoops = false; +extern Variables variables; +extern ZoneList zone_list; +extern RuleManager rule_manager; +extern WorldDatabase database; + +void ProcessConsoleInput(const char * cmdInput) +{ + static ConsoleCommand Commands[] = { + + // account controls + { &ConsoleBanCommand, "ban", "[player] {duration} {reason}", "Ban player with {optional} duration and reason." }, + { &ConsoleUnbanCommand, "unban", "[player]", "Unban a player." }, + { &ConsoleKickCommand, "kick", "[player] {reason}", "Kick player with {optional} reason." }, + + // chat controls + { &ConsoleAnnounceCommand, "announce", "[message]", "Sends Announcement message to all channels/clients." }, + { &ConsoleBroadcastCommand, "broadcast","[message]", "Sends Broadcast message to all channels/clients." }, + { &ConsoleChannelCommand, "channel", "[channel] [message]", "Sends Channel message to channel." }, + { &ConsoleTellCommand, "tell", "[player] [message]", "Sends Private message to player." }, + + // world system controls + { &ConsoleGuildCommand, "guild", "[params]", "" }, + { &ConsolePlayerCommand, "player", "[params]", "" }, + { &ConsoleSetAdminPlayer, "makeadmin", "[charname] [status=0]", "" }, + { &ConsoleZoneCommand, "zone", "[command][value]", "command = help to get help" }, + { &ConsoleWorldCommand, "world", "[params]", "" }, + { &ConsoleGetMOTDCommand, "getmotd", "", "Display current MOTD" }, + { &ConsoleSetMOTDCommand, "setmotd", "[new motd]", "Sets a new MOTD" }, + + /// misc controls + { &ConsoleWhoCommand, "who", "{zone id | player}", "Shows who is online globally, or in a given zone." }, + { &ConsoleReloadCommand, "reload", "[all | [type]]", "Reload main systems." }, + { &ConsoleRulesCommand, "rules", "{zone} {id}", "Show Global Ruleset (or Zone ruleset {optional})" }, + { &ConsoleShutdownCommand, "shutdown", "[delay]", "Gracefully shutdown world in [delay] sesconds." }, + { &ConsoleCancelShutdownCommand,"cancel", "", "Cancel shutdown command." }, + { &ConsoleExitCommand, "exit", "", "Brutally kills the world without mercy." }, + { &ConsoleExitCommand, "quit", "", "Brutally kills the world without mercy." }, + { &ConsoleTestCommand, "test", "", "Dev testing command." }, + { NULL, NULL, NULL, NULL }, + }; + + Seperator *sep = new Seperator(cmdInput, ' ', 20, 100, true); + bool found = false; + uint32 i; + + if (!sep) + return; + + if (!strcasecmp(sep->arg[0], "help") || sep->arg[0][0] == 'h' || sep->arg[0][0] == 'H' || sep->arg[0][0] == '?') { + found = true; + printf("======================================================================================================\n"); + printf("| %10s | %30s | %52s |\n", "Name", "Params", "Description"); + printf("======================================================================================================\n"); + for (i = 0; Commands[i].Name != NULL; i++) { + printf("| %10s | %30s | %52s |\n", Commands[i].Name, Commands[i].ParameterFormat, Commands[i].Description); + } + printf("======================================================================================================\n"); + printf("-[ Help formatted for 120 chars wide screen ]-\n"); + } + else { + for (i = 0; Commands[i].Name != NULL; ++i) { + if (!strcasecmp(Commands[i].Name, sep->arg[0])) { + found = true; + if (!Commands[i].CommandPointer(sep)) + printf("\nError, incorrect syntax for '%s'.\n Correct syntax is: '%s'.\n\n", Commands[i].Name, Commands[i].ParameterFormat ); + } + } + } + + if (!found) + printf("Invalid Command '%s'! Type '?' or 'help' to get a command list.\n\n", sep->arg[0]); + + fflush(stdout); + + delete sep; +} + +/************************************************* COMMANDS *************************************************/ + +bool ConsoleBanCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleUnbanCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleKickCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + + +bool ConsoleAnnounceCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleBroadcastCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + char message[4096]; + snprintf(message, sizeof(message), "%s %s", "BROADCAST:", sep->argplus[1]); + zone_list.HandleGlobalBroadcast(message); + return true; +} + +bool ConsoleChannelCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleTellCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + + +bool ConsoleWhoCommand(Seperator *sep) +{ + + // zone_list.ProcessWhoQuery(who, client); + + if (!strcasecmp(sep->arg[1], "zone")) { + printf("Who's Online in Zone"); + if (sep->IsNumber(2)) { + printf("ID %s:\n", sep->arg[2]); + printf("===============================================================================\n"); + printf("| %10s | %62s |\n", "CharID", "Name"); + printf("===============================================================================\n"); + } + else { + printf(" '%s':\n", sep->arg[2]); + printf("===============================================================================\n"); + printf("| %10s | %62s |\n", "CharID", "Name"); + printf("===============================================================================\n"); + } + } + else { + printf("Who's Online (Global):\n"); + printf("===============================================================================\n"); + printf("| %10s | %20s | %39s |\n", "CharID", "Name", "Zone"); + printf("===============================================================================\n"); + } + printf("Not Implemented... yet :)\n"); + printf("===============================================================================\n"); + return true; +} + +bool ConsoleGuildCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsolePlayerCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleSetAdminPlayer(Seperator *sep) +{ + if(!sep->arg[1] || strlen(sep->arg[1]) == 0) + return false; + + sint16 status = 0; + if(sep->IsNumber(2)) + status = atoi(sep->arg[2]); + + Client* client = zone_list.GetClientByCharName(sep->arg[1]); + + if(!client) { + printf("Client not found by char name, must be logged in\n"); + return true; + } + + if(!client->GetPlayer()) { + + printf("Player is not available for client class, try again\n"); + return true; + } + + client->SetAdminStatus(status); + if(status) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Admin status updated."); + database.UpdateAdminStatus(client->GetPlayer()->GetName(), status); + printf("Admin status for %s is updated to %i\n", client->GetPlayer()->GetName(), status); + + return true; +} + +bool ConsoleWorldCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleZoneCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) // has to be at least 1 arg (command) + return false; + + ZoneServer* zone = 0; + + if( strlen(sep->arg[2]) == 0 ) + { + // process commands without values + if (!strcasecmp(sep->arg[1], "active") ) + { + // not correct, but somehow need to access the Private zlist from World.h ??? + list zlist; + + list::iterator zone_iter; + ZoneServer* tmp = 0; + int zonesListed = 0; + + printf("> List Active Zones...\n"); + printf("======================================================================================================\n"); + printf("| %7s | %30s | %10s | %42s |\n", "ID", "Name", "Instance", "Description"); + printf("======================================================================================================\n"); + + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + zonesListed++; + printf("| %7d | %30s | %10d | %42s |\n", tmp->GetZoneID(), tmp->GetZoneName(), tmp->GetInstanceID(),tmp->GetZoneDescription()); + } + return true; + } + else if (!strcasecmp(sep->arg[1], "help") || sep->arg[1][0] == '?') + { + printf("======================================================================================================\n"); + printf("| %10s | %30s | %52s |\n", "Command", "Value", "Description"); + printf("======================================================================================================\n"); + printf("| %10s | %30s | %52s |\n", "active", "n/a", "List currently active zones"); + printf("| %10s | %30s | %52s |\n", "list", "[name]", "Lookup zone by name"); + printf("| %10s | %30s | %52s |\n", "status", "[zone_id | name | ALL]", "List zone stats"); + printf("| %10s | %30s | %52s |\n", "lock", "[zone_id | name]", "Locks a zone"); + printf("| %10s | %30s | %52s |\n", "unlock", "[zone_id | name]", "Unlocks a zone"); + printf("| %10s | %30s | %52s |\n", "shutdown", "[zone_id | name | ALL]", "Gracefully shuts down a zone"); + printf("| %10s | %30s | %52s |\n", "kill", "[zone_id | name | ALL]", "Terminates a zone"); + printf("======================================================================================================\n"); + return true; + } + else + return false; + } + else + { + if( !strcasecmp(sep->arg[1], "list") ) + { + const char* name = 0; + name = sep->argplus[2]; + map* zone_names = database.GetZoneList(name); + + if(!zone_names) + { + printf("> No zones found.\n"); + } + else + { + printf("> List zones matching '%s'...\n", sep->arg[2]); + printf("====================================================\n"); + printf("| %3s | %42s |\n", "ID", "Name"); + printf("====================================================\n"); + map::iterator itr; + + for(itr = zone_names->begin(); itr != zone_names->end(); itr++) + printf("| %3u | %42s |\n", itr->first, itr->second.c_str()); + safe_delete(zone_names); + printf("====================================================\n"); + } + return true; + } + + if( !strcasecmp(sep->arg[1], "lock") ) + { + if( sep->IsNumber(2) ) + printf("> Locking zone ID %i...\n", atoul(sep->arg[2])); + else + printf("> Locking zone '%s'...\n", sep->arg[2]); + return true; + } + + if( !strcasecmp(sep->arg[1], "unlock") ) + { + if( strlen(sep->arg[2]) > 0 && sep->IsNumber(2) ) + printf("> Unlocking zone ID %i...\n", atoi(sep->arg[2])); + else + printf("> Unlocking zone '%s'...\n", sep->arg[2]); + return true; + } + + if( !strcasecmp(sep->arg[1], "status") ) + { + if( sep->IsNumber(2) ) + { + zone = zone_list.Get(atoi(sep->arg[2]), false, false, false); + if( zone ) + { + printf("> Zone status for zone ID %i...\n", atoi(sep->arg[2])); + printf("============================================================================================\n"); + printf("| %30s | %10s | %42s |\n", "Zone", "Param", "Value"); + printf("============================================================================================\n"); + printf("| %30s | %10s | %42s |\n", zone->GetZoneName(), "locked", zone->GetZoneLockState() ? "true" : "false"); + } + else + { + printf("> Zone ID %i not running, so not locked.\n", atoi(sep->arg[2])); + } + } + else if( !strcasecmp(sep->arg[2], "ALL") ) + { + printf("> Zone status for ALL active zones...\n"); + } + else + { + printf("> Zone status for zone '%s'...\n", sep->arg[2]); + } + return true; + } + + if( !strcasecmp(sep->arg[1], "shutdown") ) + { + if( sep->IsNumber(2) ) + printf("> Shutdown zone ID %i...\n", atoi(sep->arg[2])); + else if( !strcasecmp(sep->arg[2], "ALL") ) + printf("> Shutdown ALL active zones...\n"); + else + printf("> Shutdown zone '%s'...\n", sep->arg[2]); + return true; + } + + if( !strcasecmp(sep->arg[1], "kill") ) + { + if( sep->IsNumber(2) ) + printf("> Kill zone ID %i...\n", atoi(sep->arg[2])); + else if( !strcasecmp(sep->arg[2], "ALL") ) + printf("> Kill ALL active zones...\n"); + else + printf("> Kill zone '%s'...\n", sep->arg[2]); + return true; + } + } + return false; +} + +bool ConsoleGetMOTDCommand(Seperator *sep) +{ + const char* motd = 0; + Variable* var = variables.FindVariable("motd"); + if( var == NULL || strlen (var->GetValue()) == 0){ + printf("No MOTD."); + } + else{ + motd = var->GetValue(); + printf("%s\n", motd); + } + return true; +} + +bool ConsoleSetMOTDCommand(Seperator *sep) +{ + if( strlen(sep->arg[1]) == 0 ) + return false; + + return true; +} + +bool ConsoleReloadCommand(Seperator *sep) +{ + #ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); + #else + printf("\033[1;33m"); + #endif + printf("Usage: "); + + #ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); + #else + printf("\033[1;37m"); + #endif + printf("reload [type]\n"); + + #ifdef _WIN32 + SetConsoleTextAttribute(console, 8); + #else + printf("\033[0m"); + #endif + printf("Valid [type] paramters are:\n"); + printf("===============================================================================\n"); + printf("| %21s | %51s |\n", "all", "Reloads all systems (why not just restart?)"); + printf("| %21s | %51s |\n", "structs", "Reloads structs (XMLs)"); + printf("| %21s | %51s |\n", "items", "Reload Items data"); + printf("| %21s | %51s |\n", "luasystem", "Reload LUA System Scripts"); + printf("| %21s | %51s |\n", "spawnscripts", "Reload SpawnScripts"); + printf("| %21s | %51s |\n", "quests", "Reload Quest Data and Scripts"); + printf("| %21s | %51s |\n", "spawns", "Reload ALL Spawns from DB"); + printf("| %21s | %51s |\n", "groundspawn_items", "Reload Groundspawn Items lists"); + printf("| %21s | %51s |\n", "zonescripts", "Reload Zone Scripts"); + printf("| %21s | %51s |\n", "entity_commands", "Reload Entity Commands"); + printf("| %21s | %51s |\n", "factions", "Reload Factions"); + printf("| %21s | %51s |\n", "mail", "Reload in-game Mail data"); + printf("| %21s | %51s |\n", "guilds", "Reload Guilds"); + printf("| %21s | %51s |\n", "locations", "Reload Locations data"); + printf("===============================================================================\n"); + if( strlen(sep->arg[1]) > 0 ) { + // handle reloads here + if (!strcasecmp(sep->arg[1], "spawns")) + zone_list.ReloadSpawns(); + } + + return true; +} + +bool ConsoleShutdownCommand(Seperator *sep) +{ + if ( IsNumber(sep->arg[1]) ) { + int8 shutdown_delay = atoi(sep->arg[1]); + printf("Shutdown World in %i second(s)...\n", shutdown_delay); + // shutting down gracefully, warn players. + char message[4096]; + snprintf(message, sizeof(message), "BROADCAST: Server is shutting down in %s second(s)", sep->arg[1]); + zone_list.HandleGlobalBroadcast(message); + Sleep(shutdown_delay * 1000); + } + else { + printf("Shutdown World immediately... you probably won't even see this message, huh!\n"); + } + if( !ContinueLoops ) + RunLoops = false; + return true; +} + +bool ConsoleCancelShutdownCommand(Seperator *sep) +{ + printf("Cancel World Shutdown...\n"); + ContinueLoops = true; + return true; +} + +bool ConsoleExitCommand(Seperator *sep) +{ + // I wanted this to be a little more Terminate-y... killkillkill + printf("Terminate World immediately...\n"); + RunLoops = false; + return true; +} + +bool ConsoleRulesCommand(Seperator *sep) +{ + /*if( strlen(sep->arg[1]) == 0 ) + return false;*/ + + printf("Current Active Ruleset"); + if (!strcasecmp(sep->arg[1], "zone")) + { + if (sep->IsNumber(2)) { + printf(" in Zone ID: %s\n", sep->arg[2]); + } + else + return false; + } + else + { + printf(" (global):\n"); + } + printf("===============================================================================\n"); + printf("| %20s | %20s | %29s |\n", "Category", "Type", "Value"); + printf("===============================================================================\n"); + + return true; +} + +bool ConsoleTestCommand(Seperator *sep) +{ + // devs put whatever test code in here + printf("Testing Server Guild Rules values:\n"); + printf("AutoJoin: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoin)->GetInt8()); + printf("Guild ID: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoinID)->GetInt32()); + printf("Rank: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoinDefaultRankID)->GetInt8()); + return true; +} diff --git a/source/WorldServer/Commands/ConsoleCommands.h b/source/WorldServer/Commands/ConsoleCommands.h new file mode 100644 index 0000000..1796c7d --- /dev/null +++ b/source/WorldServer/Commands/ConsoleCommands.h @@ -0,0 +1,62 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifndef _CONSOLECOMMANDS_H +#define _CONSOLECOMMANDS_H + +#include "../../common/seperator.h" + +struct ConsoleCommand +{ + bool(*CommandPointer)(Seperator *); + const char * Name; // 10 chars + const char * ParameterFormat; // 30 chars + const char * Description; // 40 chars + // = 70 chars +}; + + void ProcessConsoleInput(const char * command); + + bool ConsoleBanCommand(Seperator *sep); + bool ConsoleUnbanCommand(Seperator *sep); + bool ConsoleKickCommand(Seperator *sep); + + bool ConsoleAnnounceCommand(Seperator *sep); + bool ConsoleBroadcastCommand(Seperator *sep); + bool ConsoleChannelCommand(Seperator *sep); + bool ConsoleTellCommand(Seperator *sep); + + bool ConsoleGuildCommand(Seperator *sep); + bool ConsolePlayerCommand(Seperator *sep); + bool ConsoleSetAdminPlayer(Seperator *sep); + bool ConsoleWorldCommand(Seperator *sep); + bool ConsoleZoneCommand(Seperator *sep); + bool ConsoleGetMOTDCommand(Seperator *sep); + bool ConsoleSetMOTDCommand(Seperator *sep); + bool ConsoleWhoCommand(Seperator *sep); + bool ConsoleReloadCommand(Seperator *sep); + + bool ConsoleShutdownCommand(Seperator *sep); + bool ConsoleCancelShutdownCommand(Seperator *sep); + bool ConsoleExitCommand(Seperator *sep); + bool ConsoleRulesCommand(Seperator *sep); + bool ConsoleTestCommand(Seperator *sep); + +#endif \ No newline at end of file diff --git a/source/WorldServer/Entity.cpp b/source/WorldServer/Entity.cpp new file mode 100644 index 0000000..90f5944 --- /dev/null +++ b/source/WorldServer/Entity.cpp @@ -0,0 +1,3986 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Entity.h" +#include +#include "Items/Items.h" +#include "zoneserver.h" +#include "World.h" +#include "../common/Log.h" +#include "Spells.h" +#include "SpellProcess.h" +#include "classes.h" +#include "LuaInterface.h" +#include "ClientPacketFunctions.h" +#include "Skills.h" +#include "Rules/Rules.h" +#include "LuaInterface.h" + +extern World world; +extern MasterItemList master_item_list; +extern MasterSpellList master_spell_list; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; +extern Classes classes; +extern LuaInterface* lua_interface; + +Entity::Entity(){ + MapInfoStruct(); + max_speed = 6; + base_speed = 0.0f; + last_x = -1; + last_y = -1; + last_z = -1; + last_heading = -1; + regen_hp_rate = 0; + regen_power_rate = 0; + in_combat = false; + casting = false; + //memset(&info_struct, 0, sizeof(InfoStruct)); + memset(&features, 0, sizeof(CharFeatures)); + memset(&equipment, 0, sizeof(EQ2_Equipment)); + pet = 0; + charmedPet = 0; + deityPet = 0; + cosmeticPet = 0; + speed = 0; + speed_multiplier = 1.0f; + m_threatTransfer = 0; + group_member_info = 0; + trade = 0; + deity = 0; + MProcList.SetName("Entity::m_procList"); + MDetriments.SetName("Entity::MDetriments"); + MMaintainedSpells.SetName("Entity::MMaintainedSpells"); + MSpellEffects.SetName("Entity::MSpellEffects"); + m_procList.clear(); + control_effects.clear(); + for (int i = 0; i < CONTROL_MAX_EFFECTS; i++) + control_effects[i] = NULL; + + immunities.clear(); + + info_struct.ResetEffects(this); + + MCommandMutex.SetName("Entity::MCommandMutex"); + hasSeeInvisSpell = false; + hasSeeHideSpell = false; + + owner = 0; + m_petType = 0; + m_petSpellID = 0; + m_petSpellTier = 0; + m_petDismissing = false; + + if (!IsPlayer() && GetInfoStruct()->get_max_concentration_base() == 0) + GetInfoStruct()->set_max_concentration_base(5); +} + +Entity::~Entity(){ + MutexList::iterator itr2 = bonus_list.begin(); + while(itr2.Next()) + safe_delete(itr2.value); + ClearProcs(); + safe_delete(m_threatTransfer); + map*>::iterator itr3; + for (itr3 = control_effects.begin(); itr3 != control_effects.end(); itr3++) + safe_delete(itr3->second); + control_effects.clear(); + map*>::iterator itr4; + for (itr4 = immunities.begin(); itr4 != immunities.end(); itr4++) + safe_delete(itr4->second); + immunities.clear(); + if(!IsPlayer()) + DeleteSpellEffects(true); + safe_delete(m_threatTransfer); +} + +void Entity::DeleteSpellEffects(bool removeClient) +{ + map deletedPtrs; + + for(int i=0;i<45;i++){ + if(i<30){ + if(GetInfoStruct()->maintained_effects[i].spell_id != 0xFFFFFFFF) + { + if(deletedPtrs.find(GetInfoStruct()->maintained_effects[i].spell) == deletedPtrs.end()) + { + deletedPtrs[GetInfoStruct()->maintained_effects[i].spell] = true; + lua_interface->RemoveSpell(GetInfoStruct()->maintained_effects[i].spell, false, removeClient, "", removeClient); + if (IsPlayer()) + GetInfoStruct()->maintained_effects[i].icon = 0xFFFF; + } + + GetInfoStruct()->maintained_effects[i].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[i].spell = nullptr; + } + } + if(GetInfoStruct()->spell_effects[i].spell_id != 0xFFFFFFFF) + { + if(deletedPtrs.find(GetInfoStruct()->spell_effects[i].spell) == deletedPtrs.end()) { + if(GetInfoStruct()->spell_effects[i].spell && GetInfoStruct()->spell_effects[i].spell->spell && + GetInfoStruct()->spell_effects[i].spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_NOT_SHOWN) { + deletedPtrs[GetInfoStruct()->spell_effects[i].spell] = true; + lua_interface->RemoveSpell(GetInfoStruct()->spell_effects[i].spell, false, removeClient, "", removeClient); + } + } + GetInfoStruct()->spell_effects[i].spell_id = 0xFFFFFFFF; + GetInfoStruct()->spell_effects[i].spell = nullptr; + } + } +} + +void Entity::RemoveSpells(bool unfriendlyOnly) +{ + + for(int i=0;i<45;i++){ + if(i<30){ + if(GetInfoStruct()->maintained_effects[i].spell_id != 0xFFFFFFFF) + { + if(!unfriendlyOnly || (unfriendlyOnly && GetInfoStruct()->maintained_effects[i].spell && + !GetInfoStruct()->maintained_effects[i].spell->spell->GetSpellData()->friendly_spell)) + GetZone()->GetSpellProcess()->AddSpellCancel(GetInfoStruct()->maintained_effects[i].spell); + } + } + if(GetInfoStruct()->spell_effects[i].spell_id != 0xFFFFFFFF) + { + if(!unfriendlyOnly || (unfriendlyOnly && GetInfoStruct()->spell_effects[i].spell && + !GetInfoStruct()->spell_effects[i].spell->spell->GetSpellData()->friendly_spell)) + RemoveSpellEffect(GetInfoStruct()->spell_effects[i].spell); + } + } +} + +void Entity::MapInfoStruct() +{ +/** GETS **/ + get_string_funcs["name"] = l::bind(&InfoStruct::get_name, &info_struct); + get_int8_funcs["class1"] = l::bind(&InfoStruct::get_class1, &info_struct); + get_int8_funcs["class2"] = l::bind(&InfoStruct::get_class2, &info_struct); + get_int8_funcs["class3"] = l::bind(&InfoStruct::get_class3, &info_struct); + get_int8_funcs["race"] = l::bind(&InfoStruct::get_race, &info_struct); + get_int8_funcs["gender"] = l::bind(&InfoStruct::get_gender, &info_struct); + get_int16_funcs["level"] = l::bind(&InfoStruct::get_level, &info_struct); + get_int16_funcs["max_level"] = l::bind(&InfoStruct::get_max_level, &info_struct); + get_int16_funcs["effective_level"] = l::bind(&InfoStruct::get_effective_level, &info_struct); + get_int16_funcs["tradeskill_level"] = l::bind(&InfoStruct::get_tradeskill_level, &info_struct); + get_int16_funcs["tradeskill_max_level"] = l::bind(&InfoStruct::get_tradeskill_max_level, &info_struct); + get_int8_funcs["cur_concentration"] = l::bind(&InfoStruct::get_cur_concentration, &info_struct); + get_int8_funcs["max_concentration"] = l::bind(&InfoStruct::get_max_concentration, &info_struct); + get_int8_funcs["max_concentration_base"] = l::bind(&InfoStruct::get_max_concentration_base, &info_struct); + get_int16_funcs["cur_attack"] = l::bind(&InfoStruct::get_cur_attack, &info_struct); + get_int16_funcs["attack_base"] = l::bind(&InfoStruct::get_attack_base, &info_struct); + get_int16_funcs["cur_mitigation"] = l::bind(&InfoStruct::get_cur_mitigation, &info_struct); + get_int16_funcs["max_mitigation"] = l::bind(&InfoStruct::get_max_mitigation, &info_struct); + get_int16_funcs["mitigation_base"] = l::bind(&InfoStruct::get_mitigation_base, &info_struct); + get_int16_funcs["avoidance_display"] = l::bind(&InfoStruct::get_avoidance_display, &info_struct); + get_float_funcs["cur_avoidance"] = l::bind(&InfoStruct::get_cur_avoidance, &info_struct); + get_int16_funcs["base_avoidance_pct"] = l::bind(&InfoStruct::get_base_avoidance_pct, &info_struct); + get_int16_funcs["avoidance_base"] = l::bind(&InfoStruct::get_avoidance_base, &info_struct); + get_int16_funcs["max_avoidance"] = l::bind(&InfoStruct::get_max_avoidance, &info_struct); + get_float_funcs["parry"] = l::bind(&InfoStruct::get_parry, &info_struct); + get_float_funcs["parry_base"] = l::bind(&InfoStruct::get_parry_base, &info_struct); + get_float_funcs["deflection"] = l::bind(&InfoStruct::get_deflection, &info_struct); + get_int16_funcs["deflection_base"] = l::bind(&InfoStruct::get_deflection_base, &info_struct); + get_float_funcs["block"] = l::bind(&InfoStruct::get_block, &info_struct); + get_int16_funcs["block_base"] = l::bind(&InfoStruct::get_block_base, &info_struct); + get_float_funcs["str"] = l::bind(&InfoStruct::get_str, &info_struct); + get_float_funcs["sta"] = l::bind(&InfoStruct::get_sta, &info_struct); + get_float_funcs["agi"] = l::bind(&InfoStruct::get_agi, &info_struct); + get_float_funcs["wis"] = l::bind(&InfoStruct::get_wis, &info_struct); + get_float_funcs["intel"] = l::bind(&InfoStruct::get_intel, &info_struct); + get_float_funcs["str_base"] = l::bind(&InfoStruct::get_str_base, &info_struct); + get_float_funcs["sta_base"] = l::bind(&InfoStruct::get_sta_base, &info_struct); + get_float_funcs["agi_base"] = l::bind(&InfoStruct::get_agi_base, &info_struct); + get_float_funcs["wis_base"] = l::bind(&InfoStruct::get_wis_base, &info_struct); + get_float_funcs["intel_base"] = l::bind(&InfoStruct::get_intel_base, &info_struct); + get_int16_funcs["heat"] = l::bind(&InfoStruct::get_heat, &info_struct); + get_int16_funcs["cold"] = l::bind(&InfoStruct::get_cold, &info_struct); + get_int16_funcs["magic"] = l::bind(&InfoStruct::get_magic, &info_struct); + get_int16_funcs["mental"] = l::bind(&InfoStruct::get_mental, &info_struct); + get_int16_funcs["divine"] = l::bind(&InfoStruct::get_divine, &info_struct); + get_int16_funcs["disease"] = l::bind(&InfoStruct::get_disease, &info_struct); + get_int16_funcs["poison"] = l::bind(&InfoStruct::get_poison, &info_struct); + get_int16_funcs["disease_base"] = l::bind(&InfoStruct::get_disease_base, &info_struct); + get_int16_funcs["cold_base"] = l::bind(&InfoStruct::get_cold_base, &info_struct); + get_int16_funcs["divine_base"] = l::bind(&InfoStruct::get_divine_base, &info_struct); + get_int16_funcs["magic_base"] = l::bind(&InfoStruct::get_magic_base, &info_struct); + get_int16_funcs["mental_base"] = l::bind(&InfoStruct::get_mental_base, &info_struct); + get_int16_funcs["heat_base"] = l::bind(&InfoStruct::get_heat_base, &info_struct); + get_int16_funcs["poison_base"] = l::bind(&InfoStruct::get_poison_base, &info_struct); + get_int16_funcs["elemental_base"] = l::bind(&InfoStruct::get_elemental_base, &info_struct); + get_int16_funcs["noxious_base"] = l::bind(&InfoStruct::get_noxious_base, &info_struct); + get_int16_funcs["arcane_base"] = l::bind(&InfoStruct::get_arcane_base, &info_struct); + get_int32_funcs["coin_copper"] = l::bind(&InfoStruct::get_coin_copper, &info_struct); + get_int32_funcs["coin_silver"] = l::bind(&InfoStruct::get_coin_silver, &info_struct); + get_int32_funcs["coin_gold"] = l::bind(&InfoStruct::get_coin_gold, &info_struct); + get_int32_funcs["coin_plat"] = l::bind(&InfoStruct::get_coin_plat, &info_struct); + get_int32_funcs["bank_coin_copper"] = l::bind(&InfoStruct::get_bank_coin_copper, &info_struct); + get_int32_funcs["bank_coin_silver"] = l::bind(&InfoStruct::get_bank_coin_silver, &info_struct); + get_int32_funcs["bank_coin_gold"] = l::bind(&InfoStruct::get_bank_coin_gold, &info_struct); + get_int32_funcs["bank_coin_plat"] = l::bind(&InfoStruct::get_bank_coin_plat, &info_struct); + get_int32_funcs["status_points"] = l::bind(&InfoStruct::get_status_points, &info_struct); + get_string_funcs["deity"] = l::bind(&InfoStruct::get_deity, &info_struct); + get_int32_funcs["weight"] = l::bind(&InfoStruct::get_weight, &info_struct); + get_int32_funcs["max_weight"] = l::bind(&InfoStruct::get_max_weight, &info_struct); + get_int8_funcs["tradeskill_class1"] = l::bind(&InfoStruct::get_tradeskill_class1, &info_struct); + get_int8_funcs["tradeskill_class2"] = l::bind(&InfoStruct::get_tradeskill_class2, &info_struct); + get_int8_funcs["tradeskill_class3"] = l::bind(&InfoStruct::get_tradeskill_class3, &info_struct); + get_int32_funcs["account_age_base"] = l::bind(&InfoStruct::get_account_age_base, &info_struct); + // int8 account_age_bonus_[19]; + get_int16_funcs["absorb"] = l::bind(&InfoStruct::get_absorb, &info_struct); + get_int32_funcs["xp"] = l::bind(&InfoStruct::get_xp, &info_struct); + get_int32_funcs["xp_needed"] = l::bind(&InfoStruct::get_xp_needed, &info_struct); + get_float_funcs["xp_debt"] = l::bind(&InfoStruct::get_xp_debt, &info_struct); + get_int16_funcs["xp_yellow"] = l::bind(&InfoStruct::get_xp_yellow, &info_struct); + get_int16_funcs["xp_yellow_vitality_bar"] = l::bind(&InfoStruct::get_xp_yellow_vitality_bar, &info_struct); + get_int16_funcs["xp_blue_vitality_bar"] = l::bind(&InfoStruct::get_xp_blue_vitality_bar, &info_struct); + get_int16_funcs["xp_blue"] = l::bind(&InfoStruct::get_xp_blue, &info_struct); + get_int32_funcs["ts_xp"] = l::bind(&InfoStruct::get_ts_xp, &info_struct); + get_int32_funcs["ts_xp_needed"] = l::bind(&InfoStruct::get_ts_xp_needed, &info_struct); + get_int16_funcs["tradeskill_exp_yellow"] = l::bind(&InfoStruct::get_tradeskill_exp_yellow, &info_struct); + get_int16_funcs["tradeskill_exp_blue"] = l::bind(&InfoStruct::get_tradeskill_exp_blue, &info_struct); + get_int32_funcs["flags"] = l::bind(&InfoStruct::get_flags, &info_struct); + get_int32_funcs["flags2"] = l::bind(&InfoStruct::get_flags2, &info_struct); + get_float_funcs["xp_vitality"] = l::bind(&InfoStruct::get_xp_vitality, &info_struct); + get_float_funcs["tradeskill_xp_vitality"] = l::bind(&InfoStruct::get_tradeskill_xp_vitality, &info_struct); + get_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::get_mitigation_skill1, &info_struct); + get_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::get_mitigation_skill2, &info_struct); + get_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::get_mitigation_skill3, &info_struct); + get_int16_funcs["mitigation_pve"] = l::bind(&InfoStruct::get_mitigation_pve, &info_struct); + get_int16_funcs["mitigation_pvp"] = l::bind(&InfoStruct::get_mitigation_pvp, &info_struct); + get_float_funcs["ability_modifier"] = l::bind(&InfoStruct::get_ability_modifier, &info_struct); + get_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::get_critical_mitigation, &info_struct); + get_float_funcs["block_chance"] = l::bind(&InfoStruct::get_block_chance, &info_struct); + get_float_funcs["uncontested_parry"] = l::bind(&InfoStruct::get_uncontested_parry, &info_struct); + get_float_funcs["uncontested_block"] = l::bind(&InfoStruct::get_uncontested_block, &info_struct); + get_float_funcs["uncontested_dodge"] = l::bind(&InfoStruct::get_uncontested_dodge, &info_struct); + get_float_funcs["uncontested_riposte"] = l::bind(&InfoStruct::get_uncontested_riposte, &info_struct); + get_float_funcs["crit_chance"] = l::bind(&InfoStruct::get_crit_chance, &info_struct); + get_float_funcs["crit_bonus"] = l::bind(&InfoStruct::get_crit_bonus, &info_struct); + get_float_funcs["potency"] = l::bind(&InfoStruct::get_potency, &info_struct); + get_float_funcs["hate_mod"] = l::bind(&InfoStruct::get_hate_mod, &info_struct); + get_float_funcs["reuse_speed"] = l::bind(&InfoStruct::get_reuse_speed, &info_struct); + get_float_funcs["casting_speed"] = l::bind(&InfoStruct::get_casting_speed, &info_struct); + get_float_funcs["recovery_speed"] = l::bind(&InfoStruct::get_recovery_speed, &info_struct); + get_float_funcs["spell_reuse_speed"] = l::bind(&InfoStruct::get_spell_reuse_speed, &info_struct); + get_float_funcs["spell_multi_attack"] = l::bind(&InfoStruct::get_spell_multi_attack, &info_struct); + get_float_funcs["dps"] = l::bind(&InfoStruct::get_dps, &info_struct); + get_float_funcs["dps_multiplier"] = l::bind(&InfoStruct::get_dps_multiplier, &info_struct); + get_float_funcs["attackspeed"] = l::bind(&InfoStruct::get_attackspeed, &info_struct); + get_float_funcs["haste"] = l::bind(&InfoStruct::get_haste, &info_struct); + get_float_funcs["multi_attack"] = l::bind(&InfoStruct::get_multi_attack, &info_struct); + get_float_funcs["flurry"] = l::bind(&InfoStruct::get_flurry, &info_struct); + get_float_funcs["melee_ae"] = l::bind(&InfoStruct::get_melee_ae, &info_struct); + get_float_funcs["strikethrough"] = l::bind(&InfoStruct::get_strikethrough, &info_struct); + get_float_funcs["accuracy"] = l::bind(&InfoStruct::get_accuracy, &info_struct); + get_float_funcs["offensivespeed"] = l::bind(&InfoStruct::get_offensivespeed, &info_struct); + get_float_funcs["rain"] = l::bind(&InfoStruct::get_rain, &info_struct); + get_float_funcs["wind"] = l::bind(&InfoStruct::get_wind, &info_struct); + get_sint8_funcs["alignment"] = l::bind(&InfoStruct::get_alignment, &info_struct); + get_int32_funcs["pet_id"] = l::bind(&InfoStruct::get_pet_id, &info_struct); + get_string_funcs["pet_name"] = l::bind(&InfoStruct::get_pet_name, &info_struct); + get_float_funcs["pet_health_pct"] = l::bind(&InfoStruct::get_pet_health_pct, &info_struct); + get_float_funcs["pet_power_pct"] = l::bind(&InfoStruct::get_pet_power_pct, &info_struct); + get_int8_funcs["pet_movement"] = l::bind(&InfoStruct::get_pet_movement, &info_struct); + get_int8_funcs["pet_behavior"] = l::bind(&InfoStruct::get_pet_behavior, &info_struct); + get_int32_funcs["vision"] = l::bind(&InfoStruct::get_vision, &info_struct); + get_int8_funcs["breathe_underwater"] = l::bind(&InfoStruct::get_breathe_underwater, &info_struct); + get_string_funcs["biography"] = l::bind(&InfoStruct::get_biography, &info_struct); + get_float_funcs["drunk"] = l::bind(&InfoStruct::get_drunk, &info_struct); + + get_sint16_funcs["power_regen"] = l::bind(&InfoStruct::get_power_regen, &info_struct); + get_sint16_funcs["hp_regen"] = l::bind(&InfoStruct::get_hp_regen, &info_struct); + + get_int8_funcs["power_regen_override"] = l::bind(&InfoStruct::get_power_regen_override, &info_struct); + get_int8_funcs["hp_regen_override"] = l::bind(&InfoStruct::get_hp_regen_override, &info_struct); + + get_int8_funcs["water_type"] = l::bind(&InfoStruct::get_water_type, &info_struct); + get_int8_funcs["flying_type"] = l::bind(&InfoStruct::get_flying_type, &info_struct); + + get_int8_funcs["no_interrupt"] = l::bind(&InfoStruct::get_no_interrupt, &info_struct); + + get_int8_funcs["interaction_flag"] = l::bind(&InfoStruct::get_interaction_flag, &info_struct); + get_int8_funcs["tag1"] = l::bind(&InfoStruct::get_tag1, &info_struct); + get_int16_funcs["mood"] = l::bind(&InfoStruct::get_mood, &info_struct); + + get_int32_funcs["range_last_attack_time"] = l::bind(&InfoStruct::get_range_last_attack_time, &info_struct); + get_int32_funcs["primary_last_attack_time"] = l::bind(&InfoStruct::get_primary_last_attack_time, &info_struct); + get_int32_funcs["secondary_last_attack_time"] = l::bind(&InfoStruct::get_secondary_last_attack_time, &info_struct); + + get_int16_funcs["primary_attack_delay"] = l::bind(&InfoStruct::get_primary_attack_delay, &info_struct); + get_int16_funcs["secondary_attack_delay"] = l::bind(&InfoStruct::get_secondary_attack_delay, &info_struct); + get_int16_funcs["ranged_attack_delay"] = l::bind(&InfoStruct::get_ranged_attack_delay, &info_struct); + + get_int8_funcs["primary_weapon_type"] = l::bind(&InfoStruct::get_primary_weapon_type, &info_struct); + get_int8_funcs["secondary_weapon_type"] = l::bind(&InfoStruct::get_secondary_weapon_type, &info_struct); + get_int8_funcs["ranged_weapon_type"] = l::bind(&InfoStruct::get_ranged_weapon_type, &info_struct); + + get_int32_funcs["primary_weapon_damage_low"] = l::bind(&InfoStruct::get_primary_weapon_damage_low, &info_struct); + get_int32_funcs["primary_weapon_damage_high"] = l::bind(&InfoStruct::get_primary_weapon_damage_high, &info_struct); + get_int32_funcs["secondary_weapon_damage_low"] = l::bind(&InfoStruct::get_secondary_weapon_damage_low, &info_struct); + get_int32_funcs["secondary_weapon_damage_high"] = l::bind(&InfoStruct::get_secondary_weapon_damage_high, &info_struct); + get_int32_funcs["ranged_weapon_damage_low"] = l::bind(&InfoStruct::get_ranged_weapon_damage_low, &info_struct); + get_int32_funcs["ranged_weapon_damage_high"] = l::bind(&InfoStruct::get_ranged_weapon_damage_high, &info_struct); + + get_int8_funcs["wield_type"] = l::bind(&InfoStruct::get_wield_type, &info_struct); + get_int8_funcs["attack_type"] = l::bind(&InfoStruct::get_attack_type, &info_struct); + + get_int16_funcs["primary_weapon_delay"] = l::bind(&InfoStruct::get_primary_weapon_delay, &info_struct); + get_int16_funcs["secondary_weapon_delay"] = l::bind(&InfoStruct::get_secondary_weapon_delay, &info_struct); + get_int16_funcs["ranged_weapon_delay"] = l::bind(&InfoStruct::get_ranged_weapon_delay, &info_struct); + + get_int8_funcs["override_primary_weapon"] = l::bind(&InfoStruct::get_override_primary_weapon, &info_struct); + get_int8_funcs["override_secondary_weapon"] = l::bind(&InfoStruct::get_override_secondary_weapon, &info_struct); + get_int8_funcs["override_ranged_weapon"] = l::bind(&InfoStruct::get_override_ranged_weapon, &info_struct); + + get_int8_funcs["friendly_target_npc"] = l::bind(&InfoStruct::get_friendly_target_npc, &info_struct); + get_int32_funcs["last_claim_time"] = l::bind(&InfoStruct::get_last_claim_time, &info_struct); + + get_int8_funcs["engaged_encounter"] = l::bind(&InfoStruct::get_engaged_encounter, &info_struct); + + get_int8_funcs["first_world_login"] = l::bind(&InfoStruct::get_first_world_login, &info_struct); + + get_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::get_reload_player_spells, &info_struct); + + get_int8_funcs["group_loot_method"] = l::bind(&InfoStruct::get_group_loot_method, &info_struct); + get_int8_funcs["group_loot_items_rarity"] = l::bind(&InfoStruct::get_group_loot_items_rarity, &info_struct); + get_int8_funcs["group_auto_split"] = l::bind(&InfoStruct::get_group_auto_split, &info_struct); + get_int8_funcs["group_default_yell"] = l::bind(&InfoStruct::get_group_default_yell, &info_struct); + get_int8_funcs["group_autolock"] = l::bind(&InfoStruct::get_group_autolock, &info_struct); + get_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::get_group_lock_method, &info_struct); + get_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::get_group_solo_autolock, &info_struct); + get_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::get_group_auto_loot_method, &info_struct); + get_int8_funcs["assist_auto_attack"] = l::bind(&InfoStruct::get_assist_auto_attack, &info_struct); + + get_string_funcs["action_state"] = l::bind(&InfoStruct::get_action_state, &info_struct); + get_string_funcs["combat_action_state"] = l::bind(&InfoStruct::get_combat_action_state, &info_struct); + + get_float_funcs["max_spell_reduction"] = l::bind(&InfoStruct::get_max_spell_reduction, &info_struct); + get_int8_funcs["max_spell_reduction_override"] = l::bind(&InfoStruct::get_max_spell_reduction_override, &info_struct); + +/** SETS **/ + set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1); + set_int8_funcs["class1"] = l::bind(&InfoStruct::set_class1, &info_struct, l::_1); + set_int8_funcs["class2"] = l::bind(&InfoStruct::set_class2, &info_struct, l::_1); + set_int8_funcs["class3"] = l::bind(&InfoStruct::set_class3, &info_struct, l::_1); + set_int8_funcs["race"] = l::bind(&InfoStruct::set_race, &info_struct, l::_1); + set_int8_funcs["gender"] = l::bind(&InfoStruct::set_gender, &info_struct, l::_1); + set_int16_funcs["level"] = l::bind(&InfoStruct::set_level, &info_struct, l::_1); + set_int16_funcs["max_level"] = l::bind(&InfoStruct::set_max_level, &info_struct, l::_1); + set_int16_funcs["effective_level"] = l::bind(&InfoStruct::set_effective_level, &info_struct, l::_1); + set_int16_funcs["tradeskill_level"] = l::bind(&InfoStruct::set_tradeskill_level, &info_struct, l::_1); + set_int16_funcs["tradeskill_max_level"] = l::bind(&InfoStruct::set_tradeskill_max_level, &info_struct, l::_1); + set_int8_funcs["cur_concentration"] = l::bind(&InfoStruct::set_cur_concentration, &info_struct, l::_1); + set_int8_funcs["max_concentration"] = l::bind(&InfoStruct::set_max_concentration, &info_struct, l::_1); + set_int8_funcs["max_concentration_base"] = l::bind(&InfoStruct::set_max_concentration_base, &info_struct, l::_1); + set_int16_funcs["cur_attack"] = l::bind(&InfoStruct::set_cur_attack, &info_struct, l::_1); + set_int16_funcs["attack_base"] = l::bind(&InfoStruct::set_attack_base, &info_struct, l::_1); + set_int16_funcs["cur_mitigation"] = l::bind(&InfoStruct::set_cur_mitigation, &info_struct, l::_1); + set_int16_funcs["max_mitigation"] = l::bind(&InfoStruct::set_max_mitigation, &info_struct, l::_1); + set_int16_funcs["mitigation_base"] = l::bind(&InfoStruct::set_mitigation_base, &info_struct, l::_1); + set_int16_funcs["avoidance_display"] = l::bind(&InfoStruct::set_avoidance_display, &info_struct, l::_1); + set_float_funcs["cur_avoidance"] = l::bind(&InfoStruct::set_cur_avoidance, &info_struct, l::_1); + set_int16_funcs["base_avoidance_pct"] = l::bind(&InfoStruct::set_base_avoidance_pct, &info_struct, l::_1); + set_int16_funcs["avoidance_base"] = l::bind(&InfoStruct::set_avoidance_base, &info_struct, l::_1); + set_int16_funcs["max_avoidance"] = l::bind(&InfoStruct::set_max_avoidance, &info_struct, l::_1); + set_float_funcs["parry"] = l::bind(&InfoStruct::set_parry, &info_struct, l::_1); + set_float_funcs["parry_base"] = l::bind(&InfoStruct::set_parry_base, &info_struct, l::_1); + set_float_funcs["deflection"] = l::bind(&InfoStruct::set_deflection, &info_struct, l::_1); + set_int16_funcs["deflection_base"] = l::bind(&InfoStruct::set_deflection_base, &info_struct, l::_1); + set_float_funcs["block"] = l::bind(&InfoStruct::set_block, &info_struct, l::_1); + set_int16_funcs["block_base"] = l::bind(&InfoStruct::set_block_base, &info_struct, l::_1); + set_float_funcs["str"] = l::bind(&InfoStruct::set_str, &info_struct, l::_1); + set_float_funcs["sta"] = l::bind(&InfoStruct::set_sta, &info_struct, l::_1); + set_float_funcs["agi"] = l::bind(&InfoStruct::set_agi, &info_struct, l::_1); + set_float_funcs["wis"] = l::bind(&InfoStruct::set_wis, &info_struct, l::_1); + set_float_funcs["intel"] = l::bind(&InfoStruct::set_intel, &info_struct, l::_1); + set_float_funcs["str_base"] = l::bind(&InfoStruct::set_str_base, &info_struct, l::_1); + set_float_funcs["sta_base"] = l::bind(&InfoStruct::set_sta_base, &info_struct, l::_1); + set_float_funcs["agi_base"] = l::bind(&InfoStruct::set_agi_base, &info_struct, l::_1); + set_float_funcs["wis_base"] = l::bind(&InfoStruct::set_wis_base, &info_struct, l::_1); + set_float_funcs["intel_base"] = l::bind(&InfoStruct::set_intel_base, &info_struct, l::_1); + set_int16_funcs["heat"] = l::bind(&InfoStruct::set_heat, &info_struct, l::_1); + set_int16_funcs["cold"] = l::bind(&InfoStruct::set_cold, &info_struct, l::_1); + set_int16_funcs["magic"] = l::bind(&InfoStruct::set_magic, &info_struct, l::_1); + set_int16_funcs["mental"] = l::bind(&InfoStruct::set_mental, &info_struct, l::_1); + set_int16_funcs["divine"] = l::bind(&InfoStruct::set_divine, &info_struct, l::_1); + set_int16_funcs["disease"] = l::bind(&InfoStruct::set_disease, &info_struct, l::_1); + set_int16_funcs["poison"] = l::bind(&InfoStruct::set_poison, &info_struct, l::_1); + set_int16_funcs["disease_base"] = l::bind(&InfoStruct::set_disease_base, &info_struct, l::_1); + set_int16_funcs["cold_base"] = l::bind(&InfoStruct::set_cold_base, &info_struct, l::_1); + set_int16_funcs["divine_base"] = l::bind(&InfoStruct::set_divine_base, &info_struct, l::_1); + set_int16_funcs["magic_base"] = l::bind(&InfoStruct::set_magic_base, &info_struct, l::_1); + set_int16_funcs["mental_base"] = l::bind(&InfoStruct::set_mental_base, &info_struct, l::_1); + set_int16_funcs["heat_base"] = l::bind(&InfoStruct::set_heat_base, &info_struct, l::_1); + set_int16_funcs["poison_base"] = l::bind(&InfoStruct::set_poison_base, &info_struct, l::_1); + set_int16_funcs["elemental_base"] = l::bind(&InfoStruct::set_elemental_base, &info_struct, l::_1); + set_int16_funcs["noxious_base"] = l::bind(&InfoStruct::set_noxious_base, &info_struct, l::_1); + set_int16_funcs["arcane_base"] = l::bind(&InfoStruct::set_arcane_base, &info_struct, l::_1); + set_int32_funcs["coin_copper"] = l::bind(&InfoStruct::set_coin_copper, &info_struct, l::_1); + set_int32_funcs["coin_silver"] = l::bind(&InfoStruct::set_coin_silver, &info_struct, l::_1); + set_int32_funcs["coin_gold"] = l::bind(&InfoStruct::set_coin_gold, &info_struct, l::_1); + set_int32_funcs["coin_plat"] = l::bind(&InfoStruct::set_coin_plat, &info_struct, l::_1); + set_int32_funcs["bank_coin_copper"] = l::bind(&InfoStruct::set_bank_coin_copper, &info_struct, l::_1); + set_int32_funcs["bank_coin_silver"] = l::bind(&InfoStruct::set_bank_coin_silver, &info_struct, l::_1); + set_int32_funcs["bank_coin_gold"] = l::bind(&InfoStruct::set_bank_coin_gold, &info_struct, l::_1); + set_int32_funcs["bank_coin_plat"] = l::bind(&InfoStruct::set_bank_coin_plat, &info_struct, l::_1); + set_int32_funcs["status_points"] = l::bind(&InfoStruct::set_status_points, &info_struct, l::_1); + set_string_funcs["deity"] = l::bind(&InfoStruct::set_deity, &info_struct, l::_1); + set_int32_funcs["weight"] = l::bind(&InfoStruct::set_weight, &info_struct, l::_1); + set_int32_funcs["max_weight"] = l::bind(&InfoStruct::set_max_weight, &info_struct, l::_1); + set_int8_funcs["tradeskill_class1"] = l::bind(&InfoStruct::set_tradeskill_class1, &info_struct, l::_1); + set_int8_funcs["tradeskill_class2"] = l::bind(&InfoStruct::set_tradeskill_class2, &info_struct, l::_1); + set_int8_funcs["tradeskill_class3"] = l::bind(&InfoStruct::set_tradeskill_class3, &info_struct, l::_1); + set_int32_funcs["account_age_base"] = l::bind(&InfoStruct::set_account_age_base, &info_struct, l::_1); + // int8 account_age_bonus_[19]; + set_int16_funcs["absorb"] = l::bind(&InfoStruct::set_absorb, &info_struct, l::_1); + set_int32_funcs["xp"] = l::bind(&InfoStruct::set_xp, &info_struct, l::_1); + set_int32_funcs["xp_needed"] = l::bind(&InfoStruct::set_xp_needed, &info_struct, l::_1); + set_float_funcs["xp_debt"] = l::bind(&InfoStruct::set_xp_debt, &info_struct, l::_1); + set_int16_funcs["xp_yellow"] = l::bind(&InfoStruct::set_xp_yellow, &info_struct, l::_1); + set_int16_funcs["xp_yellow_vitality_bar"] = l::bind(&InfoStruct::set_xp_yellow_vitality_bar, &info_struct, l::_1); + set_int16_funcs["xp_blue_vitality_bar"] = l::bind(&InfoStruct::set_xp_blue_vitality_bar, &info_struct, l::_1); + set_int16_funcs["xp_blue"] = l::bind(&InfoStruct::set_xp_blue, &info_struct, l::_1); + set_int32_funcs["ts_xp"] = l::bind(&InfoStruct::set_ts_xp, &info_struct, l::_1); + set_int32_funcs["ts_xp_needed"] = l::bind(&InfoStruct::set_ts_xp_needed, &info_struct, l::_1); + set_int16_funcs["tradeskill_exp_yellow"] = l::bind(&InfoStruct::set_tradeskill_exp_yellow, &info_struct, l::_1); + set_int16_funcs["tradeskill_exp_blue"] = l::bind(&InfoStruct::set_tradeskill_exp_blue, &info_struct, l::_1); + set_int32_funcs["flags"] = l::bind(&InfoStruct::set_flags, &info_struct, l::_1); + set_int32_funcs["flags2"] = l::bind(&InfoStruct::set_flags2, &info_struct, l::_1); + set_float_funcs["xp_vitality"] = l::bind(&InfoStruct::set_xp_vitality, &info_struct, l::_1); + set_float_funcs["tradeskill_xp_vitality"] = l::bind(&InfoStruct::set_tradeskill_xp_vitality, &info_struct, l::_1); + set_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::set_mitigation_skill1, &info_struct, l::_1); + set_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::set_mitigation_skill2, &info_struct, l::_1); + set_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::set_mitigation_skill3, &info_struct, l::_1); + set_int16_funcs["mitigation_pve"] = l::bind(&InfoStruct::set_mitigation_pve, &info_struct, l::_1); + set_int16_funcs["mitigation_pvp"] = l::bind(&InfoStruct::set_mitigation_pvp, &info_struct, l::_1); + set_float_funcs["ability_modifier"] = l::bind(&InfoStruct::set_ability_modifier, &info_struct, l::_1); + set_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::set_critical_mitigation, &info_struct, l::_1); + set_float_funcs["block_chance"] = l::bind(&InfoStruct::set_block_chance, &info_struct, l::_1); + set_float_funcs["uncontested_parry"] = l::bind(&InfoStruct::set_uncontested_parry, &info_struct, l::_1); + set_float_funcs["uncontested_block"] = l::bind(&InfoStruct::set_uncontested_block, &info_struct, l::_1); + set_float_funcs["uncontested_dodge"] = l::bind(&InfoStruct::set_uncontested_dodge, &info_struct, l::_1); + set_float_funcs["uncontested_riposte"] = l::bind(&InfoStruct::set_uncontested_riposte, &info_struct, l::_1); + set_float_funcs["crit_chance"] = l::bind(&InfoStruct::set_crit_chance, &info_struct, l::_1); + set_float_funcs["crit_bonus"] = l::bind(&InfoStruct::set_crit_bonus, &info_struct, l::_1); + set_float_funcs["potency"] = l::bind(&InfoStruct::set_potency, &info_struct, l::_1); + set_float_funcs["hate_mod"] = l::bind(&InfoStruct::set_hate_mod, &info_struct, l::_1); + set_float_funcs["reuse_speed"] = l::bind(&InfoStruct::set_reuse_speed, &info_struct, l::_1); + set_float_funcs["casting_speed"] = l::bind(&InfoStruct::set_casting_speed, &info_struct, l::_1); + set_float_funcs["recovery_speed"] = l::bind(&InfoStruct::set_recovery_speed, &info_struct, l::_1); + set_float_funcs["spell_reuse_speed"] = l::bind(&InfoStruct::set_spell_reuse_speed, &info_struct, l::_1); + set_float_funcs["spell_multi_attack"] = l::bind(&InfoStruct::set_spell_multi_attack, &info_struct, l::_1); + set_float_funcs["dps"] = l::bind(&InfoStruct::set_dps, &info_struct, l::_1); + set_float_funcs["dps_multiplier"] = l::bind(&InfoStruct::set_dps_multiplier, &info_struct, l::_1); + set_float_funcs["attackspeed"] = l::bind(&InfoStruct::set_attackspeed, &info_struct, l::_1); + set_float_funcs["haste"] = l::bind(&InfoStruct::set_haste, &info_struct, l::_1); + set_float_funcs["multi_attack"] = l::bind(&InfoStruct::set_multi_attack, &info_struct, l::_1); + set_float_funcs["flurry"] = l::bind(&InfoStruct::set_flurry, &info_struct, l::_1); + set_float_funcs["melee_ae"] = l::bind(&InfoStruct::set_melee_ae, &info_struct, l::_1); + set_float_funcs["strikethrough"] = l::bind(&InfoStruct::set_strikethrough, &info_struct, l::_1); + set_float_funcs["accuracy"] = l::bind(&InfoStruct::set_accuracy, &info_struct, l::_1); + set_float_funcs["offensivespeed"] = l::bind(&InfoStruct::set_offensivespeed, &info_struct, l::_1); + set_float_funcs["rain"] = l::bind(&InfoStruct::set_rain, &info_struct, l::_1); + set_float_funcs["wind"] = l::bind(&InfoStruct::set_wind, &info_struct, l::_1); + set_sint8_funcs["alignment"] = l::bind(&InfoStruct::set_alignment, &info_struct, l::_1); + set_int32_funcs["pet_id"] = l::bind(&InfoStruct::set_pet_id, &info_struct, l::_1); + set_string_funcs["pet_name"] = l::bind(&InfoStruct::set_pet_name, &info_struct, l::_1); + set_float_funcs["pet_health_pct"] = l::bind(&InfoStruct::set_pet_health_pct, &info_struct, l::_1); + set_float_funcs["pet_power_pct"] = l::bind(&InfoStruct::set_pet_power_pct, &info_struct, l::_1); + set_int8_funcs["pet_movement"] = l::bind(&InfoStruct::set_pet_movement, &info_struct, l::_1); + set_int8_funcs["pet_behavior"] = l::bind(&InfoStruct::set_pet_behavior, &info_struct, l::_1); + set_int32_funcs["vision"] = l::bind(&InfoStruct::set_vision, &info_struct, l::_1); + set_int8_funcs["breathe_underwater"] = l::bind(&InfoStruct::set_breathe_underwater, &info_struct, l::_1); + set_string_funcs["biography"] = l::bind(&InfoStruct::set_biography, &info_struct, l::_1); + set_float_funcs["drunk"] = l::bind(&InfoStruct::set_drunk, &info_struct, l::_1); + + set_sint16_funcs["power_regen"] = l::bind(&InfoStruct::set_power_regen, &info_struct, l::_1); + set_sint16_funcs["hp_regen"] = l::bind(&InfoStruct::set_hp_regen, &info_struct, l::_1); + + set_int8_funcs["power_regen_override"] = l::bind(&InfoStruct::set_power_regen_override, &info_struct, l::_1); + set_int8_funcs["hp_regen_override"] = l::bind(&InfoStruct::set_hp_regen_override, &info_struct, l::_1); + + set_int8_funcs["water_type"] = l::bind(&InfoStruct::set_water_type, &info_struct, l::_1); + set_int8_funcs["flying_type"] = l::bind(&InfoStruct::set_flying_type, &info_struct, l::_1); + + set_int8_funcs["no_interrupt"] = l::bind(&InfoStruct::set_no_interrupt, &info_struct, l::_1); + + set_int8_funcs["interaction_flag"] = l::bind(&InfoStruct::set_interaction_flag, &info_struct, l::_1); + set_int8_funcs["tag1"] = l::bind(&InfoStruct::set_tag1, &info_struct, l::_1); + set_int16_funcs["mood"] = l::bind(&InfoStruct::set_mood, &info_struct, l::_1); + + set_int32_funcs["range_last_attack_time"] = l::bind(&InfoStruct::set_range_last_attack_time, &info_struct, l::_1); + set_int32_funcs["primary_last_attack_time"] = l::bind(&InfoStruct::set_primary_last_attack_time, &info_struct, l::_1); + set_int32_funcs["secondary_last_attack_time"] = l::bind(&InfoStruct::set_secondary_last_attack_time, &info_struct, l::_1); + + set_int16_funcs["primary_attack_delay"] = l::bind(&InfoStruct::set_primary_attack_delay, &info_struct, l::_1); + set_int16_funcs["secondary_attack_delay"] = l::bind(&InfoStruct::set_secondary_attack_delay, &info_struct, l::_1); + set_int16_funcs["ranged_attack_delay"] = l::bind(&InfoStruct::set_ranged_attack_delay, &info_struct, l::_1); + + set_int8_funcs["primary_weapon_type"] = l::bind(&InfoStruct::set_primary_weapon_type, &info_struct, l::_1); + set_int8_funcs["secondary_weapon_type"] = l::bind(&InfoStruct::set_secondary_weapon_type, &info_struct, l::_1); + set_int8_funcs["ranged_weapon_type"] = l::bind(&InfoStruct::set_ranged_weapon_type, &info_struct, l::_1); + + set_int32_funcs["primary_weapon_damage_low"] = l::bind(&InfoStruct::set_primary_weapon_damage_low, &info_struct, l::_1); + set_int32_funcs["primary_weapon_damage_high"] = l::bind(&InfoStruct::set_primary_weapon_damage_high, &info_struct, l::_1); + set_int32_funcs["secondary_weapon_damage_low"] = l::bind(&InfoStruct::set_secondary_weapon_damage_low, &info_struct, l::_1); + set_int32_funcs["secondary_weapon_damage_high"] = l::bind(&InfoStruct::set_secondary_weapon_damage_high, &info_struct, l::_1); + set_int32_funcs["ranged_weapon_damage_low"] = l::bind(&InfoStruct::set_ranged_weapon_damage_low, &info_struct, l::_1); + set_int32_funcs["ranged_weapon_damage_high"] = l::bind(&InfoStruct::set_ranged_weapon_damage_high, &info_struct, l::_1); + + set_int8_funcs["wield_type"] = l::bind(&InfoStruct::set_wield_type, &info_struct, l::_1); + set_int8_funcs["attack_type"] = l::bind(&InfoStruct::set_attack_type, &info_struct, l::_1); + + set_int16_funcs["primary_weapon_delay"] = l::bind(&InfoStruct::set_primary_weapon_delay, &info_struct, l::_1); + set_int16_funcs["secondary_weapon_delay"] = l::bind(&InfoStruct::set_secondary_weapon_delay, &info_struct, l::_1); + set_int16_funcs["ranged_weapon_delay"] = l::bind(&InfoStruct::set_ranged_weapon_delay, &info_struct, l::_1); + + set_int8_funcs["override_primary_weapon"] = l::bind(&InfoStruct::set_override_primary_weapon, &info_struct, l::_1); + set_int8_funcs["override_secondary_weapon"] = l::bind(&InfoStruct::set_override_secondary_weapon, &info_struct, l::_1); + set_int8_funcs["override_ranged_weapon"] = l::bind(&InfoStruct::set_override_ranged_weapon, &info_struct, l::_1); + + set_int8_funcs["friendly_target_npc"] = l::bind(&InfoStruct::set_friendly_target_npc, &info_struct, l::_1); + set_int32_funcs["last_claim_time"] = l::bind(&InfoStruct::set_last_claim_time, &info_struct, l::_1); + + set_int8_funcs["engaged_encounter"] = l::bind(&InfoStruct::set_engaged_encounter, &info_struct, l::_1); + + set_int8_funcs["first_world_login"] = l::bind(&InfoStruct::set_first_world_login, &info_struct, l::_1); + + set_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::set_reload_player_spells, &info_struct, l::_1); + + set_int8_funcs["group_loot_method"] = l::bind(&InfoStruct::set_group_loot_method, &info_struct, l::_1); + set_int8_funcs["group_loot_items_rarity"] = l::bind(&InfoStruct::set_group_loot_items_rarity, &info_struct, l::_1); + set_int8_funcs["group_auto_split"] = l::bind(&InfoStruct::set_group_auto_split, &info_struct, l::_1); + set_int8_funcs["group_default_yell"] = l::bind(&InfoStruct::set_group_default_yell, &info_struct, l::_1); + set_int8_funcs["group_autolock"] = l::bind(&InfoStruct::set_group_autolock, &info_struct, l::_1); + set_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::set_group_lock_method, &info_struct, l::_1); + set_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::set_group_solo_autolock, &info_struct, l::_1); + set_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::set_group_auto_loot_method, &info_struct, l::_1); + set_int8_funcs["assist_auto_attack"] = l::bind(&InfoStruct::set_assist_auto_attack, &info_struct, l::_1); + + set_string_funcs["action_state"] = l::bind(&InfoStruct::set_action_state, &info_struct, l::_1); + set_string_funcs["combat_action_state"] = l::bind(&InfoStruct::set_combat_action_state, &info_struct, l::_1); + + set_float_funcs["max_spell_reduction"] = l::bind(&InfoStruct::set_max_spell_reduction, &info_struct, l::_1); + set_int8_funcs["max_spell_reduction_override"] = l::bind(&InfoStruct::set_max_spell_reduction_override, &info_struct, l::_1); +} + +bool Entity::HasMoved(bool include_heading){ + if(GetX() == last_x && GetY() == last_y && GetZ() == last_z && ((!include_heading) || (include_heading && GetHeading() == last_heading))) + return false; + bool ret_val = true; + if(last_x == -1 && last_y == -1 && last_z == -1 && last_heading == -1){ + ret_val = false; + } + last_x = GetX(); + last_y = GetY(); + last_z = GetZ(); + last_heading = GetHeading(); + return ret_val; +} + +int16 Entity::GetStr(){ + return GetInfoStruct()->get_str(); +} + +int16 Entity::GetSta(){ + return GetInfoStruct()->get_sta(); +} + +int16 Entity::GetInt(){ + return GetInfoStruct()->get_intel(); +} + +int16 Entity::GetWis(){ + return GetInfoStruct()->get_wis(); +} + +int16 Entity::GetAgi(){ + return GetInfoStruct()->get_agi(); +} + +int16 Entity::GetPrimaryStat(){ + int8 base_class = classes.GetBaseClass(GetAdventureClass()); + if (base_class == FIGHTER) + return GetInfoStruct()->get_str(); + else if (base_class == PRIEST) + return GetInfoStruct()->get_wis(); + else if (base_class == MAGE) + return GetInfoStruct()->get_intel(); + else + return GetInfoStruct()->get_agi(); +} + +int16 Entity::GetHeatResistance(){ + return GetInfoStruct()->get_heat(); +} + +int16 Entity::GetColdResistance(){ + return GetInfoStruct()->get_cold(); +} + +int16 Entity::GetMagicResistance(){ + return GetInfoStruct()->get_magic(); +} + +int16 Entity::GetMentalResistance(){ + return GetInfoStruct()->get_mental(); +} + +int16 Entity::GetDivineResistance(){ + return GetInfoStruct()->get_divine(); +} + +int16 Entity::GetDiseaseResistance(){ + return GetInfoStruct()->get_disease(); +} + +int16 Entity::GetPoisonResistance(){ + return GetInfoStruct()->get_poison(); +} + +int8 Entity::GetConcentrationCurrent() { + return GetInfoStruct()->get_cur_concentration(); +} + +int8 Entity::GetConcentrationMax() { + return GetInfoStruct()->get_max_concentration(); +} + +int16 Entity::GetStrBase(){ + return GetInfoStruct()->get_str_base(); +} + +int16 Entity::GetStaBase(){ + return GetInfoStruct()->get_sta_base(); +} + +int16 Entity::GetIntBase(){ + return GetInfoStruct()->get_intel_base(); +} + +int16 Entity::GetWisBase(){ + return GetInfoStruct()->get_wis_base(); +} + +int16 Entity::GetAgiBase(){ + return GetInfoStruct()->get_agi_base(); +} + +int16 Entity::GetHeatResistanceBase(){ + return GetInfoStruct()->get_heat_base(); +} + +int16 Entity::GetColdResistanceBase(){ + return GetInfoStruct()->get_cold_base(); +} + +int16 Entity::GetMagicResistanceBase(){ + return GetInfoStruct()->get_magic_base(); +} + +int16 Entity::GetMentalResistanceBase(){ + return GetInfoStruct()->get_mental_base(); +} + +int16 Entity::GetDivineResistanceBase(){ + return GetInfoStruct()->get_divine_base(); +} + +int16 Entity::GetDiseaseResistanceBase(){ + return GetInfoStruct()->get_disease_base(); +} + +int16 Entity::GetPoisonResistanceBase(){ + return GetInfoStruct()->get_poison_base(); +} + +sint8 Entity::GetAlignment(){ + return GetInfoStruct()->get_alignment(); +} + +bool Entity::IsCasting(){ + return casting; +} + +void Entity::IsCasting(bool val){ + casting = val; +} + +int32 Entity::GetRangeLastAttackTime(){ + return GetInfoStruct()->get_range_last_attack_time(); +} + +void Entity::SetRangeLastAttackTime(int32 time){ + GetInfoStruct()->set_range_last_attack_time(time); +} + +int16 Entity::GetRangeAttackDelay(){ + return GetInfoStruct()->get_ranged_attack_delay(); +// if(IsPlayer()){ +// Item* item = ((Player*)this)->GetEquipmentList()->GetItem(EQ2_RANGE_SLOT); +// if(item && item->IsRanged()) +// return item->ranged_info->weapon_info.delay*100; +// } +// return 3000; +} + +int32 Entity::GetPrimaryLastAttackTime(){ + return GetInfoStruct()->get_primary_last_attack_time(); +} + +int16 Entity::GetPrimaryAttackDelay(){ + return GetInfoStruct()->get_primary_attack_delay(); +} + +void Entity::SetPrimaryAttackDelay(int16 new_delay){ + GetInfoStruct()->set_primary_attack_delay(new_delay); +} + +void Entity::SetPrimaryLastAttackTime(int32 new_time){ + GetInfoStruct()->set_primary_last_attack_time(new_time); +} + +int32 Entity::GetSecondaryLastAttackTime(){ + return GetInfoStruct()->get_secondary_last_attack_time(); +} + +int16 Entity::GetSecondaryAttackDelay(){ + return GetInfoStruct()->get_secondary_attack_delay(); +} + +void Entity::SetSecondaryAttackDelay(int16 new_delay){ + GetInfoStruct()->set_secondary_attack_delay(new_delay); +} + +void Entity::SetSecondaryLastAttackTime(int32 new_time){ + GetInfoStruct()->set_secondary_last_attack_time(new_time); +} + +void Entity::GetWeaponDamage(Item* item, int32* low_damage, int32* high_damage) { + if(!low_damage || !high_damage) + return; + int32 selected_low_dmg = item->weapon_info->damage_low3; + int32 selected_high_dmg = item->weapon_info->damage_high3; + + if(IsPlayer()) { + float skillMultiplier = rule_manager.GetGlobalRule(R_Player, LevelMasterySkillMultiplier)->GetFloat(); + if(skillMultiplier <= 0.0f) { + skillMultiplier = 1.0f; + } + int32 min_level_skill = (int32)((float)item->generic_info.adventure_default_level*skillMultiplier); + int32 rec_level_skill = (int32)((float)item->details.recommended_level*skillMultiplier); + if(min_level_skill > rec_level_skill) { + rec_level_skill = rec_level_skill; + } + + Skill* masterySkill = ((Player*)this)->skill_list.GetSkill(item->generic_info.skill_req2); + if(masterySkill) { + LogWrite(PLAYER__ERROR, 0, "Player", "Item has skill %s %u requirement", masterySkill->name.data.c_str(), item->generic_info.skill_req2); + int16 skillID = master_item_list.GetItemStatIDByName(masterySkill->name.data); + int32 skill_chance = (int32)CalculateSkillWithBonus((char*)masterySkill->name.data.c_str(), master_item_list.GetItemStatIDByName(masterySkill->name.data), false); + if(skill_chance >= min_level_skill && skill_chance < rec_level_skill) { + int32 diff_skill = rec_level_skill - skill_chance; + if(diff_skill < 1) { + selected_low_dmg = item->weapon_info->damage_low2; + selected_high_dmg = item->weapon_info->damage_high2; + } + else { + diff_skill += 1; + double logResult = log((double)diff_skill) / skillMultiplier; + if(logResult > 1.0f) { + logResult = .95f; + } + + selected_low_dmg = (int32)((double)item->weapon_info->damage_low2 * (1.0 - logResult)); + if(selected_low_dmg < item->weapon_info->damage_low3) { + selected_low_dmg = item->weapon_info->damage_low3; + } + selected_high_dmg = (int32)((double)item->weapon_info->damage_high2 * (1.0 - logResult)); + if(selected_high_dmg < item->weapon_info->damage_high3) { + selected_high_dmg = item->weapon_info->damage_high3; + } + } + } + else if(skill_chance >= rec_level_skill) { + selected_low_dmg = item->weapon_info->damage_low2; + selected_high_dmg = item->weapon_info->damage_high2; + } + } + } + + *low_damage = selected_low_dmg; + *high_damage = selected_high_dmg; +} + +void Entity::ChangePrimaryWeapon(){ + if(GetInfoStruct()->get_override_primary_weapon()) { + return; + } + + int32 str_offset_dmg = GetStrengthDamage(); + Item* item = equipment_list.GetItem(EQ2_PRIMARY_SLOT); + if(item && item->details.item_id > 0 && item->IsWeapon()){ + int32 selected_low_dmg = item->weapon_info->damage_low3; + int32 selected_high_dmg = item->weapon_info->damage_high3; + GetWeaponDamage(item, &selected_low_dmg, &selected_high_dmg); + GetInfoStruct()->set_primary_weapon_delay(item->weapon_info->delay * 100); + GetInfoStruct()->set_primary_weapon_damage_low(selected_low_dmg + str_offset_dmg); + GetInfoStruct()->set_primary_weapon_damage_high(selected_high_dmg + str_offset_dmg); + GetInfoStruct()->set_primary_weapon_type(item->GetWeaponType()); + GetInfoStruct()->set_wield_type(item->weapon_info->wield_type); + } + else{ + int16 effective_level = GetInfoStruct()->get_effective_level(); + if ( !effective_level ) + effective_level = GetLevel(); + + GetInfoStruct()->set_primary_weapon_delay(2000); + GetInfoStruct()->set_primary_weapon_damage_low((int32)1 + (effective_level * .2) + str_offset_dmg); + GetInfoStruct()->set_primary_weapon_damage_high((int32)(5 + effective_level * (effective_level/5)) + str_offset_dmg); + if(GetInfoStruct()->get_attack_type() > 0) { + GetInfoStruct()->set_primary_weapon_type(GetInfoStruct()->get_attack_type()); + } + else { + GetInfoStruct()->set_primary_weapon_type(1); + } + GetInfoStruct()->set_wield_type(2); + } +} + +void Entity::ChangeSecondaryWeapon(){ + if(GetInfoStruct()->get_override_secondary_weapon()) { + return; + } + + int32 str_offset_dmg = GetStrengthDamage(); + + Item* item = equipment_list.GetItem(EQ2_SECONDARY_SLOT); + if(item && item->details.item_id > 0 && item->IsWeapon()){ + int32 selected_low_dmg = item->weapon_info->damage_low3; + int32 selected_high_dmg = item->weapon_info->damage_high3; + GetWeaponDamage(item, &selected_low_dmg, &selected_high_dmg); + GetInfoStruct()->set_secondary_weapon_delay(item->weapon_info->delay * 100); + GetInfoStruct()->set_secondary_weapon_damage_low(selected_low_dmg + str_offset_dmg); + GetInfoStruct()->set_secondary_weapon_damage_high(selected_high_dmg + str_offset_dmg); + GetInfoStruct()->set_secondary_weapon_type(item->GetWeaponType()); + } + else{ + int16 effective_level = GetInfoStruct()->get_effective_level(); + if ( !effective_level ) + effective_level = GetLevel(); + + GetInfoStruct()->set_secondary_weapon_delay(2000); + GetInfoStruct()->set_secondary_weapon_damage_low((int32)(1 + effective_level * .2) + str_offset_dmg); + GetInfoStruct()->set_secondary_weapon_damage_high((int32)(5 + effective_level * (effective_level/6)) + str_offset_dmg); + GetInfoStruct()->set_secondary_weapon_type(1); + } +} + +void Entity::ChangeRangedWeapon(){ + if(GetInfoStruct()->get_override_ranged_weapon()) { + return; + } + + int32 str_offset_dmg = GetStrengthDamage(); + + Item* item = equipment_list.GetItem(EQ2_RANGE_SLOT); + if(item && item->details.item_id > 0 && item->IsRanged()){ + GetInfoStruct()->set_ranged_weapon_delay(item->ranged_info->weapon_info.delay*100); + GetInfoStruct()->set_ranged_weapon_damage_low(item->ranged_info->weapon_info.damage_low3 + str_offset_dmg); + GetInfoStruct()->set_ranged_weapon_damage_high(item->ranged_info->weapon_info.damage_high3 + str_offset_dmg); + GetInfoStruct()->set_ranged_weapon_type(item->GetWeaponType()); + } +} + +void Entity::UpdateWeapons() { + ChangePrimaryWeapon(); + ChangeSecondaryWeapon(); + ChangeRangedWeapon(); +} + +int32 Entity::GetStrengthDamage() { + int32 str_offset = 1; + if(IsNPC()) { + str_offset = rule_manager.GetGlobalRule(R_Combat, StrengthNPC)->GetInt32(); + if(str_offset < 1) + str_offset = 1; + } + else { + str_offset = rule_manager.GetGlobalRule(R_Combat, StrengthOther)->GetInt32(); + if(str_offset < 1) + str_offset = 1; + + } + int32 str_offset_dmg = (int32)((GetInfoStruct()->get_str() / str_offset)); + return str_offset_dmg; +} + +int32 Entity::GetPrimaryWeaponMinDamage(){ + return GetInfoStruct()->get_primary_weapon_damage_low(); +} + +int32 Entity::GetPrimaryWeaponMaxDamage(){ + return GetInfoStruct()->get_primary_weapon_damage_high(); +} + +int16 Entity::GetPrimaryWeaponDelay(){ + return GetInfoStruct()->get_primary_weapon_delay(); +} + +int16 Entity::GetSecondaryWeaponDelay(){ + return GetInfoStruct()->get_secondary_weapon_delay(); +} + +int32 Entity::GetSecondaryWeaponMinDamage(){ + return GetInfoStruct()->get_secondary_weapon_damage_low(); +} + +int32 Entity::GetSecondaryWeaponMaxDamage(){ + return GetInfoStruct()->get_secondary_weapon_damage_high(); +} + +int8 Entity::GetPrimaryWeaponType(){ + return GetInfoStruct()->get_primary_weapon_type(); +} + +int8 Entity::GetSecondaryWeaponType(){ + return GetInfoStruct()->get_secondary_weapon_type(); +} + +int32 Entity::GetRangedWeaponMinDamage(){ + return GetInfoStruct()->get_ranged_weapon_damage_low(); +} + +int32 Entity::GetRangedWeaponMaxDamage(){ + return GetInfoStruct()->get_ranged_weapon_damage_high(); +} + +int8 Entity::GetRangedWeaponType(){ + return GetInfoStruct()->get_ranged_weapon_type(); +} + +bool Entity::IsDualWield(){ + return GetInfoStruct()->get_wield_type() == 1; +} + +int8 Entity::GetWieldType(){ + return GetInfoStruct()->get_wield_type(); +} + +int16 Entity::GetRangeWeaponDelay(){ + return GetInfoStruct()->get_ranged_weapon_delay(); +} + +void Entity::SetRangeWeaponDelay(int16 new_delay){ + GetInfoStruct()->set_ranged_weapon_delay(new_delay * 100); +} +void Entity::SetRangeAttackDelay(int16 new_delay){ + GetInfoStruct()->set_ranged_attack_delay(new_delay); +} + +void Entity::SetPrimaryWeaponDelay(int16 new_delay){ + GetInfoStruct()->set_primary_weapon_delay(new_delay * 100); +} + +void Entity::SetSecondaryWeaponDelay(int16 new_delay){ + GetInfoStruct()->set_primary_weapon_delay(new_delay * 100); +} + +bool Entity::BehindTarget(Spawn* target){ + return BehindSpawn(target, GetX(), GetZ()); +} + +bool Entity::FlankingTarget(Spawn* target) { + return IsFlankingSpawn(target, GetX(), GetZ()); +} + +float Entity::GetDodgeChance(){ + float ret = 0; + + return ret; +} + +bool Entity::EngagedInCombat(){ + return in_combat; +} + +void Entity::InCombat(bool val){ + bool changeCombatState = false; + if((in_combat && !val) || (!in_combat && val)) + changeCombatState = true; + + in_combat = val; + + bool update_regen = false; + if(GetInfoStruct()->get_engaged_encounter()) { + if(!IsAggroed() || !IsEngagedInEncounter()) { + GetInfoStruct()->set_engaged_encounter(0); + update_regen = true; + } + } + + if(changeCombatState || update_regen) + SetRegenValues((GetInfoStruct()->get_effective_level() > 0) ? GetInfoStruct()->get_effective_level() : GetLevel()); +} + +void Entity::DoRegenUpdate(){ + if(!Alive() || GetHP() == 0)//dead + return; + sint32 hp = GetHP(); + sint32 power = GetPower(); + + if(hp < GetTotalHP()){ + sint16 temp = GetInfoStruct()->get_hp_regen(); + + if((hp + temp) > GetTotalHP()) + SetHP(GetTotalHP()); + else + SetHP(hp + temp); + } + if(GetPower() < GetTotalPower()){ + sint16 temp = GetInfoStruct()->get_power_regen(); + + if((power + temp) > GetTotalPower()) + SetPower(GetTotalPower()); + else + SetPower(power + temp); + } +} + +void Entity::AddMaintainedSpell(LuaSpell* luaspell){ + if (!luaspell) + return; + + Spell* spell = luaspell->spell; + MaintainedEffects* effect = GetFreeMaintainedSpellSlot(); + + if (effect){ + MMaintainedSpells.writelock(__FUNCTION__, __LINE__); + effect->spell = luaspell; + effect->spell_id = spell->GetSpellData()->id; + LogWrite(NPC__SPELLS, 5, "NPC", "AddMaintainedSpell Spell ID: %u, Concentration: %u", spell->GetSpellData()->id, spell->GetSpellData()->req_concentration); + effect->conc_used = spell->GetSpellData()->req_concentration; + effect->total_time = spell->GetSpellDuration() / 10; + effect->tier = spell->GetSpellData()->tier; + if (spell->GetSpellData()->duration_until_cancel) + effect->expire_timestamp = 0xFFFFFFFF; + else + effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration() * 100); + MMaintainedSpells.releasewritelock(__FUNCTION__, __LINE__); + } +} + +void Entity::AddSpellEffect(LuaSpell* luaspell, int32 override_expire_time){ + if (!luaspell || !luaspell->caster) + return; + + Spell* spell = luaspell->spell; + SpellEffects* old_effect = GetSpellEffect(spell->GetSpellID(), luaspell->caster); + SpellEffects* effect = 0; + if (old_effect){ + GetZone()->RemoveTargetFromSpell(old_effect->spell, this); + RemoveSpellEffect(old_effect->spell); + } + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s AddSpellEffect %s (%u).", spell->GetName(), GetName(), GetID()); + + if(!effect) + effect = GetFreeSpellEffectSlot(); + + if(effect){ + MSpellEffects.writelock(__FUNCTION__, __LINE__); + effect->spell = luaspell; + effect->spell_id = spell->GetSpellData()->id; + effect->caster = luaspell->caster; + effect->total_time = spell->GetSpellDuration()/10; + if (spell->GetSpellData()->duration_until_cancel) + effect->expire_timestamp = 0xFFFFFFFF; + else if(override_expire_time) + effect->expire_timestamp = Timer::GetCurrentTime2() + override_expire_time; + else + effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100); + effect->icon = spell->GetSpellData()->icon; + effect->icon_backdrop = spell->GetSpellData()->icon_backdrop; + effect->tier = spell->GetSpellTier(); + MSpellEffects.releasewritelock(__FUNCTION__, __LINE__); + changed = true; + info_changed = true; + AddChangedZoneSpawn(); + + if(luaspell->caster && luaspell->caster->IsPlayer() && luaspell->caster != this) + ((Player*)luaspell->caster)->GetClient()->TriggerSpellSave(); + } +} + +void Entity::RemoveMaintainedSpell(LuaSpell* luaspell){ + if (!luaspell) + return; + + bool found = false; + MMaintainedSpells.writelock(__FUNCTION__, __LINE__); + for (int i = 0; i<30; i++){ + // If we already found the spell then we are bumping all other up one so there are no gaps + // This check needs to be first so found can never be true on the first iteration (i = 0) + if (found) { + GetInfoStruct()->maintained_effects[i].slot_pos = i - 1; + GetInfoStruct()->maintained_effects[i - 1] = GetInfoStruct()->maintained_effects[i]; + + } + // Compare spells, if we found a match set the found flag + if (GetInfoStruct()->maintained_effects[i].spell == luaspell) + found = true; + + } + // if we found the spell in the array then we need to set the last element to empty + if (found) { + memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects)); + GetInfoStruct()->maintained_effects[29].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[29].icon = 0xFFFF; + GetInfoStruct()->maintained_effects[29].spell = nullptr; + } + MMaintainedSpells.releasewritelock(__FUNCTION__, __LINE__); +} + +void Entity::RemoveSpellEffect(LuaSpell* spell) { + bool found = false; + MSpellEffects.writelock(__FUNCTION__, __LINE__); + for(int i=0;i<45;i++) { + if (found) { + GetInfoStruct()->spell_effects[i-1] = GetInfoStruct()->spell_effects[i]; + } + if (GetInfoStruct()->spell_effects[i].spell == spell) + found = true; + } + if (found) { + LogWrite(SPELL__DEBUG, 0, "Spell", "%s RemoveSpellEffect %s (%u).", spell->spell->GetName(), GetName(), GetID()); + GetZone()->GetSpellProcess()->RemoveTargetFromSpell(spell, this); + memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects)); + GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF; + changed = true; + info_changed = true; + AddChangedZoneSpawn(); + + if(IsPlayer()) { + ((Player*)this)->SetCharSheetChanged(true); + } + } + + MSpellEffects.releasewritelock(__FUNCTION__, __LINE__); +} + +MaintainedEffects* Entity::GetFreeMaintainedSpellSlot(){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MMaintainedSpells.readlock(__FUNCTION__, __LINE__); + for (int i = 0; imaintained_effects[i].spell_id == 0xFFFFFFFF){ + ret = &info->maintained_effects[i]; + ret->spell_id = 0; + ret->slot_pos = i; + break; + } + } + MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +MaintainedEffects* Entity::GetMaintainedSpell(int32 spell_id){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MMaintainedSpells.readlock(__FUNCTION__, __LINE__); + for (int i = 0; imaintained_effects[i].spell_id == spell_id){ + ret = &info->maintained_effects[i]; + break; + } + } + MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Entity::GetFreeSpellEffectSlot(){ + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + for(int i=0;i<45;i++){ + if(info->spell_effects[i].spell_id == 0xFFFFFFFF){ + ret = &info->spell_effects[i]; + ret->spell_id = 0; + break; + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Entity::GetSpellEffect(int32 id, Entity* caster) { + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + for(int i = 0; i < 45; i++) { + if(info->spell_effects[i].spell_id == id) { + if (!caster || info->spell_effects[i].caster == caster){ + ret = &info->spell_effects[i]; + break; + } + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Entity::GetSpellEffectBySpellType(int8 spell_type) { + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + for(int i = 0; i < 45; i++) { + if(info->spell_effects[i].spell_id != 0xFFFFFFFF && info->spell_effects[i].spell->spell->GetSpellData()->spell_type == spell_type) { + ret = &info->spell_effects[i]; + break; + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Entity::GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer, sint32 type_group_spell_id, Entity* caster) { + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + for(int i = 0; i < 45; i++) { + if(info->spell_effects[i].spell_id != 0xFFFFFFFF) + { + if( (info->spell_effects[i].spell_id == id && linked_timer == 0 && type_group_spell_id == 0) || + (linked_timer > 0 && info->spell_effects[i].spell->spell->GetSpellData()->linked_timer == linked_timer) || + (type_group_spell_id > 0 && info->spell_effects[i].spell->spell->GetSpellData()->type_group_spell_id == type_group_spell_id)) + { + if (type_group_spell_id >= -1 && (!caster || info->spell_effects[i].caster == caster)){ + ret = &info->spell_effects[i]; + break; + } + } + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +LuaSpell* Entity::HasLinkedTimerID(LuaSpell* spell, Spawn* target, bool stackWithOtherPlayers) { + if(!spell->spell->GetSpellData()->linked_timer && !spell->spell->GetSpellData()->type_group_spell_id) + return nullptr; + LuaSpell* ret = nullptr; + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + //this for loop primarily handles self checks and 'friendly' checks + for(int i = 0; i < NUM_MAINTAINED_EFFECTS; i++) { + if(info->maintained_effects[i].spell_id != 0xFFFFFFFF) + { + if( ((info->maintained_effects[i].spell_id == spell->spell->GetSpellID() && spell->spell->GetSpellData()->type_group_spell_id >= 0) || + (info->maintained_effects[i].spell->spell->GetSpellData()->linked_timer > 0 && info->maintained_effects[i].spell->spell->GetSpellData()->linked_timer == spell->spell->GetSpellData()->linked_timer) || + (spell->spell->GetSpellData()->type_group_spell_id > 0 && spell->spell->GetSpellData()->type_group_spell_id == info->maintained_effects[i].spell->spell->GetSpellData()->type_group_spell_id)) && + ((spell->spell->GetSpellData()->friendly_spell) || + (!spell->spell->GetSpellData()->friendly_spell && spell->spell->GetSpellData()->type_group_spell_id >= -1 && spell->caster == info->maintained_effects[i].spell->caster) ) && + (target == nullptr || info->maintained_effects[i].spell->initial_target == target->GetID())) { + ret = info->maintained_effects[i].spell; + break; + } + } + } + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); + + if(!ret && !stackWithOtherPlayers && target && target->IsEntity()) + { + SpellEffects* effect = ((Entity*)target)->GetSpellEffectWithLinkedTimer(spell->spell->GetSpellID(), spell->spell->GetSpellData()->linked_timer, spell->spell->GetSpellData()->type_group_spell_id, nullptr); + if(effect) + ret = effect->spell; + } + + return ret; +} + +InfoStruct* Entity::GetInfoStruct(){ + return &info_struct; +} + +Skill* Entity::GetSkillByName(const char* name, bool check_update){ + // NPC::GetSkillByName in NPC.cpp exists for NPC's + // Player::GetSkillByName in Player.cpp exists for Player's + return 0; +} + +Skill* Entity::GetSkillByID(int32 id, bool check_update){ + // NPC::GetSkillByID in NPC.cpp exists for NPC's + // Player::GetSkillByID in Player.cpp exists for Player's + return 0; +} + +float Entity::GetMaxSpeed(){ + return max_speed; +} + +void Entity::SetMaxSpeed(float val){ + max_speed = val; +} + +float Entity::CalculateSkillStatChance(char* skillName, int16 item_stat, float max_cap, float modifier, bool add_to_skill) +{ + float skillAndItemsChance = 0.0f; + float maxBonusCap = (float)GetLevel()*rule_manager.GetGlobalRule(R_Combat, MaxSkillBonusByLevel)->GetFloat(); + Skill* skill = GetSkillByName(skillName, false); + if(skill){ + MStats.lock(); + float item_chance_or_skill = stats[item_stat]; + MStats.unlock(); + if(item_chance_or_skill > maxBonusCap) { + item_chance_or_skill = maxBonusCap; + } + + if(add_to_skill) + { + skillAndItemsChance = (((float)skill->current_val+item_chance_or_skill)/10.0f); // do we know 25 is accurate? 10 gives more 'skill' space, most cap at 70% with items + } + else + { + skillAndItemsChance = ((float)skill->current_val/10.0f); // do we know 25 is accurate? 10 gives more 'skill' space, most cap at 70% with items + + if(modifier > maxBonusCap) { + modifier = maxBonusCap; + } + // take chance percentage and add the item stats % (+1 = 1% or .01f) + skillAndItemsChance += (skillAndItemsChance*((item_chance_or_skill + modifier)/100.0f)); + } + } + + if ( max_cap > 0.0f && skillAndItemsChance > max_cap ) + skillAndItemsChance = max_cap; + + return skillAndItemsChance; +} + +float Entity::CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase) +{ + float skillAndItemsChance = 0.0f; + float maxBonusCap = GetRuleSkillMaxBonus(); + + Skill* skill = GetSkillByName(skillName, chance_skill_increase); + if(skill){ + float item_chance_or_skill = 0.0f; + if(item_stat != 0xFFFF) { + MStats.lock(); + item_chance_or_skill = stats[item_stat]; + MStats.unlock(); + } + if(item_chance_or_skill > maxBonusCap) { // would we be using their effective mentored level or actual level? + item_chance_or_skill = maxBonusCap; + } + skillAndItemsChance = skill->current_val+item_chance_or_skill; + } + + return skillAndItemsChance; +} + +float Entity::GetRuleSkillMaxBonus() { + return (float)GetLevel()*rule_manager.GetGlobalRule(R_Combat, MaxSkillBonusByLevel)->GetFloat(); +} + +void Entity::CalculateBonuses(){ + if(lua_interface->IsLuaSystemReloading()) + return; + + InfoStruct* info = &info_struct; + + int16 effective_level = info->get_effective_level() != 0 ? info->get_effective_level() : GetLevel(); + + CalculateMaxReduction(); + + info->set_block(info->get_block_base()); + + info->set_cur_attack(info->get_attack_base()); + info->set_base_avoidance_pct(info->get_avoidance_base()); + + info->set_disease(info->get_disease_base()); + info->set_divine(info->get_divine_base()); + info->set_heat(info->get_heat_base()); + info->set_magic(info->get_magic_base()); + info->set_mental(info->get_mental_base()); + info->set_cold(info->get_cold_base()); + info->set_poison(info->get_poison_base()); + info->set_elemental_base(info->get_heat()); + info->set_noxious_base(info->get_poison()); + info->set_arcane_base(info->get_magic()); + + info->set_sta(info->get_sta_base()); + info->set_agi(info->get_agi_base()); + info->set_str(info->get_str_base()); + info->set_wis(info->get_wis_base()); + info->set_intel(info->get_intel_base()); + info->set_ability_modifier(0); + info->set_critical_mitigation(0); + + info->set_block_chance(0); + info->set_crit_chance(0); + info->set_crit_bonus(0); + info->set_potency(0); + info->set_hate_mod(0); + info->set_reuse_speed(0); + info->set_casting_speed(0); + info->set_recovery_speed(0); + info->set_spell_reuse_speed(0); + info->set_spell_multi_attack(0); + info->set_dps(0); + info->set_dps_multiplier(0); + info->set_haste(0); + info->set_attackspeed(0); + info->set_multi_attack(0); + info->set_flurry(0); + info->set_melee_ae(0); + + info->set_strikethrough(0); + + info->set_accuracy(0); + + info->set_offensivespeed(0); + + MStats.lock(); + stats.clear(); + MStats.unlock(); + + ItemStatsValues* values = equipment_list.CalculateEquipmentBonuses(this); + CalculateSpellBonuses(values); + + info->set_cur_mitigation(info->get_mitigation_base()); + + int32 calc_mit_cap = effective_level * rule_manager.GetGlobalRule(R_Combat, CalculatedMitigationCapLevel)->GetInt32(); + info->set_max_mitigation(calc_mit_cap); + + int16 mit_percent = (int16)(CalculateMitigation() * 1000.0f); + info->set_mitigation_pve(mit_percent); + mit_percent = (int16)(CalculateMitigation(DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE,0,0,true) * 1000.0f); + info->set_mitigation_pvp(mit_percent); + + info->add_sta((float)values->sta); + info->add_str((float)values->str); + info->add_agi((float)values->agi); + info->add_wis((float)values->wis); + info->add_intel((float)values->int_); + + info->add_disease(values->vs_disease); + info->add_divine(values->vs_divine); + info->add_heat(values->vs_heat); + info->add_magic(values->vs_magic); + int32 sta_hp_bonus = 0.0; + int32 prim_power_bonus = 0.0; + float bonus_mod = 0.0; + if (IsPlayer()) { + bonus_mod = CalculateBonusMod(); + sta_hp_bonus = info->get_sta() * bonus_mod; + prim_power_bonus = GetPrimaryStat() * bonus_mod; + } + prim_power_bonus = floor(float(prim_power_bonus)); + sta_hp_bonus = floor(float(sta_hp_bonus)); + SetTotalHP(GetTotalHPBaseInstance() + values->health + sta_hp_bonus); + SetTotalPower(GetTotalPowerBaseInstance() + values->power + prim_power_bonus); + if(GetHP() > GetTotalHP()) + SetHP(GetTotalHP()); + if(GetPower() > GetTotalPower()) + SetPower(GetTotalPower()); + + info->add_mental(values->vs_mental); + + info->add_poison(values->vs_poison); + + info->set_max_concentration(info->get_max_concentration_base() + values->concentration); + + info->add_cold(values->vs_cold); + + info->add_mitigation_skill1(values->vs_slash); + info->add_mitigation_skill2(values->vs_pierce); + info->add_mitigation_skill3(values->vs_crush); + info->add_ability_modifier(values->ability_modifier); + info->add_critical_mitigation(values->criticalmitigation); + info->add_block_chance(values->extrashieldblockchance); + info->add_crit_chance(values->beneficialcritchance); + info->add_crit_bonus(values->critbonus); + info->add_potency(values->potency); + info->add_hate_mod(values->hategainmod); + info->add_reuse_speed(values->abilityreusespeed); + info->add_casting_speed(values->abilitycastingspeed); + info->add_recovery_speed(values->abilityrecoveryspeed); + info->add_spell_reuse_speed(values->spellreusespeed); + info->add_spell_multi_attack(values->spellmultiattackchance); + info->add_dps(values->dps); + info->add_dps_multiplier(CalculateDPSMultiplier()); + info->add_haste(values->attackspeed); + info->add_multi_attack(values->multiattackchance); + info->add_flurry(values->flurry); + info->add_melee_ae(values->aeautoattackchance); + info->add_strikethrough(values->strikethrough); + info->add_accuracy(values->accuracy); + info->add_offensivespeed(values->offensivespeed); + info->add_uncontested_block(values->uncontested_block); + info->add_uncontested_parry(values->uncontested_parry); + info->add_uncontested_dodge(values->uncontested_dodge); + info->add_uncontested_riposte(values->uncontested_riposte); + + info->set_ability_modifier(values->ability_modifier); + + float full_pct_hit = 100.0f; + + MStats.lock(); + float parryStat = stats[ITEM_STAT_PARRY]; + MStats.unlock(); + float parry_pct = CalculateSkillStatChance("Parry", ITEM_STAT_PARRYCHANCE, 70.0f, parryStat); + parry_pct += parry_pct * (info->get_cur_avoidance()/100.0f); + if(parry_pct > 70.0f) + parry_pct = 70.0f; + + info->set_parry(parry_pct); + + full_pct_hit -= parry_pct; + + float block_pct = 0.0f; + + if(GetAdventureClass() != BRAWLER) + { + Item* item = equipment_list.GetItem(EQ2_SECONDARY_SLOT); + if(item && item->details.item_id > 0 && item->IsShield()){ + // if high is set and greater than low use high, otherwise use low + int16 mitigation = item->armor_info->mitigation_high > item->armor_info->mitigation_low ? item->armor_info->mitigation_high : item->armor_info->mitigation_low; + // we frankly don't know the formula for Block, only that it uses the 'Protection' of the shield, which is the mitigation_low/mitigation_high in the armor_info + if(mitigation) + { + /*DOF Prima Guide: Shields now have the following base chances + to block: Tower (10%), Kite (10%), Round + (5%), Buckler (3%). Your chances to block + scale up or down based on the con of your + opponent.*/ + Skill* skill = master_skill_list.GetSkill(item->generic_info.skill_req1); + float baseBlock = 0.0f; + if(skill) + { + if(skill->short_name.data == "towershield" || skill->short_name.data == "kiteshield") + baseBlock = 10.0f; + else if (skill->short_name.data == "roundshield") + baseBlock = 5.0f; + else if (skill->short_name.data == "buckler") + baseBlock = 3.0f; + } + if(effective_level > mitigation) + block_pct = log10f((float)mitigation/((float)effective_level*10.0f)); + else + block_pct = log10f(((float)effective_level/(float)mitigation)) * log10f(effective_level) * 2.0f; + + if(block_pct < 0.0f) + block_pct *= -1.0f; + + block_pct += baseBlock; + + block_pct += block_pct * (info->get_cur_avoidance()/100.0f); + if(block_pct > 70.0f) + block_pct = 70.0f; + } + } + } + else + { + MStats.lock(); + float deflectionStat = stats[ITEM_STAT_DEFLECTION]; + MStats.unlock(); + block_pct = CalculateSkillStatChance("Deflection", ITEM_STAT_MINIMUMDEFLECTIONCHANCE, 70.0f, deflectionStat+1.0f); + block_pct += block_pct * (info->get_cur_avoidance()/100.0f); + } + + float block_actual = 0.0f; + if(full_pct_hit > 0.0f) + block_actual = block_pct * (full_pct_hit / 100.0f); + + info->set_block(block_actual); + full_pct_hit -= block_actual; + + MStats.lock(); + float defenseStat = stats[ITEM_STAT_DEFENSE]; + float baseAvoidanceStat = stats[ITEM_STAT_BASEAVOIDANCEBONUS]; + MStats.unlock(); + + float dodge_pct = (baseAvoidanceStat/100.0f) + CalculateSkillStatChance("Defense", ITEM_STAT_DODGECHANCE, 100.0f, defenseStat); + dodge_pct += dodge_pct * (info->get_cur_avoidance()/100.0f); + + float dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + CalculateLevelStatBonus(GetAgi()); + + info->set_avoidance_base(dodge_actual); + + float total_avoidance = parry_pct + block_actual + dodge_actual; + info->set_avoidance_display(total_avoidance); + + SetRegenValues(effective_level); + + CalculateApplyWeight(); + + UpdateWeapons(); + + safe_delete(values); +} + +float Entity::CalculateLevelStatBonus(int16 stat_value) { + int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + float result = (log10f(effective_level * stat_value) / 50.0f); // todo: break this down by stat type and give independent modifiers + return result; +} + +void Entity::CalculateApplyWeight() { + if (IsPlayer()) { + int32 prev_weight = GetInfoStruct()->get_weight(); + int32 inv_weight = ((Player*)this)->item_list.GetWeight(); + + // calculate coin + int32 coin_copper = GetInfoStruct()->get_coin_copper(); + int32 coin_silver = GetInfoStruct()->get_coin_silver(); + int32 coin_gold = GetInfoStruct()->get_coin_gold(); + int32 coin_plat = GetInfoStruct()->get_coin_plat(); + + float weight_per_stone = rule_manager.GetGlobalRule(R_Player, CoinWeightPerStone)->GetFloat(); + if(weight_per_stone < 0.0f) { + weight_per_stone = 0.0f; + } + + double weight_copper = ((double)coin_copper / weight_per_stone); + double weight_silver = ((double)coin_silver / weight_per_stone); + double weight_gold = ((double)coin_gold / weight_per_stone); + double weight_platinum = ((double)coin_plat / weight_per_stone); + int32 total_weight = (int32)(weight_copper + weight_silver + weight_gold + weight_platinum); + LogWrite(PLAYER__DEBUG, 0, "Debug", "Coin Weight Calculated to: %u. Weight_Copper: %f, Weight_Silver: %f, Weight_Gold: %f, Weight_Platinum: %f", total_weight, weight_copper, weight_silver, weight_gold, weight_platinum); + + total_weight += (int32)((double)inv_weight / 10.0); + + GetInfoStruct()->set_weight(total_weight); + + SetSpeedMultiplier(GetHighestSnare()); + ((Player*)this)->SetSpeed(GetSpeed()); + if(((Player*)this)->GetClient()) { + ((Player*)this)->GetClient()->SendControlGhost(); + } + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + ((Player*)this)->SetCharSheetChanged(true); + } + int32 max_weight = 0; + float weight_str_multiplier = rule_manager.GetGlobalRule(R_Player, MaxWeightStrengthMultiplier)->GetFloat(); + int32 base_weight = rule_manager.GetGlobalRule(R_Player, BaseWeight)->GetInt32(); + if(weight_str_multiplier < 0.0f) { + weight_str_multiplier = 0.0f; + } + + if(GetInfoStruct()->get_str() <= 0.0f) { + max_weight = base_weight; // rule for base strength + } + else { + max_weight = (int32)((double)GetInfoStruct()->get_str() * weight_str_multiplier); // rule multipler for strength + max_weight += base_weight; // rule for base strength + } + GetInfoStruct()->set_max_weight(max_weight); +} + +void Entity::SetRegenValues(int16 effective_level) +{ + bool classicRegen = rule_manager.GetGlobalRule(R_Spawn, ClassicRegen)->GetBool(); + bool override_ = (IsPlayer() && !GetInfoStruct()->get_engaged_encounter()); + + if(!GetInfoStruct()->get_hp_regen_override()) + { + sint16 regen_hp_rate = 0; + sint16 temp = 0; + + MStats.lock(); + + if(!IsAggroed() || override_) + { + if(classicRegen) + { + // classic regen only gives OUT OF COMBAT, doesn't combine in+out of combat + regen_hp_rate = (int)(effective_level*.75)+1; + temp = regen_hp_rate + stats[ITEM_STAT_HPREGEN]; + temp += stats[ITEM_STAT_HPREGENPPT]; + } + else + { + regen_hp_rate = (int)(effective_level*.75)+(int)(effective_level/10) + 1; + temp = regen_hp_rate + stats[ITEM_STAT_HPREGEN]; + temp += stats[ITEM_STAT_HPREGENPPT] + stats[ITEM_STAT_COMBATHPREGENPPT]; + } + } + else + { + regen_hp_rate = (sint16)(effective_level / 10) + 1; + temp = regen_hp_rate + stats[ITEM_STAT_COMBATHPREGENPPT]; + } + MStats.unlock(); + + GetInfoStruct()->set_hp_regen(temp); + } + + if(!GetInfoStruct()->get_power_regen_override()) + { + sint16 regen_power_rate = 0; + sint16 temp = 0; + + MStats.lock(); + if(!IsAggroed() || override_) + { + if(classicRegen) + { + regen_power_rate = effective_level + 1; + temp = regen_power_rate + stats[ITEM_STAT_MANAREGEN]; + temp += stats[ITEM_STAT_MPREGENPPT]; + } + else + { + regen_power_rate = effective_level + (int)(effective_level/10) + 1; + temp = regen_power_rate + stats[ITEM_STAT_MANAREGEN]; + temp += stats[ITEM_STAT_MPREGENPPT] + stats[ITEM_STAT_COMBATMPREGENPPT]; + } + } + else + { + regen_power_rate = (sint16)(effective_level / 10) + 1; + temp = regen_power_rate + stats[ITEM_STAT_COMBATMPREGENPPT]; + } + MStats.unlock(); + + GetInfoStruct()->set_power_regen(temp); + } +} + +EquipmentItemList* Entity::GetEquipmentList(){ + return &equipment_list; +} + +EquipmentItemList* Entity::GetAppearanceEquipmentList(){ + return &appearance_equipment_list; +} + +void Entity::SetEquipment(Item* item, int8 slot){ + std::lock_guard lk(MEquipment); + if(!item && slot < NUM_SLOTS){ + SetInfo(&equipment.equip_id[slot], 0); + SetInfo(&equipment.color[slot].red, 0); + SetInfo(&equipment.color[slot].green, 0); + SetInfo(&equipment.color[slot].blue, 0); + SetInfo(&equipment.highlight[slot].red, 0); + SetInfo(&equipment.highlight[slot].green, 0); + SetInfo(&equipment.highlight[slot].blue, 0); + } + else{ + if ( slot >= NUM_SLOTS ) + slot = item->details.slot_id; + + if( slot >= NUM_SLOTS ) + return; + + SetInfo(&equipment.equip_id[slot], item->generic_info.appearance_id); + SetInfo(&equipment.color[slot].red, item->generic_info.appearance_red); + SetInfo(&equipment.color[slot].green, item->generic_info.appearance_green); + SetInfo(&equipment.color[slot].blue, item->generic_info.appearance_blue); + SetInfo(&equipment.highlight[slot].red, item->generic_info.appearance_highlight_red); + SetInfo(&equipment.highlight[slot].green, item->generic_info.appearance_highlight_green); + SetInfo(&equipment.highlight[slot].blue, item->generic_info.appearance_highlight_blue); + } +} + +bool Entity::CheckSpellBonusRemoval(LuaSpell* spell, int16 type){ + MutexList::iterator itr = bonus_list.begin(); + while(itr.Next()){ + if(itr.value->luaspell == spell && itr.value->type == type){ + bonus_list.Remove(itr.value, true); + return true; + } + } + return false; +} + +void Entity::AddSpellBonus(LuaSpell* spell, int16 type, float value, int64 class_req, vector race_req, vector faction_req){ + CheckSpellBonusRemoval(spell, type); + BonusValues* bonus = new BonusValues; + if(spell && spell->spell) { + bonus->spell_id = spell->spell->GetSpellID(); + } + else { + bonus->spell_id = 0; + } + bonus->luaspell = spell; + bonus->type = type; + bonus->value = value; + bonus->class_req = class_req; + bonus->race_req = race_req; + bonus->faction_req = faction_req; + bonus->tier = (spell && spell->spell) ? spell->spell->GetSpellTier() : 0; + bonus_list.Add(bonus); + + if(IsNPC() || IsPlayer()) + CalculateBonuses(); +} + +BonusValues* Entity::GetSpellBonus(int32 spell_id) { + BonusValues *ret = 0; + MutexList::iterator itr = bonus_list.begin(); + while (itr.Next()) { + if (itr.value->spell_id == spell_id) { + ret = itr.value; + break; + } + } + + return ret; +} + +vector* Entity::GetAllSpellBonuses(LuaSpell* spell) { + vector* list = new vector; + MutexList::iterator itr = bonus_list.begin(); + while (itr.Next()) { + if (itr.value->luaspell == spell) + list->push_back(itr.value); + } + return list; +} + +void Entity::RemoveSpellBonus(const LuaSpell* spell, bool remove_all){ + // spell can be null! + MutexList::iterator itr = bonus_list.begin(); + while(itr.Next()){ + if(itr.value->luaspell == spell || remove_all) + bonus_list.Remove(itr.value, true); + } + + if(IsNPC() || IsPlayer()) + CalculateBonuses(); +} + +void Entity::CalculateSpellBonuses(ItemStatsValues* stats){ + if(stats){ + MutexList::iterator itr = bonus_list.begin(); + vector bv; + //First check if we meet the requirement for each bonus + bool race_match = false; + while(itr.Next()) { + if (itr.value->race_req.size() > 0) { + for (int8 i = 0; i < itr.value->race_req.size(); i++) { + if (GetRace() == itr.value->race_req[i]) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + int64 const class1 = pow(2.0, (GetAdventureClass() - 1)); + int64 const class2 = pow(2.0, (classes.GetSecondaryBaseClass(GetAdventureClass()) - 1)); + int64 const class3 = pow(2.0, (classes.GetBaseClass(GetAdventureClass()) - 1)); + if (itr.value->class_req == 0 || (itr.value->class_req & class1) == class1 || (itr.value->class_req & class2) == class2 || (itr.value->class_req & class3) == class3 && race_match ) + bv.push_back(itr.value); + } + //Sort the bonuses by spell id and luaspell + BonusValues* bonus = nullptr; + map > > sort; + for (int8 i = 0; i < bv.size(); i++){ + bonus = bv.at(i); + sort[bonus->spell_id][bonus->luaspell].push_back(bonus); + } + //Now check for the highest tier of each spell id and apply those bonuses + map >::iterator tier_itr; + map > >::iterator sort_itr; + for (sort_itr = sort.begin(); sort_itr != sort.end(); sort_itr++){ + LuaSpell* key = nullptr; + sint8 highest_tier = -1; + //Find the highest tier for this spell id + for (tier_itr = sort_itr->second.begin(); tier_itr != sort_itr->second.end(); tier_itr++){ + LuaSpell* current_spell = tier_itr->first; + sint8 current_tier = 0; + if (current_spell && current_spell->spell && ((current_tier = current_spell->spell->GetSpellTier()) > highest_tier)) { + highest_tier = current_tier; + key = current_spell; + } + } + //We've found the highest tier for this spell id, so add the bonuses + vector* final_bonuses = &sort_itr->second[key]; + for (int8 i = 0; i < final_bonuses->size(); i++) + world.AddBonuses(nullptr, stats, final_bonuses->at(i)->type, final_bonuses->at(i)->value, this); + } + } +} + +void Entity::AddMezSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_MEZ]) + control_effects[CONTROL_EFFECT_TYPE_MEZ] = new MutexList; + + MutexList* mez_spells = control_effects[CONTROL_EFFECT_TYPE_MEZ]; + + if (IsPlayer() && !IsStunned() && !IsMezImmune() && mez_spells->size(true) == 0){ + ((Player*)this)->SetPlayerControlFlag(1, 16, true); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, true); + if (!IsStifled() && !IsFeared()) + GetZone()->LockAllSpells((Player*)this); + } + + if (IsNPC() && !IsMezImmune()) + { + HaltMovement(); + } + + mez_spells->Add(spell); +} + +void Entity::RemoveMezSpell(LuaSpell* spell) { + MutexList* mez_spells = control_effects[CONTROL_EFFECT_TYPE_MEZ]; + if (!mez_spells || mez_spells->size(true) == 0) + return; + + mez_spells->Remove(spell); + if (mez_spells->size(true) == 0){ + if (IsPlayer() && !IsMezImmune() && !IsStunned()){ + if (!IsStifled() && !IsFeared()) + GetZone()->UnlockAllSpells((Player*)this); + ((Player*)this)->SetPlayerControlFlag(1, 16, false); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); + } + + if(!IsPlayer()) { + GetZone()->movementMgr->StopNavigation((Entity*)this); + ((Spawn*)this)->StopMovement(); + } + } +} + +void Entity::RemoveAllMezSpells() { + MutexList* mez_spells = control_effects[CONTROL_EFFECT_TYPE_MEZ]; + if (!mez_spells) + return; + + MutexList::iterator itr = mez_spells->begin(); + while (itr.Next()){ + LuaSpell* spell = itr.value; + if (!spell) + continue; + GetZone()->RemoveTargetFromSpell(spell, this); + RemoveDetrimentalSpell(spell); + RemoveSpellEffect(spell); + if (IsPlayer()) + ((Player*)this)->RemoveSkillBonus(spell->spell->GetSpellID()); + } + + mez_spells->clear(); + if (IsPlayer() && !IsMezImmune() && !IsStunned()){ + if (!IsStifled() && !IsFeared()) + GetZone()->UnlockAllSpells((Player*)this); + ((Player*)this)->SetPlayerControlFlag(1, 16, false); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); + } +} + +void Entity::AddStifleSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_STIFLE]) + control_effects[CONTROL_EFFECT_TYPE_STIFLE] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_STIFLE]->size(true) == 0 && !IsStifleImmune() && !IsMezzedOrStunned()) + GetZone()->LockAllSpells((Player*)this); + + control_effects[CONTROL_EFFECT_TYPE_STIFLE]->Add(spell); +} + +void Entity::RemoveStifleSpell(LuaSpell* spell) { + MutexList* stifle_list = control_effects[CONTROL_EFFECT_TYPE_STIFLE]; + if (!stifle_list || stifle_list->size(true) == 0) + return; + + stifle_list->Remove(spell); + + if (IsPlayer() && stifle_list->size(true) == 0 && !IsStifleImmune() && !IsMezzedOrStunned()) + GetZone()->UnlockAllSpells((Player*)this); +} + +void Entity::AddDazeSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_DAZE]) + control_effects[CONTROL_EFFECT_TYPE_DAZE] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_DAZE]->Add(spell); +} + +void Entity::RemoveDazeSpell(LuaSpell* spell) { + MutexList* daze_list = control_effects[CONTROL_EFFECT_TYPE_DAZE]; + if (!daze_list || daze_list->size(true) == 0) + return; + + daze_list->Remove(spell); +} + +void Entity::AddStunSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_STUN]) + control_effects[CONTROL_EFFECT_TYPE_STUN] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_STUN]->size(true) == 0 && !IsStunImmune()){ + if (!IsMezzed()){ + ((Player*)this)->SetPlayerControlFlag(1, 16, true); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, true); + if (!IsStifled() && !IsFeared()) + GetZone()->LockAllSpells((Player*)this); + } + } + + control_effects[CONTROL_EFFECT_TYPE_STUN]->Add(spell); +} + +void Entity::RemoveStunSpell(LuaSpell* spell) { + MutexList* stun_list = control_effects[CONTROL_EFFECT_TYPE_STUN]; + if (!stun_list || stun_list->size(true) == 0) + return; + + stun_list->Remove(spell); + if (stun_list->size(true) == 0){ + if (IsPlayer() && !IsMezzed() && !IsStunImmune()){ + ((Player*)this)->SetPlayerControlFlag(1, 16, false); + if (!IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); + if (!IsStifled() && !IsFeared()) + GetZone()->UnlockAllSpells((Player*)this); + } + + if(!IsPlayer()) { + GetZone()->movementMgr->StopNavigation((Entity*)this); + ((Spawn*)this)->StopMovement(); + } + } +} + +void Entity::HideDeityPet(bool val) { + if (!deityPet) + return; + + if (val) { + deityPet->AddAllowAccessSpawn(deityPet); + GetZone()->HidePrivateSpawn(deityPet); + } + else + deityPet->MakeSpawnPublic(); +} + +void Entity::HideCosmeticPet(bool val) { + if (!cosmeticPet) + return; + + if (val) { + cosmeticPet->AddAllowAccessSpawn(cosmeticPet); + GetZone()->HidePrivateSpawn(cosmeticPet); + } + else + cosmeticPet->MakeSpawnPublic(); +} + +void Entity::DismissAllPets(bool from_death, bool spawnListLocked) +{ + DismissPet(GetPet(), from_death, spawnListLocked); + DismissPet(GetCharmedPet(), from_death, spawnListLocked); + DismissPet(GetDeityPet(), from_death, spawnListLocked); + DismissPet(GetCosmeticPet(), from_death, spawnListLocked); +} + +void Entity::DismissPet(Entity* pet, bool from_death, bool spawnListLocked) { + if (!pet) + return; + + Entity* PetOwner = pet->GetOwner(); + + if(pet->IsNPC()) + { + ((NPC*)pet)->SetDismissing(true); + + // Remove the spell maintained spell + Spell* spell = master_spell_list.GetSpell(pet->GetPetSpellID(), pet->GetPetSpellTier()); + if (spell) + GetZone()->GetSpellProcess()->DeleteCasterSpell(this, spell, from_death == true ? (string)"pet_death" : (string)"canceled"); + } + + if (pet->GetPetType() == PET_TYPE_CHARMED) { + if(PetOwner) + PetOwner->SetCharmedPet(0); + + if (!from_death) { + // set the pet flag to false, owner to 0, and give the mob its old brain back + pet->SetPet(false); + pet->SetOwner(0); + if(pet->IsNPC()) + ((NPC*)pet)->SetBrain(new Brain((NPC*)pet)); + + pet->SetDismissing(false); + } + } + else if (PetOwner && pet->GetPetType() == PET_TYPE_COMBAT) + PetOwner->SetCombatPet(0); + else if (PetOwner && pet->GetPetType() == PET_TYPE_DEITY) + PetOwner->SetDeityPet(0); + else if (PetOwner && pet->GetPetType() == PET_TYPE_COSMETIC) + PetOwner->SetCosmeticPet(0); + + // if owner is player and no combat pets left reset the pet info + if (PetOwner && PetOwner->IsPlayer()) { + if (!PetOwner->GetPet() && !PetOwner->GetCharmedPet()) + ((Player*)PetOwner)->ResetPetInfo(); + } + + // remove the spawn from the world + if (!from_death && pet->GetPetType() != PET_TYPE_CHARMED) + GetZone()->RemoveSpawn(pet); +} + +float Entity::CalculateBonusMod() { + int8 level = GetLevel(); + if (level <= 20) + return 3.0; + else if (level >= 90) + return 10.0; + else + return (level - 20) * .1 + 3.0; +} + +float Entity::CalculateDPSMultiplier(){ + float dps = GetInfoStruct()->get_dps(); + + if (dps > 0){ + if (dps <= 100) + return (dps / 100 + 1); + else if (dps <= 200) + return (((dps - 100) * .25 + 100) / 100 + 1); + else if (dps <= 300) + return (((dps - 200) * .1 + 125) / 100 + 1); + else if (dps <= 900) + return (((dps - 300) * .05 + 135) / 100 + 1); + else + return (((dps - 900) * .01 + 165) / 100 + 1); + } + return 1; +} + +void Entity::AddWard(int32 spellID, WardInfo* ward) { + if (m_wardList.count(spellID) == 0) { + m_wardList[spellID] = ward; + } +} + +WardInfo* Entity::GetWard(int32 spellID) { + WardInfo* ret = 0; + + if (m_wardList.count(spellID) > 0) + ret = m_wardList[spellID]; + + return ret; +} + +void Entity::RemoveWard(int32 spellID) { + if (m_wardList.count(spellID) > 0) { + // Delete the ward info + safe_delete(m_wardList[spellID]); + // Remove from the ward list + m_wardList.erase(spellID); + } +} + +int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) { + map::iterator itr; + WardInfo* ward = 0; + LuaSpell* spell = 0; + + while (m_wardList.size() > 0 && damage > 0) { + // Get the ward with the lowest base damage + for (itr = m_wardList.begin(); itr != m_wardList.end(); itr++) { + if(itr->second->RoundTriggered) + continue; + + if (!ward || itr->second->BaseDamage < ward->BaseDamage) { + if ((itr->second->AbsorbAllDamage || itr->second->DamageLeft > 0) && + (itr->second->WardType == WARD_TYPE_ALL || + (itr->second->WardType == WARD_TYPE_PHYSICAL && damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_SLASH && damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE) || + (itr->second->WardType == WARD_TYPE_MAGICAL && ((itr->second->DamageType == 0 && damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE) || (damage_type >= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE && itr->second->DamageType == damage_type))))) + ward = itr->second; + } + } + + if (!ward) + break; + + spell = ward->Spell; + + // damage to redirect at the source (like intercept) + int32 redirectDamage = 0; + if (ward->RedirectDamagePercent) + redirectDamage = (int32)(double)damage * ((double)ward->RedirectDamagePercent / 100.0); + + // percentage the spell absorbs of all possible damage + int32 damageToAbsorb = 0; + if (ward->DamageAbsorptionPercentage > 0) + damageToAbsorb = (int32)(double)damage * ((double)ward->DamageAbsorptionPercentage/100.0); + else + damageToAbsorb = damage; + + int32 maxDamageAbsorptionAllowed = 0; + + // spells like Divine Aura have caps on health, eg. anything more than 50% damage is not absorbed + if (ward->DamageAbsorptionMaxHealthPercent > 0) + maxDamageAbsorptionAllowed = (int32)(double)GetTotalHP() * ((double)ward->DamageAbsorptionMaxHealthPercent / 100.0); + + if (maxDamageAbsorptionAllowed > 0 && damageToAbsorb >= maxDamageAbsorptionAllowed) + damageToAbsorb = 0; // its over or equal to 50% of the total hp allowed, thus this damage is not absorbed + + int32 baseDamageRemaining = damage - damageToAbsorb; + + bool hasSpellBeenRemoved = false; + if (ward->AbsorbAllDamage) + { + ward->LastAbsorbedDamage = ward->DamageLeft; + + if (!redirectDamage) + GetZone()->SendHealPacket(ward->Spell->caster, this, HEAL_PACKET_TYPE_ABSORB, damage, spell->spell->GetName()); + + damage = 0; + } + else if (damageToAbsorb >= ward->DamageLeft) { + // Damage is greater than or equal to the amount left on the ward + + ward->LastAbsorbedDamage = ward->DamageLeft; + // remove what damage we can absorb + damageToAbsorb -= ward->DamageLeft; + + // move back what couldn't be absorbed to the base dmg and apply to the overall damage + baseDamageRemaining += damageToAbsorb; + damage = baseDamageRemaining; + ward->DamageLeft = 0; + spell->damage_remaining = 0; + + if(!redirectDamage) + GetZone()->SendHealPacket(spell->caster, this, HEAL_PACKET_TYPE_ABSORB, ward->DamageLeft, spell->spell->GetName()); + + if (!ward->keepWard) { + hasSpellBeenRemoved = true; + RemoveWard(spell->spell->GetSpellID()); + GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "purged"); + } + } + else { + ward->LastAbsorbedDamage = damageToAbsorb; + // Damage is less then the amount left on the ward + ward->DamageLeft -= damageToAbsorb; + + spell->damage_remaining = ward->DamageLeft; + if (spell->caster->IsPlayer()) + ClientPacketFunctions::SendMaintainedExamineUpdate(((Player*)spell->caster)->GetClient(), spell->slot_pos, ward->DamageLeft, 1); + + if (!redirectDamage) + GetZone()->SendHealPacket(ward->Spell->caster, this, HEAL_PACKET_TYPE_ABSORB, damage, spell->spell->GetName()); + + // remaining damage not absorbed by percentage must be set + damage = baseDamageRemaining; + } + + if (redirectDamage) + { + ward->LastRedirectDamage = redirectDamage; + if (this->IsPlayer()) + { + Client* client = this->GetClient(); + if(client) { + client->Message(CHANNEL_COMBAT, "%s intercepted some of the damage intended for you!", spell->caster->GetName()); + } + } + if (spell->caster && spell->caster->IsPlayer()) + { + Client* client = ((Player*)spell->caster)->GetClient(); + if(client) { + client->Message(CHANNEL_COMBAT, "YOU intercept some of the damage intended for %s!", this->GetName()); + } + } + + if (attacker && spell->caster) + attacker->DamageSpawn(spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, redirectDamage, redirectDamage, 0, 0, false, false, false, false, spell); + } + + bool shouldRemoveSpell = false; + ward->HitCount++; // increment hit count + ward->RoundTriggered = true; + + if (ward->MaxHitCount && spell->num_triggers && spell->caster->GetZone()) + { + spell->num_triggers--; + if(spell->caster->IsPlayer()) { + ClientPacketFunctions::SendMaintainedExamineUpdate(((Player*)spell->caster)->GetClient(), spell->slot_pos, spell->num_triggers, 0); + } + } + + if (ward->MaxHitCount && ward->HitCount >= ward->MaxHitCount) // there isn't a max hit requirement with the hit count, so just go based on hit count + shouldRemoveSpell = true; + + if (shouldRemoveSpell && !hasSpellBeenRemoved) + { + RemoveWard(spell->spell->GetSpellID()); + GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "purged"); + } + + // Reset ward pointer + ward = 0; + } + + for (itr = m_wardList.begin(); itr != m_wardList.end(); itr++) { + itr->second->RoundTriggered = false; + } + + return damage; +} + +float Entity::CalculateCastingSpeedMod() { + float cast_speed = info_struct.get_casting_speed(); + + if(cast_speed > 0) + return 100 * max((float) 0.5, (float) (1 + (1 - (1 / (1 + (cast_speed * .01)))))); + else if (cast_speed < 0) + return 100 * min((float) 1.5, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01)))))); + return 0; +} + +float Entity::GetSpeed() { + float ret = speed > GetBaseSpeed() ? speed : GetBaseSpeed(); + if(IsPlayer()) { + ret = GetBaseSpeed(); + } + MStats.lock(); + + if ((IsStealthed() || IsInvis()) && stats.count(ITEM_STAT_STEALTHINVISSPEEDMOD)) { + ret += stats[ITEM_STAT_STEALTHINVISSPEEDMOD]; + } + + if (!GetInfoStruct()->get_engaged_encounter()) { + if (stats.count(ITEM_STAT_SPEED) && stats.count(ITEM_STAT_MOUNTSPEED)) { + ret += max(stats[ITEM_STAT_SPEED], stats[ITEM_STAT_MOUNTSPEED]); + } + else if (stats.count(ITEM_STAT_SPEED)) { + ret += stats[ITEM_STAT_SPEED]; + } + else if (stats.count(ITEM_STAT_MOUNTSPEED)) { + ret += stats[ITEM_STAT_MOUNTSPEED]; + } + } + else if (GetInfoStruct()->get_engaged_encounter()) { + + if (GetMaxSpeed() > 0.0f) + ret = GetMaxSpeed(); + + if (stats.count(ITEM_STAT_OFFENSIVESPEED)) { + ret += stats[ITEM_STAT_OFFENSIVESPEED]; + } + } + + MStats.unlock(); + ret *= speed_multiplier; + return ret; +} + +float Entity::GetAirSpeed() { + float ret = speed; + + if (!GetInfoStruct()->get_engaged_encounter()) + ret += stats[ITEM_STAT_MOUNTAIRSPEED]; + + ret *= speed_multiplier; + return ret; +} + +void Entity::SetThreatTransfer(ThreatTransfer* transfer) { + safe_delete(m_threatTransfer); + m_threatTransfer = transfer; +} +int8 Entity::GetTraumaCount() { + return det_count_list[DET_TYPE_TRAUMA]; +} + +int8 Entity::GetArcaneCount() { + return det_count_list[DET_TYPE_ARCANE]; +} + +int8 Entity::GetNoxiousCount() { + return det_count_list[DET_TYPE_NOXIOUS]; +} + +int8 Entity::GetElementalCount() { + return det_count_list[DET_TYPE_ELEMENTAL]; +} + +int8 Entity::GetCurseCount() { + return det_count_list[DET_TYPE_CURSE]; +} + +Mutex* Entity::GetDetrimentMutex() { + return &MDetriments; +} + +Mutex* Entity::GetMaintainedMutex() { + return &MMaintainedSpells; +} + +Mutex* Entity::GetSpellEffectMutex() { + return &MSpellEffects; +} + +bool Entity::HasCurableDetrimentType(int8 det_type) { + DetrimentalEffects* det; + bool ret = false; + MDetriments.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < detrimental_spell_effects.size(); i++){ + det = &detrimental_spell_effects.at(i); + if(det && det->det_type == det_type && !det->incurable){ + ret = true; + break; + } + } + MDetriments.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void Entity::ClearAllDetriments() { + MDetriments.writelock(__FUNCTION__, __LINE__); + detrimental_spell_effects.clear(); + det_count_list.clear(); + MDetriments.releasewritelock(__FUNCTION__, __LINE__); +} + +void Entity::CureDetrimentByType(int8 cure_count, int8 det_type, string cure_name, Entity* caster, int8 cure_level) { + if (cure_count <= 0 || GetDetTypeCount(det_type) <= 0) + return; + + vector* det_list = &detrimental_spell_effects; + DetrimentalEffects* det; + vector remove_list; + LuaSpell* spell = 0; + vector* levels; + int8 caster_class1 = 0; + int8 caster_class2 = 0; + int8 caster_class3 = 0; + InfoStruct* info_struct = 0; + bool pass_level_check = false; + + MDetriments.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; isize(); i++){ + det = &det_list->at(i); + if (det && det->det_type == det_type && !det->incurable){ + levels = det->spell->spell->GetSpellLevels(); + info_struct = det->caster->GetInfoStruct(); + pass_level_check = false; + bool has_level_checks = false; + for (int32 x = 0; x < levels->size(); x++){ + has_level_checks = true; + // class checks are worthless we can't guarantee the caster is that class + if (!cure_level || cure_level >= (levels->at(x)->spell_level / 10)){ + pass_level_check = true; + break; + } + } + + if (pass_level_check || !has_level_checks){ + remove_list.push_back(det->spell); + cure_count--; + if (cure_count == 0) + break; + } + } + } + MDetriments.releasereadlock(__FUNCTION__, __LINE__); + + for (int32 i = 0; ispell->GetName()); + GetZone()->SendDispellPacket(caster, this, cure_name, (string)remove_list.at(i)->spell->GetName(), DISPELL_TYPE_CURE); + GetZone()->GetSpellProcess()->DeleteCasterSpell(spell, "cured", false, this); + } + remove_list.clear(); +} + +void Entity::CureDetrimentByControlEffect(int8 cure_count, int8 control_type, string cure_name, Entity* caster, int8 cure_level) { + if (cure_count <= 0 || GetDetCount() <= 0) + return; + + vector* det_list = &detrimental_spell_effects; + DetrimentalEffects* det; + vector remove_list; + LuaSpell* spell = 0; + vector* levels; + int8 caster_class1 = 0; + int8 caster_class2 = 0; + int8 caster_class3 = 0; + InfoStruct* info_struct = 0; + bool pass_level_check = false; + + MDetriments.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; isize(); i++){ + det = &det_list->at(i); + if (det && det->control_effect == control_type && !det->incurable){ + levels = det->spell->spell->GetSpellLevels(); + info_struct = det->caster->GetInfoStruct(); + pass_level_check = false; + for (int32 x = 0; x < levels->size(); x++){ + if (!cure_level || cure_level >= (levels->at(x)->spell_level / 10)){ + pass_level_check = true; + break; + } + } + if (pass_level_check){ + remove_list.push_back(det->spell); + cure_count--; + if (cure_count == 0) + break; + } + } + } + MDetriments.releasereadlock(__FUNCTION__, __LINE__); + + for (int32 i = 0; iSendDispellPacket(caster, this, cure_name, (string)remove_list.at(i)->spell->GetName(), DISPELL_TYPE_CURE); + if (GetZone()) + GetZone()->RemoveTargetFromSpell(spell, this); + RemoveSpellEffect(spell); + RemoveDetrimentalSpell(spell); + } + remove_list.clear(); +} + +void Entity::RemoveDetrimentalSpell(LuaSpell* spell) { + if(!spell || (spell->spell && spell->spell->GetSpellData() && spell->spell->GetSpellData()->det_type == 0)) + return; + MDetriments.writelock(__FUNCTION__, __LINE__); + vector* det_list = &detrimental_spell_effects; + vector::iterator itr; + for(itr = det_list->begin(); itr != det_list->end(); itr++){ + if((*itr).spell == spell){ + det_count_list[(*itr).det_type]--; + det_list->erase(itr); + if(IsPlayer()) + ((Player*)this)->SetCharSheetChanged(true); + break; + } + } + MDetriments.releasewritelock(__FUNCTION__, __LINE__); +} + +int8 Entity::GetDetTypeCount(int8 det_type){ + return det_count_list[det_type]; +} + +int8 Entity::GetDetCount() { + int8 det_count = 0; + map::iterator itr; + + for(itr=det_count_list.begin(); itr != det_count_list.end(); itr++) + det_count += (*itr).second; + + return det_count; +} + +vector* Entity::GetDetrimentalSpellEffects() { + return &detrimental_spell_effects; +} + +void Entity::AddDetrimentalSpell(LuaSpell* luaspell, int32 override_expire_timestamp){ + if(!luaspell || !luaspell->caster) + return; + + Spell* spell = luaspell->spell; + DetrimentalEffects* det = GetDetrimentalEffect(spell->GetSpellID(), luaspell->caster); + DetrimentalEffects new_det; + if(det) + RemoveDetrimentalSpell(det->spell); + + SpellData* data = spell->GetSpellData(); + if(!data) + return; + + new_det.caster = luaspell->caster; + new_det.spell = luaspell; + if (spell->GetSpellData()->duration_until_cancel) + new_det.expire_timestamp = 0xFFFFFFFF; + else if(override_expire_timestamp) + new_det.expire_timestamp = override_expire_timestamp; + else + new_det.expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100); + new_det.icon = data->icon; + new_det.icon_backdrop = data->icon_backdrop; + new_det.tier = data->tier; + new_det.det_type = data->det_type; + new_det.incurable = data->incurable; + new_det.spell_id = spell->GetSpellID(); + new_det.control_effect = data->control_effect_type; + new_det.total_time = spell->GetSpellDuration()/10; + + MDetriments.writelock(__FUNCTION__, __LINE__); + detrimental_spell_effects.push_back(new_det); + det_count_list[new_det.det_type]++; + MDetriments.releasewritelock(__FUNCTION__, __LINE__); +} + +DetrimentalEffects* Entity::GetDetrimentalEffect(int32 spell_id, Entity* caster){ + vector* det_list = &detrimental_spell_effects; + DetrimentalEffects* ret = 0; + MDetriments.readlock(__FUNCTION__, __LINE__); + for(int32 i=0; isize(); i++){ + if (det_list->at(i).spell_id == spell_id && det_list->at(i).caster == caster) + ret = &det_list->at(i); + } + MDetriments.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void Entity::CancelAllStealth() { + bool did_change = false; + MutexList* stealth_list = control_effects[CONTROL_EFFECT_TYPE_STEALTH]; + if (stealth_list){ + MutexList::iterator itr = stealth_list->begin(); + while (itr.Next()){ + if (itr.value->caster == this) + GetZone()->GetSpellProcess()->AddSpellCancel(itr.value); + else{ + GetZone()->RemoveTargetFromSpell(itr.value, this); + RemoveSpellEffect(itr.value); + } + did_change = true; + } + } + MutexList* invis_list = control_effects[CONTROL_EFFECT_TYPE_INVIS]; + if (invis_list){ + MutexList::iterator invis_itr = invis_list->begin(); + while (invis_itr.Next()){ + if (invis_itr.value->caster == this) + GetZone()->GetSpellProcess()->AddSpellCancel(invis_itr.value); + else{ + GetZone()->RemoveTargetFromSpell(invis_itr.value, this); + RemoveSpellEffect(invis_itr.value); + } + did_change = true; + } + } + + if (did_change){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer()) + ((Player*)this)->SetCharSheetChanged(true); + } +} + +bool Entity::IsStealthed(){ + MutexList* stealth_list = control_effects[CONTROL_EFFECT_TYPE_STEALTH]; + return (!stealth_list || stealth_list->size(true) == 0) == false; +} + +bool Entity::CanSeeInvis(Entity* target) { + if (!target) + return true; + + if (!target->IsStealthed() && !target->IsInvis()) + return true; + if (target->IsStealthed() && HasSeeHideSpell()) + return true; + else if (target->IsInvis() && HasSeeInvisSpell()) + return true; + + return false; +} + +bool Entity::IsInvis(){ + MutexList* invis_list = control_effects[CONTROL_EFFECT_TYPE_INVIS]; + return (!invis_list || invis_list->size(true) == 0) == false; +} + +void Entity::AddStealthSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_STEALTH]) + control_effects[CONTROL_EFFECT_TYPE_STEALTH] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_STEALTH]->Add(spell); + if (control_effects[CONTROL_EFFECT_TYPE_STEALTH]->size(true) == 1){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer() && ((Player*)this)->GetClient()) + { + ((Player*)this)->SetCharSheetChanged(true); + GetZone()->SendAllSpawnsForVisChange(((Player*)this)->GetClient()); + } + } +} + +void Entity::AddInvisSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_INVIS]) + control_effects[CONTROL_EFFECT_TYPE_INVIS] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_INVIS]->Add(spell); + if (control_effects[CONTROL_EFFECT_TYPE_INVIS]->size(true) == 1){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer() && ((Player*)this)->GetClient()) + { + ((Player*)this)->SetCharSheetChanged(true); + GetZone()->SendAllSpawnsForVisChange(((Player*)this)->GetClient()); + } + } +} + +void Entity::RemoveInvisSpell(LuaSpell* spell) { + MutexList* invis_list = control_effects[CONTROL_EFFECT_TYPE_INVIS]; + if (!invis_list || invis_list->size(true) == 0) + return; + + invis_list->Remove(spell); + RemoveSpellEffect(spell); + if (invis_list->size(true) == 0){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer() && ((Player*)this)->GetClient()) + { + ((Player*)this)->SetCharSheetChanged(true); + GetZone()->SendAllSpawnsForVisChange(((Player*)this)->GetClient()); + } + } +} + +void Entity::RemoveStealthSpell(LuaSpell* spell) { + MutexList* stealth_list = control_effects[CONTROL_EFFECT_TYPE_STEALTH]; + if (!stealth_list || stealth_list->size(true) == 0) + return; + + stealth_list->Remove(spell); + RemoveSpellEffect(spell); + if (stealth_list->size() == 0){ + info_changed = true; + changed = true; + AddChangedZoneSpawn(); + if (IsPlayer() && ((Player*)this)->GetClient()) + { + ((Player*)this)->SetCharSheetChanged(true); + GetZone()->SendAllSpawnsForVisChange(((Player*)this)->GetClient()); + } + } +} + +void Entity::AddRootSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_ROOT]) + control_effects[CONTROL_EFFECT_TYPE_ROOT] = new MutexList; + + if (control_effects[CONTROL_EFFECT_TYPE_ROOT]->size(true) == 0 && !IsRootImmune()) { + if (IsPlayer()){ + if (!IsMezzedOrStunned()) + ((Player*)this)->SetPlayerControlFlag(1, 8, true); // heading movement only + } + else + SetSpeedMultiplier(0.0f); + } + + control_effects[CONTROL_EFFECT_TYPE_ROOT]->Add(spell); +} + +void Entity::RemoveRootSpell(LuaSpell* spell) { + MutexList* root_list = control_effects[CONTROL_EFFECT_TYPE_ROOT]; + if (!root_list || root_list->size(true) == 0) + return; + + root_list->Remove(spell); + if (root_list->size(true) == 0 && !IsRootImmune()) { + if (IsPlayer()){ + if (!IsMezzedOrStunned()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); // heading movement only + } + else { + // GetHighestSnare() will return 1.0f if no snares returning the spawn to full speed + SetSpeedMultiplier(GetHighestSnare()); + } + + if(!IsPlayer()) { + GetZone()->movementMgr->StopNavigation((Entity*)this); + ((Spawn*)this)->StopMovement(); + } + } +} + +void Entity::AddFearSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_FEAR]) + control_effects[CONTROL_EFFECT_TYPE_FEAR] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_FEAR]->size(true) == 0 && !IsFearImmune()){ + ((Player*)this)->SetPlayerControlFlag(4, 4, true); // feared + if (!IsMezzedOrStunned() && !IsStifled()) + GetZone()->LockAllSpells((Player*)this); + } + + if (!IsFearImmune() && IsNPC()) + { + HaltMovement(); + } + + control_effects[CONTROL_EFFECT_TYPE_FEAR]->Add(spell); +} + +void Entity::RemoveFearSpell(LuaSpell* spell){ + MutexList* fear_list = control_effects[CONTROL_EFFECT_TYPE_FEAR]; + if (!fear_list || fear_list->size(true) == 0) + return; + + fear_list->Remove(spell); + + if (IsPlayer() && fear_list->size(true) == 0 && !IsFearImmune()){ + ((Player*)this)->SetPlayerControlFlag(4, 4, false); // feared disabled + if (!IsMezzedOrStunned() && !IsStifled()) + GetZone()->LockAllSpells((Player*)this); + } + + if (IsNPC()) + { + HaltMovement(); + } +} + +void Entity::AddSnareSpell(LuaSpell* spell) { + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_SNARE]) + control_effects[CONTROL_EFFECT_TYPE_SNARE] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_SNARE]->Add(spell); + + // Don't set speed multiplier if there is a root or no snare values + MutexList* roots = control_effects[CONTROL_EFFECT_TYPE_ROOT]; + if ((!roots || roots->size(true) == 0) && snare_values.size() > 0) + SetSpeedMultiplier(GetHighestSnare()); +} + +void Entity::RemoveSnareSpell(LuaSpell* spell) { + MutexList* snare_list = control_effects[CONTROL_EFFECT_TYPE_SNARE]; + if (!snare_list || snare_list->size(true) == 0) + return; + + snare_list->Remove(spell); + snare_values.erase(spell); + + //LogWrite(PLAYER__ERROR, 0, "Debug", "snare_values.size() = %u", snare_values.size()); + + // only change speeds if there are no roots + MutexList* roots = control_effects[CONTROL_EFFECT_TYPE_ROOT]; + if (!roots || roots->size(true) == 0) { + float multiplier = GetHighestSnare(); + //LogWrite(PLAYER__ERROR, 0, "Debug", "GetHighestSnare() = %f", multiplier); + SetSpeedMultiplier(multiplier); + } +} + +void Entity::SetSnareValue(LuaSpell* spell, float snare_val) { + if (!spell) + return; + + snare_values[spell] = snare_val; +} + +float Entity::GetHighestSnare() { + // For simplicity this will return the highest snare value, which is actually the lowest value + float ret = 1.0f; + float weight_diff = 0.0f; + if (IsPlayer() && rule_manager.GetGlobalRule(R_Player, WeightInflictsSpeed)->GetBool()) { + float weight_pct_impact = rule_manager.GetGlobalRule(R_Player, WeightPercentImpact)->GetFloat(); + float weight_pct_cap = rule_manager.GetGlobalRule(R_Player, WeightPercentCap)->GetFloat(); + if(weight_pct_impact > 1.0f) { + weight_pct_impact = 1.0f; + } + if(weight_pct_cap < weight_pct_impact) { + weight_pct_impact = weight_pct_cap; + } + int32 weight = GetInfoStruct()->get_weight(); + int32 max_weight = GetInfoStruct()->get_max_weight(); + if(weight > max_weight) { + int32 diff = weight - max_weight; + weight_diff = (float)diff * weight_pct_impact; // percentage impact rule on weight "per stone", default 1% + if(weight_diff > weight_pct_cap) // cap weight impact rule + weight_diff = weight_pct_cap; // cap weight impact rule + } + } + if (snare_values.size() == 0) + return ((ret - weight_diff) < 0.0f ) ? 0.0f : (ret - weight_diff); + + map::iterator itr; + for (itr = snare_values.begin(); itr != snare_values.end(); itr++) { + if (itr->second < ret) + ret = itr->second; + } + + return ((ret - weight_diff) < 0.0f ) ? 0.0f : (ret - weight_diff); +} + +bool Entity::IsSnared() { + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_SNARE]) + return false; + + MutexList* snare_list = control_effects[CONTROL_EFFECT_TYPE_SNARE]; + return (!snare_list || snare_list->size(true) == 0) == false; +} + +bool Entity::IsMezzed(){ + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_MEZ]) + return false; + + MutexList* mez_spells = control_effects[CONTROL_EFFECT_TYPE_MEZ]; + return (!mez_spells || mez_spells->size(true) == 0 || IsMezImmune()) == false; +} + +bool Entity::IsStifled(){ + if (!control_effects[CONTROL_EFFECT_TYPE_STIFLE]) + return false; + + MutexList* stifle_list = control_effects[CONTROL_EFFECT_TYPE_STIFLE]; + return (!stifle_list || stifle_list->size(true) == 0 || IsStifleImmune()) == false; +} + +bool Entity::IsDazed(){ + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_DAZE]) + return false; + + MutexList* daze_list = control_effects[CONTROL_EFFECT_TYPE_DAZE]; + return (!daze_list || daze_list->size(true) == 0 || IsDazeImmune()) == false; +} + +bool Entity::IsStunned(){ + if (!control_effects[CONTROL_EFFECT_TYPE_STUN]) + return false; + + MutexList* stun_list = control_effects[CONTROL_EFFECT_TYPE_STUN]; + return (!stun_list || stun_list->size(true) == 0 || IsStunImmune()) == false; +} + +bool Entity::IsRooted(){ + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_ROOT]) + return false; + + MutexList* root_list = control_effects[CONTROL_EFFECT_TYPE_ROOT]; + return (!root_list || root_list->size(true) == 0 || IsRootImmune()) == false; +} + +bool Entity::IsFeared(){ + if (control_effects.size() < 1 || !control_effects[CONTROL_EFFECT_TYPE_FEAR]) + return false; + + MutexList* fear_list = control_effects[CONTROL_EFFECT_TYPE_FEAR]; + return (!fear_list || fear_list->size(true) == 0 || IsFearImmune()) == false; +} + +void Entity::AddWaterwalkSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER]) + control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER]->Add(spell); + if (control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER]->size(true) == 1 && IsPlayer()) + ((Player*)this)->SetPlayerControlFlag(3, 128, true); // enable walking underwater +} + +void Entity::RemoveWaterwalkSpell(LuaSpell* spell){ + MutexList* waterwalk_list = control_effects[CONTROL_EFFECT_TYPE_WALKUNDERWATER]; + if (!waterwalk_list || waterwalk_list->size(true) == 0) + return; + + waterwalk_list->Remove(spell); + if (waterwalk_list->size(true) == 0 && IsPlayer()) + ((Player*)this)->SetPlayerControlFlag(3, 128, false); // disable walking underwater +} + +void Entity::AddWaterjumpSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER]) + control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER] = new MutexList; + + control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER]->Add(spell); + if (control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER]->size(true) == 1 && IsPlayer()) + ((Player*)this)->SetPlayerControlFlag(4, 1, true); // enable moonjumps underwater +} + +void Entity::RemoveWaterjumpSpell(LuaSpell* spell){ + MutexList* waterjump_list = control_effects[CONTROL_EFFECT_TYPE_JUMPUNDERWATER]; + if (!waterjump_list || waterjump_list->size(true) == 0) + return; + + waterjump_list->Remove(spell); + if (waterjump_list->size(true) == 0 && IsPlayer()) + ((Player*)this)->SetPlayerControlFlag(4, 1, false); // disable moonjumps underwater +} + +void Entity::AddAOEImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_AOE]) + immunities[IMMUNITY_TYPE_AOE] = new MutexList; + + immunities[IMMUNITY_TYPE_AOE]->Add(spell); +} + +void Entity::RemoveAOEImmunity(LuaSpell* spell){ + MutexList* aoe_list = immunities[IMMUNITY_TYPE_AOE]; + if (!aoe_list || aoe_list->size(true) == 0) + return; + aoe_list->Remove(spell); +} + +bool Entity::IsAOEImmune(){ + return (immunities[IMMUNITY_TYPE_AOE] && immunities[IMMUNITY_TYPE_AOE]->size(true)); +} + +void Entity::AddStunImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_STUN]) + immunities[IMMUNITY_TYPE_STUN] = new MutexList; + + if (IsPlayer() && IsStunned() && !IsMezzed()){ + ((Player*)this)->SetPlayerControlFlag(4, 64, false); + if (!IsFeared() && !IsStifled()) + ((Player*)this)->UnlockAllSpells(); + } + + immunities[IMMUNITY_TYPE_STUN]->Add(spell); +} + +void Entity::RemoveStunImmunity(LuaSpell* spell){ + MutexList* stun_list = immunities[IMMUNITY_TYPE_STUN]; + if (!stun_list || stun_list->size(true) == 0) + return; + + stun_list->Remove(spell); + + if (IsPlayer() && IsStunned() && !IsMezzed()){ + ((Player*)this)->SetPlayerControlFlag(4, 64, true); + if (!IsFeared() && !IsStifled()) + ((Player*)this)->UnlockAllSpells(); + } +} + +bool Entity::IsStunImmune(){ + return (immunities[IMMUNITY_TYPE_STUN] && immunities[IMMUNITY_TYPE_STUN]->size(true) > 0); +} + +void Entity::AddStifleImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_STIFLE]) + immunities[IMMUNITY_TYPE_STIFLE] = new MutexList; + + if (IsPlayer() && immunities[IMMUNITY_TYPE_STIFLE]->size(true) == 0){ + if (IsStifled() && !IsMezzedOrStunned() && !IsFeared()) + ((Player*)this)->UnlockAllSpells(); + } + + immunities[IMMUNITY_TYPE_STIFLE]->Add(spell); +} + +void Entity::RemoveStifleImmunity(LuaSpell* spell){ + MutexList* stifle_list = immunities[IMMUNITY_TYPE_STIFLE]; + if (!stifle_list || stifle_list->size(true) == 0) + return; + + stifle_list->Remove(spell); + + if (IsPlayer() && IsStifled() && !IsMezzedOrStunned() && !IsFeared()) + ((Player*)this)->UnlockAllSpells(); +} + +bool Entity::IsStifleImmune(){ + return (immunities[IMMUNITY_TYPE_STIFLE] && immunities[IMMUNITY_TYPE_STIFLE]->size(true) > 0); +} + +void Entity::AddMezImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_MEZ]) + immunities[IMMUNITY_TYPE_MEZ] = new MutexList; + + if (IsPlayer() && IsMezzed() && !IsStunned()){ + ((Player*)this)->SetPlayerControlFlag(4, 64, false); + if (!IsFeared() && !IsStifled()) + ((Player*)this)->UnlockAllSpells(); + } + + immunities[IMMUNITY_TYPE_MEZ]->Add(spell); +} + +void Entity::RemoveMezImmunity(LuaSpell* spell){ + MutexList* mez_list = immunities[IMMUNITY_TYPE_MEZ]; + if (!mez_list || mez_list->size(true) == 0) + return; + + mez_list->Remove(spell); + + if (IsPlayer() && IsMezzed() && !IsStunned()){ + ((Player*)this)->SetPlayerControlFlag(4, 64, true); + if (!IsFeared() && !IsStifled()) + ((Player*)this)->LockAllSpells(); + } +} + +bool Entity::IsMezImmune(){ + return (immunities[IMMUNITY_TYPE_MEZ] && immunities[IMMUNITY_TYPE_MEZ]->size(true) > 0); +} + +void Entity::AddRootImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_ROOT]) + immunities[IMMUNITY_TYPE_ROOT] = new MutexList; + + if (IsPlayer() && IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, false); + + immunities[IMMUNITY_TYPE_ROOT]->Add(spell); +} + +void Entity::RemoveRootImmunity(LuaSpell* spell){ + MutexList* root_list = immunities[IMMUNITY_TYPE_ROOT]; + if (!root_list || root_list->size(true) == 0) + return; + + root_list->Remove(spell); + + if (IsPlayer() && IsRooted()) + ((Player*)this)->SetPlayerControlFlag(1, 8, true); +} + +bool Entity::IsRootImmune(){ + return (immunities[IMMUNITY_TYPE_ROOT] && immunities[IMMUNITY_TYPE_ROOT]->size(true) > 0); +} + +void Entity::AddFearImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_FEAR]) + immunities[IMMUNITY_TYPE_FEAR] = new MutexList; + + if (IsPlayer() && IsFeared()){ + if (!IsMezzedOrStunned() && !IsStifled()) + ((Player*)this)->UnlockAllSpells(); + ((Player*)this)->SetPlayerControlFlag(4, 4, false); + } + + immunities[IMMUNITY_TYPE_FEAR]->Add(spell); +} + +void Entity::RemoveFearImmunity(LuaSpell* spell){ + MutexList* fear_list = immunities[IMMUNITY_TYPE_FEAR]; + if (!fear_list || fear_list->size(true) == 0) + return; + + fear_list->Remove(spell); + + if (IsPlayer() && IsFeared()){ + if (!IsMezzedOrStunned() && !IsStifled()) + ((Player*)this)->LockAllSpells(); + ((Player*)this)->SetPlayerControlFlag(4, 4, true); + } +} + +bool Entity::IsFearImmune(){ + return (immunities[IMMUNITY_TYPE_FEAR] && immunities[IMMUNITY_TYPE_FEAR]->size(true) > 0); +} + +void Entity::AddDazeImmunity(LuaSpell* spell){ + if (!spell) + return; + + if (!immunities[IMMUNITY_TYPE_DAZE]) + immunities[IMMUNITY_TYPE_DAZE] = new MutexList; + + immunities[IMMUNITY_TYPE_DAZE]->Add(spell); +} + +void Entity::RemoveDazeImmunity(LuaSpell* spell){ + MutexList* daze_list = immunities[IMMUNITY_TYPE_DAZE]; + if (!daze_list || daze_list->size(true) == 0) + return; + + daze_list->Remove(spell); +} + +bool Entity::IsDazeImmune(){ + return (immunities[IMMUNITY_TYPE_DAZE] && immunities[IMMUNITY_TYPE_DAZE]->size(true) > 0); +} + +void Entity::AddImmunity(LuaSpell* spell, int16 type){ + if (!spell) + return; + + if (!immunities[type]) + immunities[type] = new MutexList; + + immunities[type]->Add(spell); +} + +void Entity::RemoveImmunity(LuaSpell* spell, int16 type){ + MutexList* list = immunities[type]; + if (!list || list->size(true) == 0) + return; + + list->Remove(spell); +} + +bool Entity::IsImmune(int16 type){ + return (immunities[type] && immunities[type]->size(true) > 0); +} + +void Entity::RemoveEffectsFromLuaSpell(LuaSpell* spell){ + if (!spell) + return; + + //Attempt to remove all applied effects from this spell when spell has been removed from just this target. Should improve performance/easier maitenance + int32 effect_bitmask = spell->effect_bitmask; + if (effect_bitmask == 0) + return; + + if (effect_bitmask & EFFECT_FLAG_STUN) + RemoveStunSpell(spell); + if (effect_bitmask & EFFECT_FLAG_ROOT) + RemoveRootSpell(spell); + if (effect_bitmask & EFFECT_FLAG_MEZ) + RemoveMezSpell(spell); + if (effect_bitmask & EFFECT_FLAG_STIFLE) + RemoveStifleSpell(spell); + if (effect_bitmask & EFFECT_FLAG_DAZE) + RemoveDazeSpell(spell); + if (effect_bitmask & EFFECT_FLAG_FEAR) + RemoveFearSpell(spell); + if (effect_bitmask & EFFECT_FLAG_SPELLBONUS) + RemoveSpellBonus(spell); + if (effect_bitmask & EFFECT_FLAG_SKILLBONUS) + RemoveSkillBonus(spell->spell->GetSpellID()); + if (effect_bitmask & EFFECT_FLAG_STEALTH) + RemoveStealthSpell(spell); + if (effect_bitmask & EFFECT_FLAG_INVIS) + RemoveInvisSpell(spell); + if (effect_bitmask & EFFECT_FLAG_SNARE) + RemoveSnareSpell(spell); + if (effect_bitmask & EFFECT_FLAG_WATERWALK) + RemoveWaterwalkSpell(spell); + if (effect_bitmask & EFFECT_FLAG_WATERJUMP) + RemoveWaterjumpSpell(spell); + if (effect_bitmask & EFFECT_FLAG_FLIGHT) + RemoveFlightSpell(spell); + if (effect_bitmask & EFFECT_FLAG_GLIDE) + RemoveGlideSpell(spell); + if (effect_bitmask & EFFECT_FLAG_AOE_IMMUNE) + RemoveAOEImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_STUN_IMMUNE) + RemoveStunImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_MEZ_IMMUNE) + RemoveMezImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_DAZE_IMMUNE) + RemoveDazeImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_ROOT_IMMUNE) + RemoveRootImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_STIFLE_IMMUNE) + RemoveStifleImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_FEAR_IMMUNE) + RemoveFearImmunity(spell); + if (effect_bitmask & EFFECT_FLAG_SAFEFALL) + RemoveSafefallSpell(spell); +} + +void Entity::RemoveSkillBonus(int32 spell_id){ + //This is a virtual, just making it so we don't have to do extra checks for player/npcs + return; +} + +void Entity::AddFlightSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_FLIGHT]) + control_effects[CONTROL_EFFECT_TYPE_FLIGHT] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_FLIGHT]->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(5, 32, true); + + control_effects[CONTROL_EFFECT_TYPE_FLIGHT]->Add(spell); +} + +void Entity::RemoveFlightSpell(LuaSpell* spell){ + MutexList* flight_list = control_effects[CONTROL_EFFECT_TYPE_FLIGHT]; + if (!flight_list || flight_list->size(true) == 0) + return; + + flight_list->Remove(spell); + if (IsPlayer() && flight_list->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(5, 32, false); +} + +void Entity::AddGlideSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_GLIDE]) + control_effects[CONTROL_EFFECT_TYPE_GLIDE] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_GLIDE]->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(4, 16, true); + + control_effects[CONTROL_EFFECT_TYPE_GLIDE]->Add(spell); +} + +void Entity::RemoveGlideSpell(LuaSpell* spell){ + MutexList* glide_list = control_effects[CONTROL_EFFECT_TYPE_GLIDE]; + if (!glide_list || glide_list->size(true) == 0) + return; + + glide_list->Remove(spell); + if (IsPlayer() && glide_list->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(4, 16, false); +} + +void Entity::AddSafefallSpell(LuaSpell* spell){ + if (!spell) + return; + + if (!control_effects[CONTROL_EFFECT_TYPE_SAFEFALL]) + control_effects[CONTROL_EFFECT_TYPE_SAFEFALL] = new MutexList; + + if (IsPlayer() && control_effects[CONTROL_EFFECT_TYPE_SAFEFALL]->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(4, 32, true); + + control_effects[CONTROL_EFFECT_TYPE_SAFEFALL]->Add(spell); +} + +void Entity::RemoveSafefallSpell(LuaSpell* spell){ + MutexList* safe_list = control_effects[CONTROL_EFFECT_TYPE_SAFEFALL]; + if (!safe_list || safe_list->size(true) == 0) + return; + + safe_list->Remove(spell); + if (IsPlayer() && safe_list->size(true) == 0) + ((Player*)this)->SetPlayerControlFlag(4, 32, false); +} + +void Entity::UpdateGroupMemberInfo(bool inGroupMgrLock, bool groupMembersLocked) { + if (!group_member_info || group_id == 0) + return; + + if(!inGroupMgrLock) + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + + if (group) + group->UpdateGroupMemberInfo(this, groupMembersLocked); + + if(!inGroupMgrLock) + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); +} + +#include "WorldDatabase.h" +extern WorldDatabase database; +void Entity::CustomizeAppearance(PacketStruct* packet) { + + bool is_soga = packet->getType_int8_ByName("is_soga") == 1 ? true : false; + int16 model_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("race_file").data); + EQ2_Color skin_color = packet->getType_EQ2_Color_ByName("skin_color"); + EQ2_Color skin_color2 = packet->getType_EQ2_Color_ByName("skin_color2"); + EQ2_Color eye_color = packet->getType_EQ2_Color_ByName("eye_color"); + EQ2_Color hair_color1 = packet->getType_EQ2_Color_ByName("hair_color1"); + EQ2_Color hair_color2 = packet->getType_EQ2_Color_ByName("hair_color2"); + EQ2_Color hair_highlight = packet->getType_EQ2_Color_ByName("hair_highlight"); + int16 hair_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("hair_file").data); + EQ2_Color hair_type_color = packet->getType_EQ2_Color_ByName("hair_type_color"); + EQ2_Color hair_type_highlight_color = packet->getType_EQ2_Color_ByName("hair_type_highlight_color"); + int16 face_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("face_file").data); + EQ2_Color hair_face_color = packet->getType_EQ2_Color_ByName("hair_face_color"); + EQ2_Color hair_face_highlight_color = packet->getType_EQ2_Color_ByName("hair_face_highlight_color"); + int16 wing_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("wing_file").data); + EQ2_Color wing_color1 = packet->getType_EQ2_Color_ByName("wing_color1"); + EQ2_Color wing_color2 = packet->getType_EQ2_Color_ByName("wing_color2"); + int16 chest_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("chest_file").data); + EQ2_Color shirt_color = packet->getType_EQ2_Color_ByName("shirt_color"); + EQ2_Color unknown_chest_color = packet->getType_EQ2_Color_ByName("unknown_chest_color"); + int16 legs_id = database.GetAppearanceID(packet->getType_EQ2_16BitString_ByName("legs_file").data); + EQ2_Color pants_color = packet->getType_EQ2_Color_ByName("pants_color"); + EQ2_Color unknown_legs_color = packet->getType_EQ2_Color_ByName("unknown_legs_color"); + EQ2_Color unknown2 = packet->getType_EQ2_Color_ByName("unknown2"); + + float eyes2[3]; + eyes2[0] = packet->getType_float_ByName("eyes2", 0) * 100; + eyes2[1] = packet->getType_float_ByName("eyes2", 1) * 100; + eyes2[2] = packet->getType_float_ByName("eyes2", 2) * 100; + + float ears[3]; + ears[0] = packet->getType_float_ByName("ears", 0) * 100; + ears[1] = packet->getType_float_ByName("ears", 1) * 100; + ears[2] = packet->getType_float_ByName("ears", 2) * 100; + + float eye_brows[3]; + eye_brows[0] = packet->getType_float_ByName("eye_brows", 0) * 100; + eye_brows[1] = packet->getType_float_ByName("eye_brows", 1) * 100; + eye_brows[2] = packet->getType_float_ByName("eye_brows", 2) * 100; + + float cheeks[3]; + cheeks[0] = packet->getType_float_ByName("cheeks", 0) * 100; + cheeks[1] = packet->getType_float_ByName("cheeks", 1) * 100; + cheeks[2] = packet->getType_float_ByName("cheeks", 2) * 100; + + float lips[3]; + lips[0] = packet->getType_float_ByName("lips", 0) * 100; + lips[1] = packet->getType_float_ByName("lips", 1) * 100; + lips[2] = packet->getType_float_ByName("lips", 2) * 100; + + float chin[3]; + chin[0] = packet->getType_float_ByName("chin", 0) * 100; + chin[1] = packet->getType_float_ByName("chin", 1) * 100; + chin[2] = packet->getType_float_ByName("chin", 2) * 100; + + float nose[3]; + nose[0] = packet->getType_float_ByName("nose", 0) * 100; + nose[1] = packet->getType_float_ByName("nose", 1) * 100; + nose[2] = packet->getType_float_ByName("nose", 2) * 100; + + sint8 body_size = (sint8)(packet->getType_float_ByName("body_size") * 100); + sint8 body_age = (sint8)(packet->getType_float_ByName("body_age") * 100); + + if (is_soga) { + appearance.soga_model_type = model_id; + features.soga_skin_color = skin_color; + features.soga_eye_color = eye_color; + features.soga_hair_color1 = hair_color1; + features.soga_hair_color2 = hair_color2; + features.soga_hair_highlight_color = hair_highlight; + features.soga_hair_type = hair_id; + features.soga_hair_type_color = hair_type_color; + features.soga_hair_type_highlight_color = hair_type_highlight_color; + features.soga_hair_face_type = face_id; + features.soga_hair_face_color = hair_face_color; + features.soga_hair_face_highlight_color = hair_face_highlight_color; + features.wing_type = wing_id; + features.wing_color1 = wing_color1; + features.wing_color2 = wing_color2; + features.soga_chest_type = chest_id; + features.shirt_color = shirt_color; + features.soga_legs_type = legs_id; + features.pants_color = pants_color; + features.soga_eye_type[0] = eyes2[0]; + features.soga_eye_type[1] = eyes2[1]; + features.soga_eye_type[2] = eyes2[2]; + features.soga_ear_type[0] = ears[0]; + features.soga_ear_type[0] = ears[1]; + features.soga_ear_type[0] = ears[2]; + features.soga_eye_brow_type[0] = eye_brows[0]; + features.soga_eye_brow_type[1] = eye_brows[1]; + features.soga_eye_brow_type[2] = eye_brows[2]; + features.soga_cheek_type[0] = cheeks[0]; + features.soga_cheek_type[1] = cheeks[1]; + features.soga_cheek_type[2] = cheeks[2]; + features.soga_lip_type[0] = lips[0]; + features.soga_lip_type[1] = lips[1]; + features.soga_lip_type[2] = lips[2]; + features.soga_chin_type[0] = chin[0]; + features.soga_chin_type[1] = chin[1]; + features.soga_chin_type[2] = chin[2]; + features.soga_nose_type[0] = nose[0]; + features.soga_nose_type[1] = nose[1]; + features.soga_nose_type[2] = nose[2]; + } + else { + appearance.model_type = model_id; + features.skin_color = skin_color; + features.eye_color = eye_color; + features.hair_color1 = hair_color1; + features.hair_color2 = hair_color2; + features.hair_highlight_color = hair_highlight; + features.hair_type = hair_id; + features.hair_type_color = hair_type_color; + features.hair_type_highlight_color = hair_type_highlight_color; + features.hair_face_type = face_id; + features.hair_face_color = hair_face_color; + features.hair_face_highlight_color = hair_face_highlight_color; + features.wing_type = wing_id; + features.wing_color1 = wing_color1; + features.wing_color2 = wing_color2; + features.chest_type = chest_id; + features.shirt_color = shirt_color; + features.legs_type = legs_id; + features.pants_color = pants_color; + features.eye_type[0] = eyes2[0]; + features.eye_type[1] = eyes2[1]; + features.eye_type[2] = eyes2[2]; + features.ear_type[0] = ears[0]; + features.ear_type[0] = ears[1]; + features.ear_type[0] = ears[2]; + features.eye_brow_type[0] = eye_brows[0]; + features.eye_brow_type[1] = eye_brows[1]; + features.eye_brow_type[2] = eye_brows[2]; + features.cheek_type[0] = cheeks[0]; + features.cheek_type[1] = cheeks[1]; + features.cheek_type[2] = cheeks[2]; + features.lip_type[0] = lips[0]; + features.lip_type[1] = lips[1]; + features.lip_type[2] = lips[2]; + features.chin_type[0] = chin[0]; + features.chin_type[1] = chin[1]; + features.chin_type[2] = chin[2]; + features.nose_type[0] = nose[0]; + features.nose_type[1] = nose[1]; + features.nose_type[2] = nose[2]; + } + + features.body_size = body_size; + features.body_age = body_age; + features.soga_body_size = body_size; + features.soga_body_age = body_age; + info_changed = true; + changed = true; +} + +void Entity::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { + // handled in npc or player + return; +} + +bool Entity::HasControlEffect(int8 type) +{ + if (type >= CONTROL_MAX_EFFECTS) + return false; + + MutexList* spell_list = control_effects[type]; + if (!spell_list || spell_list->size(true) == 0) + return false; + + return true; +} + +void Entity::HaltMovement() +{ + this->ClearRunningLocations(); + + if (GetZone()) + GetZone()->movementMgr->StopNavigation(this); + + RunToLocation(GetX(), GetY(), GetZ()); +} + +std::string Entity::GetInfoStructString(std::string field) +{ + map>::const_iterator itr = get_string_funcs.find(field); + if(itr != get_string_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return std::string(""); +} + +int8 Entity::GetInfoStructInt8(std::string field) +{ + map>::const_iterator itr = get_int8_funcs.find(field); + if(itr != get_int8_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +int16 Entity::GetInfoStructInt16(std::string field) +{ + map>::const_iterator itr = get_int16_funcs.find(field); + if(itr != get_int16_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +int32 Entity::GetInfoStructInt32(std::string field) +{ + map>::const_iterator itr = get_int32_funcs.find(field); + if(itr != get_int32_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +int64 Entity::GetInfoStructInt64(std::string field) +{ + map>::const_iterator itr = get_int64_funcs.find(field); + if(itr != get_int64_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +sint8 Entity::GetInfoStructSInt8(std::string field) +{ + map>::const_iterator itr = get_sint8_funcs.find(field); + if(itr != get_sint8_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +sint16 Entity::GetInfoStructSInt16(std::string field) +{ + map>::const_iterator itr = get_sint16_funcs.find(field); + if(itr != get_sint16_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +sint32 Entity::GetInfoStructSInt32(std::string field) +{ + map>::const_iterator itr = get_sint32_funcs.find(field); + if(itr != get_sint32_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +sint64 Entity::GetInfoStructSInt64(std::string field) +{ + map>::const_iterator itr = get_sint64_funcs.find(field); + if(itr != get_sint64_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0; +} + +float Entity::GetInfoStructFloat(std::string field) +{ + map>::const_iterator itr = get_float_funcs.find(field); + if(itr != get_float_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + return 0.0f; +} + +int64 Entity::GetInfoStructUInt(std::string field) +{ + map>::const_iterator itr = get_int8_funcs.find(field); + if(itr != get_int8_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + map>::const_iterator itr2 = get_int16_funcs.find(field); + if(itr2 != get_int16_funcs.end()) + { + auto func = (itr2->second)(); + return func; + } + map>::const_iterator itr3 = get_int32_funcs.find(field); + if(itr3 != get_int32_funcs.end()) + { + auto func = (itr3->second)(); + return func; + } + map>::const_iterator itr4 = get_int64_funcs.find(field); + if(itr4 != get_int64_funcs.end()) + { + auto func = (itr4->second)(); + return func; + } + return 0; +} + +sint64 Entity::GetInfoStructSInt(std::string field) +{ + map>::const_iterator itr = get_sint8_funcs.find(field); + if(itr != get_sint8_funcs.end()) + { + auto func = (itr->second)(); + return func; + } + map>::const_iterator itr2 = get_sint16_funcs.find(field); + if(itr2 != get_sint16_funcs.end()) + { + auto func = (itr2->second)(); + return func; + } + map>::const_iterator itr3 = get_sint32_funcs.find(field); + if(itr3 != get_sint32_funcs.end()) + { + auto func = (itr3->second)(); + return func; + } + map>::const_iterator itr4 = get_sint64_funcs.find(field); + if(itr4 != get_sint64_funcs.end()) + { + auto func = (itr4->second)(); + return func; + } + return 0; +} + + +bool Entity::SetInfoStructString(std::string field, std::string value) +{ + map>::const_iterator itr = set_string_funcs.find(field); + if(itr != set_string_funcs.end()) + { + (itr->second)(value); + return true; + } + return false; +} + + +bool Entity::SetInfoStructUInt(std::string field, int64 value) +{ + map>::const_iterator itr = set_int8_funcs.find(field); + if(itr != set_int8_funcs.end()) + { + (itr->second)((int8)value); + return true; + } + map>::const_iterator itr2 = set_int16_funcs.find(field); + if(itr2 != set_int16_funcs.end()) + { + (itr2->second)((int16)value); + return true; + } + map>::const_iterator itr3 = set_int32_funcs.find(field); + if(itr3 != set_int32_funcs.end()) + { + (itr3->second)((int32)value); + return true; + } + map>::const_iterator itr4 = set_int64_funcs.find(field); + if(itr4 != set_int64_funcs.end()) + { + (itr4->second)(value); + return true; + } + return false; +} + +bool Entity::SetInfoStructSInt(std::string field, sint64 value) +{ + map>::const_iterator itr = set_sint8_funcs.find(field); + if(itr != set_sint8_funcs.end()) + { + (itr->second)((sint8)value); + return true; + } + map>::const_iterator itr2 = set_sint16_funcs.find(field); + if(itr2 != set_sint16_funcs.end()) + { + (itr2->second)((sint16)value); + return true; + } + map>::const_iterator itr3 = set_sint32_funcs.find(field); + if(itr3 != set_sint32_funcs.end()) + { + (itr3->second)((sint32)value); + return true; + } + map>::const_iterator itr4 = set_sint64_funcs.find(field); + if(itr4 != set_sint64_funcs.end()) + { + (itr4->second)(value); + return true; + } + return false; +} + +bool Entity::SetInfoStructFloat(std::string field, float value) +{ + map>::const_iterator itr = set_float_funcs.find(field); + if(itr != set_float_funcs.end()) + { + (itr->second)(value); + return true; + } + return false; +} + +Entity* Entity::GetOwner() { + Entity* ent = nullptr; + + if(!GetZone()) { + return ent; + } + Spawn* spawn = GetZone()->GetSpawnByID(owner); + if ( spawn && spawn->IsEntity() ) + ent = (Entity*)spawn; + + return ent; +} + +bool Entity::IsEngagedInEncounter(Spawn** res) { + if(res) { + *res = nullptr; + } + bool ret = false; + set::iterator itr; + MHatedBy.lock(); + if(IsPlayer()) { + for (itr = HatedBy.begin(); itr != HatedBy.end(); itr++) { + Spawn* spawn = GetZone()->GetSpawnByID(*itr); + if (spawn && spawn->IsNPC() && ((NPC*)spawn)->Brain() && (spawn->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED || spawn->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED)) { + if((ret = ((NPC*)spawn)->Brain()->IsPlayerInEncounter(((Player*)this)->GetCharacterID()))) { + if(res) + *res = spawn; + break; + } + } + } + } + else { + for (itr = HatedBy.begin(); itr != HatedBy.end(); itr++) { + Spawn* spawn = GetZone()->GetSpawnByID(*itr); + if (spawn && spawn->IsNPC() && ((NPC*)spawn)->Brain() && (spawn->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED || spawn->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED)) { + if((ret = ((NPC*)spawn)->Brain()->IsEntityInEncounter(GetID()))) { + if(res) + *res = spawn; + break; + } + } + } + + } + MHatedBy.unlock(); + + return ret; +} + +bool Entity::IsEngagedBySpawnID(int32 id) { + bool ret = false; + Spawn* spawn = GetZone()->GetSpawnByID(id); + if (spawn && spawn->IsNPC() && ((NPC*)spawn)->Brain()) { + ret = ((NPC*)spawn)->Brain()->IsEntityInEncounter(GetID()); + } + + return ret; +} + +void Entity::SendControlEffectDetailsToClient(Client* client) { + client->Message(CHANNEL_COLOR_YELLOW, "Current control effects on %s", GetName()); + client->Message(CHANNEL_COLOR_YELLOW, "-------------------------------"); + for (int i = 0; i < CONTROL_MAX_EFFECTS; i++) { + if(control_effects[i]) { + MutexList* spells = control_effects[i]; + if(spells->size() > 0) { + MutexList::iterator itr = spells->begin(); + while(itr.Next()){ + LuaSpell* spell = itr->value; + if(spell && spell->spell && spell->spell->GetSpellData()) { + client->Message(CHANNEL_COLOR_YELLOW, "Spell %s (%u) control effect %s", spell->spell->GetName(), spell->spell->GetSpellData()->id, GetControlEffectName(i).c_str()); + } + } + } + } + } + client->Message(CHANNEL_COLOR_YELLOW, "-------------------------------"); +} + +void Entity::TerminateTrade() { + Trade* tmpTradePtr = trade; + if (tmpTradePtr) { + tmpTradePtr->CancelTrade(this); + safe_delete(tmpTradePtr); + } +} + +void Entity::CalculateMaxReduction() { + if(GetInfoStruct()->get_max_spell_reduction_override()) { + return; // override enabled, don't touch the max reduction + } + int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + // Define thresholds and corresponding maximum reduction factors + const int thresholds[] = {1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120}; // Thresholds for level differences + const float maxReductions[] = {0.2f, 0.2f, 0.15f, 0.15f, 0.1f,0.1f,0.1f,0.1f,0.1f,0.075f,0.075f,0.075f,0.05f,0.05f,0.05f,0.05f,0.05f,0.05f,0.045f,0.045f,0.045f,0.045f,0.045f,0.045f,0.045f}; // Maximum reduction factors for each threshold + int numThresholds = sizeof(thresholds) / sizeof(thresholds[0]); + + // Find the appropriate maximum reduction factor based on level difference + float maxReduction = .1f; // Default maximum reduction factor + for (int i = 0; i < numThresholds; ++i) { + if (effective_level >= thresholds[i]) { + maxReduction = maxReductions[i]; + } + else { + break; // No need to check further + } + } + + GetInfoStruct()->set_max_spell_reduction(maxReduction); +} \ No newline at end of file diff --git a/source/WorldServer/Entity.h b/source/WorldServer/Entity.h new file mode 100644 index 0000000..e97ff0d --- /dev/null +++ b/source/WorldServer/Entity.h @@ -0,0 +1,2145 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_ENTITY__ +#define __EQ2_ENTITY__ +#include "Spawn.h" +#include "../common/Mutex.h" +#include "Skills.h" +#include "MutexList.h" +#include "MutexVector.h" +#include "Trade.h" +#include +#include +#include +#include +#include + +namespace l = boost::lambda; + +class Entity; +class NPC; +class Trade; +struct LuaSpell; +struct GroupMemberInfo; + +struct BonusValues{ + int32 spell_id; + int8 tier; + int16 type; + sint32 value; + int64 class_req; + vector race_req; + vector faction_req; + LuaSpell* luaspell; +}; + +struct MaintainedEffects{ + char name[60]; //name of the spell + int32 target; + int8 target_type; + int32 spell_id; + int32 slot_pos; + int16 icon; + int16 icon_backdrop; + int8 conc_used; + int8 tier; + float total_time; + int32 expire_timestamp; + LuaSpell* spell; +}; + +struct SpellEffects{ + int32 spell_id; + Entity* caster; + float total_time; + int32 expire_timestamp; + int16 icon; + int16 icon_backdrop; + int8 tier; + LuaSpell* spell; +}; + +struct DetrimentalEffects { + int32 spell_id; + Entity* caster; + int32 expire_timestamp; + int16 icon; + int16 icon_backdrop; + int8 tier; + int8 det_type; + bool incurable; + LuaSpell* spell; + int8 control_effect; + float total_time; +}; + +enum RACE_ALIGNMENT { + ALIGNMENT_EVIL=0, + ALIGNMENT_GOOD=1 + // neutral? +}; +struct InfoStruct{ + InfoStruct() + { + name_ = std::string(""); + class1_ = 0; + class2_ = 0; + class3_ = 0; + race_ = 0; + gender_ = 0; + level_ = 0; + max_level_ = 0; + effective_level_ = 0; + tradeskill_level_ = 0; + tradeskill_max_level_ = 0; + cur_concentration_ = 0; + max_concentration_ = 0; + cur_attack_ = 0; + attack_base_ = 0; + cur_mitigation_ = 0; + max_mitigation_ = 0; + mitigation_base_ = 0; + avoidance_display_ = 0; + cur_avoidance_ = 0.0f; + base_avoidance_pct_ = 0; + avoidance_base_ = 0; + max_avoidance_ = 0; + parry_ = 0.0f; + parry_base_ = 0.0f; + deflection_ = 0; + deflection_base_ = 0; + block_ = 0; + block_base_ = 0; + str_ = 0.0f; + sta_ = 0.0f; + agi_ = 0.0f; + wis_ = 0.0f; + intel_ = 0.0f; + str_base_ = 0.0f; + sta_base_ = 0.0f; + agi_base_ = 0.0f; + wis_base_ = 0.0f; + intel_base_ = 0.0f; + heat_ = 0; + cold_ = 0; + magic_ = 0; + mental_ = 0; + divine_ = 0; + disease_ = 0; + poison_ = 0; + disease_base_ = 0; + cold_base_ = 0; + divine_base_ = 0; + magic_base_ = 0; + mental_base_ = 0; + heat_base_ = 0; + poison_base_ = 0; + elemental_base_ = 0; + noxious_base_ = 0; + arcane_base_ = 0; + coin_copper_ = 0; + coin_silver_ = 0; + coin_gold_ = 0; + coin_plat_ = 0; + bank_coin_copper_ = 0; + bank_coin_silver_ = 0; + bank_coin_gold_ = 0; + bank_coin_plat_ = 0; + status_points_ = 0; + deity_ = std::string(""); + weight_ = 0; + max_weight_ = 0; + tradeskill_class1_ = 0; + tradeskill_class2_ = 0; + tradeskill_class3_ = 0; + account_age_base_ = 0; + + memset(account_age_bonus_,0,19); + absorb_ = 0; + xp_ = 0; + xp_needed_ = 0; + xp_debt_ = 0.0f; + xp_yellow_ = 0; + xp_yellow_vitality_bar_ = 0; + xp_blue_vitality_bar_ = 0; + xp_blue_ = 0; + ts_xp_ = 0; + ts_xp_needed_ = 0; + tradeskill_exp_yellow_ = 0; + tradeskill_exp_blue_ = 0; + flags_ = 0; + flags2_ = 0; + xp_vitality_ = 0; + tradeskill_xp_vitality_ = 0; + mitigation_skill1_ = 0; + mitigation_skill2_ = 0; + mitigation_skill3_ = 0; + mitigation_pve_ = 0; + mitigation_pvp_ = 0; + ability_modifier_ = 0; + critical_mitigation_ = 0; + block_chance_ = 0; + uncontested_parry_ = 0; + uncontested_block_ = 0; + uncontested_dodge_ = 0; + uncontested_riposte_ = 0; + crit_chance_ = 0; + crit_bonus_ = 0; + potency_ = 0; + hate_mod_ = 0; + reuse_speed_ = 0; + casting_speed_ = 0; + recovery_speed_ = 0; + spell_reuse_speed_ = 0; + spell_multi_attack_ = 0; + dps_ = 0; + dps_multiplier_ = 0; + attackspeed_ = 0; + haste_ = 0; + multi_attack_ = 0; + flurry_ = 0; + melee_ae_ = 0; + strikethrough_ = 0; + accuracy_ = 0; + offensivespeed_ = 0; + rain_ = 0; + wind_ = 0; + alignment_ = 0; + pet_id_ = 0; + pet_name_ = std::string(""); + pet_health_pct_ = 0.0f; + pet_power_pct_ = 0.0f; + pet_movement_ = 0; + pet_behavior_ = 0; + vision_ = 0; + breathe_underwater_ = 0; + biography_ = std::string(""); + drunk_ = 0; + power_regen_ = 0; + hp_regen_ = 0; + + power_regen_override_ = 0; + hp_regen_override_ = 0; + + water_type_ = 0; + flying_type_ = 0; + + no_interrupt_ = 0; + + interaction_flag_ = 0; + tag1_ = 0; + mood_ = 0; + + range_last_attack_time_ = 0; + primary_last_attack_time_ = 0; + secondary_last_attack_time_ = 0; + primary_attack_delay_ = 0; + secondary_attack_delay_ = 0; + ranged_attack_delay_ = 0; + primary_weapon_type_ = 0; + secondary_weapon_type_ = 0; + ranged_weapon_type_ = 0; + primary_weapon_damage_low_ = 0; + primary_weapon_damage_high_ = 0; + secondary_weapon_damage_low_ = 0; + secondary_weapon_damage_high_ = 0; + ranged_weapon_damage_low_ = 0; + ranged_weapon_damage_high_ = 0; + wield_type_ = 0; + attack_type_ = 0; + primary_weapon_delay_ = 0; + secondary_weapon_delay_ = 0; + ranged_weapon_delay_ = 0; + + override_primary_weapon_ = 0; + override_secondary_weapon_ = 0; + override_ranged_weapon_ = 0; + + friendly_target_npc_ = 0; + last_claim_time_ = 0; + + engaged_encounter_ = 0; + + first_world_login_ = 0; + reload_player_spells_ = 0; + + group_loot_method_ = 1; + group_loot_items_rarity_ = 0; + group_auto_split_ = 1; + group_default_yell_ = 1; + group_autolock_ = 0; + group_lock_method_ = 0; + group_solo_autolock_ = 0; + group_auto_loot_method_ = 0; + assist_auto_attack_ = 0; + + action_state_ = std::string(""); + combat_action_state_ = std::string(""); + + max_spell_reduction_ = .1f; + max_spell_reduction_override_ = 0; + } + + + void SetInfoStruct(InfoStruct* oldStruct) + { + if(!oldStruct) + return; + std::lock_guard lk(classMutex); + + name_ = std::string(oldStruct->get_name()); + class1_ = oldStruct->get_class1(); + class2_ = oldStruct->get_class2(); + class3_ = oldStruct->get_class3(); + race_ = oldStruct->get_race(); + gender_ = oldStruct->get_gender(); + level_ = oldStruct->get_level(); + max_level_ = oldStruct->get_max_level(); + effective_level_ = oldStruct->get_effective_level(); + tradeskill_level_ = oldStruct->get_tradeskill_level(); + tradeskill_max_level_ = oldStruct->get_tradeskill_max_level(); + cur_concentration_ = oldStruct->get_cur_concentration(); + max_concentration_ = oldStruct->get_max_concentration(); + cur_attack_ = oldStruct->get_cur_attack(); + attack_base_ = oldStruct->get_attack_base(); + cur_mitigation_ = oldStruct->get_cur_mitigation(); + max_mitigation_ = oldStruct->get_max_mitigation(); + mitigation_base_ = oldStruct->get_mitigation_base(); + avoidance_display_ = oldStruct->get_avoidance_display(); + cur_avoidance_ = oldStruct->get_cur_avoidance(); + base_avoidance_pct_ = oldStruct->get_base_avoidance_pct(); + avoidance_base_ = oldStruct->get_avoidance_base(); + max_avoidance_ = oldStruct->get_max_avoidance(); + parry_ = oldStruct->get_parry(); + parry_base_ = oldStruct->get_parry_base(); + deflection_ = oldStruct->get_deflection(); + deflection_base_ = oldStruct->get_deflection_base(); + block_ = oldStruct->get_block(); + block_base_ = oldStruct->get_block_base(); + str_ = oldStruct->get_str(); + sta_ = oldStruct->get_sta(); + agi_ = oldStruct->get_agi(); + wis_ = oldStruct->get_wis(); + intel_ = oldStruct->get_intel(); + str_base_ = oldStruct->get_str_base(); + sta_base_ = oldStruct->get_sta_base(); + agi_base_ = oldStruct->get_agi_base(); + wis_base_ = oldStruct->get_wis_base(); + intel_base_ = oldStruct->get_intel_base(); + heat_ = oldStruct->get_heat(); + cold_ = oldStruct->get_cold(); + magic_ = oldStruct->get_magic(); + mental_ = oldStruct->get_mental(); + divine_ = oldStruct->get_divine(); + disease_ = oldStruct->get_disease(); + poison_ = oldStruct->get_poison(); + disease_base_ = oldStruct->get_disease_base(); + cold_base_ = oldStruct->get_cold_base(); + divine_base_ = oldStruct->get_divine_base(); + magic_base_ = oldStruct->get_magic_base(); + mental_base_ = oldStruct->get_mental_base(); + heat_base_ = oldStruct->get_heat_base(); + poison_base_ = oldStruct->get_poison_base(); + elemental_base_ = oldStruct->get_elemental_base(); + noxious_base_ = oldStruct->get_noxious_base(); + arcane_base_ = oldStruct->get_arcane_base(); + coin_copper_ = oldStruct->get_coin_copper(); + coin_silver_ = oldStruct->get_coin_silver(); + coin_gold_ = oldStruct->get_coin_gold(); + coin_plat_ = oldStruct->get_coin_plat(); + bank_coin_copper_ = oldStruct->get_bank_coin_copper(); + bank_coin_silver_ = oldStruct->get_bank_coin_silver(); + bank_coin_gold_ = oldStruct->get_bank_coin_gold(); + bank_coin_plat_ = oldStruct->get_bank_coin_plat(); + status_points_ = oldStruct->get_status_points(); + deity_ = std::string(""); + weight_ = oldStruct->get_weight(); + max_weight_ = oldStruct->get_max_weight(); + tradeskill_class1_ = oldStruct->get_tradeskill_class1(); + tradeskill_class2_ = oldStruct->get_tradeskill_class2(); + tradeskill_class3_ = oldStruct->get_tradeskill_class3(); + account_age_base_ = oldStruct->get_account_age_base(); + + memset(account_age_bonus_,0,19); + absorb_ = oldStruct->get_absorb(); + xp_ = oldStruct->get_xp(); + xp_needed_ = oldStruct->get_xp_needed(); + xp_debt_ = oldStruct->get_xp_debt(); + xp_yellow_ = oldStruct->get_xp_yellow(); + xp_yellow_vitality_bar_ = oldStruct->get_xp_yellow_vitality_bar(); + xp_blue_vitality_bar_ = oldStruct->get_xp_blue_vitality_bar(); + xp_blue_ = oldStruct->get_xp_blue(); + ts_xp_ = oldStruct->get_ts_xp(); + ts_xp_needed_ = oldStruct->get_ts_xp_needed(); + tradeskill_exp_yellow_ = oldStruct->get_tradeskill_exp_yellow(); + tradeskill_exp_blue_ = oldStruct->get_tradeskill_exp_blue(); + flags_ = oldStruct->get_flags(); + flags2_ = oldStruct->get_flags2(); + xp_vitality_ = oldStruct->get_xp_vitality(); + tradeskill_xp_vitality_ = oldStruct->get_tradeskill_xp_vitality(); + mitigation_skill1_ = oldStruct->get_mitigation_skill1(); + mitigation_skill2_ = oldStruct->get_mitigation_skill2(); + mitigation_skill3_ = oldStruct->get_mitigation_skill3(); + mitigation_pve_ = oldStruct->get_mitigation_pve(); + mitigation_pvp_ = oldStruct->get_mitigation_pvp(); + ability_modifier_ = oldStruct->get_ability_modifier(); + critical_mitigation_ = oldStruct->get_critical_mitigation(); + block_chance_ = oldStruct->get_block_chance(); + uncontested_parry_ = oldStruct->get_uncontested_parry(); + uncontested_block_ = oldStruct->get_uncontested_block(); + uncontested_dodge_ = oldStruct->get_uncontested_dodge(); + uncontested_riposte_ = oldStruct->get_uncontested_riposte(); + crit_chance_ = oldStruct->get_crit_chance(); + crit_bonus_ = oldStruct->get_crit_bonus(); + potency_ = oldStruct->get_potency(); + hate_mod_ = oldStruct->get_hate_mod(); + reuse_speed_ = oldStruct->get_reuse_speed(); + casting_speed_ = oldStruct->get_casting_speed(); + recovery_speed_ = oldStruct->get_recovery_speed(); + spell_reuse_speed_ = oldStruct->get_spell_reuse_speed(); + spell_multi_attack_ = oldStruct->get_spell_multi_attack(); + dps_ = oldStruct->get_dps(); + dps_multiplier_ = oldStruct->get_dps_multiplier(); + attackspeed_ = oldStruct->get_attackspeed(); + haste_ = oldStruct->get_haste(); + multi_attack_ = oldStruct->get_multi_attack(); + flurry_ = oldStruct->get_flurry(); + melee_ae_ = oldStruct->get_melee_ae(); + strikethrough_ = oldStruct->get_strikethrough(); + accuracy_ = oldStruct->get_accuracy(); + offensivespeed_ = oldStruct->get_offensivespeed(); + rain_ = oldStruct->get_rain(); + wind_ = oldStruct->get_wind(); + alignment_ = oldStruct->get_alignment(); + pet_id_ = oldStruct->get_pet_id(); + pet_name_ = std::string(oldStruct->get_pet_name()); + pet_health_pct_ = oldStruct->get_pet_health_pct(); + pet_power_pct_ = oldStruct->get_pet_power_pct(); + pet_movement_ = oldStruct->get_pet_movement(); + pet_behavior_ = oldStruct->get_pet_behavior(); + vision_ = oldStruct->get_vision(); + breathe_underwater_ = oldStruct->get_breathe_underwater(); + biography_ = std::string(oldStruct->get_biography()); + drunk_ = oldStruct->get_drunk(); + power_regen_ = oldStruct->get_power_regen(); + hp_regen_ = oldStruct->get_hp_regen(); + + power_regen_override_ = oldStruct->get_power_regen_override(); + hp_regen_override_ = oldStruct->get_hp_regen_override(); + + water_type_ = oldStruct->get_water_type(); + flying_type_ = oldStruct->get_flying_type(); + + no_interrupt_ = oldStruct->get_no_interrupt(); + + interaction_flag_ = oldStruct->get_interaction_flag(); + tag1_ = oldStruct->get_tag1(); + mood_ = oldStruct->get_mood(); + + range_last_attack_time_ = oldStruct->get_range_last_attack_time(); + primary_last_attack_time_ = oldStruct->get_primary_last_attack_time();; + secondary_last_attack_time_ = oldStruct->get_secondary_last_attack_time();; + primary_attack_delay_ = oldStruct->get_primary_attack_delay(); + secondary_attack_delay_ = oldStruct->get_secondary_attack_delay(); + ranged_attack_delay_ = oldStruct->get_ranged_attack_delay(); + primary_weapon_type_ = oldStruct->get_primary_weapon_type(); + secondary_weapon_type_ = oldStruct->get_secondary_weapon_type(); + ranged_weapon_type_ = oldStruct->get_ranged_weapon_type(); + primary_weapon_damage_low_ = oldStruct->get_primary_weapon_damage_low(); + primary_weapon_damage_high_ = oldStruct->get_primary_weapon_damage_high(); + secondary_weapon_damage_low_ = oldStruct->get_secondary_weapon_damage_low(); + secondary_weapon_damage_high_ = oldStruct->get_secondary_weapon_damage_high(); + ranged_weapon_damage_low_ = oldStruct->get_ranged_weapon_damage_low(); + ranged_weapon_damage_high_ = oldStruct->get_ranged_weapon_damage_high(); + wield_type_ = oldStruct->get_wield_type(); + attack_type_ = oldStruct->get_attack_type(); + primary_weapon_delay_ = oldStruct->get_primary_weapon_delay(); + secondary_weapon_delay_ = oldStruct->get_secondary_weapon_delay(); + ranged_weapon_delay_ = oldStruct->get_ranged_weapon_delay(); + + override_primary_weapon_ = oldStruct->get_override_primary_weapon(); + override_secondary_weapon_ = oldStruct->get_override_secondary_weapon(); + override_ranged_weapon_ = oldStruct->get_override_ranged_weapon(); + friendly_target_npc_ = oldStruct->get_friendly_target_npc(); + last_claim_time_ = oldStruct->get_last_claim_time(); + + engaged_encounter_ = oldStruct->get_engaged_encounter(); + + first_world_login_ = oldStruct->get_first_world_login(); + reload_player_spells_ = oldStruct->get_reload_player_spells(); + + action_state_ = oldStruct->get_action_state(); + combat_action_state_ = oldStruct->get_combat_action_state(); + + group_loot_method_ = oldStruct->get_group_loot_method(); + group_loot_items_rarity_ = oldStruct->get_group_loot_items_rarity(); + group_auto_split_ = oldStruct->get_group_auto_split(); + group_default_yell_ = oldStruct->get_group_default_yell(); + group_autolock_ = oldStruct->get_group_autolock(); + group_lock_method_ = oldStruct->get_group_lock_method(); + group_solo_autolock_ = oldStruct->get_group_solo_autolock(); + group_auto_loot_method_ = oldStruct->get_group_auto_loot_method(); + assist_auto_attack_ = oldStruct->get_assist_auto_attack(); + + max_spell_reduction_ = oldStruct->get_max_spell_reduction(); + max_spell_reduction_override_ = oldStruct->get_max_spell_reduction_override(); + } + //mutable std::shared_mutex mutex_; + std::string get_name() { std::lock_guard lk(classMutex); return name_; } + int8 get_class1() { std::lock_guard lk(classMutex); return class1_; } + int8 get_class2() { std::lock_guard lk(classMutex); return class2_; } + int8 get_class3() { std::lock_guard lk(classMutex); return class3_; } + int8 get_race() { std::lock_guard lk(classMutex); return race_; } + int8 get_gender() { std::lock_guard lk(classMutex); return gender_; } + int16 get_level() { std::lock_guard lk(classMutex); return level_; } + int16 get_max_level() { std::lock_guard lk(classMutex); return max_level_; } + int16 get_effective_level() { std::lock_guard lk(classMutex); return effective_level_; } + int16 get_tradeskill_level() { std::lock_guard lk(classMutex); return tradeskill_level_; } + int16 get_tradeskill_max_level() { std::lock_guard lk(classMutex); return tradeskill_max_level_; } + + int8 get_cur_concentration() { std::lock_guard lk(classMutex); return cur_concentration_; } + int8 get_max_concentration() { std::lock_guard lk(classMutex); return max_concentration_; } + int8 get_max_concentration_base() { std::lock_guard lk(classMutex); return max_concentration_base_; } + int16 get_cur_attack() { std::lock_guard lk(classMutex); return cur_attack_; } + int16 get_attack_base() { std::lock_guard lk(classMutex); return attack_base_; } + int16 get_cur_mitigation() { std::lock_guard lk(classMutex); return cur_mitigation_; } + int16 get_max_mitigation() { std::lock_guard lk(classMutex); return max_mitigation_; } + + int16 get_mitigation_base() { std::lock_guard lk(classMutex); return mitigation_base_; } + int16 get_avoidance_display() { std::lock_guard lk(classMutex); return avoidance_display_; } + float get_cur_avoidance() { std::lock_guard lk(classMutex); return cur_avoidance_; } + int16 get_base_avoidance_pct() { std::lock_guard lk(classMutex); return base_avoidance_pct_; } + int16 get_avoidance_base() { std::lock_guard lk(classMutex); return avoidance_base_; } + + float get_parry() { std::lock_guard lk(classMutex); return parry_; } + float get_parry_base() { std::lock_guard lk(classMutex); return parry_base_; } + + int16 get_max_avoidance() { std::lock_guard lk(classMutex); return max_avoidance_; } + + float get_deflection() { std::lock_guard lk(classMutex); return deflection_; } + int16 get_deflection_base() { std::lock_guard lk(classMutex); return deflection_base_; } + + float get_block() { std::lock_guard lk(classMutex); return block_; } + int16 get_block_base() { std::lock_guard lk(classMutex); return block_base_; } + + float get_str() { std::lock_guard lk(classMutex); return str_; } + float get_sta() { std::lock_guard lk(classMutex); return sta_; } + float get_agi() { std::lock_guard lk(classMutex); return agi_; } + float get_wis() { std::lock_guard lk(classMutex); return wis_; } + float get_intel() { std::lock_guard lk(classMutex); return intel_; } + float get_str_base() { std::lock_guard lk(classMutex); return str_base_; } + float get_sta_base() { std::lock_guard lk(classMutex); return sta_base_; } + float get_agi_base() { std::lock_guard lk(classMutex); return agi_base_; } + float get_wis_base() { std::lock_guard lk(classMutex); return wis_base_; } + float get_intel_base() { std::lock_guard lk(classMutex); return intel_base_; } + int16 get_heat() { std::lock_guard lk(classMutex); return heat_; } + int16 get_cold() { std::lock_guard lk(classMutex); return cold_; } + int16 get_magic() { std::lock_guard lk(classMutex); return magic_; } + int16 get_mental() { std::lock_guard lk(classMutex); return mental_; } + int16 get_divine() { std::lock_guard lk(classMutex); return divine_; } + int16 get_disease() { std::lock_guard lk(classMutex); return disease_; } + int16 get_poison() { std::lock_guard lk(classMutex); return poison_; } + int16 get_disease_base() { std::lock_guard lk(classMutex); return disease_base_; } + int16 get_cold_base() { std::lock_guard lk(classMutex); return cold_base_; } + int16 get_divine_base() { std::lock_guard lk(classMutex); return divine_base_; } + int16 get_magic_base() { std::lock_guard lk(classMutex); return magic_base_; } + int16 get_mental_base() { std::lock_guard lk(classMutex); return mental_base_; } + int16 get_heat_base() { std::lock_guard lk(classMutex); return heat_base_; } + int16 get_poison_base() { std::lock_guard lk(classMutex); return poison_base_; } + int16 get_elemental_base() { std::lock_guard lk(classMutex); return elemental_base_; } + int16 get_noxious_base() { std::lock_guard lk(classMutex); return noxious_base_; } + int16 get_arcane_base() { std::lock_guard lk(classMutex); return arcane_base_; } + int32 get_coin_copper() { std::lock_guard lk(classMutex); return coin_copper_; } + int32 get_coin_silver() { std::lock_guard lk(classMutex); return coin_silver_; } + int32 get_coin_gold() { std::lock_guard lk(classMutex); return coin_gold_; } + int32 get_coin_plat() { std::lock_guard lk(classMutex); return coin_plat_; } + int32 get_bank_coin_copper() { std::lock_guard lk(classMutex); return bank_coin_copper_; } + int32 get_bank_coin_silver() { std::lock_guard lk(classMutex); return bank_coin_silver_; } + int32 get_bank_coin_gold() { std::lock_guard lk(classMutex); return bank_coin_gold_; } + int32 get_bank_coin_plat() { std::lock_guard lk(classMutex); return bank_coin_plat_; } + int32 get_status_points() { std::lock_guard lk(classMutex); return status_points_; } + std::string get_deity() { std::lock_guard lk(classMutex); return deity_; } + int32 get_weight() { std::lock_guard lk(classMutex); return weight_; } + int32 get_max_weight() { std::lock_guard lk(classMutex); return max_weight_; } + + + //SpellEffects* & get_spell_effects() { std::lock_guard lk(classMutex); return spell_effects_; } + //MaintainedEffects* & get_maintained_effects() { std::lock_guard lk(classMutex); return maintained_effects_; } + int8 get_tradeskill_class1() { std::lock_guard lk(classMutex); return tradeskill_class1_; } + int8 get_tradeskill_class2() { std::lock_guard lk(classMutex); return tradeskill_class2_; } + int8 get_tradeskill_class3() { std::lock_guard lk(classMutex); return tradeskill_class3_; } + + int32 get_account_age_base() { std::lock_guard lk(classMutex); return account_age_base_; } + + int8 get_account_age_bonus(int8 field) { std::lock_guard lk(classMutex); return account_age_bonus_[field]; } + int16 get_absorb() { std::lock_guard lk(classMutex); return absorb_; } + int32 get_xp() { std::lock_guard lk(classMutex); return xp_; } + int32 get_xp_needed() { std::lock_guard lk(classMutex); return xp_needed_; } + float get_xp_debt() { std::lock_guard lk(classMutex); return xp_debt_; } + int16 get_xp_yellow() { std::lock_guard lk(classMutex); return xp_yellow_; } + int16 get_xp_yellow_vitality_bar() { std::lock_guard lk(classMutex); return xp_yellow_vitality_bar_; } + int16 get_xp_blue_vitality_bar() { std::lock_guard lk(classMutex); return xp_blue_vitality_bar_; } + int16 get_xp_blue() { std::lock_guard lk(classMutex); return xp_blue_; } + int32 get_ts_xp() { std::lock_guard lk(classMutex); return ts_xp_; } + int32 get_ts_xp_needed() { std::lock_guard lk(classMutex); return ts_xp_needed_; } + int16 get_tradeskill_exp_yellow() { std::lock_guard lk(classMutex); return tradeskill_exp_yellow_; } + int16 get_tradeskill_exp_blue() { std::lock_guard lk(classMutex); return tradeskill_exp_blue_; } + int32 get_flags() { std::lock_guard lk(classMutex); return flags_; } + int32 get_flags2() { std::lock_guard lk(classMutex); return flags2_; } + float get_xp_vitality() { std::lock_guard lk(classMutex); return xp_vitality_; } + float get_tradeskill_xp_vitality() { std::lock_guard lk(classMutex); return tradeskill_xp_vitality_; } + + int16 get_mitigation_skill1() { std::lock_guard lk(classMutex); return mitigation_skill1_; } + int16 get_mitigation_skill2() { std::lock_guard lk(classMutex); return mitigation_skill2_; } + int16 get_mitigation_skill3() { std::lock_guard lk(classMutex); return mitigation_skill3_; } + + int16 get_mitigation_pve() { std::lock_guard lk(classMutex); return mitigation_pve_; } + int16 get_mitigation_pvp() { std::lock_guard lk(classMutex); return mitigation_pvp_; } + + float get_ability_modifier() { std::lock_guard lk(classMutex); return ability_modifier_; } + float get_critical_mitigation() { std::lock_guard lk(classMutex); return critical_mitigation_; } + float get_block_chance() { std::lock_guard lk(classMutex); return block_chance_; } + float get_uncontested_parry() { std::lock_guard lk(classMutex); return uncontested_parry_; } + float get_uncontested_block() { std::lock_guard lk(classMutex); return uncontested_block_; } + float get_uncontested_dodge() { std::lock_guard lk(classMutex); return uncontested_dodge_; } + float get_uncontested_riposte() { std::lock_guard lk(classMutex); return uncontested_riposte_; } + float get_crit_chance() { std::lock_guard lk(classMutex); return crit_chance_; } + float get_crit_bonus() { std::lock_guard lk(classMutex); return crit_bonus_; } + float get_potency() { std::lock_guard lk(classMutex); return potency_; } + float get_hate_mod() { std::lock_guard lk(classMutex); return hate_mod_; } + float get_reuse_speed() { std::lock_guard lk(classMutex); return reuse_speed_; } + float get_casting_speed() { std::lock_guard lk(classMutex); return casting_speed_; } + float get_recovery_speed() { std::lock_guard lk(classMutex); return recovery_speed_; } + float get_spell_reuse_speed() { std::lock_guard lk(classMutex); return spell_reuse_speed_; } + float get_spell_multi_attack() { std::lock_guard lk(classMutex); return spell_multi_attack_; } + float get_dps() { std::lock_guard lk(classMutex); return dps_; } + float get_dps_multiplier() { std::lock_guard lk(classMutex); return dps_multiplier_; } + float get_attackspeed() { std::lock_guard lk(classMutex); return attackspeed_; } + float get_haste() { std::lock_guard lk(classMutex); return haste_; } + float get_multi_attack() { std::lock_guard lk(classMutex); return multi_attack_; } + float get_flurry() { std::lock_guard lk(classMutex); return flurry_; } + float get_melee_ae() { std::lock_guard lk(classMutex); return melee_ae_; } + float get_strikethrough() { std::lock_guard lk(classMutex); return strikethrough_; } + float get_accuracy() { std::lock_guard lk(classMutex); return accuracy_; } + float get_offensivespeed() { std::lock_guard lk(classMutex); return offensivespeed_; } + float get_rain() { std::lock_guard lk(classMutex); return rain_; } + float get_wind() { std::lock_guard lk(classMutex); return wind_; } + sint8 get_alignment() { std::lock_guard lk(classMutex); return alignment_; } + int32 get_pet_id() { std::lock_guard lk(classMutex); return pet_id_; } + + std::string get_pet_name() { std::lock_guard lk(classMutex); return pet_name_; } + float get_pet_health_pct() { std::lock_guard lk(classMutex); return pet_health_pct_; } + float get_pet_power_pct() { std::lock_guard lk(classMutex); return pet_power_pct_; } + int8 get_pet_movement() { std::lock_guard lk(classMutex); return pet_movement_; } + int8 get_pet_behavior() { std::lock_guard lk(classMutex); return pet_behavior_; } + int32 get_vision() { std::lock_guard lk(classMutex); return vision_; } + int8 get_breathe_underwater() { std::lock_guard lk(classMutex); return breathe_underwater_; } + std::string get_biography() { std::lock_guard lk(classMutex); return biography_; } + float get_drunk() { std::lock_guard lk(classMutex); return drunk_; } + + sint16 get_power_regen() { std::lock_guard lk(classMutex); return power_regen_; } + sint16 get_hp_regen() { std::lock_guard lk(classMutex); return hp_regen_; } + + int8 get_power_regen_override() { std::lock_guard lk(classMutex); return power_regen_override_; } + int8 get_hp_regen_override() { std::lock_guard lk(classMutex); return hp_regen_override_; } + + int8 get_water_type() { std::lock_guard lk(classMutex); return water_type_; } + int8 get_flying_type() { std::lock_guard lk(classMutex); return flying_type_; } + + int8 get_no_interrupt() { std::lock_guard lk(classMutex); return no_interrupt_; } + + int8 get_interaction_flag() { std::lock_guard lk(classMutex); return interaction_flag_; } + int8 get_tag1() { std::lock_guard lk(classMutex); return tag1_; } + int16 get_mood() { std::lock_guard lk(classMutex); return mood_; } + + int32 get_range_last_attack_time() { std::lock_guard lk(classMutex); return range_last_attack_time_; } + int32 get_primary_last_attack_time() { std::lock_guard lk(classMutex); return primary_last_attack_time_; } + int32 get_secondary_last_attack_time() { std::lock_guard lk(classMutex); return secondary_last_attack_time_; } + + int16 get_primary_attack_delay() { std::lock_guard lk(classMutex); return primary_attack_delay_; } + int16 get_secondary_attack_delay() { std::lock_guard lk(classMutex); return secondary_attack_delay_; } + int16 get_ranged_attack_delay() { std::lock_guard lk(classMutex); return ranged_attack_delay_; } + + int8 get_primary_weapon_type() { std::lock_guard lk(classMutex); return primary_weapon_type_; } + int8 get_secondary_weapon_type() { std::lock_guard lk(classMutex); return secondary_weapon_type_; } + int8 get_ranged_weapon_type() { std::lock_guard lk(classMutex); return ranged_weapon_type_; } + + int32 get_primary_weapon_damage_low() { std::lock_guard lk(classMutex); return primary_weapon_damage_low_; } + int32 get_primary_weapon_damage_high() { std::lock_guard lk(classMutex); return primary_weapon_damage_high_; } + int32 get_secondary_weapon_damage_low() { std::lock_guard lk(classMutex); return secondary_weapon_damage_low_; } + int32 get_secondary_weapon_damage_high() { std::lock_guard lk(classMutex); return secondary_weapon_damage_high_; } + int32 get_ranged_weapon_damage_low() { std::lock_guard lk(classMutex); return ranged_weapon_damage_low_; } + int32 get_ranged_weapon_damage_high() { std::lock_guard lk(classMutex); return ranged_weapon_damage_high_; } + + int8 get_wield_type() { std::lock_guard lk(classMutex); return wield_type_; } + int8 get_attack_type() { std::lock_guard lk(classMutex); return attack_type_; } + + int16 get_primary_weapon_delay() { std::lock_guard lk(classMutex); return primary_weapon_delay_; } + int16 get_secondary_weapon_delay() { std::lock_guard lk(classMutex); return secondary_weapon_delay_; } + int16 get_ranged_weapon_delay() { std::lock_guard lk(classMutex); return ranged_weapon_delay_; } + + int8 get_override_primary_weapon() { std::lock_guard lk(classMutex); return override_primary_weapon_; } + int8 get_override_secondary_weapon() { std::lock_guard lk(classMutex); return override_secondary_weapon_; } + int8 get_override_ranged_weapon() { std::lock_guard lk(classMutex); return override_ranged_weapon_; } + + int8 get_friendly_target_npc() { std::lock_guard lk(classMutex); return friendly_target_npc_; } + int32 get_last_claim_time() { std::lock_guard lk(classMutex); return last_claim_time_; } + + int8 get_engaged_encounter() { std::lock_guard lk(classMutex); return engaged_encounter_; } + + int8 get_first_world_login() { std::lock_guard lk(classMutex); return first_world_login_; } + + int8 get_reload_player_spells() { std::lock_guard lk(classMutex); return reload_player_spells_; } + + int8 get_group_loot_method() { std::lock_guard lk(classMutex); return group_loot_method_; } + int8 get_group_loot_items_rarity() { std::lock_guard lk(classMutex); return group_loot_items_rarity_; } + int8 get_group_auto_split() { std::lock_guard lk(classMutex); return group_auto_split_; } + int8 get_group_default_yell() { std::lock_guard lk(classMutex); return group_default_yell_; } + int8 get_group_autolock() { std::lock_guard lk(classMutex); return group_autolock_; } + int8 get_group_lock_method() { std::lock_guard lk(classMutex); return group_lock_method_; } + int8 get_group_solo_autolock() { std::lock_guard lk(classMutex); return group_solo_autolock_; } + int8 get_group_auto_loot_method() { std::lock_guard lk(classMutex); return group_auto_loot_method_; } + int8 get_assist_auto_attack() { std::lock_guard lk(classMutex); return assist_auto_attack_; } + + std::string get_action_state() { std::lock_guard lk(classMutex); return action_state_; } + + std::string get_combat_action_state() { std::lock_guard lk(classMutex); return combat_action_state_; } + + float get_max_spell_reduction() { std::lock_guard lk(classMutex); return max_spell_reduction_; } + int8 get_max_spell_reduction_override() { std::lock_guard lk(classMutex); return max_spell_reduction_override_; } + + void set_name(std::string value) { std::lock_guard lk(classMutex); name_ = value; } + + void set_deity(std::string value) { std::lock_guard lk(classMutex); deity_ = value; } + + void set_class1(int8 value) { std::lock_guard lk(classMutex); class1_ = value; } + void set_class2(int8 value) { std::lock_guard lk(classMutex); class2_ = value; } + void set_class3(int8 value) { std::lock_guard lk(classMutex); class3_ = value; } + + void set_race(int8 value) { std::lock_guard lk(classMutex); race_ = value; } + void set_gender(int8 value) { std::lock_guard lk(classMutex); gender_ = value; } + void set_level(int16 value) { std::lock_guard lk(classMutex); level_ = value; } + void set_max_level(int16 value) { std::lock_guard lk(classMutex); max_level_ = value; } + void set_effective_level(int16 value) { std::lock_guard lk(classMutex); effective_level_ = value; } + + void set_cur_concentration(int8 value) { std::lock_guard lk(classMutex); cur_concentration_ = value; } + void set_max_concentration(int8 value) { std::lock_guard lk(classMutex); max_concentration_ = value; } + void set_max_concentration_base(int8 value) { std::lock_guard lk(classMutex); max_concentration_base_ = value; } + + void add_cur_concentration(int8 value) { std::lock_guard lk(classMutex); cur_concentration_ += value; } + void add_max_concentration(int8 value) { std::lock_guard lk(classMutex); max_concentration_ += value; } + + void set_cur_attack(int16 value) { std::lock_guard lk(classMutex); cur_attack_ = value; } + void set_attack_base(int16 value) { std::lock_guard lk(classMutex); attack_base_ = value; } + void set_cur_mitigation(int16 value) { std::lock_guard lk(classMutex); cur_mitigation_ = value; } + void set_max_mitigation(int16 value) { std::lock_guard lk(classMutex); max_mitigation_ = value; } + void set_mitigation_base(int16 value) { std::lock_guard lk(classMutex); mitigation_base_ = value; } + void add_mitigation_base(int16 value) { std::lock_guard lk(classMutex); mitigation_base_ += value; } + + void set_avoidance_display(int16 value) { std::lock_guard lk(classMutex); avoidance_display_ = value; } + void set_cur_avoidance(float value) { std::lock_guard lk(classMutex); cur_avoidance_ = value; } + void set_base_avoidance_pct(int16 value) { std::lock_guard lk(classMutex); base_avoidance_pct_ = value; } + void set_avoidance_base(int16 value) { std::lock_guard lk(classMutex); avoidance_base_ = value; } + void set_max_avoidance(int16 value) { std::lock_guard lk(classMutex); max_avoidance_ = value; } + void set_parry(float value) { std::lock_guard lk(classMutex); parry_ = value; } + void set_parry_base(float value) { std::lock_guard lk(classMutex); parry_base_ = value; } + void set_deflection(int16 value) { std::lock_guard lk(classMutex); deflection_ = value; } + void set_deflection_base(float value) { std::lock_guard lk(classMutex); deflection_base_ = value; } + void set_block(float value) { std::lock_guard lk(classMutex); block_ = value; } + void set_block_base(int16 value) { std::lock_guard lk(classMutex); block_base_ = value; } + + void set_str(float value) { std::lock_guard lk(classMutex); str_ = value; } + void set_sta(float value) { std::lock_guard lk(classMutex); sta_ = value; } + void set_agi(float value) { std::lock_guard lk(classMutex); agi_ = value; } + void set_wis(float value) { std::lock_guard lk(classMutex); wis_ = value; } + void set_intel(float value) { std::lock_guard lk(classMutex); intel_ = value; } + + void add_str(float value) { std::lock_guard lk(classMutex); if(str_ + value < 0.0f) str_ = 0.0f; else str_ += value; } + void add_sta(float value) { std::lock_guard lk(classMutex); if(sta_ + value < 0.0f) sta_ = 0.0f; else sta_ += value; } + void add_agi(float value) { std::lock_guard lk(classMutex); if(agi_ + value < 0.0f) agi_ = 0.0f; else agi_ += value; } + void add_wis(float value) { std::lock_guard lk(classMutex); if(wis_ + value < 0.0f) wis_ = 0.0f; else wis_ += value; } + void add_intel(float value) { std::lock_guard lk(classMutex); if(intel_ + value < 0.0f) intel_ = 0.0f; else intel_ += value; } + + void set_str_base(float value) { std::lock_guard lk(classMutex); str_base_ = value; } + void set_sta_base(float value) { std::lock_guard lk(classMutex); sta_base_ = value; } + void set_agi_base(float value) { std::lock_guard lk(classMutex); agi_base_ = value; } + void set_wis_base(float value) { std::lock_guard lk(classMutex); wis_base_ = value; } + void set_intel_base(float value) { std::lock_guard lk(classMutex); intel_base_ = value; } + + void set_heat(int16 value) { std::lock_guard lk(classMutex); heat_ = value; } + void set_cold(int16 value) { std::lock_guard lk(classMutex); cold_ = value; } + void set_magic(int16 value) { std::lock_guard lk(classMutex); magic_ = value; } + void set_mental(int16 value) { std::lock_guard lk(classMutex); mental_ = value; } + void set_divine(int16 value) { std::lock_guard lk(classMutex); divine_ = value; } + void set_disease(int16 value) { std::lock_guard lk(classMutex); disease_ = value; } + void set_poison(int16 value) { std::lock_guard lk(classMutex); poison_ = value; } + + void add_heat(sint16 value) { std::lock_guard lk(classMutex); if((sint32)heat_ + value < 0) heat_ = 0; else heat_ += value; } + void add_cold(sint16 value) { std::lock_guard lk(classMutex); if((sint32)cold_ + value < 0) cold_ = 0; else cold_ += value; } + void add_magic(sint16 value) { std::lock_guard lk(classMutex); if((sint32)magic_ + value < 0) magic_ = 0; else magic_ += value; } + void add_mental(sint16 value) { std::lock_guard lk(classMutex); if((sint32)mental_ + value < 0) mental_ = 0; else mental_ += value; } + void add_divine(sint16 value) { std::lock_guard lk(classMutex); if((sint32)divine_ + value < 0) divine_ = 0; else divine_ += value; } + void add_disease(sint16 value) { std::lock_guard lk(classMutex); if((sint32)disease_ + value < 0) disease_ = 0; else disease_ += value; } + void add_poison(sint16 value) { std::lock_guard lk(classMutex); if((sint32)poison_ + value < 0) poison_ = 0; else poison_ += value; } + + void set_disease_base(int16 value) { std::lock_guard lk(classMutex); disease_base_ = value; } + void set_cold_base(int16 value) { std::lock_guard lk(classMutex); cold_base_ = value; } + void set_divine_base(int16 value) { std::lock_guard lk(classMutex); divine_base_ = value; } + void set_magic_base(int16 value) { std::lock_guard lk(classMutex); magic_base_ = value; } + void set_mental_base(int16 value) { std::lock_guard lk(classMutex); mental_base_ = value; } + void set_heat_base(int16 value) { std::lock_guard lk(classMutex); heat_base_ = value; } + void set_poison_base(int16 value) { std::lock_guard lk(classMutex); poison_base_ = value; } + void set_elemental_base(int16 value) { std::lock_guard lk(classMutex); elemental_base_ = value; } + void set_noxious_base(int16 value) { std::lock_guard lk(classMutex); noxious_base_ = value; } + void set_arcane_base(int16 value) { std::lock_guard lk(classMutex); arcane_base_ = value; } + + void set_tradeskill_level(int16 value) { std::lock_guard lk(classMutex); tradeskill_level_ = value; } + void set_tradeskill_max_level(int16 value) { std::lock_guard lk(classMutex); tradeskill_max_level_ = value; } + + void set_tradeskill_class1(int8 value) { std::lock_guard lk(classMutex); tradeskill_class1_ = value; } + void set_tradeskill_class2(int8 value) { std::lock_guard lk(classMutex); tradeskill_class2_ = value; } + void set_tradeskill_class3(int8 value) { std::lock_guard lk(classMutex); tradeskill_class3_ = value; } + + void set_account_age_base(int32 value) { std::lock_guard lk(classMutex); account_age_base_ = value; } + + void set_xp_vitality(float value) { std::lock_guard lk(classMutex); xp_vitality_ = value; } + + void add_xp_vitality(float value) { std::lock_guard lk(classMutex); xp_vitality_ += value; } + + void set_tradeskill_xp_vitality(float value) { std::lock_guard lk(classMutex); tradeskill_xp_vitality_ = value; } + + void set_absorb(int16 value) { std::lock_guard lk(classMutex); absorb_ = value; } + + void set_xp(int32 value) { std::lock_guard lk(classMutex); xp_ = value; } + void set_xp_needed(int32 value) { std::lock_guard lk(classMutex); xp_needed_ = value; } + + void set_xp_debt(float value) { std::lock_guard lk(classMutex); if(std::isnan(value)) value = 0.0f; xp_debt_ = value; } + + void set_xp_yellow(int16 value) { std::lock_guard lk(classMutex); xp_yellow_ = value; } + void set_xp_blue(int16 value) { std::lock_guard lk(classMutex); xp_blue_ = value; } + + void set_xp_yellow_vitality_bar(int16 value) { std::lock_guard lk(classMutex); xp_yellow_vitality_bar_ = value; } + void set_xp_blue_vitality_bar(int16 value) { std::lock_guard lk(classMutex); xp_blue_vitality_bar_ = value; } + + void set_ts_xp(int32 value) { std::lock_guard lk(classMutex); ts_xp_ = value; } + void set_ts_xp_needed(int32 value) { std::lock_guard lk(classMutex); ts_xp_needed_ = value; } + + void set_tradeskill_exp_yellow(int16 value) { std::lock_guard lk(classMutex); tradeskill_exp_yellow_ = value; } + void set_tradeskill_exp_blue(int16 value) { std::lock_guard lk(classMutex); tradeskill_exp_blue_ = value; } + + void set_flags(int32 value) { std::lock_guard lk(classMutex); flags_ = value; } + void set_flags2(int32 value) { std::lock_guard lk(classMutex); flags2_ = value; } + + void set_coin_plat(int32 value) { std::lock_guard lk(classMutex); coin_plat_ = value; } + void set_coin_gold(int32 value) { std::lock_guard lk(classMutex); coin_gold_ = value; } + void set_coin_silver(int32 value) { std::lock_guard lk(classMutex); coin_silver_ = value; } + void set_coin_copper(int32 value) { std::lock_guard lk(classMutex); coin_copper_ = value; } + + void add_coin_plat(int32 value) { std::lock_guard lk(classMutex); if((sint64)coin_plat_ + value < 0) coin_plat_ = 0; else coin_plat_ += value; } + void add_coin_gold(int32 value) { std::lock_guard lk(classMutex); if((sint64)coin_gold_ + value < 0) coin_gold_ = 0; else coin_gold_ += value; } + void add_coin_silver(int32 value) { std::lock_guard lk(classMutex); if((sint64)coin_silver_ + value < 0) coin_silver_ = 0; else coin_silver_ += value; } + void add_coin_copper(int32 value) { std::lock_guard lk(classMutex); if((sint64)coin_copper_ + value < 0) coin_copper_ = 0; else coin_copper_ += value; } + + void set_bank_coin_plat(int32 value) { std::lock_guard lk(classMutex); bank_coin_plat_ = value; } + void set_bank_coin_gold(int32 value) { std::lock_guard lk(classMutex); bank_coin_gold_ = value; } + void set_bank_coin_silver(int32 value) { std::lock_guard lk(classMutex); bank_coin_silver_ = value; } + void set_bank_coin_copper(int32 value) { std::lock_guard lk(classMutex); bank_coin_copper_ = value; } + + void add_bank_coin_plat(int32 value) { std::lock_guard lk(classMutex); if((sint64)bank_coin_plat_ + value < 0) bank_coin_plat_ = 0; else bank_coin_plat_ += value; } + void add_bank_coin_gold(int32 value) { std::lock_guard lk(classMutex); if((sint64)bank_coin_gold_ + value < 0) bank_coin_gold_ = 0; else bank_coin_gold_ += value; } + void add_bank_coin_silver(int32 value) { std::lock_guard lk(classMutex); if((sint64)bank_coin_silver_ + value < 0) bank_coin_silver_ = 0; else bank_coin_silver_ += value; } + void add_bank_coin_copper(int32 value) { std::lock_guard lk(classMutex); if((sint64)bank_coin_copper_ + value < 0) bank_coin_copper_ = 0; else bank_coin_copper_ += value; } + + void set_status_points(int32 value) { std::lock_guard lk(classMutex); status_points_ = value; } + void add_status_points(int32 value) { std::lock_guard lk(classMutex); if((sint64)status_points_ + value < 0) status_points_ = 0; else status_points_ += value; } + bool subtract_status_points(int32 value) { std::lock_guard lk(classMutex); if(value > status_points_) return false; status_points_ -= value; return true; } + + void set_mitigation_skill1(int16 value) { std::lock_guard lk(classMutex); mitigation_skill1_ = value; } + void set_mitigation_skill2(int16 value) { std::lock_guard lk(classMutex); mitigation_skill2_ = value; } + void set_mitigation_skill3(int16 value) { std::lock_guard lk(classMutex); mitigation_skill3_ = value; } + + void set_mitigation_pve(int16 value) { std::lock_guard lk(classMutex); mitigation_pve_ = value; } + void set_mitigation_pvp(int16 value) { std::lock_guard lk(classMutex); mitigation_pvp_ = value; } + + void add_mitigation_skill1(int16 value) { std::lock_guard lk(classMutex); if((sint32)mitigation_skill1_ + value < 0) mitigation_skill1_ = 0; else mitigation_skill1_ += value; } + void add_mitigation_skill2(int16 value) { std::lock_guard lk(classMutex); if((sint32)mitigation_skill2_ + value < 0) mitigation_skill2_ = 0; else mitigation_skill2_ += value; } + void add_mitigation_skill3(int16 value) { std::lock_guard lk(classMutex); if((sint32)mitigation_skill3_ + value < 0) mitigation_skill3_ = 0; else mitigation_skill3_ += value; } + + void set_ability_modifier(float value) { std::lock_guard lk(classMutex); ability_modifier_ = value; } + + void add_ability_modifier(float value) { std::lock_guard lk(classMutex); if(ability_modifier_ + value < 0.0f) ability_modifier_ = 0.0f; else ability_modifier_ += value; } + + void set_critical_mitigation(float value) { std::lock_guard lk(classMutex); critical_mitigation_ = value; } + + void add_critical_mitigation(float value) { std::lock_guard lk(classMutex); if(critical_mitigation_ + value < 0.0f) critical_mitigation_ = 0.0f; else critical_mitigation_ += value; } + + void set_block_chance(float value) { std::lock_guard lk(classMutex); block_chance_ = value; } + void set_uncontested_parry(float value) { std::lock_guard lk(classMutex); uncontested_parry_ = value; } + void set_uncontested_block(float value) { std::lock_guard lk(classMutex); uncontested_block_ = value; } + void set_uncontested_dodge(float value) { std::lock_guard lk(classMutex); uncontested_dodge_ = value; } + void set_uncontested_riposte(float value) { std::lock_guard lk(classMutex); uncontested_riposte_ = value; } + void set_crit_chance(float value) { std::lock_guard lk(classMutex); crit_chance_ = value; } + void set_crit_bonus(float value) { std::lock_guard lk(classMutex); crit_bonus_ = value; } + void set_potency(float value) { std::lock_guard lk(classMutex); potency_ = value; } + void set_hate_mod(float value) { std::lock_guard lk(classMutex); hate_mod_ = value; } + void set_reuse_speed(float value) { std::lock_guard lk(classMutex); reuse_speed_ = value; } + void set_casting_speed(float value) { std::lock_guard lk(classMutex); casting_speed_ = value; } + void set_recovery_speed(float value) { std::lock_guard lk(classMutex); recovery_speed_ = value; } + void set_spell_reuse_speed(float value) { std::lock_guard lk(classMutex); spell_reuse_speed_ = value; } + void set_spell_multi_attack(float value) { std::lock_guard lk(classMutex); spell_multi_attack_ = value; } + void set_dps(float value) { std::lock_guard lk(classMutex); dps_ = value; } + void set_dps_multiplier(float value) { std::lock_guard lk(classMutex); dps_multiplier_ = value; } + void set_attackspeed(float value) { std::lock_guard lk(classMutex); attackspeed_ = value; } + void set_haste(float value) { std::lock_guard lk(classMutex); haste_ = value; } + void set_multi_attack(float value) { std::lock_guard lk(classMutex); multi_attack_ = value; } + void set_flurry(float value) { std::lock_guard lk(classMutex); flurry_ = value; } + void set_melee_ae(float value) { std::lock_guard lk(classMutex); melee_ae_ = value; } + void set_strikethrough(float value) { std::lock_guard lk(classMutex); strikethrough_ = value; } + void set_accuracy(float value) { std::lock_guard lk(classMutex); accuracy_ = value; } + void set_offensivespeed(float value) { std::lock_guard lk(classMutex); offensivespeed_ = value; } + + // crash client if float values above 1.0 are sent + void set_rain(float value) { std::lock_guard lk(classMutex); if(value > 1.0f) value = 1.0f; else if(value < 0.0f) value = 0.0f; rain_ = value; } + void set_wind(float value) { std::lock_guard lk(classMutex); if(value > 1.0f) value = 1.0f; else if(value < 0.0f) value = 0.0f; wind_ = value; } + + void add_block_chance(float value) { std::lock_guard lk(classMutex); if(block_chance_ + value < 0.0f) block_chance_ = 0.0f; else block_chance_ += value; } + void add_uncontested_parry(float value) { std::lock_guard lk(classMutex); if(uncontested_parry_ + value < 0.0f) uncontested_parry_ = 0.0f; else uncontested_parry_ += value; } + void add_uncontested_block(float value) { std::lock_guard lk(classMutex); if(uncontested_block_ + value < 0.0f) uncontested_block_ = 0.0f; else uncontested_block_ += value; } + void add_uncontested_dodge(float value) { std::lock_guard lk(classMutex); if(uncontested_dodge_ + value < 0.0f) uncontested_dodge_ = 0.0f; else uncontested_dodge_ += value; } + void add_uncontested_riposte(float value) { std::lock_guard lk(classMutex); if(uncontested_riposte_ + value < 0.0f) uncontested_riposte_ = 0.0f; else uncontested_riposte_ += value; } + void add_crit_chance(float value) { std::lock_guard lk(classMutex); if(crit_chance_ + value < 0.0f) crit_chance_ = 0.0f; else crit_chance_ += value; } + void add_crit_bonus(float value) { std::lock_guard lk(classMutex); if(crit_bonus_ + value < 0.0f) crit_bonus_ = 0.0f; else crit_bonus_ += value; } + void add_potency(float value) { std::lock_guard lk(classMutex); if(potency_ + value < 0.0f) potency_ = 0.0f; else potency_ += value; } + void add_hate_mod(float value) { std::lock_guard lk(classMutex); if(hate_mod_ + value < 0.0f) hate_mod_ = 0.0f; else hate_mod_ += value; } + void add_reuse_speed(float value) { std::lock_guard lk(classMutex); reuse_speed_ += value; } + void add_casting_speed(float value) { std::lock_guard lk(classMutex); casting_speed_ += value; } + void add_recovery_speed(float value) { std::lock_guard lk(classMutex); recovery_speed_ += value; } + void add_spell_reuse_speed(float value) { std::lock_guard lk(classMutex); spell_reuse_speed_ += value; } + void add_spell_multi_attack(float value) { std::lock_guard lk(classMutex); spell_multi_attack_ += value; } + void add_dps(float value) { std::lock_guard lk(classMutex); if(dps_ + value < 0.0f) dps_ = 0.0f; else dps_ += value; } + void add_dps_multiplier(float value) { std::lock_guard lk(classMutex); if(dps_multiplier_ + value < 0.0f) dps_multiplier_ = 0.0f; else dps_multiplier_ += value; } + void add_attackspeed(float value) { std::lock_guard lk(classMutex); if(attackspeed_ + value < 0.0f) attackspeed_ = 0.0f; else attackspeed_ += value; } + void add_haste(float value) { std::lock_guard lk(classMutex); if(haste_ + value < 0.0f) haste_ = 0.0f; else haste_ += value; } + void add_multi_attack(float value) { std::lock_guard lk(classMutex); if(multi_attack_ + value < 0.0f) multi_attack_ = 0.0f; else multi_attack_ += value; } + void add_flurry(float value) { std::lock_guard lk(classMutex); if(flurry_ + value < 0.0f) flurry_ = 0.0f; else flurry_ += value; } + void add_melee_ae(float value) { std::lock_guard lk(classMutex); if(melee_ae_ + value < 0.0f) melee_ae_ = 0.0f; else melee_ae_ += value; } + void add_strikethrough(float value) { std::lock_guard lk(classMutex); if(strikethrough_ + value < 0.0f) strikethrough_ = 0.0f; else strikethrough_ += value; } + void add_accuracy(float value) { std::lock_guard lk(classMutex); if(accuracy_ + value < 0.0f) accuracy_ = 0.0f; else accuracy_ += value; } + void add_offensivespeed(float value) { std::lock_guard lk(classMutex); if(offensivespeed_ + value < 0.0f) offensivespeed_ = 0.0f; else offensivespeed_ += value; } + void add_rain(float value) { std::lock_guard lk(classMutex); if(rain_ + value < 0.0f) rain_ = 0.0f; else rain_ += value; } + void add_wind(float value) { std::lock_guard lk(classMutex); if(wind_ + value < 0.0f) wind_ = 0.0f; else wind_ += value; } + + void set_alignment(int8 value) { std::lock_guard lk(classMutex); alignment_ = value; } + + void set_pet_id(int32 value) { std::lock_guard lk(classMutex); pet_id_ = value; } + void set_pet_name(std::string value) { std::lock_guard lk(classMutex); pet_name_ = value; } + + void set_pet_movement(int8 value) { std::lock_guard lk(classMutex); pet_movement_ = value; } + void set_pet_behavior(int8 value) { std::lock_guard lk(classMutex); pet_behavior_ = value; } + void set_pet_health_pct(float value) { std::lock_guard lk(classMutex); pet_health_pct_ = value; } + void set_pet_power_pct(float value) { std::lock_guard lk(classMutex); pet_power_pct_ = value; } + + void set_weight(int32 value) { std::lock_guard lk(classMutex); weight_ = value; } + void set_max_weight(int32 value) { std::lock_guard lk(classMutex); max_weight_ = value; } + + void set_vision(int32 value) { std::lock_guard lk(classMutex); vision_ = value; } + void set_breathe_underwater(int8 value) { std::lock_guard lk(classMutex); breathe_underwater_ = value; } + void set_drunk(float value) { std::lock_guard lk(classMutex); drunk_ = value; } + + void set_biography(std::string value) { std::lock_guard lk(classMutex); biography_ = value; } + + void set_power_regen(sint16 value) { std::lock_guard lk(classMutex); power_regen_ = value; } + void set_hp_regen(sint16 value) { std::lock_guard lk(classMutex); hp_regen_ = value; } + + void set_power_regen_override(int8 value) { std::lock_guard lk(classMutex); power_regen_override_ = value; } + void set_hp_regen_override(int8 value) { std::lock_guard lk(classMutex); hp_regen_override_ = value; } + + void set_water_type(int8 value) { std::lock_guard lk(classMutex); water_type_ = value; } + void set_flying_type(int8 value) { std::lock_guard lk(classMutex); flying_type_ = value; } + + void set_no_interrupt(int8 value) { std::lock_guard lk(classMutex); no_interrupt_ = value; } + + void set_interaction_flag(int8 value) { std::lock_guard lk(classMutex); interaction_flag_ = value; } + void set_tag1(int8 value) { std::lock_guard lk(classMutex); tag1_ = value; } + void set_mood(int16 value) { std::lock_guard lk(classMutex); mood_ = value; } + + void set_range_last_attack_time(int32 value) { std::lock_guard lk(classMutex); range_last_attack_time_ = value; } + void set_primary_last_attack_time(int32 value) { std::lock_guard lk(classMutex); primary_last_attack_time_ = value; } + void set_secondary_last_attack_time(int32 value) { std::lock_guard lk(classMutex); secondary_last_attack_time_ = value; } + + void set_primary_attack_delay(int16 value) { std::lock_guard lk(classMutex); primary_attack_delay_ = value; } + void set_secondary_attack_delay(int16 value) { std::lock_guard lk(classMutex); secondary_attack_delay_ = value; } + void set_ranged_attack_delay(int16 value) { std::lock_guard lk(classMutex); ranged_attack_delay_ = value; } + + void set_primary_weapon_type(int8 value) { std::lock_guard lk(classMutex); primary_weapon_type_ = value; } + void set_secondary_weapon_type(int8 value) { std::lock_guard lk(classMutex); secondary_weapon_type_ = value; } + void set_ranged_weapon_type(int8 value) { std::lock_guard lk(classMutex); ranged_weapon_type_ = value; } + + void set_primary_weapon_damage_low(int32 value) { std::lock_guard lk(classMutex); primary_weapon_damage_low_ = value; } + void set_primary_weapon_damage_high(int32 value) { std::lock_guard lk(classMutex); primary_weapon_damage_high_ = value; } + void set_secondary_weapon_damage_low(int32 value) { std::lock_guard lk(classMutex); secondary_weapon_damage_low_ = value; } + void set_secondary_weapon_damage_high(int32 value) { std::lock_guard lk(classMutex); secondary_weapon_damage_high_ = value; } + void set_ranged_weapon_damage_low(int32 value) { std::lock_guard lk(classMutex); ranged_weapon_damage_low_ = value; } + void set_ranged_weapon_damage_high(int32 value) { std::lock_guard lk(classMutex); ranged_weapon_damage_high_ = value; } + + void set_wield_type(int8 value) { std::lock_guard lk(classMutex); wield_type_ = value; } + void set_attack_type(int8 value) { std::lock_guard lk(classMutex); attack_type_ = value; } + + void set_primary_weapon_delay(int16 value) { std::lock_guard lk(classMutex); primary_weapon_delay_ = value; } + void set_secondary_weapon_delay(int16 value) { std::lock_guard lk(classMutex); secondary_weapon_delay_ = value; } + void set_ranged_weapon_delay(int16 value) { std::lock_guard lk(classMutex); ranged_weapon_delay_ = value; } + + void set_override_primary_weapon(int8 value) { std::lock_guard lk(classMutex); override_secondary_weapon_ = value; } + void set_override_secondary_weapon(int8 value) { std::lock_guard lk(classMutex); override_secondary_weapon_ = value; } + void set_override_ranged_weapon(int8 value) { std::lock_guard lk(classMutex); override_ranged_weapon_ = value; } + void set_friendly_target_npc(int8 value) { std::lock_guard lk(classMutex); friendly_target_npc_ = value; } + void set_last_claim_time(int32 value) { std::lock_guard lk(classMutex); last_claim_time_ = value; } + + void set_engaged_encounter(int8 value) { std::lock_guard lk(classMutex); engaged_encounter_ = value; } + + void set_first_world_login(int8 value) { std::lock_guard lk(classMutex); first_world_login_ = value; } + + void set_reload_player_spells(int8 value) { std::lock_guard lk(classMutex); reload_player_spells_ = value; } + + void set_group_loot_method(int8 value) { std::lock_guard lk(classMutex); group_loot_method_ = value; } + void set_group_loot_items_rarity(int8 value) { std::lock_guard lk(classMutex); group_loot_items_rarity_ = value; } + void set_group_auto_split(int8 value) { std::lock_guard lk(classMutex); group_auto_split_ = value; } + void set_group_default_yell(int8 value) { std::lock_guard lk(classMutex); group_default_yell_ = value; } + void set_group_autolock(int8 value) { std::lock_guard lk(classMutex); group_autolock_ = value; } + void set_group_lock_method(int8 value) { std::lock_guard lk(classMutex); group_lock_method_ = value; } + void set_group_solo_autolock(int8 value) { std::lock_guard lk(classMutex); group_solo_autolock_ = value; } + void set_group_auto_loot_method(int8 value) { std::lock_guard lk(classMutex); group_auto_loot_method_ = value; } + + void set_assist_auto_attack(int8 value) { std::lock_guard lk(classMutex); assist_auto_attack_ = value; } + + void set_action_state(std::string value) { std::lock_guard lk(classMutex); action_state_ = value; } + + void set_combat_action_state(std::string value) { std::lock_guard lk(classMutex); combat_action_state_ = value; } + + void set_max_spell_reduction(float value) { std::lock_guard lk(classMutex); max_spell_reduction_ = value; } + + void set_max_spell_reduction_override(int8 value) { std::lock_guard lk(classMutex); max_spell_reduction_override_ = value; } + + void ResetEffects(Spawn* spawn) + { + for(int i=0;i<45;i++){ + if(i<30){ + maintained_effects[i].spell_id = 0xFFFFFFFF; + if (spawn->IsPlayer()) + maintained_effects[i].icon = 0xFFFF; + + maintained_effects[i].spell = nullptr; + } + spell_effects[i].spell_id = 0xFFFFFFFF; + spell_effects[i].spell = nullptr; + } + } + + // maintained via their own mutex + SpellEffects spell_effects[45]; + MaintainedEffects maintained_effects[30]; +private: + std::string name_; + int8 class1_; + int8 class2_; + int8 class3_; + int8 race_; + int8 gender_; + int16 level_; + int16 max_level_; + int16 effective_level_; + int16 tradeskill_level_; + int16 tradeskill_max_level_; + + int8 cur_concentration_; + int8 max_concentration_; + int8 max_concentration_base_; + int16 cur_attack_; + int16 attack_base_; + int16 cur_mitigation_; + int16 max_mitigation_; + int16 mitigation_base_; + int16 avoidance_display_; + float cur_avoidance_; + int16 base_avoidance_pct_; + int16 avoidance_base_; + int16 max_avoidance_; + float parry_; + float parry_base_; + float deflection_; + int16 deflection_base_; + float block_; + int16 block_base_; + float riposte_; + float riposte_base_; + float str_; //int16 + float sta_; //int16 + float agi_;//int16 + float wis_;//int16 + float intel_;//int16 + float str_base_;//int16 + float sta_base_;//int16 + float agi_base_;//int16 + float wis_base_;//int16 + float intel_base_;//int16 + int16 heat_; + int16 cold_; + int16 magic_; + int16 mental_; + int16 divine_; + int16 disease_; + int16 poison_; + int16 disease_base_; + int16 cold_base_; + int16 divine_base_; + int16 magic_base_; + int16 mental_base_; + int16 heat_base_; + int16 poison_base_; + int16 elemental_base_; + int16 noxious_base_; + int16 arcane_base_; + int32 coin_copper_; + int32 coin_silver_; + int32 coin_gold_; + int32 coin_plat_; + int32 bank_coin_copper_; + int32 bank_coin_silver_; + int32 bank_coin_gold_; + int32 bank_coin_plat_; + + int32 status_points_; + std::string deity_; + int32 weight_; + int32 max_weight_; + int8 tradeskill_class1_; + int8 tradeskill_class2_; + int8 tradeskill_class3_; + int32 account_age_base_; + int8 account_age_bonus_[19]; + int16 absorb_; + int32 xp_; + int32 xp_needed_; + float xp_debt_; + int16 xp_yellow_; + int16 xp_yellow_vitality_bar_; + int16 xp_blue_vitality_bar_; + int16 xp_blue_; + int32 ts_xp_; + int32 ts_xp_needed_; + int16 tradeskill_exp_yellow_; + int16 tradeskill_exp_blue_; + int32 flags_; + int32 flags2_; + float xp_vitality_; + float tradeskill_xp_vitality_; + int16 mitigation_skill1_; + int16 mitigation_skill2_; + int16 mitigation_skill3_; + int16 mitigation_pve_; + int16 mitigation_pvp_; + float ability_modifier_; + float critical_mitigation_; + float block_chance_; + float uncontested_parry_; + float uncontested_block_; + float uncontested_dodge_; + float uncontested_riposte_; + + float crit_chance_; + float crit_bonus_; + float potency_; + float hate_mod_; + float reuse_speed_; + float casting_speed_; + float recovery_speed_; + float spell_reuse_speed_; + float spell_multi_attack_; + float dps_; + float dps_multiplier_; + float attackspeed_; + float haste_; + float multi_attack_; + float flurry_; + float melee_ae_; + float strikethrough_; + float accuracy_; + float offensivespeed_; + float rain_; + float wind_; + sint8 alignment_; + + int32 pet_id_; + std::string pet_name_; + float pet_health_pct_; + float pet_power_pct_; + int8 pet_movement_; + int8 pet_behavior_; + + int32 vision_; + int8 breathe_underwater_; + std::string biography_; + float drunk_; + + sint16 power_regen_; + sint16 hp_regen_; + + int8 power_regen_override_; + int8 hp_regen_override_; + + int8 water_type_; + int8 flying_type_; + + int8 no_interrupt_; + + int8 interaction_flag_; + int8 tag1_; + int16 mood_; + + int32 range_last_attack_time_; + int32 primary_last_attack_time_; + int32 secondary_last_attack_time_; + int16 primary_attack_delay_; + int16 secondary_attack_delay_; + int16 ranged_attack_delay_; + int8 primary_weapon_type_; + int8 secondary_weapon_type_; + int8 ranged_weapon_type_; + int32 primary_weapon_damage_low_; + int32 primary_weapon_damage_high_; + int32 secondary_weapon_damage_low_; + int32 secondary_weapon_damage_high_; + int32 ranged_weapon_damage_low_; + int32 ranged_weapon_damage_high_; + int8 wield_type_; + int8 attack_type_; + int16 primary_weapon_delay_; + int16 secondary_weapon_delay_; + int16 ranged_weapon_delay_; + + int8 override_primary_weapon_; + int8 override_secondary_weapon_; + int8 override_ranged_weapon_; + + int8 friendly_target_npc_; + int32 last_claim_time_; + + int8 engaged_encounter_; + + int8 first_world_login_; + int8 reload_player_spells_; + + int8 group_loot_method_; + int8 group_loot_items_rarity_; + int8 group_auto_split_; + int8 group_default_yell_; + int8 group_autolock_; + int8 group_lock_method_; + int8 group_solo_autolock_; + int8 group_auto_loot_method_; + + int8 assist_auto_attack_; + + std::string action_state_; + std::string combat_action_state_; + + float max_spell_reduction_; + int8 max_spell_reduction_override_; + + // when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock + std::mutex classMutex; +}; + +struct WardInfo { + LuaSpell* Spell; + int32 BaseDamage; + int32 DamageLeft; + int8 WardType; + int8 DamageType; + bool keepWard; + int32 DamageAbsorptionPercentage; + int32 DamageAbsorptionMaxHealthPercent; + int32 RedirectDamagePercent; + + int32 LastRedirectDamage; + int32 LastAbsorbedDamage; + + int32 HitCount; + int32 MaxHitCount; + + bool AbsorbAllDamage; // damage is always absorbed, usually spells based on hits, when we pass damage in AddWard as 0 this will be set to true + + bool RoundTriggered; +}; + +#define WARD_TYPE_ALL 0 +#define WARD_TYPE_PHYSICAL 1 +#define WARD_TYPE_MAGICAL 2 + +struct Proc { + LuaSpell* spell; + Item* item; + float chance; + int32 spellid; + int8 health_ratio; + bool below_health; + bool target_health; + int8 damage_type; + bool extended_version; +}; + +#define PROC_TYPE_OFFENSIVE 1 +#define PROC_TYPE_DEFENSIVE 2 +#define PROC_TYPE_PHYSICAL_OFFENSIVE 3 +#define PROC_TYPE_PHYSICAL_DEFENSIVE 4 +#define PROC_TYPE_MAGICAL_OFFENSIVE 5 +#define PROC_TYPE_MAGICAL_DEFENSIVE 6 +#define PROC_TYPE_BLOCK 7 +#define PROC_TYPE_PARRY 8 +#define PROC_TYPE_RIPOSTE 9 +#define PROC_TYPE_EVADE 10 +#define PROC_TYPE_HEALING 11 +#define PROC_TYPE_BENEFICIAL 12 +#define PROC_TYPE_DEATH 13 +#define PROC_TYPE_KILL 14 +#define PROC_TYPE_DAMAGED 15 +#define PROC_TYPE_DAMAGED_MELEE 16 +#define PROC_TYPE_DAMAGED_MAGIC 17 +#define PROC_TYPE_RANGED_ATTACK 18 +#define PROC_TYPE_RANGED_DEFENSE 19 + +struct ThreatTransfer { + int32 Target; + float Amount; + LuaSpell* Spell; +}; + +#define DET_TYPE_TRAUMA 1 +#define DET_TYPE_ARCANE 2 +#define DET_TYPE_NOXIOUS 3 +#define DET_TYPE_ELEMENTAL 4 +#define DET_TYPE_CURSE 5 + +#define DISPELL_TYPE_CURE 0 +#define DISPELL_TYPE_DISPELL 1 + +#define CONTROL_EFFECT_TYPE_MEZ 1 +#define CONTROL_EFFECT_TYPE_STIFLE 2 +#define CONTROL_EFFECT_TYPE_DAZE 3 +#define CONTROL_EFFECT_TYPE_STUN 4 +#define CONTROL_EFFECT_TYPE_ROOT 5 +#define CONTROL_EFFECT_TYPE_FEAR 6 +#define CONTROL_EFFECT_TYPE_WALKUNDERWATER 7 +#define CONTROL_EFFECT_TYPE_JUMPUNDERWATER 8 +#define CONTROL_EFFECT_TYPE_INVIS 9 +#define CONTROL_EFFECT_TYPE_STEALTH 10 +#define CONTROL_EFFECT_TYPE_SNARE 11 +#define CONTROL_EFFECT_TYPE_FLIGHT 12 +#define CONTROL_EFFECT_TYPE_GLIDE 13 +#define CONTROL_EFFECT_TYPE_SAFEFALL 14 +#define CONTROL_MAX_EFFECTS 15 // always +1 to highest control effect + +#define IMMUNITY_TYPE_MEZ 1 +#define IMMUNITY_TYPE_STIFLE 2 +#define IMMUNITY_TYPE_DAZE 3 +#define IMMUNITY_TYPE_STUN 4 +#define IMMUNITY_TYPE_ROOT 5 +#define IMMUNITY_TYPE_FEAR 6 +#define IMMUNITY_TYPE_AOE 7 +#define IMMUNITY_TYPE_TAUNT 8 +#define IMMUNITY_TYPE_RIPOSTE 9 +#define IMMUNITY_TYPE_STRIKETHROUGH 10 + +//class Spell; +//class ZoneServer; + +//The entity class is for NPCs and Players, spawns which are able to fight +class Entity : public Spawn{ +public: + Entity(); + virtual ~Entity(); + + void DeleteSpellEffects(bool removeClient = false); + void RemoveSpells(bool unfriendlyOnly = false); + void MapInfoStruct(); + virtual float GetDodgeChance(); + virtual void AddMaintainedSpell(LuaSpell* spell); + virtual void AddSpellEffect(LuaSpell* spell, int32 override_expire_time = 0); + virtual void RemoveMaintainedSpell(LuaSpell* spell); + virtual void RemoveSpellEffect(LuaSpell* spell); + virtual void AddSkillBonus(int32 spell_id, int32 skill_id, float value); + void AddDetrimentalSpell(LuaSpell* spell, int32 override_expire_timestamp = 0); + DetrimentalEffects* GetDetrimentalEffect(int32 spell_id, Entity* caster); + virtual MaintainedEffects* GetMaintainedSpell(int32 spell_id); + void RemoveDetrimentalSpell(LuaSpell* spell); + void SetDeity(int8 new_deity){ + deity = new_deity; + } + int8 GetDeity(){ return deity; } + EquipmentItemList* GetEquipmentList(); + EquipmentItemList* GetAppearanceEquipmentList(); + + bool IsEntity(){ return true; } + float CalculateSkillStatChance(char* skill, int16 item_stat, float max_cap = 0.0f, float modifier = 0.0f, bool add_to_skill = false); + float CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase); + float GetRuleSkillMaxBonus(); + void CalculateBonuses(); + float CalculateLevelStatBonus(int16 stat_value); + void CalculateApplyWeight(); + void SetRegenValues(int16 effective_level); + float CalculateBonusMod(); + float CalculateDPSMultiplier(); + float CalculateCastingSpeedMod(); + + InfoStruct* GetInfoStruct(); + + int16 GetStr(); + int16 GetSta(); + int16 GetInt(); + int16 GetWis(); + int16 GetAgi(); + int16 GetPrimaryStat(); + + int16 GetHeatResistance(); + int16 GetColdResistance(); + int16 GetMagicResistance(); + int16 GetMentalResistance(); + int16 GetDivineResistance(); + int16 GetDiseaseResistance(); + int16 GetPoisonResistance(); + + int16 GetStrBase(); + int16 GetStaBase(); + int16 GetIntBase(); + int16 GetWisBase(); + int16 GetAgiBase(); + + int16 GetHeatResistanceBase(); + int16 GetColdResistanceBase(); + int16 GetMagicResistanceBase(); + int16 GetMentalResistanceBase(); + int16 GetDivineResistanceBase(); + int16 GetDiseaseResistanceBase(); + int16 GetPoisonResistanceBase(); + + int8 GetConcentrationCurrent(); + int8 GetConcentrationMax(); + + sint8 GetAlignment(); + void SetAlignment(sint8 new_value); + + bool HasMoved(bool include_heading); + void SetHPRegen(int16 new_val); + int16 GetHPRegen(); + void DoRegenUpdate(); + MaintainedEffects* GetFreeMaintainedSpellSlot(); + SpellEffects* GetFreeSpellEffectSlot(); + SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0); + SpellEffects* GetSpellEffectBySpellType(int8 spell_type); + SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0); + LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true); + + //flags + int32 GetFlags() { return info_struct.get_flags(); } + int32 GetFlags2() { return info_struct.get_flags2(); } + bool query_flags(int flag) { + if (flag > 63) return false; + if (flag < 32) return ((info_struct.get_flags() & (1 << flag))?true:false); + return ((info_struct.get_flags2() & (1 << (flag - 32)))?true:false); + } + float GetMaxSpeed(); + void SetMaxSpeed(float val); + //combat stuff: + int32 GetRangeLastAttackTime(); + void SetRangeLastAttackTime(int32 time); + int16 GetRangeAttackDelay(); + int16 GetRangeWeaponDelay(); + void SetRangeWeaponDelay(int16 new_delay); + void SetRangeAttackDelay(int16 new_delay); + int32 GetPrimaryLastAttackTime(); + int16 GetPrimaryAttackDelay(); + void SetPrimaryAttackDelay(int16 new_delay); + void SetPrimaryLastAttackTime(int32 new_time); + void SetPrimaryWeaponDelay(int16 new_delay); + int32 GetSecondaryLastAttackTime(); + int16 GetSecondaryAttackDelay(); + void SetSecondaryAttackDelay(int16 new_delay); + void SetSecondaryLastAttackTime(int32 new_time); + void SetSecondaryWeaponDelay(int16 new_delay); + int32 GetPrimaryWeaponMinDamage(); + int32 GetPrimaryWeaponMaxDamage(); + int32 GetSecondaryWeaponMinDamage(); + int32 GetSecondaryWeaponMaxDamage(); + int32 GetRangedWeaponMinDamage(); + int32 GetRangedWeaponMaxDamage(); + int8 GetPrimaryWeaponType(); + int8 GetSecondaryWeaponType(); + int8 GetRangedWeaponType(); + int8 GetWieldType(); + int16 GetPrimaryWeaponDelay(); + int16 GetSecondaryWeaponDelay(); + bool IsDualWield(); + bool BehindTarget(Spawn* target); + bool FlankingTarget(Spawn* target); + + void GetWeaponDamage(Item* item, int32* low_damage, int32* high_damage); + void ChangePrimaryWeapon(); + void ChangeSecondaryWeapon(); + void ChangeRangedWeapon(); + void UpdateWeapons(); + int32 GetStrengthDamage(); + virtual Skill* GetSkillByName(const char* name, bool check_update = false); + virtual Skill* GetSkillByID(int32 id, bool check_update = false); + bool AttackAllowed(Entity* target, float distance = 0, bool range_attack = false); + Item* GetAmmoFromSlot(bool is_ammo, bool is_thrown); + bool PrimaryWeaponReady(); + bool SecondaryWeaponReady(); + bool RangeWeaponReady(); + void MeleeAttack(Spawn* victim, float distance, bool primary, bool multi_attack = false); + void RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack = false); + bool SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false, int8 override_packet_type = 0, bool take_power = false); + bool ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg); + bool SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false, string custom_spell_name=""); + int8 DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell = nullptr); + float GetDamageTypeResistPercentage(int8 damage_type); + Skill* GetSkillByWeaponType(int8 type, int8 damage_type, bool update); + bool DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, bool take_power = false, LuaSpell* spell = 0); + float CalculateMitigation(int8 type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, int8 damage_type = 0, int16 attacker_level = 0, bool for_pvp = false); + void AddHate(Entity* attacker, sint32 hate); + bool CheckInterruptSpell(Entity* attacker); + bool CheckFizzleSpell(LuaSpell* spell); + void KillSpawn(Spawn* dead, int8 type = 0, int8 damage_type = 0, int16 kill_blow_type = 0); + void HandleDeathExperienceDebt(Spawn* killer); + void SetAttackDelay(bool primary = false, bool ranged = false); + float CalculateAttackSpeedMod(); + virtual void ProcessCombat(); + + bool EngagedInCombat(); + virtual void InCombat(bool val); + + bool IsCasting(); + void IsCasting(bool val); + void SetMount(int16 mount_id, int8 red = 0xFF, int8 green = 0xFF, int8 blue = 0xFF, bool setUpdateFlags = true) + { + if (mount_id == 0) { + EQ2_Color color; + color.red = 0; + color.green = 0; + color.blue = 0; + SetMountColor(&color); + SetMountSaddleColor(&color); + } + else + { + EQ2_Color color; + color.red = red; + color.green = green; + color.blue = blue; + SetMountColor(&color); + SetMountSaddleColor(&color); + } + SetInfo(&features.mount_model_type, mount_id, setUpdateFlags); + } + + void SetEquipment(Item* item, int8 slot = 255); + void SetEquipment(int8 slot, int16 type, int8 red, int8 green, int8 blue, int8 h_r, int8 h_g, int8 h_b){ + std::lock_guard lk(MEquipment); + if(slot >= NUM_SLOTS) + return; + + SetInfo(&equipment.equip_id[slot], type); + SetInfo(&equipment.color[slot].red, red); + SetInfo(&equipment.color[slot].green, green); + SetInfo(&equipment.color[slot].blue, blue); + SetInfo(&equipment.highlight[slot].red, h_r); + SetInfo(&equipment.highlight[slot].green, h_g); + SetInfo(&equipment.highlight[slot].blue, h_b); + } + void SetHairType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_type, new_val, setUpdateFlags); + } + void SetHairColor1(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_color1, new_val, setUpdateFlags); + } + void SetHairColor2(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_color2, new_val, setUpdateFlags); + } + void SetSogaHairColor1(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_color1, new_val, setUpdateFlags); + } + void SetSogaHairColor2(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_color2, new_val, setUpdateFlags); + } + void SetHairHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_highlight_color, new_val, setUpdateFlags); + } + void SetSogaHairHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_highlight_color, new_val, setUpdateFlags); + } + void SetHairColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_type_color, new_val, setUpdateFlags); + } + void SetSogaHairColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_type_color, new_val, setUpdateFlags); + } + void SetHairTypeHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_type_highlight_color, new_val, setUpdateFlags); + } + void SetSogaHairTypeHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_type_highlight_color, new_val, setUpdateFlags); + } + void SetFacialHairType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_face_type, new_val, setUpdateFlags); + } + void SetFacialHairColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_face_color, new_val, setUpdateFlags); + } + void SetSogaFacialHairColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_face_color, new_val, setUpdateFlags); + } + void SetFacialHairHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.hair_face_highlight_color, new_val, setUpdateFlags); + } + void SetSogaFacialHairHighlightColor(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_face_highlight_color, new_val, setUpdateFlags); + } + void SetWingType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.wing_type, new_val, setUpdateFlags); + } + void SetWingColor1(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.wing_color1, new_val, setUpdateFlags); + } + void SetWingColor2(EQ2_Color new_val, bool setUpdateFlags = true){ + SetInfo(&features.wing_color2, new_val, setUpdateFlags); + } + void SetChestType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.chest_type, new_val, setUpdateFlags); + } + void SetLegsType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.legs_type, new_val, setUpdateFlags); + } + void SetSogaHairType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_type, new_val, setUpdateFlags); + } + void SetSogaFacialHairType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_hair_face_type, new_val, setUpdateFlags); + } + void SetSogaChestType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_chest_type, new_val, setUpdateFlags); + } + void SetSogaLegType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&features.soga_legs_type, new_val, setUpdateFlags); + } + void SetSkinColor(EQ2_Color color){ + SetInfo(&features.skin_color, color); + } + void SetSogaSkinColor(EQ2_Color color){ + SetInfo(&features.soga_skin_color, color); + } + void SetModelColor(EQ2_Color color){ + SetInfo(&features.model_color, color); + } + void SetSogaModelColor(EQ2_Color color){ + SetInfo(&features.soga_model_color, color); + } + void SetCombatVoice(int16 val, bool setUpdateFlags = true) { + SetInfo(&features.combat_voice, val, setUpdateFlags); + } + void SetEmoteVoice(int16 val, bool setUpdateFlags = true) { + SetInfo(&features.emote_voice, val, setUpdateFlags); + } + int16 GetCombatVoice(){ return features.combat_voice; } + int16 GetEmoteVoice(){ return features.emote_voice; } + int16 GetMount(){ return features.mount_model_type; } + void SetMountSaddleColor(EQ2_Color* color){ + SetInfo(&features.mount_saddle_color, *color); + } + void SetMountColor(EQ2_Color* color){ + SetInfo(&features.mount_color, *color); + } + void SetEyeColor(EQ2_Color eye_color){ + SetInfo(&features.eye_color, eye_color); + } + void SetSogaEyeColor(EQ2_Color eye_color){ + SetInfo(&features.soga_eye_color, eye_color); + } + int16 GetHairType(){ + return features.hair_type; + } + int16 GetFacialHairType(){ + return features.hair_face_type; + } + int16 GetWingType(){ + return features.wing_type; + } + int16 GetChestType(){ + return features.chest_type; + } + int16 GetLegsType(){ + return features.legs_type; + } + int16 GetSogaHairType(){ + return features.soga_hair_type; + } + int16 GetSogaFacialHairType(){ + return features.soga_hair_face_type; + } + int16 GetSogaChestType(){ + return features.soga_chest_type; + } + int16 GetSogaLegType(){ + return features.soga_legs_type; + } + EQ2_Color* GetSkinColor(){ + return &features.skin_color; + } + EQ2_Color* GetModelColor(){ + return &features.model_color; + } + EQ2_Color* GetSogaModelColor(){ + return &features.soga_model_color; + } + EQ2_Color* GetEyeColor(){ + return &features.eye_color; + } + EQ2_Color* GetMountSaddleColor(){ + return &features.mount_saddle_color; + } + EQ2_Color* GetMountColor(){ + return &features.mount_color; + } + // should only be accessed through MEquipment mutex + EQ2_Equipment equipment; + CharFeatures features; + + void AddSpellBonus(LuaSpell* spell, int16 type, float value, int64 class_req =0, vector race_req = vector(), vector faction_req = vector()); + BonusValues* GetSpellBonus(int32 spell_id); + vector* GetAllSpellBonuses(LuaSpell* spell); + bool CheckSpellBonusRemoval(LuaSpell* spell, int16 type); + void RemoveSpellBonus(const LuaSpell* spell, bool remove_all = false); + void RemoveAllSpellBonuses(); + void CalculateSpellBonuses(ItemStatsValues* stats); + void AddMezSpell(LuaSpell* spell); + void RemoveMezSpell(LuaSpell* spell); + void RemoveAllMezSpells(); + bool IsMezzed(); + void AddStifleSpell(LuaSpell* spell); + void RemoveStifleSpell(LuaSpell* spell); + bool IsStifled(); + void AddDazeSpell(LuaSpell* spell); + void RemoveDazeSpell(LuaSpell* spell); + bool IsDazed(); + void AddStunSpell(LuaSpell* spell); + void RemoveStunSpell(LuaSpell* spell); + bool IsStunned(); + bool IsMezzedOrStunned() {return IsMezzed() || IsStunned();} + void AddRootSpell(LuaSpell* spell); + void RemoveRootSpell(LuaSpell* spell); + bool IsRooted(); + void AddFearSpell(LuaSpell* spell); + void RemoveFearSpell(LuaSpell* spell); + bool IsFeared(); + void AddSnareSpell(LuaSpell* spell); + void RemoveSnareSpell(LuaSpell* spell); + void SetSnareValue(LuaSpell* spell, float snare_val); + bool IsSnared(); + float GetHighestSnare(); + + bool HasControlEffect(int8 type); + + void HaltMovement(); + + + void SetCombatPet(Entity* pet) { this->pet = pet; } + void SetCharmedPet(Entity* pet) { charmedPet = pet; } + void SetDeityPet(Entity* pet) { deityPet = pet; } + void SetCosmeticPet(Entity* pet) { cosmeticPet = pet; } + Entity* GetPet() { return pet; } + Entity* GetCharmedPet() { return charmedPet; } + Entity* GetDeityPet() { return deityPet; } + Entity* GetCosmeticPet() { return cosmeticPet; } + /// Check to see if the entity has a combat pet + /// True if the entity has a combat pet + bool HasPet() { return (pet || charmedPet) ? true : false; } + + void HideDeityPet(bool val); + void HideCosmeticPet(bool val); + void DismissPet(Entity* pet, bool from_death = false, bool spawnListLocked = false); + void DismissAllPets(bool from_death = false, bool spawnListLocked = false); + + void SetOwner(Entity* owner) { if (owner) { this->owner = owner->GetID(); } else { owner = 0; } } + Entity* GetOwner(); + int8 GetPetType() { return m_petType; } + void SetPetType(int8 val) { m_petType = val; } + void SetPetSpellID(int32 val) { m_petSpellID = val; } + int32 GetPetSpellID() { return m_petSpellID; } + void SetPetSpellTier(int8 val) { m_petSpellTier = val; } + int8 GetPetSpellTier() { return m_petSpellTier; } + bool IsDismissing() { return m_petDismissing; } + void SetDismissing(bool val) { m_petDismissing = val; } + + /// Creates a loot chest to drop in the world + /// Pointer to the chest + NPC* DropChest(); + + /// Add a ward to the entities ward list + /// Spell id of the ward to add + /// WardInfo* of the ward we are adding + void AddWard(int32 spellID, WardInfo* ward); + + /// Gets ward info for the given spell id + /// The spell id of the ward we want to get + /// WardInfo for the given spell id + WardInfo* GetWard(int32 spellID); + + /// Removes the ward with the given spell id + /// The spell id of the ward to remove + void RemoveWard(int32 spellID); + + /// Subtracts the given damage from the wards + /// The damage to subtract from the wards + /// The amount of damage left after wards + int32 CheckWards(Entity* attacker, int32 damage, int8 damage_type); + + map stats; + + /// Adds a proc to the list of current procs + /// The type of proc to add + /// The percent chance the proc has to go off + /// The item the proc is coming from if any + /// The spell the proc is coming from if any + void AddProc(int8 type, float chance, Item* item = 0, LuaSpell* spell = 0, int8 damage_type = 0, int8 hp_ratio = 0, bool below_health = false, bool target_health = false, bool extended_version = false); + + /// Removes a proc from the list of current procs + /// Item the proc is from + /// Spell the proc is from + void RemoveProc(Item* item = 0, LuaSpell* spell = 0); + + /// Cycles through the proc list and executes them if they can go off + /// The proc type to check + /// The target of the proc if it goes off + void CheckProcs(int8 type, Spawn* target); + + /// Clears the entire proc list + void ClearProcs(); + + float GetSpeed(); + float GetAirSpeed(); + float GetBaseSpeed() { return base_speed; } + void SetSpeed(float val, bool override_ = false) { if ((base_speed == 0.0f && val > 0.0f) || override_) base_speed = val; speed = val; } + void SetSpeedMultiplier(float val) { speed_multiplier = val; } + + void SetThreatTransfer(ThreatTransfer* transfer); + ThreatTransfer* GetThreatTransfer() { return m_threatTransfer; } + int8 GetTraumaCount(); + int8 GetArcaneCount(); + int8 GetNoxiousCount(); + int8 GetElementalCount(); + int8 GetCurseCount(); + int8 GetDetTypeCount(int8 det_type); + int8 GetDetCount(); + bool HasCurableDetrimentType(int8 det_type); + Mutex* GetDetrimentMutex(); + Mutex* GetMaintainedMutex(); + Mutex* GetSpellEffectMutex(); + void ClearAllDetriments(); + void CureDetrimentByType(int8 cure_count, int8 det_type, string cure_name, Entity* caster, int8 cure_level = 0); + void CureDetrimentByControlEffect(int8 cure_count, int8 det_type, string cure_name, Entity* caster, int8 cure_level = 0); + vector* GetDetrimentalSpellEffects(); + void RemoveEffectsFromLuaSpell(LuaSpell* spell); + virtual void RemoveSkillBonus(int32 spell_id); + + virtual bool CanSeeInvis(Entity* target); + void CancelAllStealth(); + bool IsStealthed(); + bool IsInvis(); + void AddInvisSpell(LuaSpell* spell); + void AddStealthSpell(LuaSpell* spell); + void RemoveStealthSpell(LuaSpell* spell); + void RemoveInvisSpell(LuaSpell* spell); + void AddWaterwalkSpell(LuaSpell* spell); + void AddWaterjumpSpell(LuaSpell* spell); + void RemoveWaterwalkSpell(LuaSpell* spell); + void RemoveWaterjumpSpell(LuaSpell* spell); + void AddAOEImmunity(LuaSpell* spell); + bool IsAOEImmune(); + void RemoveAOEImmunity(LuaSpell* spell); + void AddStunImmunity(LuaSpell* spell); + void RemoveStunImmunity(LuaSpell* spell); + bool IsStunImmune(); + void AddStifleImmunity(LuaSpell* spell); + void RemoveStifleImmunity(LuaSpell* spell); + bool IsStifleImmune(); + void AddMezImmunity(LuaSpell* spell); + void RemoveMezImmunity(LuaSpell* spell); + bool IsMezImmune(); + void AddRootImmunity(LuaSpell* spell); + void RemoveRootImmunity(LuaSpell* spell); + bool IsRootImmune(); + void AddFearImmunity(LuaSpell* spell); + void RemoveFearImmunity(LuaSpell* spell); + bool IsFearImmune(); + void AddDazeImmunity(LuaSpell* spell); + void RemoveDazeImmunity(LuaSpell* spell); + bool IsDazeImmune(); + void AddImmunity(LuaSpell* spell, int16 type); + void RemoveImmunity(LuaSpell* spell, int16 type); + bool IsImmune(int16 type); + void AddFlightSpell(LuaSpell* spell); + void RemoveFlightSpell(LuaSpell* spell); + void AddSafefallSpell(LuaSpell* spell); + void RemoveSafefallSpell(LuaSpell* spell); + void AddGlideSpell(LuaSpell* spell); + void RemoveGlideSpell(LuaSpell* spell); + + GroupMemberInfo* GetGroupMemberInfo() { return group_member_info; } + void SetGroupMemberInfo(GroupMemberInfo* info) { group_member_info = info; } + void UpdateGroupMemberInfo(bool inGroupMgrLock=false, bool groupMembersLocked=false); + + void CustomizeAppearance(PacketStruct* packet); + + Trade* trade; + + // Keep track of entities that hate this spawn. + set HatedBy; + std::mutex MHatedBy; + + bool IsAggroed() { + int32 size = 0; + + MHatedBy.lock(); + size = HatedBy.size(); + MHatedBy.unlock(); + + return size > 0; + } + + Mutex MCommandMutex; + + bool HasSeeInvisSpell() { return hasSeeInvisSpell; } + void SetSeeInvisSpell(bool val) { hasSeeInvisSpell = val; } + + bool HasSeeHideSpell() { return hasSeeHideSpell; } + void SetSeeHideSpell(bool val) { hasSeeHideSpell = val; } + + void SetInfoStruct(InfoStruct* struct_) { info_struct.SetInfoStruct(struct_); } + + std::string GetInfoStructString(std::string field); + int8 GetInfoStructInt8(std::string field); + int16 GetInfoStructInt16(std::string field); + int32 GetInfoStructInt32(std::string field); + int64 GetInfoStructInt64(std::string field); + sint8 GetInfoStructSInt8(std::string field); + sint16 GetInfoStructSInt16(std::string field); + sint32 GetInfoStructSInt32(std::string field); + sint64 GetInfoStructSInt64(std::string field); + int64 GetInfoStructUInt(std::string field); + sint64 GetInfoStructSInt(std::string field); + float GetInfoStructFloat(std::string field); + + + bool SetInfoStructString(std::string field, std::string value); + bool SetInfoStructUInt(std::string field, int64 value); + bool SetInfoStructSInt(std::string field, sint64 value); + bool SetInfoStructFloat(std::string field, float value); + + float CalculateSpellDamageReduction(float spellDamage, int16 competitorLevel); + sint32 CalculateHateAmount(Spawn* target, sint32 amt); + sint32 CalculateHealAmount(Spawn* target, sint32 amt, int8 crit_mod, bool* crit, bool skip_crit_mod = false); + sint32 CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, LuaSpell* spell); + sint32 CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_type, int8 damage_type, int8 spell_target_type); + sint32 CalculateFormulaByStat(sint32 value, int16 stat); + int32 CalculateFormulaByStat(int32 value, int16 stat); + int32 CalculateFormulaBonus(int32 value, float percent_bonus); + float CalculateSpellDamageReduction(float spellDamage, float resistancePercentage, int16 attackerLevel); + + float GetStat(int32 item_stat) { + float item_chance_or_skill = 0.0f; + MStats.lock(); + item_chance_or_skill = stats[item_stat]; + MStats.unlock(); + return item_chance_or_skill; + } + + bool IsEngagedInEncounter(Spawn** res = nullptr); + bool IsEngagedBySpawnID(int32 id); + void SendControlEffectDetailsToClient(Client* client); + + std::string GetControlEffectName(int8 control_effect_type) { + switch(control_effect_type) { + case CONTROL_EFFECT_TYPE_MEZ: { + return "Mesmerize"; + break; + } + case CONTROL_EFFECT_TYPE_STIFLE:{ + return "Stifle"; + break; + } + case CONTROL_EFFECT_TYPE_DAZE:{ + return "Daze"; + break; + } + case CONTROL_EFFECT_TYPE_STUN:{ + return "Stun"; + break; + } + case CONTROL_EFFECT_TYPE_ROOT:{ + return "Root"; + break; + } + case CONTROL_EFFECT_TYPE_FEAR:{ + return "Fear"; + break; + } + case CONTROL_EFFECT_TYPE_WALKUNDERWATER:{ + return "WalkUnderwater"; + break; + } + case CONTROL_EFFECT_TYPE_JUMPUNDERWATER:{ + return "JumpUnderwater"; + break; + } + case CONTROL_EFFECT_TYPE_INVIS:{ + return "Invisible"; + break; + } + case CONTROL_EFFECT_TYPE_STEALTH:{ + return "Stealth"; + break; + } + case CONTROL_EFFECT_TYPE_SNARE:{ + return "Snare"; + break; + } + case CONTROL_EFFECT_TYPE_FLIGHT:{ + return "Flight"; + break; + } + case CONTROL_EFFECT_TYPE_GLIDE:{ + return "Glide"; + break; + } + case CONTROL_EFFECT_TYPE_SAFEFALL:{ + return "SafeFall"; + break; + } + default: { + return "Undefined"; + break; + } + } + } + + void TerminateTrade(); + + void CalculateMaxReduction(); + // when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock + std::mutex MEquipment; + std::mutex MStats; + + Mutex MMaintainedSpells; + Mutex MSpellEffects; +protected: + bool in_combat; + int8 m_petType; + int32 owner; + // m_petSpellID holds the spell id used to create/control this pet + int32 m_petSpellID; + int8 m_petSpellTier; + bool m_petDismissing; +private: + MutexList bonus_list; + map*> control_effects; + map*> immunities; + float max_speed; + int8 deity; + sint16 regen_hp_rate; + sint16 regen_power_rate; + float last_x; + float last_y; + float last_z; + float last_heading; + bool casting; + InfoStruct info_struct; + map det_count_list; + Mutex MDetriments; + vector detrimental_spell_effects; + // Pointers for the 4 types of pets (Summon, Charm, Deity, Cosmetic) + Entity* pet; + Entity* charmedPet; + Entity* deityPet; + Entity* cosmeticPet; + + // int32 = spell id, WardInfo* = pointer to ward info + map m_wardList; + + // int8 = type, vector = list of pointers to proc info + map > m_procList; + Mutex MProcList; + + /// Actually calls the lua script to cast the proc + /// Proc to be cast + /// Type of proc going off + /// Target of the proc + bool CastProc(Proc* proc, int8 type, Spawn* target); + + float base_speed; + float speed; + float speed_multiplier; + + map snare_values; + + ThreatTransfer* m_threatTransfer; + + GroupMemberInfo* group_member_info; + + bool hasSeeInvisSpell; + bool hasSeeHideSpell; + + // GETs + map > get_float_funcs; + map > get_int64_funcs; + map > get_int32_funcs; + map > get_int16_funcs; + map > get_int8_funcs; + + map > get_sint64_funcs; + map > get_sint32_funcs; + map > get_sint16_funcs; + map > get_sint8_funcs; + + map > get_string_funcs; + + // SETs + map > set_float_funcs; + map > set_int64_funcs; + map > set_int32_funcs; + map > set_int16_funcs; + map > set_int8_funcs; + + map > set_sint64_funcs; + map > set_sint32_funcs; + map > set_sint16_funcs; + map > set_sint8_funcs; + + map > set_string_funcs; +}; + +#endif diff --git a/source/WorldServer/Factions.cpp b/source/WorldServer/Factions.cpp new file mode 100644 index 0000000..00d6bcc --- /dev/null +++ b/source/WorldServer/Factions.cpp @@ -0,0 +1,183 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Factions.h" +#include "client.h" + +extern MasterFactionList master_faction_list; +extern ConfigReader configReader; + + +PlayerFaction::PlayerFaction(){ + MFactionUpdateNeeded.SetName("PlayerFaction::MFactionUpdateNeeded"); +} + +sint32 PlayerFaction::GetMaxValue(sint8 con){ + if(con < 0) + return con * 10000; + else + return (con * 10000) + 9999; +} + +sint32 PlayerFaction::GetMinValue(sint8 con){ + if(con <= 0) + return (con * 10000) - 9999; + else + return (con * 10000); +} + +bool PlayerFaction::ShouldAttack(int32 faction_id){ + return (GetCon(faction_id) <= -4); +} + +sint8 PlayerFaction::GetCon(int32 faction_id){ + if(faction_id <= 10){ + if(faction_id == 0) + return 0; + return (faction_id-5); + } + + sint32 value = GetFactionValue(faction_id); + if(value >= -9999 && value <= 9999) + return 0; + else{ + if(value>= 40000) + return 4; + else if(value <= -40000) + return -4; + return (sint8)(value/10000); + } +} + +int8 PlayerFaction::GetPercent(int32 faction_id){ + if(faction_id <= 10) + return 0; + sint8 con = GetCon(faction_id); + sint32 value = GetFactionValue(faction_id); + if(con != 0){ + if(value <= 0) + value *= -1; + if(con < 0) + con *= -1; + value -= con * 10000; + value *= 100; + return value / 10000; + } + else{ + value += 10000; + value *= 100; + return value / 20000; + } +} + +EQ2Packet* PlayerFaction::FactionUpdate(int16 version){ + EQ2Packet* ret = 0; + Faction* faction = 0; + PacketStruct* packet = configReader.getStruct("WS_FactionUpdate", version); + MFactionUpdateNeeded.lock(); + if(packet){ + packet->setArrayLengthByName("num_factions", faction_update_needed.size()); + for(int32 i=0;isetArrayDataByName("faction_id", faction->id, i); + packet->setArrayDataByName("name", faction->name.c_str(), i); + packet->setArrayDataByName("description", faction->description.c_str(), i); + packet->setArrayDataByName("category", faction->type.c_str(), i); + packet->setArrayDataByName("con", GetCon(faction->id), i); + packet->setArrayDataByName("percentage", GetPercent(faction->id), i); + packet->setArrayDataByName("value", GetFactionValue(faction->id), i); + } + } + ret = packet->serialize(); + safe_delete(packet); + } + faction_update_needed.clear(); + MFactionUpdateNeeded.unlock(); + return ret; +} + +sint32 PlayerFaction::GetFactionValue(int32 faction_id){ + if(faction_id <= 10) + return 0; + + //devn00b: This always seems to return 1, even if the player infact has no faction. since we handle this via a check in zoneserver.cpp (processfaction) + //if(faction_values.count(faction_id) == 0) + //return master_faction_list.GetDefaultFactionValue(faction_id); //faction_values[faction_id] = master_faction_list.GetDefaultFactionValue(faction_id); + + return faction_values[faction_id]; +} + +bool PlayerFaction::ShouldIncrease(int32 faction_id){ + if(faction_id <= 10 || master_faction_list.GetIncreaseAmount(faction_id) == 0) + return false; + return true; +} + +bool PlayerFaction::ShouldDecrease(int32 faction_id){ + if(faction_id <= 10 || master_faction_list.GetDecreaseAmount(faction_id) == 0) + return false; + return true; +} + +bool PlayerFaction::IncreaseFaction(int32 faction_id, int32 amount){ + if(faction_id <= 10) + return true; + bool ret = true; + if(amount == 0) + amount = master_faction_list.GetIncreaseAmount(faction_id); + faction_values[faction_id] += amount; + if(faction_values[faction_id] >= 50000){ + faction_values[faction_id] = 50000; + ret = false; + } + MFactionUpdateNeeded.lock(); + faction_update_needed.push_back(faction_id); + MFactionUpdateNeeded.unlock(); + return ret; +} + +bool PlayerFaction::DecreaseFaction(int32 faction_id, int32 amount){ + if(faction_id <= 10) + return true; + bool ret = true; + if(amount == 0) + amount = master_faction_list.GetDecreaseAmount(faction_id); + if(amount != 0){ + faction_values[faction_id] -= amount; + if(faction_values[faction_id] <= -50000){ + faction_values[faction_id] = -50000; + ret = false; + } + } + else + ret = false; + MFactionUpdateNeeded.lock(); + faction_update_needed.push_back(faction_id); + MFactionUpdateNeeded.unlock(); + return ret; +} + +bool PlayerFaction::SetFactionValue(int32 faction_id, sint32 value){ + faction_values[faction_id] = value; + MFactionUpdateNeeded.lock(); + faction_update_needed.push_back(faction_id); + MFactionUpdateNeeded.unlock(); + return true; +} diff --git a/source/WorldServer/Factions.h b/source/WorldServer/Factions.h new file mode 100644 index 0000000..ae88107 --- /dev/null +++ b/source/WorldServer/Factions.h @@ -0,0 +1,139 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQ2_FACTIONS +#define EQ2_FACTIONS + +#include "../common/ConfigReader.h" +#include "../common/Mutex.h" + +struct Faction { + int32 id; + string name; + string type; + string description; + int16 negative_change; + int16 positive_change; + sint32 default_value; +}; + +class MasterFactionList{ +public: + MasterFactionList(){ + + } + ~MasterFactionList(){ + Clear(); + } + void Clear() { + map::iterator iter; + for(iter = global_faction_list.begin();iter != global_faction_list.end(); iter++){ + safe_delete(iter->second); + } + + hostile_factions.clear(); + friendly_factions.clear(); + } + sint32 GetDefaultFactionValue(int32 faction_id){ + if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id]) + return global_faction_list[faction_id]->default_value; + return 0; + } + Faction* GetFaction(char* name){ + return faction_name_list[name]; + } + Faction* GetFaction(int32 id){ + if(global_faction_list.count(id) > 0) + return global_faction_list[id]; + return 0; + } + void AddFaction(Faction* faction){ + global_faction_list[faction->id] = faction; + faction_name_list[faction->name] = faction; + } + sint32 GetIncreaseAmount(int32 faction_id){ + if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id]) + return global_faction_list[faction_id]->positive_change; + return 0; + } + sint32 GetDecreaseAmount(int32 faction_id){ + if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id]) + return global_faction_list[faction_id]->negative_change; + return 0; + } + int32 GetFactionCount(){ + return global_faction_list.size(); + } + void AddHostileFaction(int32 faction_id, int32 hostile_faction_id){ + hostile_factions[faction_id].push_back(hostile_faction_id); + } + void AddFriendlyFaction(int32 faction_id, int32 friendly_faction_id){ + friendly_factions[faction_id].push_back(friendly_faction_id); + } + vector* GetFriendlyFactions(int32 faction_id){ + if(friendly_factions.count(faction_id) > 0) + return &friendly_factions[faction_id]; + else + return 0; + } + vector* GetHostileFactions(int32 faction_id){ + if(hostile_factions.count(faction_id) > 0) + return &hostile_factions[faction_id]; + else + return 0; + } + const char* GetFactionNameByID(int32 faction_id) { + if (faction_id > 0 && global_faction_list.count(faction_id) > 0) + return global_faction_list[faction_id]->name.c_str(); + return 0; + } +private: + map > friendly_factions; + map > hostile_factions; + map global_faction_list; + map faction_name_list; +}; + +class PlayerFaction{ +public: + PlayerFaction(); + sint32 GetMaxValue(sint8 con); + sint32 GetMinValue(sint8 con); + EQ2Packet* FactionUpdate(int16 version); + sint32 GetFactionValue(int32 faction_id); + bool ShouldIncrease(int32 faction_id); + bool ShouldDecrease(int32 faction_id); + bool IncreaseFaction(int32 faction_id, int32 amount = 0); + bool DecreaseFaction(int32 faction_id, int32 amount = 0); + bool SetFactionValue(int32 faction_id, sint32 value); + sint8 GetCon(int32 faction_id); + int8 GetPercent(int32 faction_id); + map* GetFactionValues(){ + return &faction_values; + } + bool ShouldAttack(int32 faction_id); + +private: + Mutex MFactionUpdateNeeded; + vector faction_update_needed; + map faction_values; + map faction_percent; +}; +#endif + diff --git a/source/WorldServer/GroundSpawn.cpp b/source/WorldServer/GroundSpawn.cpp new file mode 100644 index 0000000..93a4a09 --- /dev/null +++ b/source/WorldServer/GroundSpawn.cpp @@ -0,0 +1,575 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "GroundSpawn.h" +#include "World.h" +#include "Spells.h" +#include "Rules/Rules.h" +#include "../common/MiscFunctions.h" +#include "../common/Log.h" + +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern World world; +extern RuleManager rule_manager; + +GroundSpawn::GroundSpawn(){ + packet_num = 0; + appearance.difficulty = 0; + spawn_type = 2; + appearance.pos.state = 129; + number_harvests = 0; + num_attempts_per_harvest = 0; + groundspawn_id = 0; + MHarvest.SetName("GroundSpawn::MHarvest"); + MHarvestUse.SetName("GroundSpawn::MHarvestUse"); + randomize_heading = true; // we by default randomize heading of groundspawns DB overrides +} + +GroundSpawn::~GroundSpawn(){ + +} + +EQ2Packet* GroundSpawn::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +int8 GroundSpawn::GetNumberHarvests(){ + return number_harvests; +} + +void GroundSpawn::SetNumberHarvests(int8 val){ + number_harvests = val; +} + +int8 GroundSpawn::GetAttemptsPerHarvest(){ + return num_attempts_per_harvest; +} + +void GroundSpawn::SetAttemptsPerHarvest(int8 val){ + num_attempts_per_harvest = val; +} + +int32 GroundSpawn::GetGroundSpawnEntryID(){ + return groundspawn_id; +} + +void GroundSpawn::SetGroundSpawnEntryID(int32 val){ + groundspawn_id = val; +} + +void GroundSpawn::SetCollectionSkill(const char* val){ + if(val) + collection_skill = string(val); +} + +const char* GroundSpawn::GetCollectionSkill(){ + return collection_skill.c_str(); +} + +void GroundSpawn::ProcessHarvest(Client* client) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Process harvesting for player '%s' (%u)", client->GetPlayer()->GetName(), client->GetPlayer()->GetID()); + + MHarvest.lock(); + + vector* groundspawn_entries = GetZone()->GetGroundSpawnEntries(groundspawn_id); + vector* groundspawn_items = GetZone()->GetGroundSpawnEntryItems(groundspawn_id); + + Item* master_item = 0; + Item* master_rare = 0; + Item* item = 0; + Item* item_rare = 0; + + int16 lowest_skill_level = 0; + int16 table_choice = 0; + int32 item_choice = 0; + int32 rare_choice = 0; + int8 harvest_type = 0; + int32 item_harvested = 0; + int8 reward_total = 1; + int32 rare_harvested = 0; + int8 rare_item = 0; + bool is_collection = false; + + if (!groundspawn_entries || !groundspawn_items) { + LogWrite(GROUNDSPAWN__ERROR, 3, "GSpawn", "No groundspawn entries or items assigned to groundspawn id: %u", groundspawn_id); + client->Message(CHANNEL_COLOR_RED, "Error: There are no groundspawn entries or items assigned to groundspawn id: %u", groundspawn_id); + MHarvest.unlock(); + return; + } + + if (number_harvests == 0) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Total harvests depleated for groundspawn id: %u", groundspawn_id); + client->SimpleMessage(CHANNEL_COLOR_RED, "Error: This spawn has nothing more to harvest!"); + MHarvest.unlock(); + return; + } + + Skill* skill = 0; + if (collection_skill == "Collecting") { + skill = client->GetPlayer()->GetSkillByName("Gathering"); + is_collection = true; + } + else + skill = client->GetPlayer()->GetSkillByName(collection_skill.c_str()); // Fix: #576 - don't skill up yet with GetSkillByName(skill, true), we might be trying to harvest low level + + if (!skill) { + LogWrite(GROUNDSPAWN__WARNING, 3, "GSpawn", "Player '%s' lacks the skill: '%s'", client->GetPlayer()->GetName(), collection_skill.c_str()); + client->Message(CHANNEL_COLOR_RED, "Error: You do not have the '%s' skill!", collection_skill.c_str()); + MHarvest.unlock(); + return; + } + + int16 totalSkill = skill->current_val; + int32 skillID = master_item_list.GetItemStatIDByName(collection_skill); + int16 max_skill_req_groundspawn = 0; + if(skillID != 0xFFFFFFFF) + { + ((Entity*)client->GetPlayer())->MStats.lock(); + totalSkill += ((Entity*)client->GetPlayer())->stats[skillID]; + ((Entity*)client->GetPlayer())->MStats.unlock(); + } + + for (int8 i = 0; i < num_attempts_per_harvest; i++) { + vector mod_groundspawn_entries; + + if (groundspawn_entries) { + vector highest_match; + vector::iterator itr; + + GroundSpawnEntry* entry = 0; // current data + GroundSpawnEntry* selected_table = 0; // selected table data + + // first, iterate through groundspawn_entries, discard tables player cannot use + for (itr = groundspawn_entries->begin(); itr != groundspawn_entries->end(); itr++) { + entry = *itr; + + if(entry->min_skill_level > max_skill_req_groundspawn) + max_skill_req_groundspawn = entry->min_skill_level; + + // if player lacks skill, skip table + if (entry->min_skill_level > totalSkill) + continue; + // if bonus, but player lacks level, skip table + if (entry->bonus_table && (client->GetPlayer()->GetLevel() < entry->min_adventure_level)) + continue; + + // build modified entries table + mod_groundspawn_entries.push_back(entry); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "Keeping groundspawn_entry: %i", entry->min_skill_level); + } + + // if anything remains, find lowest min_skill_level in remaining set(s) + if (mod_groundspawn_entries.size() > 0) { + vector::iterator itr; + GroundSpawnEntry* entry = 0; + + for (itr = mod_groundspawn_entries.begin(); itr != mod_groundspawn_entries.end(); itr++) { + entry = *itr; + + // find the low range of available tables for random roll + if (lowest_skill_level > entry->min_skill_level || lowest_skill_level == 0) + lowest_skill_level = entry->min_skill_level; + } + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Lowest Skill Level: %i", lowest_skill_level); + } + else { + // if no tables chosen, you must lack the skills + // TODO: move this check to LUA when harvest command is first selected + client->Message(CHANNEL_COLOR_RED, "You lack the skills to harvest this node!"); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "All groundspawn_entry tables tossed! No Skills? Something broke?"); + MHarvest.unlock(); + return; + } + + // now roll to see which table to use + table_choice = MakeRandomInt(lowest_skill_level, totalSkill); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random INT for Table by skill level: %i", table_choice); + + int16 highest_score = 0; + for (itr = mod_groundspawn_entries.begin(); itr != mod_groundspawn_entries.end(); itr++) { + entry = *itr; + + // determines the highest min_skill_level in the current set of tables (if multiple tables) + if (table_choice >= entry->min_skill_level && (highest_score == 0 || highest_score < table_choice)) { + // removes old highest for the new one + highest_match.clear(); + highest_score = entry->min_skill_level; + } + // if the score = level, push into highest_match set + if (highest_score == entry->min_skill_level) + highest_match.push_back(entry); + } + + // if there is STILL more than 1 table player qualifies for, rand() and pick one + if (highest_match.size() > 1) { + int16 rand_index = rand() % highest_match.size(); + selected_table = highest_match.at(rand_index); + } + else if (highest_match.size() > 0) + selected_table = highest_match.at(0); + + // by this point, we should have 1 table who's min skill matches the score (selected_table) + if (selected_table) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Using Table: %i, %i, %i, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %i", + selected_table->min_skill_level, + selected_table->min_adventure_level, + selected_table->bonus_table, + selected_table->harvest1, + selected_table->harvest3, + selected_table->harvest5, + selected_table->harvest_imbue, + selected_table->harvest_rare, + selected_table->harvest10, + selected_table->harvest_coin); + + + // roll 1-100 for chance-to-harvest percentage + float chance = MakeRandomFloat(0, 100); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random FLOAT for harvest percentages: %.2f", chance); + + // starting with the lowest %, select a harvest type + reward qty + if (chance <= selected_table->harvest10 && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 10 items + Rare Item from table : %i", selected_table->min_skill_level); + harvest_type = 6; + reward_total = 10; + } + else if (chance <= selected_table->harvest_rare && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest Rare Item from table : %i", selected_table->min_skill_level); + harvest_type = 5; + } + else if (chance <= selected_table->harvest_imbue && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest Imbue Item from table : %i", selected_table->min_skill_level); + harvest_type = 4; + } + else if (chance <= selected_table->harvest5 && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 5 Items from table : %i", selected_table->min_skill_level); + harvest_type = 3; + reward_total = 5; + } + else if (chance <= selected_table->harvest3 && is_collection == false) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 3 Items from table : %i", selected_table->min_skill_level); + harvest_type = 2; + reward_total = 3; + } + else if (chance <= selected_table->harvest1 || totalSkill >= skill->max_val || is_collection) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 1 Item from table : %i", selected_table->min_skill_level); + harvest_type = 1; + } + else + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest nothing..."); + + float node_maxskill_multiplier = rule_manager.GetGlobalRule(R_Player, HarvestSkillUpMultiplier)->GetFloat(); + if(node_maxskill_multiplier <= 0.0f) { + node_maxskill_multiplier = 1.0f; + } + int16 skillup_max_skill_allowed = (int16)((float)max_skill_req_groundspawn*node_maxskill_multiplier); + if (!is_collection && skill && skill->current_val < skillup_max_skill_allowed) { + skill = client->GetPlayer()->GetSkillByName(collection_skill.c_str(), true); // Fix: #576 - skill up after min skill and adv level checks + } + } + + // once you know how many and what type of item to harvest, pick an item from the list + if (harvest_type) { + vector mod_groundspawn_items; + vector mod_groundspawn_rares; + vector mod_groundspawn_imbue; + + vector::iterator itr; + GroundSpawnEntryItem* entry = 0; + + // iterate through groundspawn_items, discard items player cannot roll for + for (itr = groundspawn_items->begin(); itr != groundspawn_items->end(); itr++) { + entry = *itr; + + // if this is a Rare, or an Imbue, but is_rare flag is 0, skip item + if ((harvest_type == 5 || harvest_type == 4) && entry->is_rare == 0) + continue; + // if it is a 1, 3, or 5 and is_rare = 1, skip + else if (harvest_type < 4 && entry->is_rare == 1) + continue; + + // if the grid_id on the item matches player grid, or is 0, keep the item + if (!entry->grid_id || (entry->grid_id == client->GetPlayer()->GetLocation())) { + // build modified entries table + if ((entry->is_rare == 1 && harvest_type == 5) || (entry->is_rare == 1 && harvest_type == 6)) { + // if the matching item is rare, or harvest10 push to mod rares + mod_groundspawn_rares.push_back(entry); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_rare_item: %u", entry->item_id); + } + if (entry->is_rare == 0 && harvest_type != 4 && harvest_type != 5) { + // if the matching item is normal,or harvest 10 push to mod items + mod_groundspawn_items.push_back(entry); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_common_item: %u", entry->item_id); + } + if (entry->is_rare == 2 && harvest_type == 4) { + // if the matching item is imbue item, push to mod imbue + mod_groundspawn_imbue.push_back(entry); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_imbue_item: %u", entry->item_id); + } + } + } + + // if any items remain in the list, random to see which one gets awarded + if (mod_groundspawn_items.size() > 0) { + // roll to see which item index to use + item_choice = rand() % mod_groundspawn_items.size(); + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random INT for which item to award: %i", item_choice); + + // set item_id to be awarded + item_harvested = mod_groundspawn_items[item_choice]->item_id; + + // if reward is rare, set flag + rare_item = mod_groundspawn_items[item_choice]->is_rare; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID to award: %u, Rare = %i", item_harvested, item_rare); + + // if 10+rare, handle additional "rare" reward + if (harvest_type == 6) { + // make sure there is a rare table to choose from! + if (mod_groundspawn_rares.size() > 0) { + // roll to see which rare index to use + rare_choice = rand() % mod_groundspawn_rares.size(); + + // set (rare) item_id to be awarded + rare_harvested = mod_groundspawn_rares[rare_choice]->item_id; + + // we're picking a rare here, so obviously this is true ;) + rare_item = 1; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "RARE Item ID to award: %u", rare_harvested); + } + else { + // all rare entries were eliminated above, or none are assigned. Either way, shouldn't be here! + LogWrite(GROUNDSPAWN__ERROR, 3, "GSpawn", "Groundspawn Entry for '%s' (%i) has no RARE items!", GetName(), GetID()); + } + } + } + else if (mod_groundspawn_rares.size() > 0) { + // roll to see which rare index to use + item_choice = rand() % mod_groundspawn_rares.size(); + + // set (rare) item_id to be awarded + item_harvested = mod_groundspawn_rares[item_choice]->item_id; + + // we're picking a rare here, so obviously this is true ;) + rare_item = 1; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "RARE Item ID to award: %u", rare_harvested); + } + else if (mod_groundspawn_imbue.size() > 0) { + // roll to see which rare index to use + item_choice = rand() % mod_groundspawn_imbue.size(); + + // set (rare) item_id to be awarded + item_harvested = mod_groundspawn_imbue[item_choice]->item_id; + + // we're picking a rare here, so obviously this is true ;) + rare_item = 0; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "imbue Item ID to award: %u", rare_harvested); + } + + + + + else { + // all item entries were eliminated above, or none are assigned. Either way, shouldn't be here! + LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Groundspawn Entry for '%s' (%i) has no items!", GetName(), GetID()); + } + + // if an item was harvested, send updates to client, add item to inventory + if (item_harvested) { + char tmp[200] = { 0 }; + + // set Normal item harvested + master_item = master_item_list.GetItem(item_harvested); + if (master_item) { + // set details of Normal item + item = new Item(master_item); + // set how many of this item the player receives + item->details.count = reward_total; + + // chat box update for normal item (todo: verify output text) + client->Message(CHANNEL_HARVESTING, "You %s %i %s from the %s.", GetHarvestMessageName(true).c_str(), item->details.count, item->CreateItemLink(client->GetVersion(), true).c_str(), GetName()); + // add Normal item to player inventory + bool itemDeleted = false; + client->AddItem(item, &itemDeleted); + + if(!itemDeleted) { + //Check if the player has a harvesting quest for this + client->GetPlayer()->CheckQuestsHarvestUpdate(item, reward_total); + + // if this is a 10+rare, handle sepErately + if (harvest_type == 6 && rare_item == 1) { + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is Normal. Qty %i", item_harvested, item->details.count); + + // send Normal harvest message to client + sprintf(tmp, "\\#64FFFFYou have %s:\12\\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str()); + client->SendPopupMessage(10, tmp, "ui_harvested_normal", 2.25, 0xFF, 0xFF, 0xFF); + client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_ITEMS_HARVESTED, item->details.count); + + // set Rare item harvested + master_rare = master_item_list.GetItem(rare_harvested); + if (master_rare) { + // set details of Rare item + item_rare = new Item(master_rare); + // count of Rare is always 1 + item_rare->details.count = 1; + + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is RARE!", rare_harvested); + + // send Rare harvest message to client + sprintf(tmp, "\\#FFFF6ERare item found!\12%s: \\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item_rare->details.count, item_rare->name.c_str()); + client->Message(CHANNEL_HARVESTING, "You have found a rare item!"); + client->SendPopupMessage(11, tmp, "ui_harvested_rare", 2.25, 0xFF, 0xFF, 0xFF); + client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_RARES_HARVESTED, item_rare->details.count); + + // chat box update for rare item (todo: verify output text) + client->Message(CHANNEL_HARVESTING, "You %s %i %s from the %s.", GetHarvestMessageName(true).c_str(), item_rare->details.count, item->CreateItemLink(client->GetVersion(), true).c_str(), GetName()); + // add Rare item to player inventory + client->AddItem(item_rare); + //Check if the player has a harvesting quest for this + client->GetPlayer()->CheckQuestsHarvestUpdate(item_rare, 1); + } + } + else if (rare_item == 1) { + // if harvest signaled rare or imbue type + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is RARE! Qty: %i", item_harvested, item->details.count); + + // send Rare harvest message to client + sprintf(tmp, "\\#FFFF6ERare item found!\12%s: \\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str()); + client->Message(CHANNEL_HARVESTING, "You have found a rare item!"); + client->SendPopupMessage(11, tmp, "ui_harvested_rare", 2.25, 0xFF, 0xFF, 0xFF); + client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_RARES_HARVESTED, item->details.count); + } + else { + // send Normal harvest message to client + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is Normal. Qty %i", item_harvested, item->details.count); + sprintf(tmp, "\\#64FFFFYou have %s:\12\\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str()); + client->SendPopupMessage(10, tmp, "ui_harvested_normal", 2.25, 0xFF, 0xFF, 0xFF); + client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_ITEMS_HARVESTED, item->details.count); + } + + } + } + else { + // error! + LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Error: Item ID Not Found - %u", item_harvested); + client->Message(CHANNEL_COLOR_RED, "Error: Unable to find item id %u", item_harvested); + } + // decrement # of pulls on this node before it despawns + number_harvests--; + } + else { + // if no item harvested + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "No item_harvested"); + client->Message(CHANNEL_HARVESTING, "You failed to %s anything from %s.", GetHarvestMessageName(true, true).c_str(), GetName()); + } + } + else { + // if no harvest type + LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "No harvest_type"); + client->Message(CHANNEL_HARVESTING, "You failed to %s anything from %s.", GetHarvestMessageName(true, true).c_str(), GetName()); + } + } + } // cycle through num_attempts_per_harvest + MHarvest.unlock(); + + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "Process harvest complete for player '%s' (%u)", client->GetPlayer()->GetName(), client->GetPlayer()->GetID()); +} + +string GroundSpawn::GetHarvestMessageName(bool present_tense, bool failure){ + string ret = ""; + if((collection_skill == "Gathering" ||collection_skill == "Collecting") && !present_tense) + ret = "gathered"; + else if(collection_skill == "Gathering" || collection_skill == "Collecting") + ret = "gather"; + else if(collection_skill == "Mining" && !present_tense) + ret = "mined"; + else if(collection_skill == "Mining") + ret = "mine"; + else if (collection_skill == "Fishing" && !present_tense) + ret = "fished"; + else if(collection_skill == "Fishing") + ret = "fish"; + else if(collection_skill == "Trapping" && !present_tense && !failure) + ret = "acquired"; + else if(collection_skill == "Trapping" && failure) + ret = "trap"; + else if(collection_skill == "Trapping") + ret = "acquire"; + else if(collection_skill == "Foresting" && !present_tense) + ret = "forested"; + else if(collection_skill == "Foresting") + ret = "forest"; + else if (collection_skill == "Collecting") + ret = "collect"; + return ret; +} + +string GroundSpawn::GetHarvestSpellType(){ + string ret = ""; + if(collection_skill == "Gathering" || collection_skill == "Collecting") + ret = "gather"; + else if(collection_skill == "Mining") + ret = "mine"; + else if(collection_skill == "Trapping") + ret = "trap"; + else if(collection_skill == "Foresting") + ret = "chop"; + else if(collection_skill == "Fishing") + ret = "fish"; + return ret; +} + +string GroundSpawn::GetHarvestSpellName() { + string ret = ""; + if (collection_skill == "Collecting") + ret = "Gathering"; + else + ret = collection_skill; + return ret; +} + +void GroundSpawn::HandleUse(Client* client, string type){ + if(!client || type.length() == 0) + return; + //The following check disables the use of the groundspawn if spawn access is not granted + if (client) { + bool meets_quest_reqs = MeetsSpawnAccessRequirements(client->GetPlayer()); + if (!meets_quest_reqs && (GetQuestsRequiredOverride() & 2) == 0) + return; + else if (meets_quest_reqs && appearance.show_command_icon != 1) + return; + } + + MHarvestUse.lock(); + if (type == GetHarvestSpellType() && MeetsSpawnAccessRequirements(client->GetPlayer())) { + Spell* spell = master_spell_list.GetSpellByName(GetHarvestSpellName().c_str()); + if (spell) + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()->GetTarget(), true, true); + } + else if (appearance.show_command_icon == 1 && MeetsSpawnAccessRequirements(client->GetPlayer())) { + EntityCommand* entity_command = FindEntityCommand(type); + if (entity_command) + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + } + MHarvestUse.unlock(); +} diff --git a/source/WorldServer/GroundSpawn.h b/source/WorldServer/GroundSpawn.h new file mode 100644 index 0000000..f830686 --- /dev/null +++ b/source/WorldServer/GroundSpawn.h @@ -0,0 +1,86 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_GroundSpawn__ +#define __EQ2_GroundSpawn__ + +#include "Spawn.h" +#include "client.h" +#include "../common/Mutex.h" + +class GroundSpawn : public Spawn { +public: + GroundSpawn(); + virtual ~GroundSpawn(); + GroundSpawn* Copy(){ + GroundSpawn* new_spawn = new GroundSpawn(); + new_spawn->size = size; + new_spawn->SetPrimaryCommands(&primary_command_list); + new_spawn->SetSecondaryCommands(&secondary_command_list); + new_spawn->database_id = database_id; + new_spawn->primary_command_list_id = primary_command_list_id; + new_spawn->secondary_command_list_id = secondary_command_list_id; + memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData)); + new_spawn->faction_id = faction_id; + new_spawn->target = 0; + new_spawn->SetTotalHP(GetTotalHP()); + new_spawn->SetTotalPower(GetTotalPower()); + new_spawn->SetHP(GetHP()); + new_spawn->SetPower(GetPower()); + new_spawn->SetNumberHarvests(number_harvests); + new_spawn->SetAttemptsPerHarvest(num_attempts_per_harvest); + new_spawn->SetGroundSpawnEntryID(groundspawn_id); + new_spawn->SetCollectionSkill(collection_skill.c_str()); + SetQuestsRequired(new_spawn); + new_spawn->forceMapCheck = forceMapCheck; + new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); + new_spawn->SetLootTier(GetLootTier()); + new_spawn->SetLootDropType(GetLootDropType()); + new_spawn->SetRandomizeHeading(GetRandomizeHeading()); + return new_spawn; + } + bool IsGroundSpawn(){ return true; } + EQ2Packet* serialize(Player* player, int16 version); + int8 GetNumberHarvests(); + void SetNumberHarvests(int8 val); + int8 GetAttemptsPerHarvest(); + void SetAttemptsPerHarvest(int8 val); + int32 GetGroundSpawnEntryID(); + void SetGroundSpawnEntryID(int32 val); + void ProcessHarvest(Client* client); + void SetCollectionSkill(const char* val); + const char* GetCollectionSkill(); + string GetHarvestMessageName(bool present_tense = false, bool failure = false); + string GetHarvestSpellType(); + string GetHarvestSpellName(); + void HandleUse(Client* client, string type); + + void SetRandomizeHeading(bool val) { randomize_heading = val; } + bool GetRandomizeHeading() { return randomize_heading; } +private: + int8 number_harvests; + int8 num_attempts_per_harvest; + int32 groundspawn_id; + string collection_skill; + Mutex MHarvest; + Mutex MHarvestUse; + bool randomize_heading; +}; +#endif + diff --git a/source/WorldServer/Guilds/Guild.cpp b/source/WorldServer/Guilds/Guild.cpp new file mode 100644 index 0000000..0c74ad1 --- /dev/null +++ b/source/WorldServer/Guilds/Guild.cpp @@ -0,0 +1,2347 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include +#include +#include +#include "Guild.h" +#include "../Player.h" +#include "../client.h" +#include "../World.h" +#include "../zoneserver.h" +#include "../WorldDatabase.h" +#include "../../common/Log.h" + +extern ConfigReader configReader; +extern ZoneList zone_list; +extern WorldDatabase database; +extern World world; + +/*************************************************************************************************************************************************** + * GUILD + ***************************************************************************************************************************************************/ + +Guild::Guild() { + id = 0; + memset(name, 0, sizeof(name)); + level = 1; + formed_date = 0; + memset(motd, 0, sizeof(motd)); + exp_current = 111; + exp_to_next_level = 2521; + recruiting_min_level = 1; + recruiting_play_style = GUILD_RECRUITING_PLAYSTYLE_NONE; + recruiting_flags.Put(GUILD_RECRUITING_FLAG_TRAINING, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_FIGHTERS, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_PRIESTS, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_SCOUTS, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_MAGES, 0); + recruiting_flags.Put(GUILD_RECRUITING_FLAG_TRADESKILLERS, 0); + recruiting_desc_tags.Put(0, GUILD_RECRUITING_DESC_TAG_NONE); + recruiting_desc_tags.Put(1, GUILD_RECRUITING_DESC_TAG_NONE); + recruiting_desc_tags.Put(2, GUILD_RECRUITING_DESC_TAG_NONE); + recruiting_desc_tags.Put(3, GUILD_RECRUITING_DESC_TAG_NONE); + banks[0].name = "Bank 1"; + banks[1].name = "Bank 2"; + banks[2].name = "Bank 3"; + banks[3].name = "Bank 4"; + save_needed = false; + member_save_needed = false; + events_save_needed = false; + ranks_save_needed = false; + event_filters_save_needed = false; + points_history_save_needed = false; + recruiting_save_needed = false; + mMembers.SetName("Guild::members"); +} + +Guild::~Guild() { + map::iterator guild_member_itr; + + mMembers.writelock(__FUNCTION__, __LINE__); + for (guild_member_itr = members.begin(); guild_member_itr != members.end(); guild_member_itr++) { + deque::iterator point_history_itr; + for (point_history_itr = guild_member_itr->second->point_history.begin(); point_history_itr != guild_member_itr->second->point_history.end(); point_history_itr++) + safe_delete(*point_history_itr); + safe_delete_array(guild_member_itr->second->recruiter_picture_data); + safe_delete(guild_member_itr->second); + } + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + deque::iterator guild_events_itr; + for (guild_events_itr = guild_events.begin(); guild_events_itr != guild_events.end(); guild_events_itr++) + safe_delete(*guild_events_itr); + MutexMap*>::iterator permissions_itr = permissions.begin(); + while (permissions_itr.Next()) + safe_delete(permissions_itr.second); + MutexMap*>::iterator filter_itr = event_filters.begin(); + while (filter_itr.Next()) + safe_delete(filter_itr.second); + for (int32 i = 0; i < 4; i++) { + deque::iterator bank_event_itr; + for (bank_event_itr = banks[i].events.begin(); bank_event_itr != banks[i].events.end(); bank_event_itr++) + safe_delete(*bank_event_itr); + } +} + +void Guild::SetName(const char* name, bool send_packet) { + + assert(name); + + strncpy(this->name, name, sizeof(this->name)); + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Setting Guild Name to '%s'...", name); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::SetLevel(int8 level, bool send_packet) { + + this->level = level; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Setting Guild Level to %i...", level); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::SetMOTD(const char *motd, bool send_packet) { + + assert(motd); + + strncpy(this->motd, motd, sizeof(this->motd)); + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Setting Guild MOTD text:\n'%s'", motd); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::SetEXPCurrent(int64 exp, bool send_packet) { + + exp_current = exp; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Setting Guild Current XP to %u", exp); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::AddEXPCurrent(sint64 exp, bool send_packet) { + + bool ret = false; + char message[128]; + char adjective[16]; + + if (exp > 0 && level < GUILD_MAX_LEVEL) { + exp_current += exp; + if (exp_current >= exp_to_next_level) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild %s Level UP! New Level: %i (current XP: %ul)", name, level, exp_current); + int64 left_over = exp_current - exp_to_next_level; + level++; + exp_to_next_level *= 2; + exp_current = left_over; + AddNewGuildEvent(GUILD_EVENT_GUILD_LEVEL_UP, "The guild gained a level and is now level %u.", Timer::GetUnixTimeStamp(), true, level); + SendMessageToGuild(GUILD_EVENT_GUILD_LEVEL_UP, "The guild gained a level and is now level %u.", level); + + if (level % 10 == 0) { + if (level == 10) + strncpy(adjective, "bold", sizeof(adjective)); + else if (level == 20) + strncpy(adjective, "daring", sizeof(adjective)); + else if (level == 30) + strncpy(adjective, "gallant", sizeof(adjective)); + else if (level == 40) + strncpy(adjective, "noble", sizeof(adjective)); + else if (level == 50) + strncpy(adjective, "heroic", sizeof(adjective)); + else if (level == 60) + strncpy(adjective, "lordly", sizeof(adjective)); + else if (level == 70) + strncpy(adjective, "legendary", sizeof(adjective)); + else if (level == 80) + strncpy(adjective, "epic", sizeof(adjective)); + else + strncpy(adjective, "too uber for cheerios", sizeof(adjective) - 1); + sprintf(message, "The %s guild <%s> has attained level %u", adjective, name, level); + zone_list.HandleGlobalAnnouncement(message); + } + } + save_needed = true; + ret = true; + } + else if (exp < 0) { + } + if (ret && send_packet) + SendGuildUpdate(); +} + +void Guild::SetEXPToNextLevel(int64 exp, bool send_packet) { + + exp_to_next_level = exp; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Guild XP to next level: %ul", exp_to_next_level); + SendGuildUpdate(); + save_needed = true; + } +} + +void Guild::SetRecruitingShortDesc(const char* new_desc, bool send_packet) { + + assert(new_desc); + + recruiting_short_desc = string(new_desc); + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting short desc:\n%s", recruiting_short_desc.c_str()); + SendGuildUpdate(); + recruiting_save_needed = true; + } +} + +void Guild::SetRecruitingFullDesc(const char* new_desc, bool send_packet) { + + assert(new_desc); + + recruiting_full_desc = string(new_desc); + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting full desc:\n%s", recruiting_full_desc.c_str()); + SendGuildUpdate(); + recruiting_save_needed = true; + } +} + +void Guild::SetRecruitingMinLevel(int8 new_level, bool send_packet) { + + recruiting_min_level = new_level; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting min_level: %i", recruiting_min_level); + SendGuildUpdate(); + recruiting_save_needed = true; + } +} + +void Guild::SetRecruitingPlayStyle(int8 new_play_style, bool send_packet) { + + recruiting_play_style = new_play_style; + if (send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting play style: %i", recruiting_play_style); + SendGuildUpdate(); + recruiting_save_needed = true; + } +} + +bool Guild::SetRecruitingDescTag(int8 index, int8 tag, bool send_packet) { + + bool ret = false; + if (index <= 3) { + recruiting_desc_tags.Put(index, tag); + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Recruiting descriptive tag index: %i, tag: %i", index, tag); + SendGuildUpdate(); + recruiting_save_needed = true; + } + return ret; +} + +int8 Guild::GetRecruitingDescTag(int8 index) { + + int8 ret = 0; + if (index <= 3) + ret = recruiting_desc_tags.Get(index); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Recruiting descriptive tag index: %i, value: %i", index, ret); + return ret; +} + +bool Guild::SetPermission(int8 rank, int8 permission, int8 value, bool send_packet) { + + bool ret = false; + if (value == 0 || value == 1) { + if (permissions.count(rank) == 0) + permissions.Put(rank, new MutexMap); + permissions.Get(rank)->Put(permission, value); + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Permissions - Rank: %i, Permission: %i, Value: %i", rank, permission, value); + SendGuildUpdate(); + ranks_save_needed = true; + } + return ret; +} + +int8 Guild::GetPermission(int8 rank, int8 permission) { + + int8 ret = 0; + if (permissions.count(rank) > 0 && permissions.Get(rank)->count(permission) > 0) + ret = permissions.Get(rank)->Get(permission); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Permissions - Rank: %i, Permission: %i, Value: %i", rank, permission, ret); + return ret; +} + +bool Guild::SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet) { + + bool ret = false; + if ((category == GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY || category == GUILD_EVENT_FILTER_CATEGORY_BROADCAST) && (value == 0 || value == 1)) { + if (event_filters.count(event_id) == 0) + event_filters.Put(event_id, new MutexMap); + event_filters.Get(event_id)->Put(category, value); + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Set Guild Event Filter - EventID: %i, Category: %i, Value: %i", event_id, category, value); + SendGuildUpdate(); + event_filters_save_needed = true; + } + return ret; +} + +int8 Guild::GetEventFilter(int8 event_id, int8 category) { + + int8 ret = 0; + if (event_filters.count(event_id) > 0 && event_filters.Get(event_id)->count(category) > 0) { + ret = event_filters.Get(event_id)->Get(category); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Event Filter - EventID: %i, Category: %i, Value: %i", event_id, category, ret); + } + return ret; +} + +int32 Guild::GetNumUniqueAccounts() { + + map::iterator itr; + map accounts; + + mMembers.readlock(__FUNCTION__, __LINE__); + if (members.size() > 0) { + for (itr = members.begin(); itr != members.end(); itr++) { + if (accounts.count(itr->second->account_id) == 0) + accounts[itr->second->account_id] = true; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Found %i Unique Account(s) in Guild", accounts.size()); + return accounts.size(); +} + +int32 Guild::GetNumRecruiters() { + + map::iterator itr; + int32 ret = 0; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (itr->second->recruiter_id > 0 && (itr->second->member_flags & GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD)) + ret++; + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Found %i Recruiter(s) in Guild", ret); + return ret; +} + +int32 Guild::GetNextRecruiterID() { + + map::iterator itr; + map tmp; + int32 i, ret = 0; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (itr->second->recruiter_id > 0) + tmp[itr->second->recruiter_id] = true; + } + for (i = 1; i < 0xFFFFFFFF; i++) { + if (tmp.count(i) == 0) { + ret = i; + break; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Next Guild Recruiter ID: %i", ret); + return ret; +} + +int64 Guild::GetNextEventID() { + + GuildEvent *ge; + int64 ret = 1; + + if (guild_events.size() > 0) { + ge = guild_events.front(); + ret = ge->event_id + 1; + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Next Guild Event ID: %i", ret); + return ret; +} + +GuildMember * Guild::GetGuildMemberOnline(Client *client) { + + map::iterator itr; + GuildMember *ret = 0; + + assert(client); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (itr->second->character_id == client->GetCharacterID()) { + ret = itr->second; + break; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 3, "Guilds", "Guild Member Online: %s", ret->name); + return ret; +} + +GuildMember * Guild::GetGuildMember(Player *player) { + + assert(player); + return GetGuildMember(player->GetCharacterID()); +} + +GuildMember * Guild::GetGuildMember(int32 character_id) { + + GuildMember *ret = 0; + + mMembers.readlock(__FUNCTION__, __LINE__); + if (members.count(character_id) > 0) + ret = members[character_id]; + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "%s: %i", __FUNCTION__, character_id); + return ret; +} + +GuildMember * Guild::GetGuildMember(const char *player_name) { + + map::iterator itr; + GuildMember *ret = 0; + + assert(player_name); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!strncmp(player_name, itr->second->name, sizeof(itr->second->name))) { + ret = itr->second; + break; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "%s: %s", __FUNCTION__, player_name); + return ret; +} + +vector * Guild::GetGuildRecruiters() { + + map::iterator itr; + vector *ret = 0; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (itr->second->recruiter_id > 0 && (itr->second->member_flags & GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD)) { + if (!ret) + ret = new vector; + ret->push_back(itr->second); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Recruiter '%s' (%i)", itr->second->name, itr->second->recruiter_id); + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +GuildEvent * Guild::GetGuildEvent(int64 event_id) { + + deque::iterator itr; + GuildEvent* ret = 0; + + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) { + if ((*itr)->event_id == event_id) { + ret = *itr; + break; + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Get Guild Event: %s (%lli)", ret->description.c_str(), ret->event_id); + return ret; +} + +bool Guild::SetRankName(int8 rank, const char *name, bool send_packet) { + + assert(name); + + ranks.Put(rank, string(name)); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set Guild Rank Name: %s (%i)", name, rank); + SendGuildUpdate(); + ranks_save_needed = true; + } + return true; +} + +const char * Guild::GetRankName(int8 rank) { + + if (ranks.count(rank) > 0) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Get Guild Rank Name: %s (%i)", ranks.Get(rank).c_str(), rank); + return ranks.Get(rank).c_str(); + } + return 0; +} + +bool Guild::SetRecruitingFlag(int8 flag, int8 value, bool send_packet) { + + bool ret = false; + + if (recruiting_flags.count(flag) > 0 && (value == 0 || value == 1)) { + recruiting_flags.Put(flag, value); + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set Guild Recruiting Flag: %i, Value: %i", flag, value); + SendGuildUpdate(); + recruiting_save_needed = true; + } + return ret; +} + +int8 Guild::GetRecruitingFlag(int8 flag) { + + int8 value = 0; + if (recruiting_flags.count(flag) > 0) + value = recruiting_flags.Get(flag); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Get Guild Recruiting Flag: %i, Value: %i", flag, value); + return value; +} + +bool Guild::SetGuildRecruiter(Client* client, const char* name, bool value, bool send_packet) { + + GuildMember *gm; + const char *awarder_name; + + assert(client); + assert(name); + + if (!(gm = GetGuildMember(name))) + return false; + + awarder_name = client->GetPlayer()->GetName(); + if (value) { + gm->recruiter_id = GetNextRecruiterID(); + AddNewGuildEvent(GUILD_EVENT_BECOMES_RECRUITER, "%s awarded %s Guild Recruiting Permission.", Timer::GetUnixTimeStamp(), true, awarder_name, name); + SendMessageToGuild(GUILD_EVENT_BECOMES_RECRUITER, "%s awarded %s Guild Recruiting Permission.", awarder_name, name); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s makes %s a guild recruiter.", awarder_name, name); + } + else { + gm->recruiter_id = 0; + AddNewGuildEvent(GUILD_EVENT_NO_LONGER_RECRUITER, "%s revoked %s's Guild Recruiting Permission.", Timer::GetUnixTimeStamp(), true, awarder_name, name); + SendMessageToGuild(GUILD_EVENT_NO_LONGER_RECRUITER, "%s revoked %s's Guild Recruiting Permission.", awarder_name, name); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s removes %s from guild recruiters.", awarder_name, name); + } + + if (send_packet) { + SendGuildMember(gm); + member_save_needed = true; + } + + return true; +} + +bool Guild::SetGuildRecruiterDescription(Client *client, const char *description, bool send_packet) { + + GuildMember *gm; + + assert(client); + assert(description); + + if (!(gm = GetGuildMember(client->GetPlayer()))) + return false; + + gm->recruiter_description = string(description); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set guild recruiter description:\n%s", gm->recruiter_description.c_str()); + SendGuildRecruiterInfo(client, client->GetPlayer()); + member_save_needed = true; + } + + return true; +} + +bool Guild::ToggleGuildRecruiterAdventureClass(Client *client, bool send_packet) { + + GuildMember *gm; + + assert(client); + + if (!(gm = GetGuildMember(client->GetPlayer()))) + return false; + + gm->recruiting_show_adventure_class = (gm->recruiting_show_adventure_class == 0 ? 1 : 0); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Toggle guild recruiter adventure class = %i", gm->recruiting_show_adventure_class); + SendGuildRecruiterInfo(client, client->GetPlayer()); + } + + return true; +} + +bool Guild::SetGuildMemberNote(const char *name, const char *note, bool send_packet) { + + GuildMember *gm; + + assert(name); + assert(note); + + if (!(gm = GetGuildMember(name))) + return false; + + gm->note = string(note); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set guild member note:\n%s", gm->note.c_str()); + SendGuildMember(gm); + member_save_needed = true; + } + + return true; +} + +bool Guild::SetGuildOfficerNote(const char *name, const char *note, bool send_packet) { + + GuildMember *gm; + + assert(name); + assert(note); + + if (!(gm = GetGuildMember(name))) + return false; + + gm->officer_note = string(note); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Set guild officer note:\n%s", gm->officer_note.c_str()); + SendGuildMember(gm); + member_save_needed = true; + } + + return true; +} + +bool Guild::AddNewGuildMember(Client *client, const char *invited_by, int8 rank) { + + Player *player; + GuildMember *gm; + + assert(client); + + player = client->GetPlayer(); + assert(player); + + if (members.count(player->GetCharacterID()) == 0 && !player->GetGuild() && ((Player*)player)->GetClient()) { + gm = new GuildMember; + + gm->account_id = ((Player*)player)->GetClient()->GetAccountID(); + gm->character_id = player->GetCharacterID(); + strncpy(gm->name, player->GetName(), sizeof(gm->name)); + gm->guild_status = 0; + gm->points = 0.0; + gm->adventure_class = player->GetAdventureClass(); + gm->adventure_level = player->GetLevel(); + gm->tradeskill_class = player->GetTradeskillClass(); + gm->tradeskill_level = player->GetTSLevel(); + gm->rank = rank; + gm->zone = string(player->GetZone()->GetZoneDescription()); + gm->join_date = Timer::GetUnixTimeStamp(); + gm->last_login_date = gm->join_date; + gm->recruiter_id = 0; + gm->member_flags = GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + gm->recruiting_show_adventure_class = 1; + gm->recruiter_picture_data_size = 0; + gm->recruiter_picture_data = 0; + mMembers.writelock(__FUNCTION__, __LINE__); + members[player->GetCharacterID()] = gm; + mMembers.releasewritelock(__FUNCTION__, __LINE__); + player->SetGuild(this); + string subtitle; + subtitle.append("<").append(GetName()).append(">"); + player->SetSubTitle(subtitle.c_str()); + + if (invited_by) + client->SimpleMessage(CHANNEL_NARRATIVE, "You accept the invite into the guild."); + else { + client->SimpleMessage(CHANNEL_NARRATIVE, "You have formed the guild."); + LogWrite(GUILD__DEBUG, 0, "Guilds", "New Guild formed: %s", GetName()); + } + + client->PlaySound("ui_joined"); + SendGuildUpdate(client); + SendGuildMember(gm); + SendGuildMOTD(client); + SendGuildEventList(client); + SendGuildBankEventList(client); + SendAllGuildEvents(client); + SendGuildMemberList(client); + if(client->GetVersion() > 561) + client->GetCurrentZone()->SendUpdateTitles(client->GetPlayer()); + + if (invited_by) { + AddNewGuildEvent(GUILD_EVENT_MEMBER_JOINS, "%s has accepted %s's invitation to join %s.", Timer::GetUnixTimeStamp(), true, player->GetName(), invited_by, GetName()); + SendMessageToGuild(GUILD_EVENT_MEMBER_JOINS, "%s has accepted %s's invitation to join %s.", player->GetName(), invited_by, GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s invited %s to join guild: %s", invited_by, player->GetName(), GetName()); + } + + member_save_needed = true; + } + + return true; +} + +bool Guild::AddGuildMember(GuildMember *guild_member) { + + assert(guild_member); + + mMembers.writelock(__FUNCTION__, __LINE__); + assert(members.count(guild_member->character_id) == 0); + members[guild_member->character_id] = guild_member; + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + // spammy + LogWrite(GUILD__DEBUG, 5, "Guilds", "Added Player: %s (%i) to Guild: %s", guild_member->name, guild_member->character_id, GetName()); + + return true; +} + +void Guild::RemoveGuildMember(int32 character_id, bool send_packet) { + GuildMember *gm = 0; + Client *client; + + mMembers.writelock(__FUNCTION__, __LINE__); + if (members.count(character_id) > 0) { + gm = members[character_id]; + members.erase(gm->character_id); + } + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + if (gm) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Remove Player: %s (%i) from Guild: %s", members[character_id]->name, character_id, GetName()); + + if ((client = zone_list.GetClientByCharID(character_id))) { + client->GetPlayer()->SetGuild(0); + client->GetPlayer()->SetSubTitle(""); + client->GetCurrentZone()->SendUpdateTitles(client->GetPlayer()); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You were removed from the guild."); + } + + database.DeleteGuildMember(this, gm->character_id); + + if (send_packet) { + SendGuildMemberLeave(gm->character_id); + SendGuildUpdate(); + SendGuildMemberList(); + } + + AddNewGuildEvent(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", Timer::GetUnixTimeStamp(), true, gm->name); + SendMessageToGuild(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", gm->name); + + safe_delete_array(gm->recruiter_picture_data); + safe_delete(gm); + } +} + +void Guild::RemoveAllGuildMembers() { + map::iterator itr; + Client *client; + + mMembers.writelock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) { + client->GetPlayer()->SetGuild(0); + client->GetPlayer()->SetSubTitle(""); + } + safe_delete(itr->second); + } + members.clear(); + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + LogWrite(GUILD__DEBUG, 0, "Guilds", "Removed ALL members from Guild: %s", GetName()); + +} + +bool Guild::DemoteGuildMember(Client *client, const char *name, bool send_packet) { + + GuildMember *gm; + const char *demoter_name; + bool ret = false; + + assert(client); + assert(name); + + mMembers.readlock(__FUNCTION__, __LINE__); + if ((gm = GetGuildMember(name)) && gm->rank != GUILD_RANK_RECRUIT) { + demoter_name = client->GetPlayer()->GetName(); + gm->rank++; + + AddNewGuildEvent(GUILD_EVENT_MEMBER_DEMOTED, "%s has demoted %s to %s.", Timer::GetUnixTimeStamp(), true, demoter_name, name, ranks.Get(gm->rank).c_str()); + SendMessageToGuild(GUILD_EVENT_MEMBER_DEMOTED, "%s has demoted %s to %s.", demoter_name, name, ranks.Get(gm->rank).c_str()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s demoted %s to %s in Guild: %s", demoter_name, name, ranks.Get(gm->rank).c_str(), GetName()); + + ret = true; + + if (send_packet) { + SendGuildMember(gm); + SendGuildMemberList(); + member_save_needed = true; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Guild::PromoteGuildMember(Client *client, const char *name, bool send_packet) { + + GuildMember *gm; + const char *promoter_name; + bool ret = false; + + assert(client); + assert(name); + + mMembers.readlock(__FUNCTION__, __LINE__); + if ((gm = GetGuildMember(name)) && gm->rank != GUILD_RANK_LEADER) { + promoter_name = client->GetPlayer()->GetName(); + gm->rank--; + + AddNewGuildEvent(GUILD_EVENT_MEMBER_PROMOTED, "%s has promoted %s to %s.", Timer::GetUnixTimeStamp(), true, promoter_name, name, ranks.Get(gm->rank).c_str()); + SendMessageToGuild(GUILD_EVENT_MEMBER_PROMOTED, "%s has promoted %s to %s.", promoter_name, name, ranks.Get(gm->rank).c_str()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s promoted %s to %s in Guild: %s", promoter_name, name, ranks.Get(gm->rank).c_str(), GetName()); + + if (send_packet) { + SendGuildMember(gm); + SendGuildMemberList(); + member_save_needed = true; + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Guild::KickGuildMember(Client *client, const char *name, bool send_packet) { + + GuildMember *gm; + Client *kicked_client; + const char *kicker_name; + + assert(client); + assert(name); + + if (!(gm = GetGuildMember(name))) + return false; + + kicker_name = client->GetPlayer()->GetName(); + + if (!strncmp(kicker_name, gm->name, sizeof(gm->name))) { + AddNewGuildEvent(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", Timer::GetUnixTimeStamp(), true, gm->name); + SendMessageToGuild(GUILD_EVENT_MEMBER_LEAVES, "%s left the guild.", gm->name); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Player: %s has left guild: %s", gm->name, GetName()); + } + else { + AddNewGuildEvent(GUILD_EVENT_MEMBER_LEAVES, "%s kicked %s from the guild.", Timer::GetUnixTimeStamp(), true, kicker_name, gm->name); + SendMessageToGuild(GUILD_EVENT_MEMBER_LEAVES, "%s kicked %s from the guild.", kicker_name, gm->name); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Player: %s was kicked from guild: %s by %s.", gm->name, GetName(), kicker_name); + } + + if ((kicked_client = zone_list.GetClientByCharID(gm->character_id))) { + kicked_client->GetPlayer()->SetGuild(0); + kicked_client->GetPlayer()->SetSubTitle(""); + if (!strncmp(client->GetPlayer()->GetName(), gm->name, sizeof(gm->name))) + kicked_client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You left the guild."); + else + kicked_client->Message(CHANNEL_COLOR_YELLOW, "You were kicked from the guild by %s.", kicker_name); + } + + mMembers.writelock(__FUNCTION__, __LINE__); + members.erase(gm->character_id); + mMembers.releasewritelock(__FUNCTION__, __LINE__); + + database.DeleteGuildMember(this, gm->character_id); + + if (send_packet) { + SendGuildMemberLeave(gm->character_id); + SendGuildUpdate(); + SendGuildMemberList(); + } + + safe_delete_array(gm->recruiter_picture_data); + safe_delete(gm); + + return true; +} + +bool Guild::InvitePlayer(Client *client, const char *name, bool send_packet) { + + Client *client_invite; + Player *player_invite; + PacketStruct *packet; + + assert(client); + assert(name); + + if (!(client_invite = zone_list.GetClientByCharName(string(name)))) { + client->Message(CHANNEL_NARRATIVE, "%s couldn't be found.", name); + LogWrite(GUILD__WARNING, 0, "Guilds", "Attempted to invite %s to guild %s: Player Not Found", name, GetName()); + return false; + } + + player_invite = client_invite->GetPlayer(); + + if (player_invite->GetGuild()) { + client->Message(CHANNEL_NARRATIVE, "%s is already in a guild.", player_invite->GetName()); + LogWrite(GUILD__WARNING, 0, "Guilds", "Attempted to invite %s to guild %s: Already in a guild", player_invite->GetName(), GetName()); + return false; + } + + if (client_invite->GetPendingGuildInvite()->guild) { + client->Message(CHANNEL_NARRATIVE, "%s is already considering joining a guild.", player_invite->GetName()); + LogWrite(GUILD__WARNING, 0, "Guilds", "Attempted to invite %s to guild %s: Pending Invite elsewhere", player_invite->GetName(), GetName()); + return false; + } + + if (!(packet = configReader.getStruct("WS_ReceiveOffer", client_invite->GetVersion()))) + return false; + + packet->setDataByName("type", 2); + packet->setMediumStringByName("name", client->GetPlayer()->GetName()); + packet->setDataByName("unknown2", 1); + packet->setMediumStringByName("guild_name", GetName()); + client_invite->QueuePacket(packet->serialize()); + + client->Message(CHANNEL_NARRATIVE, "You have invited %s to join %s.", player_invite->GetName(), GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s invited %s to guild %s", client->GetPlayer()->GetName(), player_invite->GetName(), GetName()); + + client_invite->SetPendingGuildInvite(this, client->GetPlayer()); + + safe_delete(packet); + return true; +} + +bool Guild::AddPointsToAll(Client *client, float points, const char *comment, bool send_packet) { + + map::iterator itr; + vector character_ids; + GuildMember *gm; + Client *client_to; + + assert(client); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + gm = itr->second; + + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_RECEIVE_POINTS)) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not allowed to receive points! Skipping...", gm->character_id); + continue; + } + + gm->points += points; + character_ids.push_back(gm->character_id); + + AddPointHistory(gm, Timer::GetUnixTimeStamp(), client->GetPlayer()->GetName(), points, comment); + if ((client_to = zone_list.GetClientByCharID(gm->character_id))) + { + client_to->Message(CHANNEL_GUILD_CHAT, "%s increased your guild member points by %.1f.", client->GetPlayer()->GetName(), points); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "\tAwarded By: %s +%.1f pts to Player: %s", client->GetPlayer()->GetName(), points, gm->name); + } + + SendGuildMember(gm); //tmp + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + client->Message(CHANNEL_GUILD_CHAT, "Points modified for %u guild members.", character_ids.size()); + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s modified points for %u members. Reason: %s", client->GetPlayer()->GetName(), character_ids.size(), points, comment); + SendGuildModification(points, &character_ids); + member_save_needed = true; + points_history_save_needed = true; + } + + return true; +} + +bool Guild::AddPointsToAllOnline(Client *client, float points, const char *comment, bool send_packet) { + + map::iterator itr; + vector character_ids; + GuildMember *gm; + Client *client_to; + + assert(client); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + gm = itr->second; + + if (!(client_to = zone_list.GetClientByCharID(gm->character_id))) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not online to receive points! Skipping...", gm->character_id); + continue; + } + + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_RECEIVE_POINTS)) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not allowed to receive points! Skipping...", gm->character_id); + continue; + } + + gm->points += points; + character_ids.push_back(gm->character_id); + + AddPointHistory(gm, Timer::GetUnixTimeStamp(), client->GetPlayer()->GetName(), points, comment); + client_to->Message(CHANNEL_GUILD_CHAT, "%s increased your guild member points by %.1f.", client->GetPlayer()->GetName(), points); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "\tAwarded By: %s +%.1f pts to Player: %s", client->GetPlayer()->GetName(), points, gm->name); + + SendGuildMember(gm); //tmp + + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + client->Message(CHANNEL_GUILD_CHAT, "Points modified for %u guild members.", character_ids.size()); + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s modified points for %u members. Reason: %s", client->GetPlayer()->GetName(), character_ids.size(), points, comment); + SendGuildModification(points, &character_ids); + member_save_needed = true; + points_history_save_needed = true; + } + + return true; +} + +bool Guild::AddPointsToGroup(Client *client, float points, const char *comment, bool send_packet) { + + deque::iterator itr; + deque* group_members; + vector character_ids; + GroupMemberInfo *gmi; + GuildMember *gm; + + assert(client); + + if (!client->GetPlayer()->GetGroupMemberInfo()) { + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Cannot assign points because you aren't in a group."); + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s tried to assign points for group and failed: Not in a group", client->GetPlayer()->GetName()); + return false; + } + + mMembers.readlock(__FUNCTION__, __LINE__); + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(client->GetPlayer()->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* group_members = group->GetMembers(); + for (itr = group_members->begin(); itr != group_members->end(); itr++) { + gmi = *itr; + + if (!gmi->client) + continue; + + if (gmi->client->GetPlayer()->GetGuild() != this) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not in guild to receive group points! Skipping...", gmi->client->GetPlayer()->GetCharacterID()); + continue; + } + + if (!(gm = members[gmi->client->GetCharacterID()]) || !permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_RECEIVE_POINTS)) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not allowed to receive points! Skipping...", gm->character_id); + continue; + } + + gm->points += points; + character_ids.push_back(gm->character_id); + + AddPointHistory(gm, Timer::GetUnixTimeStamp(), client->GetPlayer()->GetName(), points, comment); + gmi->client->Message(CHANNEL_GUILD_CHAT, "%s increased your guild member points by %.1f.", client->GetPlayer()->GetName(), points); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "\tAwarded By: %s +%.1f pts to Player: %s", client->GetPlayer()->GetName(), points, gm->name); + + SendGuildMember(gm); //tmp + + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + client->Message(CHANNEL_GUILD_CHAT, "Points modified for %u guild members.", character_ids.size()); + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s modified points for %u members. Reason: %s", client->GetPlayer()->GetName(), character_ids.size(), points, comment); + SendGuildModification(points, &character_ids); + member_save_needed = true; + points_history_save_needed = true; + } + + return true; +} + +bool Guild::AddPointsToRaid(Client *client, float points, const char *comment, bool send_packet) { + + assert(client); + LogWrite(MISC__TODO, 1, "TODO", "Implement Raiding\n%s, %s, %i", __FILE__, __FUNCTION__, __LINE__); + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Cannot assign points because you aren't in a raid."); + return false; +} + +bool Guild::AddPointsToGuildMember(Client *client, float points, const char *name, const char *comment, bool send_packet) { + + vector character_ids; + GuildMember *gm; + Client *client_to; + + assert(client); + assert(name); + + if (!(gm = GetGuildMember(name))) + return false; + + mMembers.readlock(__FUNCTION__, __LINE__); + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_RECEIVE_POINTS)) { + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "PlayerID: %i not allowed to receive points! Skipping...", gm->character_id); + client->Message(CHANNEL_GUILD_CHAT, "%s does not have permission to receive guild points.", gm->name); + return false; + } + + gm->points += points; + character_ids.push_back(gm->character_id); + + if ((client_to = zone_list.GetClientByCharID(gm->character_id))) { + client_to->Message(CHANNEL_GUILD_CHAT, "%s increased your guild member points by %.1f.", client->GetPlayer()->GetName(), points); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 0, "Guilds", "\tAwarded By: %s +%.1f pts to Player: %s", client->GetPlayer()->GetName(), points, gm->name); + } + + LogWrite(GUILD__DEBUG, 0, "Guilds", "%s modified points for 1 guild member. Reason: %s", client->GetPlayer()->GetName(), comment); + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Points modified for 1 guild member."); + AddPointHistory(gm, Timer::GetUnixTimeStamp(), client->GetPlayer()->GetName(), points, comment); + + if (send_packet) { + SendGuildMember(gm); //tmp + + SendGuildModification(points, &character_ids); + member_save_needed = true; + points_history_save_needed = true; + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + +bool Guild::AddPointHistory(GuildMember *guild_member, int32 date, const char *modified_by, float points, const char *comment, bool new_point_history) { + PointHistory *ph, *ph_delete; + deque *ph_list; + + assert(guild_member); + assert(modified_by); + + ph = new PointHistory; + ph->date = date; + ph->modified_by = string(modified_by); + ph->points = points; + if (comment) + ph->comment = string(comment); + ph->saved_needed = new_point_history; + + mMembers.readlock(__FUNCTION__, __LINE__); + ph_list = &guild_member->point_history; + if (ph_list->size() == GUILD_MAX_POINT_HISTORY) { + ph_delete = ph_list->back(); + database.DeleteGuildPointHistory(this, guild_member->character_id, ph_delete); + safe_delete(ph_delete); + ph_list->pop_back(); + } + + ph_list->push_front(ph); + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + +void Guild::ViewGuildMemberPoints(Client *client, const char * name) { + + deque *ph_list; + deque::iterator itr; + PointHistory *ph; + GuildMember *gm; + PacketStruct *packet; + int32 i; + + assert(client); + assert(name); + + if (!(gm = GetGuildMember(name))) + return; + + if (!(packet = configReader.getStruct("WS_RequestGuildEventDetails", client->GetVersion()))) + return; + + mMembers.readlock(__FUNCTION__, __LINE__); + ph_list = &gm->point_history; + i = 0; + + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("character_id", client->GetCharacterID()); + packet->setDataByName("guild_id", id); + packet->setArrayLengthByName("num_events", ph_list->size()); + + // this log entry may be excessive... test it out. + LogWrite(GUILD__DEBUG, 0, "Guilds", "account_id: %i, character_id: %i, guild_id: %i, num_events: %i", client->GetAccountID(), client->GetCharacterID(), id, ph_list->size()); + + for (itr = ph_list->begin(); itr != ph_list->end(); itr++) { + ph = *itr; + packet->setArrayDataByName("date", ph->date, i); + packet->setArrayDataByName("modified_by", ph->modified_by.c_str(), i); + packet->setArrayDataByName("comment", ph->comment.c_str(), i); + packet->setArrayDataByName("points", ph->points, i); + // this log entry may be excessive... test it out. + LogWrite(GUILD__DEBUG, 0, "Guilds", "date: %i, modified_by: %i, comment: %i, points: %.1f", ph->date, ph->modified_by.c_str(), ph->comment.c_str(), ph->points); + i++; + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + //DumpPacket(packet->serialize()); + + client->QueuePacket(packet->serialize()); + safe_delete(packet); +} + +bool Guild::ChangeMemberFlag(Client *client, int8 member_flag, int8 value, bool send_packet) { + + GuildMember *gm; + bool ret = false; + + assert (client); + + if (!(gm = GetGuildMemberOnline(client))) + return false; + + mMembers.readlock(__FUNCTION__, __LINE__); + switch (member_flag) { + case GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD: { + if (value > 0 && !(gm->member_flags & GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD)) { + gm->member_flags += GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD; + ret = true; + } + else if (value == 0 && gm->member_flags & GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD) { + gm->member_flags -= GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD; + ret = true; + } + break; + } + case GUILD_MEMBER_FLAGS_NOTIFY_LOGINS: { + if (value > 0 && !(gm->member_flags & GUILD_MEMBER_FLAGS_NOTIFY_LOGINS)) { + gm->member_flags += GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Guild online notifications are now enabled."); + ret = true; + } + else if (value == 0 && gm->member_flags & GUILD_MEMBER_FLAGS_NOTIFY_LOGINS) { + gm->member_flags -= GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Guild online notifications are now disabled."); + ret = true; + } + break; + } + case GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS: { + if (value > 1 && !(gm->member_flags & GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS)) { + gm->member_flags += GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS; + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Guild events are now disabled for this character."); + ret = true; + } + else if (value == 0 && gm->member_flags & GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS) { + gm->member_flags -= GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS; + client->SimpleMessage(CHANNEL_GUILD_CHAT, "Guild events are now enabled for this character."); + ret = true; + } + break; + } + default: + break; + } + + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Member Flag '%i' changed to %i", member_flag, value); + member_save_needed = true; + SendGuildMember(client, gm); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} +bool Guild::UpdateGuildStatus(Player *player ,int32 Status) { + GuildMember *gm; + assert(player); + assert(members.count(player->GetCharacterID()) > 0); + mMembers.readlock(__FUNCTION__, __LINE__); + gm = members[player->GetCharacterID()]; + gm->guild_status += Status; + mMembers.releasereadlock(__FUNCTION__, __LINE__); + member_save_needed = true; + return true; +} +bool Guild::UpdateGuildMemberInfo(Player *player) { + + GuildMember *gm; + + assert(player); + assert(members.count(player->GetCharacterID()) > 0); + + LogWrite(GUILD__DEBUG, 0, "Guilds", "Updating Guild Member Info for Player: %i", player->GetCharacterID()); + + mMembers.readlock(__FUNCTION__, __LINE__); + gm = members[player->GetCharacterID()]; + gm->adventure_class = player->GetAdventureClass(); + gm->adventure_level = player->GetLevel(); + gm->tradeskill_class = player->GetTradeskillClass(); + gm->tradeskill_level = player->GetTSLevel(); + gm->zone = string(player->GetZone()->GetZoneDescription()); + gm->last_login_date = database.GetCharacterTimeStamp(player->GetCharacterID()); + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + +void Guild::AddGuildEvent(int64 event_id, int32 type, const char *description, int32 date, int8 locked) { + + LogWrite(GUILD__DEBUG, 3, "Guilds", "Guild: %s", GetName()); + LogWrite(GUILD__DEBUG, 3, "Guilds", "Add Guild Event: %lli, %i, %s", event_id, type, string(description).c_str()); + + GuildEvent *ge; + + assert(description); +// assert(event_filters.Get(type)->Get(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY)); + assert(guild_events.size() < GUILD_MAX_EVENTS); + + ge = new GuildEvent; + ge->event_id = event_id; + ge->type = type; + ge->description = string(description); + ge->date = date; + ge->locked = locked; + ge->save_needed = false; + guild_events.push_back(ge); +} + +void Guild::AddNewGuildEvent(int32 type, const char *description, int32 date, bool send_packet, ...) { + + deque::reverse_iterator itr; + GuildEvent *ge, *current_ge; + char buffer[4096]; + va_list argptr; + + assert(description); + + va_start(argptr, send_packet); + vsnprintf(buffer, sizeof(buffer), description, argptr); + va_end(argptr); + + ge = new GuildEvent; + ge->event_id = GetNextEventID(); + ge->type = type; + ge->description = string(buffer); + ge->date = date; + ge->locked = 0; + + if (!event_filters.Get(type)->Get(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY)) { + database.SaveHiddenGuildEvent(this, ge); + return; + } + + if (guild_events.size() == GUILD_MAX_EVENTS) { + for (itr = guild_events.rbegin(); itr != guild_events.rend(); itr++) { + current_ge = *itr; + + if (current_ge->locked == 0) { + database.ArchiveGuildEvent(this, current_ge); + safe_delete(current_ge); + guild_events.erase(--itr.base()); + guild_events.push_front(ge); + break; + } + } + } + else + guild_events.push_front(ge); + + if (send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Some Add New Guild Event thing happened, not sure what..."); + SendNewGuildEvent(ge); + ge->save_needed = true; + events_save_needed = true; + } +} + +bool Guild::LockGuildEvent(int64 event_id, bool lock, bool send_packet) { + + bool ret = false; + GuildEvent* ge = GetGuildEvent(event_id); + if (ge) { + if (lock) { + ge->locked = 1; + if (send_packet) + SendGuildEventAction(GUILD_EVENT_ACTION_LOCK, ge); + } + else { + ge->locked = 0; + if (send_packet) + SendGuildEventAction(GUILD_EVENT_ACTION_UNLOCK, ge); + } + ret = true; + } + if (ret && send_packet) { + LogWrite(GUILD__DEBUG, 0, "Guilds", "Toggle guild event lock, EventID: %lli, value: %i", event_id, lock); + ge->save_needed = true; + events_save_needed = true; + } + return ret; +} + +bool Guild::DeleteGuildEvent(int64 event_id, bool send_packet) { + + bool ret = false; + deque::iterator itr; + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) { + GuildEvent* ge = *itr; + if (ge->event_id == event_id) { + if (send_packet) + SendGuildEventAction(GUILD_EVENT_ACTION_DELETE, ge); + database.DeleteGuildEvent(this, ge->event_id); + safe_delete(ge); + guild_events.erase(itr); + ret = true; + break; + } + } + LogWrite(GUILD__DEBUG, 0, "Guilds", "Delete guild event, EventID: %lli", event_id); + return ret; +} + +int32 Guild::GetPermissionsPacketValue(int8 rank, int32 start, int32 end) { + + int32 ret = 0; + for (int32 i = start; i <= end; i++) { + if (permissions.count(rank) > 0 && permissions.Get(rank)->count(i) > 0 && permissions.Get(rank)->Get(i)) { + if (i >= 0 && i <= 31) + ret += (int32)pow(2.0, (double)i); + else if (i >= 32 && i <= 63) + ret += (int32)pow(2.0, (double)(i - 32)); + } + } + return ret; +} + +int32 Guild::GetEventFilterPacketValue(int8 category, int32 start, int32 end) { + + int32 ret = 0; + for (int32 i = start; i <= end; i++) { + if (event_filters.count(i) > 0 && event_filters.Get(i)->count(category) > 0 && event_filters.Get(i)->Get(category)) { + if (i >= 0 && i <= 31) + ret += (int32)pow(2.0, (double)i); + else if (i >= 32 && i <= 63) + ret += (int32)pow(2.0, (double)(i - 32)); + else if (i >= 64 && i <= 95) + ret += (int32)pow(2.0, (double)(i - 64)); + } + } + return ret; +} + +int8 Guild::GetRecruitingLookingForPacketValue() { + + int8 ret = 0; + MutexMap::iterator itr = recruiting_flags.begin(); + while (itr.Next()) { + if (itr.second) + ret += (int8)pow(2.0, (double)itr.first); + } + return ret; +} + +void Guild::SendGuildMOTD(Client* client) { + + if (client && strlen(motd) > 0) + client->Message(CHANNEL_GUILD_MOTD, "Guild MOTD: %s", motd); + + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild MOTD.\n'%s'", motd); +} + +void Guild::SendGuildEventList() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildEventList(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild Event List (%s).", __FUNCTION__); +} + +void Guild::SendGuildEventList(Client* client) { + + if (client) { + PacketStruct* packet = configReader.getStruct("WS_GuildEventList", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setArrayLengthByName("num_events", guild_events.size()); + deque::iterator itr; + int32 i = 0; + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) { + packet->setArrayDataByName("event_id", (*itr)->event_id, i); + packet->setArrayDataByName("locked", (*itr)->locked, i); + i++; + } + client->QueuePacket(packet->serialize()); + //DumpPacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild Event List (%s).", __FUNCTION__); +} + +void Guild::SendGuildEventDetails() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildEventDetails(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild Event Details (%s).", __FUNCTION__); +} + +void Guild::SendGuildEventDetails(Client* client) { + + if (client) { + PacketStruct* packet = configReader.getStruct("WS_GuildEventDetails", client->GetVersion()); + if (packet) { + deque::iterator itr; + int32 i = 0; + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) { + packet->setArrayDataByName("event_id", (*itr)->event_id, i); + i++; + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild Event Details (%s).", __FUNCTION__); +} + +void Guild::SendAllGuildEvents() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendAllGuildEvents(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent ALL guild Events (%s).", __FUNCTION__); +} + +void Guild::SendAllGuildEvents(Client* client) { + + if (client) { + deque::iterator itr; + for (itr = guild_events.begin(); itr != guild_events.end(); itr++) + SendOldGuildEvent(client, *itr); + } + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent ALL guild Events (%s).", __FUNCTION__); +} + +void Guild::SendOldGuildEvent(Client* client, GuildEvent* guild_event) { + + if (client && guild_event) { + PacketStruct* packet = configReader.getStruct("WS_RequestGuildInfo", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("event_id", guild_event->event_id); + packet->setDataByName("date", guild_event->date); + packet->setDataByName("type", guild_event->type); + packet->setMediumStringByName("description", guild_event->description.c_str()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 3, "Guilds", "Sent OLD guild Events."); +} + +void Guild::SendNewGuildEvent(GuildEvent* guild_event) { + + map::iterator itr; + Client *client; + + assert (guild_event); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendNewGuildEvent(client, guild_event); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent NEW guild Events. (%s)", __FUNCTION__); +} + +void Guild::SendNewGuildEvent(Client* client, GuildEvent* guild_event) { + + if (client && guild_event) { + PacketStruct* packet = configReader.getStruct("WS_GuildEventAdd", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("event_id", guild_event->event_id); + packet->setDataByName("type", guild_event->type); + packet->setDataByName("date", guild_event->date); + packet->setDataByName("description", guild_event->description.c_str()); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent NEW guild Events. (%s)", __FUNCTION__); +} + +void Guild::SendGuildEventAction(int8 action, GuildEvent* guild_event) { + + map::iterator itr; + Client *client; + + assert(guild_event); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildEventAction(client, action, guild_event); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild events Action. (%s)", __FUNCTION__); +} + +void Guild::SendGuildEventAction(Client* client, int8 action, GuildEvent* guild_event) { + + if (guild_event) { + PacketStruct* packet = configReader.getStruct("WS_GuildEventAction", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("event_id", guild_event->event_id); + packet->setDataByName("action", action); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild events Action. (%s)", __FUNCTION__); +} + +void Guild::SendGuildBankEventList() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildBankEventList(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild bank events list. (%s)", __FUNCTION__); +} + +void Guild::SendGuildBankEventList(Client* client) { + + if (client) { + for (int32 i = 0; i < 4; i++) { + PacketStruct* packet = configReader.getStruct("WS_GuildBankEventList", client->GetVersion()); + if (packet) { + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("bank_number", i); + packet->setArrayLengthByName("num_events", banks[i].events.size()); + deque::iterator itr; + for (itr = banks[i].events.begin(); itr != banks[i].events.end(); itr++) + packet->setArrayDataByName("event_id", (*itr)->event_id, i); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild bank events list. (%s)", __FUNCTION__); +} + +void Guild::SendGuildUpdate() { + + map::iterator itr; + Client *client; + + LogWrite(GUILD__DEBUG, 1, "Guilds", "SendGuildUpdate to all guild member clients online... (%s)", __FUNCTION__); + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildUpdate(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void Guild::SendGuildUpdate(Client* client) { + + if (client) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "SendGuildUpdate to client online... (%s)", __FUNCTION__); + + PacketStruct* packet = configReader.getStruct("WS_GuildUpdate", client->GetVersion()); + if (packet) { + packet->setMediumStringByName("guild_name", GetName()); + packet->setMediumStringByName("guild_motd", motd); + packet->setDataByName("guild_id", id); + packet->setDataByName("guild_level", level); + packet->setDataByName("unknown", 1); + packet->setDataByName("formed_date", formed_date); + packet->setDataByName("unique_accounts", GetNumUniqueAccounts()); + packet->setDataByName("num_members", members.size()); + packet->setDataByName("exp_current", exp_current); + packet->setDataByName("exp_to_next_level", exp_to_next_level); + packet->setDataByName("event_filter_retain1", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, 0, 31)); + packet->setDataByName("event_filter_retain2", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, 32, 63)); + packet->setDataByName("event_filter_retain3", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, 63, 92)); + packet->setDataByName("event_filter_retain4", 0); + packet->setDataByName("event_filter_broadcast1", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_BROADCAST, 0, 31)); + packet->setDataByName("event_filter_broadcast2", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_BROADCAST, 32, 63)); + packet->setDataByName("event_filter_broadcast3", GetEventFilterPacketValue(GUILD_EVENT_FILTER_CATEGORY_BROADCAST, 64, 92)); + packet->setDataByName("event_filter_broadcast4", 0); + packet->setDataByName("recruiting_looking_for", GetRecruitingLookingForPacketValue()); + packet->setDataByName("recruiting_desc_tag1", GetRecruitingDescTag(0)); + packet->setDataByName("recruiting_desc_tag2", GetRecruitingDescTag(1)); + packet->setDataByName("recruiting_desc_tag3", GetRecruitingDescTag(2)); + packet->setDataByName("recruiting_desc_tag4", GetRecruitingDescTag(3)); + packet->setDataByName("recruiting_playstyle", recruiting_play_style); + packet->setDataByName("recruiting_min_level", recruiting_min_level); + packet->setMediumStringByName("recuiting_short_description", recruiting_short_desc.c_str()); + packet->setMediumStringByName("recruiting_full_description", recruiting_full_desc.c_str()); + packet->setMediumStringByName("rank0_name", ranks.Get(GUILD_RANK_LEADER).c_str()); + packet->setDataByName("rank0_permissions1", GetPermissionsPacketValue(GUILD_RANK_LEADER, 0, 31)); + packet->setDataByName("rank0_permissions2", GetPermissionsPacketValue(GUILD_RANK_LEADER, 32, 44)); + packet->setDataByName("rank0_permissions_unused", 0); + packet->setMediumStringByName("rank1_name", ranks.Get(GUILD_RANK_SENIOR_OFFICER).c_str()); + packet->setDataByName("rank1_permissions1", GetPermissionsPacketValue(GUILD_RANK_SENIOR_OFFICER, 0, 31)); + packet->setDataByName("rank1_permissions2", GetPermissionsPacketValue(GUILD_RANK_SENIOR_OFFICER, 32, 44)); + packet->setDataByName("rank1_permissions_unused", 0); + packet->setMediumStringByName("rank2_name", ranks.Get(GUILD_RANK_OFFICER).c_str()); + packet->setDataByName("rank2_permissions1", GetPermissionsPacketValue(GUILD_RANK_OFFICER, 0, 31)); + packet->setDataByName("rank2_permissions2", GetPermissionsPacketValue(GUILD_RANK_OFFICER, 32, 44)); + packet->setDataByName("rank2_permissions_unused", 0); + packet->setMediumStringByName("rank3_name", ranks.Get(GUILD_RANK_SENIOR_MEMBER).c_str()); + packet->setDataByName("rank3_permissions1", GetPermissionsPacketValue(GUILD_RANK_SENIOR_MEMBER, 0, 31)); + packet->setDataByName("rank3_permissions2", GetPermissionsPacketValue(GUILD_RANK_SENIOR_MEMBER, 32, 44)); + packet->setDataByName("rank3_permissions_unused", 0); + packet->setMediumStringByName("rank4_name", ranks.Get(GUILD_RANK_MEMBER).c_str()); + packet->setDataByName("rank4_permissions1", GetPermissionsPacketValue(GUILD_RANK_MEMBER, 0, 31)); + packet->setDataByName("rank4_permissions2", GetPermissionsPacketValue(GUILD_RANK_MEMBER, 32, 44)); + packet->setDataByName("rank4_permissions_unused", 0); + packet->setMediumStringByName("rank5_name", ranks.Get(GUILD_RANK_JUNIOR_MEMBER).c_str()); + packet->setDataByName("rank5_permissions1", GetPermissionsPacketValue(GUILD_RANK_JUNIOR_MEMBER, 0, 31)); + packet->setDataByName("rank5_permissions2", GetPermissionsPacketValue(GUILD_RANK_JUNIOR_MEMBER, 32, 44)); + packet->setDataByName("rank5_permissions_unused", 0); + packet->setMediumStringByName("rank6_name", ranks.Get(GUILD_RANK_INITIATE).c_str()); + packet->setDataByName("rank6_permissions1", GetPermissionsPacketValue(GUILD_RANK_INITIATE, 0, 31)); + packet->setDataByName("rank6_permissions2", GetPermissionsPacketValue(GUILD_RANK_INITIATE, 32, 44)); + packet->setDataByName("rank6_permissions_unused", 0); + packet->setMediumStringByName("rank7_name", ranks.Get(GUILD_RANK_RECRUIT).c_str()); + packet->setDataByName("rank7_permissions1", GetPermissionsPacketValue(GUILD_RANK_RECRUIT, 0, 31)); + packet->setDataByName("rank7_permissions2", GetPermissionsPacketValue(GUILD_RANK_RECRUIT, 32, 44)); + packet->setDataByName("rank7_permissions_unused", 0); + packet->setMediumStringByName("bank1_name", banks[0].name.c_str()); + packet->setMediumStringByName("bank2_name", banks[1].name.c_str()); + packet->setMediumStringByName("bank3_name", banks[2].name.c_str()); + packet->setMediumStringByName("bank4_name", banks[3].name.c_str()); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Guild::SendGuildMemberList() { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildMemberList(client); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member list to all clients."); +} + +void Guild::SendGuildMemberList(Client* client) { + + map::iterator itr; + GuildMember *gm; + + if (client) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member list to a client."); + PacketStruct* packet = configReader.getStruct("WS_GuildMembershipResponse", client->GetVersion()); + if (packet) { + packet->setDataByName("guild_id", id); + packet->setDataByName("character_id_to", client->GetCharacterID()); + + mMembers.readlock(__FUNCTION__, __LINE__); + packet->setArrayLengthByName("num_members", members.size()); + int32 i = 0; + for (itr = members.begin(); itr != members.end(); itr++) { + gm = itr->second; + packet->setArrayDataByName("account_id", gm->account_id, i); + packet->setArrayDataByName("character_id", gm->character_id, i); + packet->setArrayDataByName("name", gm->name, i); + packet->setArrayDataByName("unknown2", 0, i); + packet->setArrayDataByName("unknown3", 1, i); + packet->setArrayDataByName("adventure_class", gm->adventure_class, i); + packet->setArrayDataByName("adventure_level", gm->adventure_level, i); + packet->setArrayDataByName("tradeskill_class", gm->tradeskill_class, i); + packet->setArrayDataByName("tradeskill_level", gm->tradeskill_level, i); + packet->setArrayDataByName("rank", gm->rank, i); + packet->setArrayDataByName("member_flags", gm->member_flags, i); + packet->setArrayDataByName("join_date", gm->join_date, i); + packet->setArrayDataByName("guild_status", gm->guild_status, i); + packet->setArrayDataByName("last_login", gm->last_login_date, i); + packet->setArrayDataByName("recruiter_id", gm->recruiter_id, i); + packet->setArrayDataByName("points", gm->points, i); + if (zone_list.GetClientByCharID(gm->character_id)) + packet->setArrayDataByName("zone", gm->zone.c_str(), i); + packet->setArrayDataByName("note", gm->note.c_str(), i); + packet->setArrayDataByName("officer_note", gm->officer_note.c_str(), i); + i++; + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Guild::SendGuildMember(Player* player, bool include_zone) { + + map::iterator itr; + Client *client; + GuildMember *gm; + + assert(player); + + mMembers.readlock(__FUNCTION__, __LINE__); + if (members.count(player->GetCharacterID()) > 0) { + gm = members[player->GetCharacterID()]; + + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildMember(client, gm, include_zone); + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member."); +} + +void Guild::SendGuildMember(GuildMember* gm, bool include_zone) { + + map::iterator itr; + Client *client; + + assert(gm); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildMember(client, gm, include_zone); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member."); +} + +void Guild::SendGuildMember(Client* client, GuildMember* gm, bool include_zone) { + + if (client && gm) { + PacketStruct* packet = configReader.getStruct("WS_JoinGuildNotify", client->GetVersion()); + if (packet) { + packet->setDataByName("guild_id", id); + packet->setDataByName("character_id", gm->character_id); + packet->setDataByName("account_id", gm->account_id); + packet->setMediumStringByName("name", gm->name); + packet->setDataByName("unknown2", 0); + packet->setDataByName("unknown3", 1); + packet->setDataByName("adventure_class", gm->adventure_class); + packet->setDataByName("adventure_level", gm->adventure_level); + packet->setDataByName("tradeskill_class", gm->tradeskill_class); + packet->setDataByName("tradeskill_level", gm->tradeskill_level); + packet->setDataByName("rank", gm->rank); + packet->setDataByName("member_flags", gm->member_flags); + packet->setDataByName("join_date", gm->join_date); + packet->setDataByName("guild_status", gm->guild_status); + packet->setDataByName("last_login", gm->last_login_date); + packet->setDataByName("recruiter_id", gm->recruiter_id); + packet->setDataByName("points", gm->points); + packet->setMediumStringByName("note", gm->note.c_str()); + packet->setMediumStringByName("officer_note", gm->officer_note.c_str()); + if (include_zone && zone_list.GetClientByCharID(gm->character_id)) { + packet->setMediumStringByName("zone", gm->zone.c_str()); + //DumpPacket(packet->serialize()); + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member to a client."); +} + +void Guild::SendGuildModification(float points, vector* character_ids) { + + map::iterator itr; + Client *client; + + if (character_ids) { + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildModification(client, points, character_ids); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild modification to all clients."); +} + +void Guild::SendGuildModification(Client* client, float points, vector* character_ids) { + + if (client && character_ids) { + PacketStruct* packet = configReader.getStruct("WS_ModifyGuild", client->GetVersion()); + if (packet) { + packet->setDataByName("guild_id", id); + packet->setDataByName("unknown2", 0xFFFFFFFF); + packet->setDataByName("points", points); + packet->setArrayLengthByName("num_character_ids", character_ids->size()); + for (int32 i = 0; i < character_ids->size(); i++) + packet->setArrayDataByName("character_id", character_ids->at(i), i); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild modification to a client."); +} + +void Guild::GuildMemberLogin(Client *client, bool first_login) { + + map::iterator itr; + Client *client_to; + char buf[128]; + + assert(client); + + UpdateGuildMemberInfo(client->GetPlayer()); + if (first_login) + SendGuildMOTD(client); + SendGuildUpdate(client); + if (first_login) + SendGuildMember(client->GetPlayer(), false); + SendGuildRecruiterInfo(client, client->GetPlayer()); + SendGuildEventList(client); + SendGuildBankEventList(client); + SendGuildMember(client->GetPlayer()); + SendGuildEventDetails(client); + uchar blah5[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x00,0x00,0x00}; + uchar blah6[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x01,0x00,0x00}; + uchar blah7[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x02,0x00,0x00}; + uchar blah8[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x03,0x00,0x00}; + + //DumpPacket(blah5, sizeof(blah5)); + //DumpPacket(blah6, sizeof(blah6)); + //DumpPacket(blah7, sizeof(blah7)); + //DumpPacket(blah8, sizeof(blah8)); + + client->QueuePacket(new EQ2Packet(OP_RequestGuildBankEventDetailsMs, blah5, sizeof(blah5))); + client->QueuePacket(new EQ2Packet(OP_RequestGuildBankEventDetailsMs, blah6, sizeof(blah6))); + client->QueuePacket(new EQ2Packet(OP_RequestGuildBankEventDetailsMs, blah7, sizeof(blah7))); + client->QueuePacket(new EQ2Packet(OP_RequestGuildBankEventDetailsMs, blah8, sizeof(blah8))); + if (first_login) + SendAllGuildEvents(client); + SendGuildMemberList(client); + if (first_login) { + snprintf(buf, sizeof(buf), "Guildmate: %s has logged in", client->GetPlayer()->GetName()); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client_to = zone_list.GetClientByCharID(itr->second->character_id))) + client_to->SimpleMessage(CHANNEL_GUILD_MEMBER_ONLINE, buf); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + if (first_login){ + uchar blah1[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + uchar blah2[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00}; + uchar blah3[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00}; + uchar blah4[] = {/*0xFF,0x09,0x01,*/0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00}; + + //DumpPacket(blah1, sizeof(blah1)); + //DumpPacket(blah2, sizeof(blah2)); + //DumpPacket(blah3, sizeof(blah3)); + //DumpPacket(blah4, sizeof(blah4)); + + client->QueuePacket(new EQ2Packet(OP_GuildBankUpdateMsg, blah1, sizeof(blah1))); + client->QueuePacket(new EQ2Packet(OP_GuildBankUpdateMsg, blah2, sizeof(blah2))); + client->QueuePacket(new EQ2Packet(OP_GuildBankUpdateMsg, blah3, sizeof(blah3))); + client->QueuePacket(new EQ2Packet(OP_GuildBankUpdateMsg, blah4, sizeof(blah4))); + } + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Member logged in."); +} + +void Guild::GuildMemberLogoff(Player *player) { + + map::iterator itr; + GuildMember *gm; + Client *client; + char buf[128]; + + assert(player); + + mMembers.readlock(__FUNCTION__, __LINE__); + if ((gm = GetGuildMember(player))) { + snprintf(buf, sizeof(buf), "Guildmate: %s has logged out", player->GetName()); + gm->zone.clear(); + + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) { + SendGuildMember(client, gm, false); + client->SimpleMessage(CHANNEL_GUILD_MEMBER_ONLINE, buf); + } + } + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Member logged out."); +} + +void Guild::SendGuildMemberLeave(int32 character_id) { + + map::iterator itr; + Client *client; + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if ((client = zone_list.GetClientByCharID(itr->second->character_id))) + SendGuildMemberLeave(client, character_id); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member left the guild to all clients."); +} + +void Guild::SendGuildMemberLeave(Client* client, int32 character_id) { + + PacketStruct* packet = configReader.getStruct("WS_LeaveGuildNotify", client->GetVersion()); + if (packet) { + packet->setDataByName("guild_id", id); + packet->setDataByName("character_id", character_id); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild member left the guild to a client."); +} + +void Guild::SendGuildRecruitingDetails(Client* client) { + + if (client) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild recruiting details to a client."); + PacketStruct* packet = configReader.getStruct("WS_GuildRecruitingDetails", client->GetVersion()); + if (packet) { + vector* recruiters = GetGuildRecruiters(); + vector::iterator itr; + packet->setDataByName("guild_id", id); + packet->setDataByName("recruiting_full_description", recruiting_full_desc.c_str()); + if (recruiters) { + int32 i = 0; + packet->setArrayLengthByName("num_recruiters", recruiters->size()); + for (itr = recruiters->begin(); itr != recruiters->end(); itr++) { + GuildMember* gm = *itr; + packet->setArrayDataByName("adventure_class", gm->adventure_class, i); + packet->setArrayDataByName("adventure_level", gm->adventure_level, i); + packet->setArrayDataByName("tradeskill_class", gm->tradeskill_class, i); + packet->setArrayDataByName("tradeskill_level", gm->tradeskill_level, i); + packet->setArrayDataByName("show_adventure_class", gm->recruiting_show_adventure_class, i); + packet->setArrayDataByName("unknown2", 4, i); + packet->setArrayDataByName("unknown3", 2, i); + packet->setSubArrayLengthByName("num_bytes", gm->recruiter_picture_data_size, i); + if (gm->recruiter_picture_data_size > 0) { + for (int16 j = 0; j < gm->recruiter_picture_data_size; j++) + packet->setSubArrayDataByName("picture_byte", gm->recruiter_picture_data[j], i, j); + } + packet->setArrayDataByName("char_name", gm->name, i); + packet->setArrayDataByName("recruiter_description", gm->recruiter_description.c_str(), i); + i++; + } + safe_delete(recruiters); + } + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Guild::SendGuildRecruitingImages(Client* client) { + if (client) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild recruiting images to a client."); + PacketStruct* packet = configReader.getStruct("WS_GuildRecruitingImage", client->GetVersion()); + if (packet) { + vector* recruiters = GetGuildRecruiters(); + packet->setDataByName("guild_id", id); + if (recruiters && recruiters->size() > 0) { + GuildMember* gm = recruiters->at(0); + packet->setArrayLengthByName("num_bytes", gm->recruiter_picture_data_size); + if (gm->recruiter_picture_data_size > 0) { + for (int16 i = 0; i < gm->recruiter_picture_data_size; i++) + packet->setArrayDataByName("picture_byte", gm->recruiter_picture_data[i], i); + } + safe_delete(recruiters); + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Guild::SendGuildRecruiterInfo(Client* client, Player* player) { + + if (client && player) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "Sent guild recruiter info to a client."); + GuildMember* gm = GetGuildMember(player); + if (gm) { + PacketStruct* packet = configReader.getStruct("WS_GuildRecruitingMemberInfo", client->GetVersion()); + if (packet) { + packet->setDataByName("character_id", gm->character_id); + packet->setDataByName("unknown", 1); + packet->setDataByName("adventure_class", gm->adventure_class); + packet->setDataByName("adventure_level", gm->adventure_level); + packet->setDataByName("tradeskill_class", gm->tradeskill_class); + packet->setDataByName("tradeskill_level", gm->tradeskill_level); + packet->setDataByName("show_adventure_class", gm->recruiting_show_adventure_class); + packet->setDataByName("unknown3", 0); + + // hack! + gm->recruiter_picture_data_size = 0; + packet->setArrayLengthByName("num_bytes", gm->recruiter_picture_data_size); + if (gm->recruiter_picture_data_size > 0) { + for (int16 i = 0; i < gm->recruiter_picture_data_size; i++) + packet->setArrayDataByName("picture_byte", gm->recruiter_picture_data[i], i); + } + + packet->setMediumStringByName("recruiter_name", gm->name); + packet->setMediumStringByName("recruiter_description", gm->recruiter_description.c_str()); + //DumpPacket(packet->serialize()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void Guild::HandleGuildSay(Client* sender, const char* message) { + + map::iterator itr; + GuildMember *gm; + Client *client; + + assert(sender); + assert(message); + + if (!(gm = GetGuildMemberOnline(sender))) + return; + + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_SPEAK_IN_GUILD_CHAT)) { + sender->SimpleMessage(CHANNEL_NARRATIVE, "You do not have permission to speak in guild chat."); + return; + } + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (permissions.Get(itr->second->rank)->Get(GUILD_PERMISSIONS_SEE_GUILD_CHAT)) + client->GetCurrentZone()->HandleChatMessage(client, sender->GetPlayer(), client->GetPlayer()->GetName(), CHANNEL_GUILD_SAY, message, 0, 0, false, sender->GetPlayer()->GetCurrentLanguage()); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Say"); +} + +void Guild::HandleOfficerSay(Client* sender, const char* message) { + + map::iterator itr; + GuildMember *gm; + Client *client; + + assert(sender); + assert(message); + + if (!(gm = GetGuildMemberOnline(sender))) + return; + + if (!permissions.Get(gm->rank)->Get(GUILD_PERMISSIONS_SPEAK_IN_OFFICER_CHAT)) { + sender->SimpleMessage(CHANNEL_NARRATIVE, "You do not have permission to speak in officer chat."); + return; + } + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (permissions.Get(itr->second->rank)->Get(GUILD_PERMISSIONS_SEE_OFFICER_CHAT)) + client->GetCurrentZone()->HandleChatMessage(client, sender->GetPlayer(), client->GetPlayer()->GetName(), CHANNEL_OFFICER_SAY, message, 0, 0, false, sender->GetPlayer()->GetCurrentLanguage()); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Officer Say"); +} + +void Guild::SendMessageToGuild(int8 event_type, const char* message, ...) { + + map::iterator itr; + Client *client; + va_list argptr; + char buffer[4096]; + + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + + mMembers.readlock(__FUNCTION__, __LINE__); + for (itr = members.begin(); itr != members.end(); itr++) { + if (!(client = zone_list.GetClientByCharID(itr->second->character_id))) + continue; + + if (event_filters.Get(itr->second->rank)->Get(GUILD_EVENT_FILTER_CATEGORY_BROADCAST)) + client->SimpleMessage(CHANNEL_GUILD_EVENT, buffer); + } + mMembers.releasereadlock(__FUNCTION__, __LINE__); + LogWrite(GUILD__DEBUG, 0, "Guilds", "Sent message to entire guild."); +} + +string Guild::GetEpicMobDeathMessage(const char* player_name, const char* mob_name) { + + char message[256]; + int8 choice; + + assert(player_name); + assert(mob_name); + + choice = (rand() % 5) + 1; + if (choice == 1) + snprintf(message, sizeof(message), "%s was slain by %s in a thunderous engagement!", mob_name, player_name); + else if (choice == 2) + snprintf(message, sizeof(message), "%s was slain by %s in a titanic struggle!", mob_name, player_name); + else if (choice == 3) + snprintf(message, sizeof(message), "%s was slain by %s's heroic might!", mob_name, player_name); + else if (choice == 4) + snprintf(message, sizeof(message), "%s slew %s in an earth shaking battle!", player_name, mob_name); + else + snprintf(message, sizeof(message), "%s slew %s in a heroic clash!", player_name, mob_name); + + LogWrite(GUILD__DEBUG, 0, "Guilds", "Guild Epic Mob Death message sent."); + return string(message); +} + +/*************************************************************************************************************************************************************** + * GUILDLIST + ***************************************************************************************************************************************************************/ + +GuildList::GuildList() { +} + +GuildList::~GuildList() { + + MutexMap::iterator itr = guild_list.begin(); + while (itr.Next()) + safe_delete(itr.second); +} + +bool GuildList::AddGuild(Guild* guild) { + + bool ret = false; + if (guild && guild_list.count(guild->GetID()) == 0) { + guild_list.Put(guild->GetID(), guild); + ret = true; + } + return ret; +} + +Guild* GuildList::GetGuild(int32 guild_id) { + + Guild* ret = 0; + if (guild_list.count(guild_id) > 0) + ret = guild_list.Get(guild_id); + return ret; +} + +Guild* GuildList::GetGuild(const char* guild_name) { + + Guild* ret = 0; + MutexMap::iterator itr = guild_list.begin(); + while (itr.Next()) { + if (strncasecmp(itr.second->GetName(), guild_name, strlen(guild_name)) == 0) { + ret = itr.second; + break; + } + } + return ret; +} + +bool GuildList::RemoveGuild(Guild* guild, bool delete_data) { + + bool ret = false; + if (guild && guild_list.count(guild->GetID()) > 0) { + guild_list.erase(guild->GetID(), false, delete_data); + ret = true; + } + return ret; +} + +bool GuildList::RemoveGuild(int32 guild_id, bool delete_data) { + + bool ret = false; + if (guild_list.count(guild_id) > 0) { + guild_list.erase(guild_id, false, delete_data); + ret = true; + } + return ret; +} diff --git a/source/WorldServer/Guilds/Guild.h b/source/WorldServer/Guilds/Guild.h new file mode 100644 index 0000000..9ce4923 --- /dev/null +++ b/source/WorldServer/Guilds/Guild.h @@ -0,0 +1,450 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifndef GUILD_H_ +#define GUILD_H_ + +#include +#include +#include +#include "../../common/Mutex.h" +#include "../MutexMap.h" +using namespace std; + +class ZoneServer; +class Client; +class Player; + +#define GUILD_RANK_LEADER 0 +#define GUILD_RANK_SENIOR_OFFICER 1 +#define GUILD_RANK_OFFICER 2 +#define GUILD_RANK_SENIOR_MEMBER 3 +#define GUILD_RANK_MEMBER 4 +#define GUILD_RANK_JUNIOR_MEMBER 5 +#define GUILD_RANK_INITIATE 6 +#define GUILD_RANK_RECRUIT 7 + +#define GUILD_PERMISSIONS_INVITE 0 +#define GUILD_PERMISSIONS_RMEOVE_MEMBER 1 +#define GUILD_PERMISSIONS_PROMOTE_MEMBER 2 +#define GUILD_PERMISSIONS_DEMOTE_MEMBER 3 +#define GUILD_PERMISSIONS_CHANGE_MOTD 6 +#define GUILD_PERMISSIONS_CHANGE_PERMISSIONS 7 +#define GUILD_PERMISSIONS_CHANGE_RANK_NAMES 8 +#define GUILD_PERMISSIONS_SEE_OFFICER_NOTES 9 +#define GUILD_PERMISSIONS_EDIT_OFFICER_NOTES 10 +#define GUILD_PERMISSIONS_SEE_OFFICER_CHAT 11 +#define GUILD_PERMISSIONS_SPEAK_IN_OFFICER_CHAT 12 +#define GUILD_PERMISSIONS_SEE_GUILD_CHAT 13 +#define GUILD_PERMISSIONS_SPEAK_IN_GUILD_CHAT 14 +#define GUILD_PERMISSIONS_EDIT_PERSONAL_NOTES 15 +#define GUILD_PERMISSIONS_EDIT_PERSONAL_NOTES_OTHERS 16 +#define GUILD_PERMISSIONS_EDIT_EVENT_FILTERS 17 +#define GUILD_PERMISSIONS_EDIT_EVENTS 18 +#define GUILD_PERMISSIONS_PURCHASE_STATUS_ITEMS 19 +#define GUILD_PERMISSIONS_DISPLAY_GUILD_NAME 20 +#define GUILD_PERMISSIONS_SEND_EMAIL_TO_GUILD 21 +#define GUILD_PERMISSIONS_BANK1_SEE_CONTENTS 22 +#define GUILD_PERMISSIONS_BANK2_SEE_CONTENTS 23 +#define GUILD_PERMISSIONS_BANK3_SEE_CONTENTS 24 +#define GUILD_PERMISSIONS_BANK4_SEE_CONTENTS 25 +#define GUILD_PERMISSIONS_BANK1_DEPOSIT 26 +#define GUILD_PERMISSIONS_BANK2_DEPOSIT 27 +#define GUILD_PERMISSIONS_BANK3_DEPOSIT 28 +#define GUILD_PERMISSIONS_BANK4_DEPOSIT 29 +#define GUILD_PERMISSIONS_BANK1_WITHDRAWL 30 +#define GUILD_PERMISSIONS_BANK2_WITHDRAWL 31 +#define GUILD_PERMISSIONS_BANK3_WITHDRAWL 32 +#define GUILD_PERMISSIONS_BANK4_WITHDRAWL 33 +#define GUILD_PERMISSIONS_EDIT_RECRUITING_SETTINGS 35 +#define GUILD_PERMISSIONS_MAKE_OTHERS_RECRUITERS 36 +#define GUILD_PERMISSIONS_SEE_RECRUITING_SETTINGS 37 +#define GUILD_PERMISSIONS_ASSIGN_POINTS 43 +#define GUILD_PERMISSIONS_RECEIVE_POINTS 44 + +#define GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY 0 +#define GUILD_EVENT_FILTER_CATEGORY_BROADCAST 1 + +#define GUILD_EVENT_GUILD_LEVEL_UP 0 +#define GUILD_EVENT_GUILD_LEVEL_DOWN 1 +#define GUILD_EVENT_DISCOVERS_ITEM 2 +#define GUILD_EVENT_GAINS_ADV_LEVEL_1_10 3 +#define GUILD_EVENT_GAINS_ADV_LEVEL_11_20 4 +#define GUILD_EVENT_GAINS_ADV_LEVEL_21_30 5 +#define GUILD_EVENT_GAINS_ADV_LEVEL_31_40 6 +#define GUILD_EVENT_GAINS_ADV_LEVEL_41_50 7 +#define GUILD_EVENT_GAINS_TS_LEVEL_1_10 8 +#define GUILD_EVENT_GAINS_TS_LEVEL_11_20 9 +#define GUILD_EVENT_GAINS_TS_LEVEL_21_30 10 +#define GUILD_EVENT_GAINS_TS_LEVEL_31_40 11 +#define GUILD_EVENT_GAINS_TS_LEVEL_41_50 12 +#define GUILD_EVENT_MEMBER_JOINS 13 +#define GUILD_EVENT_MEMBER_LEAVES 14 +#define GUILD_EVENT_MEMBER_PROMOTED 15 +#define GUILD_EVENT_MEMBER_DEMOTED 16 +#define GUILD_EVENT_COMPLETES_HERITAGE_QUEST 19 +#define GUILD_EVENT_KILLS_EPIC_MONSTER 20 +#define GUILD_EVENT_LOOTS_ARTIFACT 21 +#define GUILD_EVENT_LOOTS_FABELED_ITEM 22 +#define GUILD_EVENT_LOOTS_LEGENDARY_ITEM 23 +#define GUILD_EVENT_COMPLETES_WRIT 24 +#define GUILD_EVENT_LOOTS_MYTHICAL_ITEM 25 +#define GUILD_EVENT_GAINS_ADV_LEVEL_10 26 +#define GUILD_EVENT_GAINS_ADV_LEVEL_20 27 +#define GUILD_EVENT_GAINS_ADV_LEVEL_30 28 +#define GUILD_EVENT_GAINS_ADV_LEVEL_40 29 +#define GUILD_EVENT_GAINS_ADV_LEVEL_50 30 +#define GUILD_EVENT_GAINS_TS_LEVEL_10 31 +#define GUILD_EVENT_GAINS_TS_LEVEL_20 32 +#define GUILD_EVENT_GAINS_TS_LEVEL_30 33 +#define GUILD_EVENT_GAINS_TS_LEVEL_40 34 +#define GUILD_EVENT_GAINS_TS_LEVEL_50 35 +#define GUILD_EVENT_GAINS_ADV_LEVEL_51_60 37 +#define GUILD_EVENT_GAINS_TS_LEVEL_51_60 38 +#define GUILD_EVENT_GAINS_ADV_LEVEL_60 39 +#define GUILD_EVENT_GAINS_TS_LEVEL_60 40 +#define GUILD_EVENT_GAINS_ADV_LEVEL_61_70 41 +#define GUILD_EVENT_GAINS_TS_LEVEL_61_70 42 +#define GUILD_EVENT_GAINS_ADV_LEVEL_70 43 +#define GUILD_EVENT_GAINS_TS_LEVEL_70 44 +#define GUILD_EVENT_GAINS_AA_10 45 +#define GUILD_EVENT_GAINS_AA_20 46 +#define GUILD_EVENT_GAINS_AA_30 47 +#define GUILD_EVENT_GAINS_AA_40 48 +#define GUILD_EVENT_GAINS_AA_50 49 +#define GUILD_EVENT_GAINS_AA_1_10 50 +#define GUILD_EVENT_GAINS_AA_11_20 51 +#define GUILD_EVENT_GAINS_AA_21_30 52 +#define GUILD_EVENT_GAINS_AA_31_40 53 +#define GUILD_EVENT_GAINS_AA_41_50 54 +#define GUILD_EVENT_BECOMES_RECRUITER 55 +#define GUILD_EVENT_NO_LONGER_RECRUITER 56 +#define GUILD_EVENT_HERALDY_CHANGE 57 +#define GUILD_EVENT_GAINS_AA_60 58 +#define GUILD_EVENT_GAINS_AA_70 59 +#define GUILD_EVENT_GAINS_AA_80 60 +#define GUILD_EVENT_GAINS_AA_90 61 +#define GUILD_EVENT_GAINS_AA_100 62 +#define GUILD_EVENT_GAINS_AA_51_60 63 +#define GUILD_EVENT_GAINS_AA_61_70 64 +#define GUILD_EVENT_GAINS_AA_71_80 65 +#define GUILD_EVENT_GAINS_AA_81_90 66 +#define GUILD_EVENT_GAINS_AA_91_100 67 +#define GUILD_EVENT_GAINS_ADV_LEVEL_80 68 +#define GUILD_EVENT_GAINS_TS_LEVEL_80 69 +#define GUILD_EVENT_GAINS_ADV_LEVEL_71_80 70 +#define GUILD_EVENT_GAINS_TS_LEVEL_71_80 71 +#define GUILD_EVENT_GAINS_AA_110 72 +#define GUILD_EVENT_GAINS_AA_120 73 +#define GUILD_EVENT_GAINS_AA_130 74 +#define GUILD_EVENT_GAINS_AA_140 75 +#define GUILD_EVENT_GAINS_AA_101_110 76 +#define GUILD_EVENT_GAINS_AA_111_120 77 +#define GUILD_EVENT_GAINS_AA_121_130 78 +#define GUILD_EVENT_GAINS_AA_131_140 79 +#define GUILD_EVENT_GAINS_AA_150 80 +#define GUILD_EVENT_GAINS_AA_141_150 81 +#define GUILD_EVENT_GAINS_AA_160 82 +#define GUILD_EVENT_GAINS_AA_170 83 +#define GUILD_EVENT_GAINS_AA_180 84 +#define GUILD_EVENT_GAINS_AA_190 85 +#define GUILD_EVENT_GAINS_AA_200 86 +#define GUILD_EVENT_GAINS_AA_151_160 87 +#define GUILD_EVENT_GAINS_AA_161_170 88 +#define GUILD_EVENT_GAINS_AA_171_180 89 +#define GUILD_EVENT_GAINS_AA_181_190 90 +#define GUILD_EVENT_GAINS_AA_191_200 91 +#define GUILD_EVENT_EARNS_ACHIEVEMENT 92 + +#define GUILD_RECRUITING_FLAG_TRAINING 0 +#define GUILD_RECRUITING_FLAG_FIGHTERS 1 +#define GUILD_RECRUITING_FLAG_PRIESTS 2 +#define GUILD_RECRUITING_FLAG_SCOUTS 3 +#define GUILD_RECRUITING_FLAG_MAGES 4 +#define GUILD_RECRUITING_FLAG_TRADESKILLERS 5 + +#define GUILD_RECRUITING_PLAYSTYLE_NONE 0 +#define GUILD_RECRUITING_PLAYSTYLE_CASUAL 1 +#define GUILD_RECRUITING_PLAYSTYLE_HARDCORE 2 + +#define GUILD_RECRUITING_DESC_TAG_NONE 0 +#define GUILD_RECRUITING_DESC_TAG_GOOD 1 +#define GUILD_RECRUITING_DESC_TAG_EVIL 2 +#define GUILD_RECRUITING_DESC_TAG_CHATTY 3 +#define GUILD_RECRUITING_DESC_TAG_ORGANIZED 4 +#define GUILD_RECRUITING_DESC_TAG_ROLEPLAY 5 +#define GUILD_RECRUITING_DESC_TAG_ENJOY_QUESTS 6 +#define GUILD_RECRUITING_DESC_TAG_ENJOY_RAIDS 7 +#define GUILD_RECRUITING_DESC_TAG_ODD_HOURS 8 +#define GUILD_RECRUITING_DESC_TAG_CRAFTER_ORIENTED 9 +#define GUILD_RECRUITING_DESC_TAG_FAMILY_FRIENDLY 10 +#define GUILD_RECRUITING_DESC_TAG_MATURE_HUMOR 11 +#define GUILD_RECRUITING_DESC_TAG_INMATES_RUN 12 +#define GUILD_RECRUITING_DESC_TAG_VERY_FUNNY 13 +#define GUILD_RECRUITING_DESC_TAG_HUMOR_CAUES_PAIN 14 +#define GUILD_RECRUITING_DESC_TAG_SERIOUS 15 + +#define GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD 1 +#define GUILD_MEMBER_FLAGS_NOTIFY_LOGINS 2 +#define GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS 4 + +#define GUILD_EVENT_ACTION_LOCK 0 +#define GUILD_EVENT_ACTION_UNLOCK 1 +#define GUILD_EVENT_ACTION_DELETE 2 + +#define GUILD_MAX_LEVEL 80 +#define GUILD_MAX_POINT_HISTORY 50 +#define GUILD_MAX_EVENTS 500 +#define GUILD_MAX_LOCKED_EVENTS 200 + +struct PointHistory { + int32 date; + string modified_by; + string comment; + float points; + bool saved_needed; +}; + +struct GuildMember { + int32 character_id; + int32 account_id; + int32 recruiter_id; //00 00 00 00 if not a guild recruiter + char name[64]; + int32 guild_status; + float points; + int8 adventure_class; + int8 adventure_level; + int8 tradeskill_class; + int8 tradeskill_level; + int8 rank; + int8 member_flags; + string zone; + int32 join_date; + int32 last_login_date; + string note; + string officer_note; + string recruiter_description; + unsigned char* recruiter_picture_data; + int16 recruiter_picture_data_size; + int8 recruiting_show_adventure_class; + deque point_history; +}; + +struct GuildEvent { + int64 event_id; + int32 date; + int32 type; + string description; + int8 locked; + bool save_needed; +}; + +struct GuildBankEvent { + int64 event_id; + int32 date; + int32 type; + string description; +}; + +struct Bank { + string name; + deque events; +}; + +class Guild { +public: + Guild(); + virtual ~Guild(); + void SetID(int32 id_in) {id = id_in;} + void SetName(const char* name, bool send_packet = true); + void SetLevel(int8 level, bool send_packet = true); + void SetFormedDate(int32 formed_date_in) {formed_date = formed_date_in;} + void SetMOTD(const char *motd, bool send_packet = true); + int32 GetID() const {return id;} + const char* GetName() const {return name;} + int8 GetLevel() const {return level;} + int32 GetFormedDate() const {return formed_date;} + const char * GetMOTD() const {return motd;} + void SetEXPCurrent(int64 exp, bool send_packet = true); + void AddEXPCurrent(sint64 exp, bool send_packet = true); + int64 GetEXPCurrent() const {return exp_current;} + void SetEXPToNextLevel(int64 exp, bool send_packet = true); + int64 GetEXPToNextLevel() const {return exp_to_next_level;} + void SetRecruitingShortDesc(const char* new_desc, bool send_packet = true); + string GetRecruitingShortDesc() const {return recruiting_short_desc;} + void SetRecruitingFullDesc(const char* new_desc, bool send_packet = true); + string GetRecruitingFullDesc() const {return recruiting_full_desc;} + void SetRecruitingMinLevel(int8 new_level, bool send_packet = true); + int8 GetRecruitingMinLevel() const {return recruiting_min_level;} + void SetRecruitingPlayStyle(int8 new_play_style, bool send_packet = true); + int8 GetRecruitingPlayStyle() const {return recruiting_play_style;} + bool SetRecruitingDescTag(int8 index, int8 tag, bool send_packet = true); + int8 GetRecruitingDescTag(int8 index); + bool SetPermission(int8 rank, int8 permission, int8 value, bool send_packet = true); + int8 GetPermission(int8 rank, int8 permission); + bool SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet = true); + int8 GetEventFilter(int8 event_id, int8 category); + int32 GetNumUniqueAccounts(); + int32 GetNumRecruiters(); + int32 GetNextRecruiterID(); + int64 GetNextEventID(); + GuildMember* GetGuildMemberOnline(Client* client); + GuildMember* GetGuildMember(Player* player); + GuildMember* GetGuildMember(int32 character_id); + GuildMember* GetGuildMember(const char* player_name); + vector* GetGuildRecruiters(); + GuildEvent* GetGuildEvent(int64 event_id); + bool SetRankName(int8 rank, const char* name, bool send_packet = true); + const char* GetRankName(int8 rank); + bool SetRecruitingFlag(int8 flag, int8 value, bool send_packet = true); + int8 GetRecruitingFlag(int8 flag); + bool SetGuildRecruiter(Client* client, const char* name, bool value, bool send_packet = true); + bool SetGuildRecruiterDescription(Client* client, const char* description, bool send_packet = true); + bool ToggleGuildRecruiterAdventureClass(Client* client, bool send_packet = true); + bool SetGuildMemberNote(const char* name, const char* note, bool send_packet = true); + bool SetGuildOfficerNote(const char* name, const char* note, bool send_packet = true); + bool AddNewGuildMember(Client* client, const char* invited_by = 0, int8 rank = GUILD_RANK_RECRUIT); + bool AddGuildMember(GuildMember* guild_member); + void RemoveGuildMember(int32 character_id, bool send_packet = true); + void RemoveAllGuildMembers(); + bool DemoteGuildMember(Client* client, const char* name, bool send_packet = true); + bool PromoteGuildMember(Client* client, const char* name, bool send_packet = true); + bool KickGuildMember(Client* client, const char* name, bool send_packet = true); + bool InvitePlayer(Client* client, const char* name, bool send_packet = true); + bool AddPointsToAll(Client* client, float points, const char* comment = 0, bool send_packet = true); + bool AddPointsToAllOnline(Client* client, float points, const char* comment = 0, bool send_packet = true); + bool AddPointsToGroup(Client* client, float points, const char* comment = 0, bool send_packet = true); + bool AddPointsToRaid(Client* client, float points, const char* comment = 0, bool send_packet = true); + bool AddPointsToGuildMember(Client* client, float points, const char* name, const char* comment = 0, bool send_packet = true); + bool AddPointHistory(GuildMember* guild_member, int32 date, const char* modified_by, float points, const char* comment = 0, bool new_point_history = true); + void ViewGuildMemberPoints(Client* client, const char* name); + bool ChangeMemberFlag(Client* client, int8 member_flag, int8 value, bool send_packet = true); + bool UpdateGuildMemberInfo(Player* player); + bool UpdateGuildStatus(Player *player, int32 Status); + void AddGuildEvent(int64 event_id, int32 type, const char* description, int32 date, int8 locked); + void AddNewGuildEvent(int32 type, const char* description, int32 date, bool send_packet = true, ...); + bool LockGuildEvent(int64 event_id, bool lock, bool send_packet = true); + bool DeleteGuildEvent(int64 event_id, bool send_packet = true); + void SendGuildMOTD(Client* client); + void SendGuildEventList(); + void SendGuildEventList(Client* client); + void SendGuildEventDetails(); + void SendGuildEventDetails(Client* client); + void SendAllGuildEvents(); + void SendAllGuildEvents(Client* client); + void SendOldGuildEvent(Client* client, GuildEvent* guild_event); + void SendNewGuildEvent(GuildEvent* guild_event); + void SendNewGuildEvent(Client* client, GuildEvent* guild_event); + void SendGuildEventAction(int8 action, GuildEvent* guild_event); + void SendGuildEventAction(Client* client, int8 action, GuildEvent* guild_event); + void SendGuildBankEventList(); + void SendGuildBankEventList(Client* client); + void SendGuildUpdate(); + void SendGuildUpdate(Client* client); + void SendGuildMemberList(); + void SendGuildMemberList(Client* client); + void SendGuildMember(Player* player, bool include_zone = true); + void SendGuildMember(GuildMember* gm, bool include_zone = true); + void SendGuildMember(Client* client, GuildMember* gm, bool include_zone = true); + void SendGuildModification(float points, vector* character_ids); + void SendGuildModification(Client* client, float points, vector* character_ids); + void GuildMemberLogin(Client *client, bool first_login = false); + void GuildMemberLogoff(Player *player); + void SendGuildMemberLeave(int32 character_id); + void SendGuildMemberLeave(Client* client, int32 character_id); + void SendGuildRecruitingDetails(Client* client); + void SendGuildRecruitingImages(Client* client); + void SendGuildRecruiterInfo(Client* client, Player* player); + void HandleGuildSay(Client* sender, const char* message); + void HandleOfficerSay(Client* sender, const char* message); + void SendMessageToGuild(int8 event_type, const char* message, ...); + void SetSaveNeeded(bool val) {save_needed = val;} + bool GetSaveNeeded() {return save_needed;} + void SetMemberSaveNeeded(bool val) {member_save_needed = val;} + bool GetMemberSaveNeeded() {return member_save_needed;} + void SetEventsSaveNeeded(bool val) {events_save_needed = val;} + bool GetEventsSaveNeeded() {return events_save_needed;} + void SetRanksSaveNeeded(bool val) {ranks_save_needed = val;} + bool GetRanksSaveNeeded() {return ranks_save_needed;} + void SetEventFiltersSaveNeeded(bool val) {event_filters_save_needed = val;} + bool GetEventFiltersSaveNeeded() {return event_filters_save_needed;} + void SetPointsHistorySaveNeeded(bool val) {points_history_save_needed = val;} + bool GetPointsHistorySaveNeeded() {return points_history_save_needed;} + void SetRecruitingSaveNeeded(bool val) {recruiting_save_needed = val;} + bool GetRecruitingSaveNeeded() {return recruiting_save_needed;} + map* GetGuildMembers() {return &members;} + Mutex * GetGuildMembersMutex() {return &mMembers;} + deque* GetGuildEvents() {return &guild_events;} + MutexMap*>* GetPermissions() {return &permissions;} + MutexMap* GetGuildRanks() {return &ranks;} + MutexMap* GetRecruitingFlags() {return &recruiting_flags;} + MutexMap* GetRecruitingDescTags() {return &recruiting_desc_tags;} + int8 GetRecruitingLookingForPacketValue(); + static string GetEpicMobDeathMessage(const char* player_name, const char* mob_name); + +private: + int32 id; + char name[64]; + int8 level; + int32 formed_date; + char motd[256]; + int64 exp_current; + int64 exp_to_next_level; + string recruiting_short_desc; + string recruiting_full_desc; + int8 recruiting_min_level; + int8 recruiting_play_style; + MutexMap ranks; + map members; + Mutex mMembers; + deque guild_events; + MutexMap*> permissions; + MutexMap*> event_filters; + MutexMap recruiting_flags; + MutexMap recruiting_desc_tags; + Bank banks[4]; + int32 GetPermissionsPacketValue(int8 rank, int32 start, int32 end); + int32 GetEventFilterPacketValue(int8 category, int32 start, int32 end); + bool save_needed; + bool member_save_needed; + bool events_save_needed; + bool event_filters_save_needed; + bool ranks_save_needed; + bool points_history_save_needed; + bool recruiting_save_needed; +}; + +class GuildList { +public: + GuildList(); + virtual ~GuildList(); + bool AddGuild(Guild* guild); + Guild* GetGuild(int32 guild_id); + Guild* GetGuild(const char* guild_name); + bool RemoveGuild(Guild* guild, bool delete_data = false); + bool RemoveGuild(int32 guild_id, bool delete_data = false); + int32 GetNumGuilds() {return guild_list.size();} + MutexMap* GetGuilds() {return &guild_list;} + +private: + MutexMap guild_list; +}; + +#endif diff --git a/source/WorldServer/Guilds/GuildDB.cpp b/source/WorldServer/Guilds/GuildDB.cpp new file mode 100644 index 0000000..5c0ddae --- /dev/null +++ b/source/WorldServer/Guilds/GuildDB.cpp @@ -0,0 +1,582 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Guild.h" + +extern GuildList guild_list; +extern RuleManager rule_manager; + +void WorldDatabase::LoadGuilds() { + int32 num_guilds = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds`"); + while (result && (row = mysql_fetch_row(result))) { + LogWrite(GUILD__DEBUG, 1, "Guilds", "%u. %s", atoul(row[0]), row[1]); + Guild* guild = new Guild; + guild->SetID(atoul(row[0])); + guild->SetName(row[1]); + if (row[2]) + guild->SetMOTD(row[2], false); + guild->SetLevel(atoi(row[3]), false); + guild->SetEXPCurrent(atoul(row[4]), false); + guild->SetEXPToNextLevel(atoul(row[5]), false); + guild->SetFormedDate(atoul(row[6])); + + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoaded %i guild members.", LoadGuildMembers(guild)); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Ranks..."); + LoadGuildRanks(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Event Filters..."); + LoadGuildEventFilters(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Events..."); + LoadGuildEvents(guild); + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Recruiting..."); + LoadGuildRecruiting(guild); + guild_list.AddGuild(guild); + num_guilds++; + } + LogWrite(GUILD__INFO, 0, "Guilds", "\tLoaded %u Guild(s)", num_guilds); +} + +int32 WorldDatabase::LoadGuildMembers(Guild* guild) { + int32 num_members = 0; + Query query; + MYSQL_ROW row; + char *name; + int32 char_id; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `char_id`, `recruiter_id`, `guild_status`, `points`, `rank_id`, `member_flags`, `join_date`, `note`, `officer_note`, `recruiting_message`, `recruiter_picture_data` FROM `guild_members` WHERE `guild_id`=%u", guild->GetID()); + + while (result && (row = mysql_fetch_row(result))) { + char_id = atoul(row[0]); + if (!(name = GetCharacterName(char_id))) { + LogWrite(GUILD__ERROR, 0, "Guilds", "WorldDatabase::LoadGuildMembers Cannot find guild member with character id %u.", char_id); + continue; + } + + GuildMember* gm = new GuildMember; + gm->character_id = char_id; + gm->recruiter_id = atoul(row[1]); + gm->guild_status = atoul(row[2]); + gm->points = atof(row[3]); + gm->rank = atoi(row[4]); + gm->member_flags = atoi(row[5]); + gm->join_date = atoul(row[6]); + if (row[7]) + gm->note = string(row[7]); + if (row[8]) + gm->officer_note = string(row[8]); + if (row[9]) + gm->recruiter_description = string(row[9]); + int16 recruiter_picture_data_size = 0; + if (row[10] && (recruiter_picture_data_size = strlen(row[10])) > 0) { + gm->recruiter_picture_data_size = recruiter_picture_data_size / 2; + gm->recruiter_picture_data = new unsigned char[gm->recruiter_picture_data_size]; + unsigned char* cpy = gm->recruiter_picture_data; + const char* str = row[10]; + char high, low; + for (const char* ptr = str; *ptr; ptr += 2) { + high = tolower(*ptr); + low = tolower(*(ptr+1)); + if (isdigit(high)) + high = high - '0'; + else if (high >= 'a' && high <= 'f') + high = (high - 'a') + 10; + else { + LogWrite(GUILD__ERROR, 0, "Guilds", "Guild mate with id %u has corrupt picture data. Data not loading.", gm->character_id); + safe_delete_array(gm->recruiter_picture_data); + gm->recruiter_picture_data_size = 0; + break; + } + if (isdigit(low)) + low = low - '0'; + else if (low >= 'a' && low <= 'f') + low = (low - 'a') + 10; + else { + LogWrite(GUILD__ERROR, 0, "Guilds", "Guild mate with id %u has corrupt picture data. Data not loading.", gm->character_id); + safe_delete_array(gm->recruiter_picture_data); + gm->recruiter_picture_data_size = 0; + break; + } + *cpy++ = low | (high << 4); + } + /*for (int16 i = 0; i < gm->recruiter_picture_data_size; i++) + if (i<10) + printf("int:%u hex:%x\n", gm->recruiter_picture_data[i], gm->recruiter_picture_data[i]);*/ + } + else { + gm->recruiter_picture_data_size = 0; + gm->recruiter_picture_data = 0; + } + strncpy(gm->name, name, sizeof(gm->name)); + gm->account_id = GetCharacterAccountID(char_id); + gm->adventure_class = GetCharacterClass(char_id); + gm->adventure_level = GetCharacterLevel(char_id); + gm->tradeskill_class = 0; + gm->tradeskill_level = 0; + gm->last_login_date = GetCharacterTimeStamp(char_id); + gm->zone = GetZoneDescription(GetCharacterCurrentZoneID(char_id)); + gm->recruiting_show_adventure_class = 1; + LoadGuildPointsHistory(guild, gm); + guild->AddGuildMember(gm); + safe_delete_array(name); + num_members++; + } + return num_members; +} + +void WorldDatabase::LoadGuildEvents(Guild* guild) { + if (guild) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `event_id`, `event_date`, `event_type`, `description`, `locked` FROM `guild_events` WHERE `guild_id`=%u AND `display`=1 AND `archived`=0 ORDER BY `event_date` DESC LIMIT 0, %u", guild->GetID(), GUILD_MAX_EVENTS); + while (result && (row = mysql_fetch_row(result))) + guild->AddGuildEvent(atoi64(row[0]), atoul(row[2]), row[3], atoul(row[1]), atoi(row[4])); + } +} + +void WorldDatabase::LoadGuildRanks(Guild* guild) { + + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Loading Ranks for guild id: %u", guild->GetID()); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `rank_id`, `rank_name`, `permission1`, `permission2` FROM `guild_ranks` WHERE `guild_id`=%u", guild->GetID()); + while (result && (row = mysql_fetch_row(result))) { + int8 rank_id = atoi(row[0]); + int32 permission1 = atoul(row[2]); + int32 permission2 = atoul(row[3]); + guild->SetRankName(rank_id, row[1], false); + LogWrite(GUILD__DEBUG, 5, "Guilds", "\tLoading rank_id: %i", rank_id); + LogWrite(GUILD__DEBUG, 5, "Guilds", "\tPermission1: %ul, Permission2: %ul", permission1, permission2); + for (int32 i = 0; i <= 44; i++) { + int32 bitwise_val; + if (i < 32) { + bitwise_val = (int32)pow(2.0, (double)(i)); + guild->SetPermission(rank_id, i, permission1 & bitwise_val ? 1 : 0, false); + LogWrite(GUILD__DEBUG, 5, "Guilds", "\tSetting Permission %u to %u", i, permission1 & bitwise_val ? 1 : 0); + } + else { + bitwise_val = (int32)pow(2.0, (double)(i - 32)); + guild->SetPermission(rank_id, i, permission2 & bitwise_val ? 1 : 0, false); + LogWrite(GUILD__DEBUG, 5, "Guilds", "\tSetting Permission %u to %u", i, permission2 & bitwise_val ? 1 : 0); + } + } + } + } +} + +void WorldDatabase::LoadGuildEventFilters(Guild* guild) { + if (guild) { + Query query; + MYSQL_ROW row; + bool event_filter_added = false; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `event_id`, `retain`, `broadcast` FROM `guild_event_filters` WHERE `guild_id`=%u", guild->GetID()); + while (result && (row = mysql_fetch_row(result))) { + guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, atoi(row[1]), false); + guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_BROADCAST, atoi(row[2]), false); + if (!event_filter_added) + event_filter_added = true; + } + + if (!event_filter_added) + LoadGuildDefaultEventFilters(guild); + } +} + +void WorldDatabase::LoadGuildPointsHistory(Guild* guild, GuildMember* guild_member) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result; + + assert(guild); + assert(guild_member); + + result = query.RunQuery2(Q_SELECT, "SELECT `points_date`, `modified_by`, `comment`, `points` FROM `guild_points_history` WHERE `guild_id`=%u AND `char_id`=%u ORDER BY `points_date` DESC", guild->GetID(), guild_member->character_id); + while (result && (row = mysql_fetch_row(result))) + guild->AddPointHistory(guild_member, atoul(row[0]), row[1], atof(row[3]), row[2], false); +} + +void WorldDatabase::LoadGuildRecruiting(Guild* guild) { + if (guild) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `short_desc`, `full_desc`, `min_level`, `play_style`, `looking_for`, `descriptive_tag1`, `descriptive_tag2`, `descriptive_tag3`, `descriptive_tag4` FROM `guild_recruiting` WHERE `guild_id`=%u", guild->GetID()); + while (result && (row = mysql_fetch_row(result))) { + if (row[0]) + guild->SetRecruitingShortDesc(row[0], false); + if (row[1]) + guild->SetRecruitingFullDesc(row[1], false); + guild->SetRecruitingMinLevel(atoi(row[2]), false); + guild->SetRecruitingPlayStyle(atoi(row[3]), false); + for (int32 i = 0; i <= 5; i++) { + int32 bitwise_val = (int32)pow(2.0, (double)i); + guild->SetRecruitingFlag(i, atoi(row[4]) & bitwise_val ? 1 : 0, false); + } + guild->SetRecruitingDescTag(0, atoi(row[5]), false); + guild->SetRecruitingDescTag(1, atoi(row[6]), false); + guild->SetRecruitingDescTag(2, atoi(row[7]), false); + guild->SetRecruitingDescTag(3, atoi(row[8]), false); + } + } +} + +void WorldDatabase::SaveGuild(Guild* guild, bool new_guild) { + Query query; + + assert(guild); + + if (new_guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving NEW Guild '%s' (%u) data...", guild->GetName(), guild->GetID()); + query.RunQuery2(Q_INSERT, "INSERT INTO `guilds` (`name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on`) " + "VALUES ('%s', '%s', %i, %llu, %llu, %u)", + getSafeEscapeString(guild->GetName()).c_str(), getSafeEscapeString(guild->GetMOTD()).c_str(), guild->GetLevel(), guild->GetEXPCurrent(), guild->GetEXPToNextLevel(), guild->GetFormedDate()); + guild->SetID(query.GetLastInsertedID()); + } + else { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Guild '%s' (%u) data...", guild->GetName(), guild->GetID()); + query.RunQuery2(Q_UPDATE, "UPDATE `guilds` " + "SET `name`='%s', `motd`='%s', `level`=%i, `xp`=%llu, `xp_needed`=%llu, `formed_on`=%u WHERE `id`=%u", + getSafeEscapeString(guild->GetName()).c_str(), getSafeEscapeString(guild->GetMOTD()).c_str(), guild->GetLevel(), guild->GetEXPCurrent(), guild->GetEXPToNextLevel(), guild->GetFormedDate(), guild->GetID()); + } + guild->SetSaveNeeded(false); +} + +void WorldDatabase::SaveGuildMembers(Guild* guild) { + map* members; + map::iterator itr; + Mutex *mMembers; + GuildMember *gm; + Query query, query2; + + assert(guild); + + members = guild->GetGuildMembers(); + mMembers = guild->GetGuildMembersMutex(); + + mMembers->readlock(__FUNCTION__, __LINE__); + for (itr = members->begin(); itr != members->end(); itr++) { + gm = itr->second; + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Member '%s' (%u) data...", gm->name, gm->character_id); + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_members` (`guild_id`, `char_id`, `recruiter_id`, `guild_status`, `points`, `rank_id`, `member_flags`, `join_date`, `note`, `officer_note`, `recruiting_message`, `recruiter_picture_data`) VALUES (%u, %u, %u, %u, %f, %u, %u, %u, '%s', '%s', '%s', NULL) ON DUPLICATE KEY UPDATE `guild_id`=%u, `recruiter_id`=%u, `guild_status`=%u, `points`=%f, `rank_id`=%u, `member_flags`=%u, `join_date`=%u, `note`='%s', `officer_note`='%s', `recruiting_message`='%s', `recruiter_picture_data`=NULL", guild->GetID(), gm->character_id, gm->recruiter_id, gm->guild_status, gm->points, gm->rank, gm->member_flags, gm->join_date, getSafeEscapeString(gm->note.c_str()).c_str(), getSafeEscapeString(gm->officer_note.c_str()).c_str(), getSafeEscapeString(gm->recruiter_description.c_str()).c_str(), guild->GetID(), gm->recruiter_id, gm->guild_status, gm->points, gm->rank, gm->member_flags, gm->join_date, getSafeEscapeString(gm->note.c_str()).c_str(), getSafeEscapeString(gm->officer_note.c_str()).c_str(), getSafeEscapeString(gm->recruiter_description.c_str()).c_str()); + if (gm && gm->recruiter_picture_data_size > 0 && gm->recruiter_picture_data) { + stringstream ss_hex; + stringstream ss_query; + ss_hex.flags(ios::hex); + for (int16 i = 0; i < gm->recruiter_picture_data_size; i++) + ss_hex << setfill('0') << setw(2) << (int)gm->recruiter_picture_data[i]; + ss_query << "UPDATE `guild_members` SET `recruiter_picture_data`='" << ss_hex.str() << "' WHERE `char_id`=" << gm->character_id; + query2.RunQuery2(ss_query.str(), Q_UPDATE); + } + } + guild->SetMemberSaveNeeded(false); + mMembers->releasereadlock(__FUNCTION__, __LINE__); +} + +void WorldDatabase::SaveGuildEvents(Guild* guild) { + if (guild) { + deque* guild_events = guild->GetGuildEvents(); + deque::iterator itr; + for (itr = guild_events->begin(); itr != guild_events->end(); itr++) { + GuildEvent* ge = *itr; + if (!ge->save_needed) + continue; + + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Events for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_events` (`guild_id`, `event_id`, `event_date`, `event_type`, `description`, `display`, `locked`, `archived`) " + "VALUES (%u, %llu, %u, %u, '%s', 1, %u, 0) " + "ON DUPLICATE KEY UPDATE `locked`=%i", + guild->GetID(), ge->event_id, ge->date, ge->type, getSafeEscapeString(ge->description.c_str()).c_str(), ge->locked, ge->locked); + ge->save_needed = false; + } + guild->SetEventsSaveNeeded(false); + } +} + +void WorldDatabase::SaveGuildRanks(Guild* guild) { + if (guild) { + MutexMap*>* permissions = guild->GetPermissions(); + MutexMap* ranks = guild->GetGuildRanks(); + MutexMap::iterator ranks_itr = ranks->begin(); + while (ranks_itr.Next()) { + int32 permission1 = 0; + int32 permission2 = 0; + for (int32 i = 0; i <= 44; i++) { + if (permissions->count(ranks_itr.first) > 0 && permissions->Get(ranks_itr.first)->count(i) > 0 && permissions->Get(ranks_itr.first)->Get(i)) { + if (i < 32) + permission1 += (int32)pow(2.0, (double)i); + else + permission2 += (int32)pow(2.0, (double)(i - 32)); + } + } + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Ranks for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_ranks` (`guild_id`, `rank_id`, `rank_name`, `permission1`, `permission2`) " + "VALUES (%u, %u, '%s', %u, %u) " + "ON DUPLICATE KEY UPDATE `rank_name`='%s', `permission1`=%u, permission2=%u", + guild->GetID(), ranks_itr.first, getSafeEscapeString(ranks_itr.second.c_str()).c_str(), permission1, permission2, getSafeEscapeString(ranks_itr.second.c_str()).c_str(), permission1, permission2); + } + guild->SetRanksSaveNeeded(false); + } +} + +void WorldDatabase::SaveGuildEventFilters(Guild* guild) { + int32 i; + assert(guild); + + for (i = 0; i < 93; i++) { + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild EventFilters for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_event_filters` (`guild_id`, `event_id`, `retain`, `broadcast`) " + "VALUES (%u, %u, %u, %u) " + "ON DUPLICATE KEY UPDATE `retain`=%u, `broadcast`=%u", + guild->GetID(), i, guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_BROADCAST), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_BROADCAST)); + } + + guild->SetEventFiltersSaveNeeded(false); +} + +void WorldDatabase::SaveGuildPointsHistory(Guild* guild) { + map *members; + map::iterator itr; + Mutex *mMembers; + deque *ph_list; + deque::iterator ph_itr; + PointHistory* ph; + + assert (guild); + + members = guild->GetGuildMembers(); + mMembers = guild->GetGuildMembersMutex(); + + mMembers->readlock(__FUNCTION__, __LINE__); + for (itr = members->begin(); itr != members->end(); itr++) { + ph_list = &itr->second->point_history; + + for (ph_itr = ph_list->begin(); ph_itr != ph_list->end(); ph_itr++) { + ph = *ph_itr; + if (!ph->saved_needed) + continue; + + LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Point History for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_points_history` (`guild_id`, `char_id`, `points_date`, `modified_by`, `comment`, `points`) " + "VALUES (%u, %u, %u, '%s', '%s', %f)", + guild->GetID(), itr->first, ph->date, getSafeEscapeString(ph->modified_by.c_str()).c_str(), getSafeEscapeString(ph->comment.c_str()).c_str(), ph->points); + ph->saved_needed = false; + } + } + guild->SetPointsHistorySaveNeeded(false); + mMembers->releasereadlock(__FUNCTION__, __LINE__); +} + +void WorldDatabase::SaveGuildRecruiting(Guild* guild) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Recruiting info for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_recruiting` (`guild_id`, `short_desc`, `full_desc`, `min_level`, `play_style`, `looking_for`, `descriptive_tag1`, `descriptive_tag2`, `descriptive_tag3`, `descriptive_tag4`) VALUES (%u, '%s', '%s', %u, %u, %u, %u, %u, %u, %u) ON DUPLICATE KEY UPDATE `short_desc`='%s', `full_desc`='%s', `min_level`=%u, `play_style`=%u, `looking_for`=%u, `descriptive_tag1`=%u, `descriptive_tag2`=%u, `descriptive_tag3`=%u, `descriptive_tag4`=%u", guild->GetID(), getSafeEscapeString(guild->GetRecruitingShortDesc().c_str()).c_str(), getSafeEscapeString(guild->GetRecruitingFullDesc().c_str()).c_str(), guild->GetRecruitingMinLevel(), guild->GetRecruitingPlayStyle(), guild->GetRecruitingLookingForPacketValue(), guild->GetRecruitingDescTag(0), guild->GetRecruitingDescTag(1), guild->GetRecruitingDescTag(2), guild->GetRecruitingDescTag(3), getSafeEscapeString(guild->GetRecruitingShortDesc().c_str()).c_str(), getSafeEscapeString(guild->GetRecruitingFullDesc().c_str()).c_str(), guild->GetRecruitingMinLevel(), guild->GetRecruitingPlayStyle(), guild->GetRecruitingLookingForPacketValue(), guild->GetRecruitingDescTag(0), guild->GetRecruitingDescTag(1), guild->GetRecruitingDescTag(2), guild->GetRecruitingDescTag(3)); + guild->SetRecruitingSaveNeeded(false); + } +} + +void WorldDatabase::DeleteGuild(Guild* guild) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `guilds` WHERE `id`=%u", guild->GetID()); + } +} + +void WorldDatabase::DeleteGuildMember(Guild* guild, int32 character_id) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Character (%u) from guild '%s' (%u)...", character_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `guild_members` WHERE `guild_id`=%u AND `char_id`=%u", guild->GetID(), character_id); + } +} + +void WorldDatabase::DeleteGuildEvent(Guild* guild, int64 event_id) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Event (%u) from guild '%s' (%u)...", event_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `guild_events` WHERE `guild_id`=%u AND `event_id`=%u", guild->GetID(), event_id); + } +} + +void WorldDatabase::DeleteGuildPointHistory(Guild* guild, int32 character_id, PointHistory* point_history) { + if (guild && point_history) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting PointHistory for Character (%u) from guild '%s' (%u)...", character_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `guild_points_history` WHERE `guild_id`=%u AND `char_id`=%u AND `points_date`=%u", guild->GetID(), character_id, point_history->date); + } +} + +void WorldDatabase::ArchiveGuildEvent(Guild* guild, GuildEvent* guild_event) { + if (guild && guild_event) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Archiving Event (%u) for guild '%s' (%u)...", guild_event->event_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `guild_events` SET `archived`=1 WHERE `guild_id`=%u AND `event_id`=%u", guild->GetID(), guild_event->event_id); + } +} + +void WorldDatabase::SaveHiddenGuildEvent(Guild* guild, GuildEvent* guild_event) { + if (guild && guild_event) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Hidden Event (%u) for guild '%s' (%u)...", guild_event->event_id, guild->GetName(), guild->GetID()); + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_events` (`guild_id`, `event_id`, `event_date`, `event_type`, `description`, `display`, `locked`, `archived`) VALUES (%u, %u, %u, %u, '%s', 0, %u, 0)", guild->GetID(), guild_event->event_id, guild_event->type, guild_event->date, getSafeEscapeString(guild_event->description.c_str()).c_str(), guild_event->locked); + } +} + +int32 WorldDatabase::GetGuildIDByCharacterID(int32 char_id) { + if(char_id > 0) + { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Look up guild ID for player ID: '%u'...", char_id); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM guilds, guild_members WHERE guilds.id = guild_members.guild_id AND char_id = %u ", char_id); + while (result && (row = mysql_fetch_row(result))) { + if( row[0] ) + return atoul(row[0]); + } + } + return 0; +} + +void WorldDatabase::LoadGuildDefaultRanks(Guild* guild) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Load/Set Default Ranks for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT DISTINCT `rank_id`, `rank_name`, `permission1`, `permission2` FROM `guild_ranks_defaults`"); + while (result && (row = mysql_fetch_row(result))) { + int8 rank_id = atoi(row[0]); + int32 permission1 = atoul(row[2]); + int32 permission2 = atoul(row[3]); + + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tSetting RankID %i, permission1: %u, permission2: %u", rank_id, permission1, permission2); + + guild->SetRankName(rank_id, row[1], false); + for (int32 i = 0; i <= 44; i++) { + int32 bitwise_val; + if (i < 32) { + bitwise_val = (int32)pow(2.0, (double)i); + guild->SetPermission(rank_id, i, permission1 & bitwise_val ? 1 : 0, false); + } + else { + bitwise_val = (int32)pow(2.0, (double)(i - 32)); + guild->SetPermission(rank_id, i, permission2 & bitwise_val ? 1 : 0, false); + } + } + } + } +} + +void WorldDatabase::LoadGuildDefaultEventFilters(Guild* guild) { + if (guild) { + LogWrite(GUILD__DEBUG, 3, "Guilds", "Load/Set Default Event Filters for guild '%s' (%u)...", guild->GetName(), guild->GetID()); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT DISTINCT `event_id`, `retain`, `broadcast` FROM `guild_event_defaults`"); + while (result && (row = mysql_fetch_row(result))) { + + LogWrite(GUILD__DEBUG, 3, "Guilds", "\tSetting Event Filter %i, retain: %i, broadcast: %i", atoi(row[0]), atoi(row[1]), atoi(row[2])); + + guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, atoi(row[1]), false); + guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_BROADCAST, atoi(row[2]), false); + } + } +} + +bool WorldDatabase::AddNewPlayerToServerGuild(int32 account_id, int32 char_id) +{ + // Check if this servers rule allow auto-joining Server guild + int8 autojoin = rule_manager.GetGlobalRule(R_World, GuildAutoJoin)->GetInt8(); + if( autojoin ) + { + // if so, what is the guild ID of the default server guild? + int32 guild_id = rule_manager.GetGlobalRule(R_World, GuildAutoJoinID)->GetInt32(); + Guild* guild = 0; + guild = guild_list.GetGuild(guild_id); + if (!guild) + { + // guild was not valid, abort! + LogWrite(GUILD__ERROR, 1, "Guilds", "Guild ID %u not found! Cannot autojoin members!", guild_id); + return false; + } + else + { + // guild was found, so what default Rank to make the players? if not set, use 7 (recruit) + int8 rank_id = rule_manager.GetGlobalRule(R_World, GuildAutoJoinDefaultRankID)->GetInt8(); + if(!rank_id) + rank_id = 7; + + // assuming all is good, insert the new guild member here... + GuildMember *gm = new GuildMember(); + + gm->account_id = account_id; + gm->character_id = char_id; + char* name = GetCharacterName(gm->character_id); + strncpy(gm->name, name, sizeof(gm->name)); + gm->guild_status = 0; + gm->points = 0.0; + //gm->adventure_class = player->GetAdventureClass(); + //gm->adventure_level = player->GetLevel(); + //gm->tradeskill_class = player->GetTradeskillClass(); + //gm->tradeskill_level = player->GetTSLevel(); + gm->rank = rank_id; + gm->zone = string(""); + gm->join_date = Timer::GetUnixTimeStamp(); + gm->last_login_date = gm->join_date; + gm->recruiter_id = 0; + gm->member_flags = GUILD_MEMBER_FLAGS_NOTIFY_LOGINS; + gm->recruiting_show_adventure_class = 1; + gm->recruiter_picture_data_size = 0; + gm->recruiter_picture_data = 0; + + guild->AddGuildMember(gm); + + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `guild_members` (`guild_id`, `char_id`, `join_date`, `rank_id`) VALUES (%u, %u, %u, %i)", + guild_id, char_id, gm->join_date, rank_id); + + LogWrite(GUILD__DEBUG, 3, "Guilds", "Auto-join player (%u) to server guild '%s' (%u) at rank %i...", char_id, guild->GetName(), guild_id, rank_id); + + // success! + return true; + } + } + // do not auto-join server guild + return false; +} + + diff --git a/source/WorldServer/HeroicOp/HeroicOp.cpp b/source/WorldServer/HeroicOp/HeroicOp.cpp new file mode 100644 index 0000000..9ed1902 --- /dev/null +++ b/source/WorldServer/HeroicOp/HeroicOp.cpp @@ -0,0 +1,315 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "HeroicOp.h" +#include "../../common/Log.h" +#include "../Rules/Rules.h" + +extern MasterHeroicOPList master_ho_list; +extern RuleManager rule_manager; + +HeroicOP::HeroicOP() { + m_complete = 0; + m_currentStage = 0; + m_wheel = 0; + m_target = 0; + m_startTime = 0; + m_totalTime = 0; + m_shifted = false; + for (int8 i = 0; i < 6; i++) + countered[i] = 0; +} + +HeroicOP::~HeroicOP() { + starters.clear(); +} + +void HeroicOP::SetWheel(HeroicOPWheel* val) { + if (!m_wheel) + m_wheel = val; + else + LogWrite(SPELL__ERROR, 0, "HO", "Attempted to set the wheel on a heroic op with a wheel already set"); +} + +void HeroicOP::SetTarget(int32 val) { + m_target = val; +} + +bool HeroicOP::UpdateHeroicOP(int16 icon) { + bool ret = false; + vector::iterator itr; + vector temp; + HeroicOPStarter* starter = 0; + + LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, wheel exists: %u, looking for icon %u", m_currentStage, m_wheel ? 1 : 0, icon); + // If no wheel is set we are dealing with a starter chain still. + if (!m_wheel) { + // Loop through the starter chains + for (itr = starters.begin(); itr != starters.end(); itr++) { + starter = *itr; + // See if the icon matches the ability at our current stage, if not add it to a list to be removed + if (starter->abilities[m_currentStage] == icon) + ret = true; + else + temp.push_back(*itr); + } + + if (ret) { + // ret = true so we had a match, first thing to do is remove those that didn't match + vector::iterator remove_itr; + for (remove_itr = temp.begin(); remove_itr != temp.end(); remove_itr++) + { + std::vector::iterator it = std::find(starters.begin(), starters.end(), *remove_itr); + starters.erase(it); + } + + // now advance the current stage + m_currentStage++; + + // Temp pointer to hold the completed chain, if any + HeroicOPStarter* complete_starter = 0; + + // now loop through those that are left and check the next stage abilities for a 0xFFFF + for (itr = starters.begin(); itr != starters.end(); itr++) { + starter = *itr; + // Found one that is 0xFFFF, means the starter chain is done, get a wheel and reset the stage to 0 + if ((starter->abilities[m_currentStage] == 0xFFFF)) { + LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, starter reset (new stage 0)", m_currentStage); + // reset the stage + ResetStage(); + // geth the wheel + m_wheel = master_ho_list.GetWheel(starter); + // store the starter chain that is completed + complete_starter = starter; + // set the start time to now + SetStartTime(Timer::GetCurrentTime2()); + // set the total time to complete the real to was the admin set in rules (default 10.0f) + SetTotalTime(rule_manager.GetGlobalRule(R_Zone, HOTime)->GetFloat()); + // We set a wheel so we are done, kill the loop + break; + } + } + + // Check to see if the completed start chain pointer was set + if (complete_starter) { + LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, complete_starter set", m_currentStage); + // clear the starter list + starters.clear(); + // add the completed starter back in, we do this in case we need this starter again we can just do starters.at(0), for example shifting the wheel + starters.push_back(complete_starter); + } + } + } + else { + LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, wheel order: %u", m_currentStage, m_wheel->order); + // Wheel was set so we need to check the order it needs to be completed in. + if (m_wheel->order == 0) { + // No order + + // Flag used to see if we can shift the wheel + bool can_shift = true; + // Check the icons and flag the ability as countered if there is a match + for (int8 i = 0; i < 6; i++) { + if (countered[i] == 1) { + // progress made on this wheel so we can't shift it + can_shift = false; + } + if (m_wheel->abilities[i] == icon) { + countered[i] = 1; + ret = true; + } + } + + if (ret) { + // As we found a match lets loop through to see if we completed the ho + bool finished = true; + for (int8 i = 0; i < 6; i++) { + // if the ability is not 0xFFFF and countered is 0 then we still have more to go + if (m_wheel->abilities[i] != 0xFFFF && countered[i] == 0) { + finished = false; + break; + } + } + + // is we finished the ho set the complete flag + if (finished) + SetComplete(2); + } + + if (!ret && can_shift && m_wheel->shift_icon == icon) { + // can shift, icon matched shift icon, and no progress made + ret = ShiftWheel(); + } + } + else { + // In order + + // Check to see if we can shift the wheel + if (countered[0] == 0 && icon == m_wheel->shift_icon) { + // Can only shift the icon if nothing has completed yet (countered[0] = 0) + ret = ShiftWheel(); + } + // Check the current stage and compare it to the icon + else if (m_wheel->abilities[m_currentStage] == icon) { + // Is a match so flag this stage as done + countered[m_currentStage] = 1; + // Advance the stage + m_currentStage++; + // Set the return value to true + ret = true; + // Check the next stage, if it is over 6 or equal to 0xFFFF flag the HO as complete + if (m_currentStage > 6 || m_wheel->abilities[m_currentStage] == 0xFFFF) + SetComplete(2); + } + } + } + + return ret; +} + +void HeroicOP::AddStarterChain(HeroicOPStarter* starter) { + starters.push_back(starter); +} + +bool HeroicOP::ShiftWheel() { + // Can only shift once so if we already have return out + if (HasShifted()) + return false; + + // Clear the wheel + m_wheel = 0; + + // Get a new Wheel + SetWheel(master_ho_list.GetWheel(starters.at(0))); + + // Set the ho as shifted + m_shifted = true; + + return true; +} + +MasterHeroicOPList::MasterHeroicOPList() { +} + +MasterHeroicOPList::~MasterHeroicOPList() { + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + vector temp; + vector::iterator itr4; + + // loop through the m_hoList to delete the pointers + for (itr = m_hoList.begin(); itr != m_hoList.end(); itr++) { + // loop through the second map of the m_hoList + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + // loop through the vector of the second map and delete the pointers + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) + safe_delete(*itr3); + + // clear the vector + itr2->second.clear(); + // put the starter in a temp list to delete later + temp.push_back(itr2->first); + } + // clear the seond map + itr->second.clear(); + } + // clear the m_hoList + m_hoList.clear(); + + // Delete the starters + for (itr4 = temp.begin(); itr4 != temp.end(); itr4++) + safe_delete(*itr4); + + // clear the temp vector + temp.clear(); +} + +void MasterHeroicOPList::AddStarter(int8 start_class, HeroicOPStarter* starter) { + if (m_hoList.count(start_class) == 0 || m_hoList[start_class].count(starter) == 0) { + m_hoList[start_class][starter]; // This adds the starter with out giving it a vector of wheels yet. + } +} + +void MasterHeroicOPList::AddWheel(int32 starter_id, HeroicOPWheel* wheel) { + map > >::iterator itr; + map >::iterator itr2; + bool found = false; + + // Loop through the list and add the wheel to the correct starter + for (itr = m_hoList.begin(); itr != m_hoList.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + if (itr2->first->id == starter_id) { + // Found a match, add the wheel, set the flag to break the first loop, and break this loop + itr2->second.push_back(wheel); + found = true; + break; + } + } + // If we found a match break the first loop + if (found) + break; + } + + // No match found give an error. + if (!found) + LogWrite(SPELL__DEBUG, 0, "HO", "Attempted to add a wheel to a starter (%u) that doesn't exsist", starter_id); +} + +HeroicOP* MasterHeroicOPList::GetHeroicOP(int8 class_id) { + if (m_hoList.count(class_id) == 0) { + LogWrite(SPELL__ERROR, 0, "HO", "No HO's found for the given class (%i)", class_id); + return 0; + } + + map >::iterator itr; + HeroicOP* ret = new HeroicOP(); + + // Loop through the starters for this class and add them to the HO + for (itr = m_hoList[class_id].begin(); itr != m_hoList[class_id].end(); itr++) + ret->AddStarterChain(itr->first); + + return ret; +} + +HeroicOPWheel* MasterHeroicOPList::GetWheel(HeroicOPStarter* starter) { + if (!starter) + return 0; + + if (m_hoList.count(starter->start_class) == 0) { + LogWrite(SPELL__ERROR, 0, "HO", "Start class (%u) not found", starter->start_class); + return 0; + } + + if (m_hoList[starter->start_class].count(starter) == 0) { + LogWrite(SPELL__ERROR, 0, "HO", "Wheel not found for the provided starter (%u)", starter->id); + return 0; + } + + int index = MakeRandomInt(0, m_hoList[starter->start_class][starter].size() - 1); + + if(index < m_hoList[starter->start_class][starter].size()) + return m_hoList[starter->start_class][starter].at(index); + else + LogWrite(SPELL__ERROR, 0, "HO", "Wheel index %u for heroic_ops starter ID %u NOT Found!! Wheel starter_class %u, wheel size: %u. Wheels that match starter_link_id -> Starter 'id' missing.", index, starter->id, starter->start_class, m_hoList[starter->start_class][starter].size()); + + return nullptr; +} + diff --git a/source/WorldServer/HeroicOp/HeroicOp.h b/source/WorldServer/HeroicOp/HeroicOp.h new file mode 100644 index 0000000..a5c9fcf --- /dev/null +++ b/source/WorldServer/HeroicOp/HeroicOp.h @@ -0,0 +1,156 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifndef __HEROICOP_H__ +#define __HEROICOP_H__ + +#include +#include + +#include "../../common/types.h" + +using namespace std; + +struct HeroicOPStarter { + int32 id; + int8 start_class; + int16 starter_icon; + int16 abilities[6]; +}; + +struct HeroicOPWheel { + int8 order; + int16 shift_icon; + float chance; + int16 abilities[6]; + int32 spell_id; +}; + +class HeroicOP { +public: + HeroicOP(); + ~HeroicOP(); + + /// Sets the complete flag for this Heroic OP + /// The value to set the complete flag to, 1 = failed 2 = finished + void SetComplete(int8 val) { m_complete = val; } + + /// Sets the current stage of the starter chain or the wheel chain is at + /// The stage to set this Heroic OP to + void SetStage(int8 val) { m_currentStage = val; } + + /// Sets the wheel for this Heroic OP + /// The wheel we are setting the Heroic OP to + void SetWheel(HeroicOPWheel* val); + + /// Sets the start time for the wheel + /// Value to set the start time to + void SetStartTime(int32 val) { m_startTime = val; } + + /// Sets the total time to complete the wheel + /// Value to set the total time to + void SetTotalTime(float val) { m_totalTime = val; } + + /// Sets the target of this HO + /// The ID of the spawn + void SetTarget(int32 val); + + /// Gets the complete flag for this Heroic OP + /// 0 = not complete, 1 = complete, 2+= failed + int8 GetComplete() { return m_complete; } + + /// Gets the wheel for this heroic op + HeroicOPWheel* GetWheel() { return m_wheel; } + + /// Gets a pointer to the list of starter chains + vector* GetStarterChains() { return &starters; } + + /// Gets the current stage the HO is on + int8 GetStage() { return m_currentStage; } + + /// Gets the start time for the wheel + int32 GetStartTime() { return m_startTime; } + + /// Gets the total time players have to complete the wheel + float GetTotalTime() { return m_totalTime; } + + /// Gets the ID of this HO's target + int32 GetTarget() { return m_target; } + + /// + bool HasShifted() { return m_shifted; } + + /// Checks to see if the given icon will advance the Heroic OP + /// The icon that is trying to advance the Heroic OP + /// True if the icon advanced the HO + bool UpdateHeroicOP(int16 icon); + + /// Reset the stage to 0 + void ResetStage() { m_currentStage = 0; } + + /// Adds a starter chain to the Heroic OP + /// The starter chain to add + void AddStarterChain(HeroicOPStarter* starter); + + /// Attempts to shift the wheel + bool ShiftWheel(); + + int8 countered[6]; + +private: + int8 m_complete; + int8 m_currentStage; + int32 m_startTime; + float m_totalTime; + int32 m_target; + bool m_shifted; + HeroicOPWheel* m_wheel; + vector starters; +}; + +class MasterHeroicOPList { +public: + MasterHeroicOPList(); + ~MasterHeroicOPList(); + + /// Adds the starter chain to the list + /// Class id for the starter chain + /// Starter chain to add + void AddStarter(int8 start_class, HeroicOPStarter* starter); + + /// Add the wheel chain to the list + /// Id of the starter this wheel belongs to + /// Wheel to add + void AddWheel(int32 starter_id, HeroicOPWheel* wheel); + + /// Creates a new HO + /// Class ID starting the HO + HeroicOP* GetHeroicOP(int8 class_id); + + /// Gets a random wheel from the given starter + /// The starter to determine what wheels to choose from + HeroicOPWheel* GetWheel(HeroicOPStarter* starter); + +private: + // map > > + map > > m_hoList; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/HeroicOp/HeroicOpDB.cpp b/source/WorldServer/HeroicOp/HeroicOpDB.cpp new file mode 100644 index 0000000..3701080 --- /dev/null +++ b/source/WorldServer/HeroicOp/HeroicOpDB.cpp @@ -0,0 +1,79 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "../WorldDatabase.h" +#include "../../common/Log.h" +#include "HeroicOp.h" + +extern MasterHeroicOPList master_ho_list; + +void WorldDatabase::LoadHOStarters() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `starter_class`, `starter_icon`, `ability1`, `ability2`, `ability3`, `ability4`, `ability5`, `ability6` FROM `heroic_ops` WHERE `ho_type`='Starter'"); + + if (result && mysql_num_rows(result) > 0) { + int32 count = 0; + while ((row = mysql_fetch_row(result))) { + HeroicOPStarter* starter = new HeroicOPStarter; + starter->id = atoul(row[0]); + starter->start_class = atoi(row[1]); + starter->starter_icon = atoi(row[2]); + starter->abilities[0] = atoi(row[3]); + starter->abilities[1] = atoi(row[4]); + starter->abilities[2] = atoi(row[5]); + starter->abilities[3] = atoi(row[6]); + starter->abilities[4] = atoi(row[7]); + starter->abilities[5] = atoi(row[8]); + master_ho_list.AddStarter(starter->start_class, starter); + count++; + } + + LogWrite(WORLD__INFO, 0, "World", "- Loaded %u starter chains", count); + } +} + +void WorldDatabase::LoadHOWheel() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `starter_link_id`, `chain_order`, `shift_icon`, `spell_id`, `chance`, `ability1`, `ability2`, `ability3`, `ability4`, `ability5`, `ability6` FROM `heroic_ops` WHERE `ho_type`='Wheel'"); + + if (result && mysql_num_rows(result) > 0) { + int32 count = 0; + while ((row = mysql_fetch_row(result))) { + HeroicOPWheel* wheel = new HeroicOPWheel; + wheel->order = atoi(row[1]); + wheel->shift_icon = atoi(row[2]); + wheel->spell_id = atoul(row[3]); + wheel->chance = atof(row[4]); + wheel->abilities[0] = atoi(row[5]); + wheel->abilities[1] = atoi(row[6]); + wheel->abilities[2] = atoi(row[7]); + wheel->abilities[3] = atoi(row[8]); + wheel->abilities[4] = atoi(row[9]); + wheel->abilities[5] = atoi(row[10]); + + master_ho_list.AddWheel(atoul(row[0]), wheel); + count++; + } + + LogWrite(WORLD__INFO, 0, "World", "- Loaded %u HO wheels", count); + } +} diff --git a/source/WorldServer/HeroicOp/HeroicOpPackets.cpp b/source/WorldServer/HeroicOp/HeroicOpPackets.cpp new file mode 100644 index 0000000..3f144d0 --- /dev/null +++ b/source/WorldServer/HeroicOp/HeroicOpPackets.cpp @@ -0,0 +1,158 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "../ClientPacketFunctions.h" +#include "../../common/Log.h" +#include "HeroicOp.h" +#include "../Spells.h" + +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; + +void ClientPacketFunctions::SendHeroicOPUpdate(Client* client, HeroicOP* ho) { + if (!client) { + LogWrite(PACKET__ERROR, 0, "Packets", "SendHeroicOPUpdate() called without a valid client"); + return; + } + + if (!ho) { + LogWrite(PACKET__ERROR, 0, "Packets", "SendHeroicOPUpdate() called without a valid HO"); + return; + } + + PacketStruct* packet = configReader.getStruct("WS_HeroicOpportunity", client->GetVersion()); + Spell* spell = 0; + if (packet) { + packet->setDataByName("id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer())); + if (ho->GetWheel()) { + spell = master_spell_list.GetSpell(ho->GetWheel()->spell_id, 1); + if (!spell) { + LogWrite(SPELL__ERROR, 0, "HO", "Unable to get the spell (%u)", ho->GetWheel()->spell_id); + return; + } + + packet->setDataByName("name", spell->GetName()); + packet->setDataByName("description", spell->GetDescription()); + packet->setDataByName("order", ho->GetWheel()->order); + packet->setDataByName("time_total", ho->GetTotalTime()); + packet->setDataByName("time_left", max(0.0f, (float)(((ho->GetStartTime() + (ho->GetTotalTime() * 1000)) - Timer::GetCurrentTime2()) / 1000))); + // This is not displayed in the wheel so set it to 0xFFFF + packet->setDataByName("starter_icon", 0xFFFF); + + if (ho->HasShifted()) + packet->setDataByName("shift_icon", 0xFFFF); + else + packet->setDataByName("shift_icon", ho->GetWheel()->shift_icon); + + // If completed set special values + if (ho->GetComplete() > 0) { + packet->setDataByName("wheel_type", 2); + packet->setDataByName("unknown", ho->GetComplete()); + } + + char temp[20]; + char ability[20]; + + // Set the icons for the whee; + for (int8 i = 1; i < 7; i++) { + strcpy(ability, "icon"); + itoa(i, temp, 10); + strcat(ability, temp); + packet->setDataByName(ability, ho->GetWheel()->abilities[i-1]); + } + + // Flag the icons that are completed + for (int8 i = 1; i < 7; i++) { + strcpy(ability, "countered"); + itoa(i, temp, 10); + strcat(ability, temp); + packet->setDataByName(ability, ho->countered[i-1]); + } + + } + else { + if (ho->GetComplete() > 0) { + // This will make the ui element vanish + packet->setDataByName("wheel_type", 5); + packet->setDataByName("unknown", 8); + } + else { + packet->setDataByName("wheel_type", 4); + } + + packet->setDataByName("icon1", 0xFFFF); + packet->setDataByName("icon2", 0xFFFF); + packet->setDataByName("icon3", 0xFFFF); + packet->setDataByName("icon4", 0xFFFF); + packet->setDataByName("icon5", 0xFFFF); + packet->setDataByName("icon6", 0xFFFF); + packet->setDataByName("shift_icon", 0xFFFF); + + int8 index = 1; + char temp[20]; + char ability[20]; + vector::iterator itr; + for (itr = ho->GetStarterChains()->begin(); itr != ho->GetStarterChains()->end(); itr++, index++) { + if (index > 6 ) + break; + + strcpy(ability, "icon"); + itoa(index, temp, 10); + strcat(ability, temp); + + packet->setDataByName(ability, (*itr)->abilities[ho->GetStage()]); + + // Only set this once + if (index == 1) + packet->setDataByName("starter_icon", (*itr)->starter_icon); + } + } + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} + + +/* + + + + + + + + + + + + + + + + + + + + + + + + +*/ \ No newline at end of file diff --git a/source/WorldServer/Housing/HousingDB.cpp b/source/WorldServer/Housing/HousingDB.cpp new file mode 100644 index 0000000..c90c9b5 --- /dev/null +++ b/source/WorldServer/Housing/HousingDB.cpp @@ -0,0 +1,131 @@ +#include "../WorldDatabase.h" +#include "../World.h" + +extern World world; + +void WorldDatabase::LoadHouseZones() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT * FROM `houses`"); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + world.AddHouseZone(atoul(row[0]), row[1], atoi64(row[2]), atoul(row[3]), atoi64(row[4]), atoul(row[5]), atoi(row[6]), atoi(row[7]), atoi(row[8]), atoul(row[9]), atoul(row[10]), atof(row[11]), atof(row[12]), atof(row[13]), atof(row[14])); + } + } +} + +int64 WorldDatabase::AddPlayerHouse(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due) { + Query query; + string insert = string("INSERT INTO character_houses (char_id, house_id, instance_id, upkeep_due) VALUES (%u, %u, %u, %u) "); + query.RunQuery2(Q_INSERT, insert.c_str(), char_id, house_id, instance_id, upkeep_due); + + int64 unique_id = query.GetLastInsertedID(); + return unique_id; +} + +void WorldDatabase::SetHouseUpkeepDue(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due) { + Query query; + string update = string("UPDATE character_houses set upkeep_due=%u where char_id = %u and house_id = %u and instance_id = %u"); + query.RunQuery2(Q_UPDATE, update.c_str(), upkeep_due, char_id, house_id, instance_id); +} + + +void WorldDatabase::UpdateHouseEscrow(int32 house_id, int32 instance_id, int64 amount_coins, int32 amount_status) { + Query query; + string update = string("UPDATE character_houses set escrow_coins = %llu, escrow_status = %u where house_id = %u and instance_id = %u"); + query.RunQuery2(Q_UPDATE, update.c_str(), amount_coins, amount_status, house_id, instance_id); +} + + +void WorldDatabase::RemovePlayerHouse(int32 char_id, int32 house_id) { +} + +void WorldDatabase::LoadPlayerHouses() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT h.id, h.char_id, h.house_id, h.instance_id, h.upkeep_due, h.escrow_coins, h.escrow_status, c.name FROM character_houses h, characters c WHERE h.char_id = c.id"); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + world.AddPlayerHouse(atoul(row[1]), atoul(row[2]), atoi64(row[0]), atoul(row[3]), atoul(row[4]), atoi64(row[5]), atoul(row[6]), row[7]); + } + } +} + +void WorldDatabase::LoadDeposits(PlayerHouse* ph) +{ + if (!ph) + return; + ph->deposits.clear(); + ph->depositsMap.clear(); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select timestamp, amount, last_amount, status, last_status, name from character_house_deposits where house_id = %u and instance_id = %u order by timestamp asc limit 255", ph->house_id, ph->instance_id); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + Deposit d; + d.timestamp = atoul(row[0]); + + int64 outVal = strtoull(row[1], NULL, 0); + d.amount = outVal; + + outVal = strtoull(row[2], NULL, 0); + d.last_amount = outVal; + + d.status = atoul(row[3]); + d.last_status = atoul(row[4]); + + d.name = string(row[5]); + + ph->deposits.push_back(d); + ph->depositsMap.insert(make_pair(d.name, d)); + } + } +} + +void WorldDatabase::LoadHistory(PlayerHouse* ph) +{ + if (!ph) + return; + ph->history.clear(); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select timestamp, amount, status, reason, name, pos_flag from character_house_history where house_id = %u and instance_id = %u order by timestamp asc limit 255", ph->house_id, ph->instance_id); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + HouseHistory h; + h.timestamp = atoul(row[0]); + + int64 outVal = strtoull(row[1], NULL, 0); + h.amount = outVal; + + h.status = atoul(row[2]); + + h.reason = string(row[3]); + h.name = string(row[4]); + + h.pos_flag = atoul(row[5]); + + ph->history.push_back(h); + } + } +} + + +void WorldDatabase::AddHistory(PlayerHouse* house, char* name, char* reason, int32 timestamp, int64 amount, int32 status, int8 pos_flag) +{ + if (!house) + return; + + HouseHistory h(Timer::GetUnixTimeStamp(), amount, string(name), string(reason), status, pos_flag); + house->history.push_back(h); + + Query query; + string insert = string("INSERT INTO character_house_history (timestamp, house_id, instance_id, name, amount, status, reason, pos_flag) VALUES (%u, %u, %u, '%s', %llu, %u, '%s', %u) "); + query.RunQuery2(Q_INSERT, insert.c_str(), timestamp, house->house_id, house->instance_id, name, amount, status, reason, pos_flag); +} \ No newline at end of file diff --git a/source/WorldServer/Housing/HousingPackets.cpp b/source/WorldServer/Housing/HousingPackets.cpp new file mode 100644 index 0000000..9b30783 --- /dev/null +++ b/source/WorldServer/Housing/HousingPackets.cpp @@ -0,0 +1,454 @@ +#include "../ClientPacketFunctions.h" +#include "../World.h" +#include "../client.h" +#include "../WorldDatabase.h" +#include "../Rules/Rules.h" + +extern ConfigReader configReader; +extern World world; +extern WorldDatabase database; +extern RuleManager rule_manager; + +void ClientPacketFunctions::SendHousePurchase(Client* client, HouseZone* hz, int32 spawnID) { + PacketStruct* packet = configReader.getStruct("WS_PlayerHousePurchase", client->GetVersion()); + if (packet) { + int8 disable_alignment_req = rule_manager.GetGlobalRule(R_Player, DisableHouseAlignmentRequirement)->GetInt8(); + packet->setDataByName("house_name", hz->name.c_str()); + packet->setDataByName("house_id", hz->id); + packet->setDataByName("spawn_id", spawnID); + packet->setDataByName("purchase_coins", hz->cost_coin); + packet->setDataByName("purchase_status", hz->cost_status); + packet->setDataByName("upkeep_coins", hz->upkeep_coin); + packet->setDataByName("upkeep_status", hz->upkeep_status); + packet->setDataByName("vendor_vault_slots", hz->vault_slots); + string req; + if (hz->alignment > 0 && !disable_alignment_req) { + req = "You must be of "; + if (hz->alignment == 1) + req.append("Good"); + else + req.append("Evil"); + req.append(" alignment"); + } + if (hz->guild_level > 0) { + if (req.length() > 0) { + req.append(", and a guild level of "); + char temp[5]; + sprintf(temp, "%i", hz->guild_level); + req.append(temp); + //req.append(std::to_string(static_cast(hz->guild_level))); + } + else { + req.append("Requires a guild of level "); + char temp[5]; + sprintf(temp, "%i", hz->guild_level); + req.append(temp); + //req.append(std::to_string(static_cast(hz->guild_level))) + req.append(" or above"); + } + } + if (req.length() > 0) { + req.append(" in order to purchase a home within the "); + req.append(hz->name); + req.append("."); + } + + packet->setDataByName("additional_reqs", req.c_str()); + + bool enable_buy = true; + if (hz->alignment > 0 && client->GetPlayer()->GetAlignment() != hz->alignment && !disable_alignment_req) + enable_buy = false; + if (hz->guild_level > 0 && (!client->GetPlayer()->GetGuild() || (client->GetPlayer()->GetGuild() && client->GetPlayer()->GetGuild()->GetLevel() < hz->guild_level))) + enable_buy = false; + + packet->setDataByName("enable_buy", enable_buy ? 1 : 0); + //packet->PrintPacket(); + client->QueuePacket(packet->serialize()); + } + + safe_delete(packet); +} + +void ClientPacketFunctions::SendHousingList(Client* client) { + if(client->GetVersion() <= 561) { + return; // not supported + } + + std::vector houses = world.GetAllPlayerHouses(client->GetCharacterID()); + // this packet must be sent first otherwise it blocks out the enter house option after paying upkeep + PacketStruct* packet = configReader.getStruct("WS_CharacterHousingList", client->GetVersion()); + if(!packet) { + return; + } + packet->setArrayLengthByName("num_houses", houses.size()); + for (int i = 0; i < houses.size(); i++) + { + PlayerHouse* ph = (PlayerHouse*)houses[i]; + HouseZone* hz = world.GetHouseZone(ph->house_id); + string name; + name = ph->player_name; + name.append("'s "); + name.append(hz->name); + packet->setArrayDataByName("house_id", ph->unique_id, i); + string zone_name = database.GetZoneName(hz->zone_id); + if(zone_name.length() > 0) + packet->setArrayDataByName("zone", zone_name.c_str(), i); + packet->setArrayDataByName("house_city", hz->name.c_str(), i); + packet->setArrayDataByName("house_address", "", i); // need this pulled from live + packet->setArrayDataByName("house_description", name.c_str(), i); + packet->setArrayDataByName("index", i, i); // they send 2, 4, 6, 8 as the index ID's on the client.. + + // this seems to be some kind of timestamp, if we keep updating then in conjunction with upkeep_due + // in SendBaseHouseWindow/WS_PlayerHouseBaseScreen being a >0 number we can access 'enter house' + + int32 upkeep_due = 0; + + if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0) + upkeep_due = ph->upkeep_due - Timer::GetUnixTimeStamp(); + + if ( client->GetVersion() >= 63119 ) + packet->setArrayDataByName("unknown2a", 0xFFFFFFFF, i); + else + packet->setArrayDataByName("unknown2", 0xFFFFFFFF, i); + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void ClientPacketFunctions::SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID) { + // if we don't send this then the enter house option won't be available if upkeep is paid + if (!hz || !ph) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "HouseZone or PlayerHouse missing and cannot send SendBaseHouseWindow"); + return; + } + + string name; + name = ph->player_name; + name.append("'s "); + name.append(hz->name); + + if (spawnID) + SendHousingList(client); + + int32 upkeep_due = 0; + + if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0) + upkeep_due = ph->upkeep_due - Timer::GetUnixTimeStamp(); + + // need this to enable the "enter house" button + PacketStruct* packet = nullptr; + + + if(client->GetVersion() > 561 && client->GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE + && client->GetCurrentZone()->GetInstanceType() != GUILD_HOUSE_INSTANCE) { + packet = configReader.getStruct("WS_UpdateHouseAccessDataMsg", client->GetVersion()); + + if(!packet) { + return; // we need this for these clients or enter house will not work properly + } + if (packet) { + packet->setDataByName("house_id", 0xFFFFFFFFFFFFFFFF); + packet->setDataByName("success", (upkeep_due > 0) ? 0xFFFFFFFF : 0); + packet->setDataByName("unknown2", 0xFFFFFFFF); + packet->setDataByName("unknown3", 0xFFFFFFFF); + } + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + + packet = configReader.getStruct("WS_PlayerHouseBaseScreen", client->GetVersion()); + if (packet) { + packet->setDataByName("house_id", ph->unique_id); + packet->setDataByName("spawn_id", spawnID); + packet->setDataByName("character_id", client->GetPlayer()->GetCharacterID()); + packet->setDataByName("house_name", name.c_str()); + packet->setDataByName("zone_name", hz->name.c_str()); + packet->setDataByName("upkeep_cost_coins", hz->upkeep_coin); + packet->setDataByName("upkeep_cost_status", hz->upkeep_status); + + packet->setDataByName("upkeep_due", upkeep_due); + + packet->setDataByName("escrow_balance_coins", ph->escrow_coins); + packet->setDataByName("escrow_balance_status", ph->escrow_status); + // temp - set priv level to owner for now + packet->setDataByName("privlage_level", 4); + // temp - set house type to personal house for now + packet->setDataByName("house_type", 0); + + if(client->GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE + || client->GetCurrentZone()->GetInstanceType() == GUILD_HOUSE_INSTANCE) { + packet->setDataByName("inside_house", 1); + packet->setDataByName("public_access_level", 1); + } + packet->setDataByName("num_access", 0); + packet->setDataByName("num_history", 0); + + // allows deposits/history to be seen -- at this point seems plausible supposed to be 'inside_house'..? + packet->setDataByName("unknown3", (ph->deposits.size() || ph->history.size()) ? 1 : 0); + + packet->setArrayLengthByName("num_deposit", ph->deposits.size()); + list::iterator itr; + int d = 0; + for (itr = ph->deposits.begin(); itr != ph->deposits.end(); itr++) + { + packet->setArrayDataByName("deposit_name", itr->name.c_str(), d); + packet->setArrayDataByName("deposit_total_coin", itr->amount, d); + packet->setArrayDataByName("deposit_time_stamp", itr->timestamp, d); + packet->setArrayDataByName("deposit_last_coin", itr->last_amount, d); + packet->setArrayDataByName("deposit_total_status", itr->status, d); + packet->setArrayDataByName("deposit_last_status", itr->last_status, d); + d++; + } + + + packet->setArrayLengthByName("num_history", ph->history.size()); + list::iterator hitr; + d = 0; + for (hitr = ph->history.begin(); hitr != ph->history.end(); hitr++) + { + packet->setArrayDataByName("history_name", hitr->name.c_str(), d); + packet->setArrayDataByName("history_coins", hitr->amount, d); + packet->setArrayDataByName("history_status", hitr->status, d); + packet->setArrayDataByName("history_time_stamp", hitr->timestamp, d); + packet->setArrayDataByName("history_reason", hitr->reason.c_str(), d); + packet->setArrayDataByName("history_add_flag", hitr->pos_flag, d); + d++; + } + + EQ2Packet* pack = packet->serialize(); + //DumpPacket(pack); + client->QueuePacket(pack); + } + safe_delete(packet); +} + +void ClientPacketFunctions::SendHouseVisitWindow(Client* client, vector houses) { + PacketStruct* packet = configReader.getStruct("WS_DisplayVisitScreen", client->GetVersion()); + if (packet) { + vector::iterator itr; + packet->setArrayLengthByName("num_houses", houses.size()); + int16 i = 0; + for (itr = houses.begin(); itr != houses.end(); itr++) { + PlayerHouse* ph = *itr; + if (ph) { + HouseZone* hz = world.GetHouseZone(ph->house_id); + if (hz) { + packet->setArrayDataByName("house_id", ph->unique_id, i); + packet->setArrayDataByName("house_owner", ph->player_name.c_str(), i); + packet->setArrayDataByName("house_location", hz->name.c_str(), i); + packet->setArrayDataByName("house_zone", client->GetCurrentZone()->GetZoneName(), i); + + if ( string(client->GetPlayer()->GetName()).compare(ph->player_name) == 0 ) + packet->setArrayDataByName("access_level", 4, i); + else + packet->setArrayDataByName("access_level", 1, i); + packet->setArrayDataByName("visit_flag", 0, i); // 0 = allowed to visit, 1 = owner hasn't paid upkeep + i++; + } + } + } + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} + +/* + + + + + + + + + + + + + + +*/ + + +void ClientPacketFunctions::SendLocalizedTextMessage(Client* client) +{ + /*** + -- OP_ReloadLocalizedTxtMsg -- +5/26/2020 19:08:41 +69.174.200.100 -> 192.168.1.1 +0000: 01 FF 63 01 62 00 00 00 1C 00 49 72 6F 6E 74 6F ..c.b.....Ironto +0010: 65 73 20 45 61 73 74 20 4C 61 72 67 65 20 49 6E es East Large In +0020: 6E 20 52 6F 6F 6D 07 01 00 00 00 1C 00 49 72 6F n Room.......Iro +0030: 6E 74 6F 65 73 20 45 61 73 74 20 4C 61 72 67 65 ntoes East Large +0040: 20 49 6E 6E 20 52 6F 6F 6D 07 02 00 00 00 1C 00 Inn Room....... +0050: 49 72 6F 6E 74 6F 65 73 20 45 61 73 74 20 4C 61 Irontoes East La +0060: 72 67 65 20 49 6E 6E 20 52 6F 6F 6D 07 03 00 00 rge Inn Room.... +0070: 00 1C 00 49 72 6F 6E 74 6F 65 73 20 45 61 73 74 ...Irontoes East +0080: 20 4C 61 72 67 65 20 49 6E 6E 20 52 6F 6F 6D 07 Large Inn Room. +0090: 04 00 00 00 1C 00 49 72 6F 6E 74 6F 65 73 20 45 ......Irontoes E +00A0: 61 73 74 20 4C 61 72 67 65 20 49 6E 6E 20 52 6F ast Large Inn Ro +00B0: 6F 6D 07 05 00 00 00 1C 00 49 72 6F 6E 74 6F 65 om.......Irontoe +00C0: 73 20 45 61 73 74 20 4C 61 72 67 65 20 49 6E 6E s East Large Inn +00D0: 20 52 6F 6F 6D 07 06 00 00 00 1C 00 49 72 6F 6E Room.......Iron +00E0: 74 6F 65 73 20 45 61 73 74 20 4C 61 72 67 65 20 toes East Large +00F0: 49 6E 6E 20 52 6F 6F 6D 07 07 00 00 00 19 00 51 Inn Room.......Q +0100: 65 79 6E 6F 73 20 47 75 69 6C 64 20 48 61 6C 6C eynos Guild Hall +0110: 2C 20 54 69 65 72 20 31 07 08 00 00 00 16 00 4C , Tier 1.......L +0120: 69 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 74 65 ion's Mane Suite +0130: 20 52 6F 6F 6D 07 09 00 00 00 16 00 4C 69 6F 6E Room.......Lion +0140: 27 73 20 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 's Mane Suite Ro +0150: 6F 6D 07 0A 00 00 00 16 00 4C 69 6F 6E 27 73 20 om.......Lion's +0160: 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 6F 6D 07 Mane Suite Room. +0170: 0B 00 00 00 16 00 4C 69 6F 6E 27 73 20 4D 61 6E ......Lion's Man +0180: 65 20 53 75 69 74 65 20 52 6F 6F 6D 07 0C 00 00 e Suite Room.... +0190: 00 0E 00 32 20 4C 75 63 69 65 20 53 74 72 65 65 ...2 Lucie Stree +01A0: 74 07 0D 00 00 00 0F 00 31 37 20 54 72 61 6E 71 t.......17 Tranq +01B0: 75 69 6C 20 57 61 79 07 0E 00 00 00 0E 00 38 20 uil Way.......8 +01C0: 4C 75 63 69 65 20 53 74 72 65 65 74 07 0F 00 00 Lucie Street.... +01D0: 00 0F 00 31 32 20 4C 75 63 69 65 20 53 74 72 65 ...12 Lucie Stre +01E0: 65 74 07 10 00 00 00 0F 00 31 38 20 4C 75 63 69 et.......18 Luci +01F0: 65 20 53 74 72 65 65 74 07 11 00 00 00 0F 00 32 e Street.......2 +0200: 30 20 4C 75 63 69 65 20 53 74 72 65 65 74 07 12 0 Lucie Street.. +0210: 00 00 00 0E 00 33 20 54 72 61 6E 71 75 69 6C 20 .....3 Tranquil +0220: 57 61 79 07 13 00 00 00 0E 00 37 20 54 72 61 6E Way.......7 Tran +0230: 71 75 69 6C 20 57 61 79 07 14 00 00 00 0F 00 31 quil Way.......1 +0240: 33 20 54 72 61 6E 71 75 69 6C 20 57 61 79 07 15 3 Tranquil Way.. +0250: 00 00 00 0F 00 31 35 20 54 72 61 6E 71 75 69 6C .....15 Tranquil +0260: 20 57 61 79 07 16 00 00 00 19 00 51 65 79 6E 6F Way.......Qeyno +0270: 73 20 47 75 69 6C 64 20 48 61 6C 6C 2C 20 54 69 s Guild Hall, Ti +0280: 65 72 20 32 07 17 00 00 00 0F 00 38 20 45 72 6F er 2.......8 Ero +0290: 6C 6C 69 73 69 20 4C 61 6E 65 07 18 00 00 00 0F llisi Lane...... +02A0: 00 35 20 45 72 6F 6C 6C 69 73 69 20 4C 61 6E 65 .5 Erollisi Lane +02B0: 07 19 00 00 00 0E 00 35 20 4B 61 72 61 6E 61 20 .......5 Karana +02C0: 43 6F 75 72 74 07 1A 00 00 00 0D 00 32 20 42 61 Court.......2 Ba +02D0: 79 6C 65 20 43 6F 75 72 74 07 1B 00 00 00 0D 00 yle Court....... +02E0: 34 20 42 61 79 6C 65 20 43 6F 75 72 74 07 1C 00 4 Bayle Court... +02F0: 00 00 16 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 ....Lion's Mane +0300: 53 75 69 74 65 20 52 6F 6F 6D 07 1D 00 00 00 16 Suite Room...... +0310: 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 .Lion's Mane Sui +0320: 74 65 20 52 6F 6F 6D 07 1E 00 00 00 16 00 4C 69 te Room.......Li +0330: 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 74 65 20 on's Mane Suite +0340: 52 6F 6F 6D 07 1F 00 00 00 16 00 4C 69 6F 6E 27 Room.......Lion' +0350: 73 20 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 6F s Mane Suite Roo +0360: 6D 07 20 00 00 00 0E 00 35 20 4C 75 63 69 65 20 m. .....5 Lucie +0370: 53 74 72 65 65 74 07 21 00 00 00 0F 00 32 30 20 Street.!.....20 +0380: 4B 61 72 61 6E 61 20 43 6F 75 72 74 07 22 00 00 Karana Court.".. +0390: 00 0E 00 39 20 4C 75 63 69 65 20 53 74 72 65 65 ...9 Lucie Stree +03A0: 74 07 23 00 00 00 0F 00 31 35 20 4C 75 63 69 65 t.#.....15 Lucie +03B0: 20 53 74 72 65 65 74 07 24 00 00 00 0F 00 31 37 Street.$.....17 +03C0: 20 4C 75 63 69 65 20 53 74 72 65 65 74 07 25 00 Lucie Street.%. +03D0: 00 00 0F 00 32 31 20 4C 75 63 69 65 20 53 74 72 ....21 Lucie Str +03E0: 65 65 74 07 26 00 00 00 0E 00 36 20 4B 61 72 61 eet.&.....6 Kara +03F0: 6E 61 20 43 6F 75 72 74 07 27 00 00 00 0F 00 31 na Court.'.....1 +0400: 32 20 4B 61 72 61 6E 61 20 43 6F 75 72 74 07 28 2 Karana Court.( +0410: 00 00 00 0F 00 31 34 20 4B 61 72 61 6E 61 20 43 .....14 Karana C +0420: 6F 75 72 74 07 29 00 00 00 0F 00 31 38 20 4B 61 ourt.).....18 Ka +0430: 72 61 6E 61 20 43 6F 75 72 74 07 2A 00 00 00 1E rana Court.*.... +0440: 00 43 6F 6E 63 6F 72 64 69 75 6D 20 54 6F 77 65 .Concordium Towe +0450: 72 20 4D 61 67 69 63 61 6C 20 4D 61 6E 6F 72 07 r Magical Manor. +0460: 2B 00 00 00 15 00 41 72 63 61 6E 65 20 41 63 61 +.....Arcane Aca +0470: 64 65 6D 79 20 50 6F 72 74 61 6C 07 2C 00 00 00 demy Portal.,... +0480: 13 00 43 6F 75 72 74 20 6F 66 20 74 68 65 20 4D ..Court of the M +0490: 61 73 74 65 72 07 2D 00 00 00 13 00 43 69 74 79 aster.-.....City +04A0: 20 6F 66 20 4D 69 73 74 20 45 73 74 61 74 65 07 of Mist Estate. +04B0: 2E 00 00 00 10 00 44 61 72 6B 6C 69 67 68 74 20 ......Darklight +04C0: 50 61 6C 61 63 65 07 2F 00 00 00 11 00 44 65 65 Palace./.....Dee +04D0: 70 77 61 74 65 72 20 52 65 74 72 65 61 74 07 30 pwater Retreat.0 +04E0: 00 00 00 24 00 44 68 61 6C 67 61 72 20 50 72 65 ...$.Dhalgar Pre +04F0: 63 69 70 69 63 65 20 6F 66 20 74 68 65 20 44 65 cipice of the De +0500: 65 70 20 50 6F 72 74 61 6C 07 31 00 00 00 12 00 ep Portal.1..... +0510: 44 69 6D 65 6E 73 69 6F 6E 61 6C 20 50 6F 63 6B Dimensional Pock +0520: 65 74 07 32 00 00 00 0B 00 44 6F 6A 6F 20 50 6F et.2.....Dojo Po +0530: 72 74 61 6C 07 33 00 00 00 21 00 45 6C 61 62 6F rtal.3...!.Elabo +0540: 72 61 74 65 20 45 73 74 61 74 65 20 6F 66 20 55 rate Estate of U +0550: 6E 72 65 73 74 20 50 6F 72 74 61 6C 07 34 00 00 nrest Portal.4.. +0560: 00 11 00 45 74 68 65 72 6E 65 72 65 20 45 6E 63 ...Ethernere Enc +0570: 6C 61 76 65 07 35 00 00 00 10 00 45 76 65 72 66 lave.5.....Everf +0580: 72 6F 73 74 20 50 6F 72 74 61 6C 07 36 00 00 00 rost Portal.6... +0590: 16 00 46 65 61 72 66 75 6C 20 52 65 74 72 65 61 ..Fearful Retrea +05A0: 74 20 50 6F 72 74 61 6C 07 37 00 00 00 0F 00 46 t Portal.7.....F +05B0: 65 6C 77 69 74 68 65 20 50 6F 72 74 61 6C 07 38 elwithe Portal.8 +05C0: 00 00 00 10 00 46 72 65 65 62 6C 6F 6F 64 20 50 .....Freeblood P +05D0: 6F 72 74 61 6C 07 39 00 00 00 0C 00 46 72 69 67 ortal.9.....Frig +05E0: 68 74 20 4D 61 6E 6F 72 07 3A 00 00 00 11 00 47 ht Manor.:.....G +05F0: 61 6C 6C 65 6F 6E 20 6F 66 20 44 72 65 61 6D 73 alleon of Dreams +0600: 07 3B 00 00 00 14 00 48 61 6C 6C 20 6F 66 20 74 .;.....Hall of t +0610: 68 65 20 43 68 61 6D 70 69 6F 6E 07 3C 00 00 00 he Champion.<... +0620: 10 00 48 75 61 20 4D 65 69 6E 20 52 65 74 72 65 ..Hua Mein Retre +0630: 61 74 07 3D 00 00 00 1C 00 49 73 6C 65 20 6F 66 at.=.....Isle of +0640: 20 52 65 66 75 67 65 20 50 72 65 73 74 69 67 65 Refuge Prestige +0650: 20 48 6F 6D 65 07 3E 00 00 00 0F 00 4B 65 72 61 Home.>.....Kera +0660: 66 79 72 6D 27 73 20 4C 61 69 72 07 3F 00 00 00 fyrm's Lair.?... +0670: 0E 00 4B 72 6F 6D 7A 65 6B 20 50 6F 72 74 61 6C ..Kromzek Portal +0680: 07 40 00 00 00 10 00 4C 61 76 61 73 74 6F 72 6D .@.....Lavastorm +0690: 20 50 6F 72 74 61 6C 07 41 00 00 00 0E 00 4C 69 Portal.A.....Li +06A0: 62 72 61 72 79 20 50 6F 72 74 61 6C 07 42 00 00 brary Portal.B.. +06B0: 00 0B 00 4D 61 72 61 20 45 73 74 61 74 65 07 43 ...Mara Estate.C +06C0: 00 00 00 21 00 4D 61 6A 27 44 75 6C 20 41 73 74 ...!.Maj'Dul Ast +06D0: 72 6F 6E 6F 6D 65 72 27 73 20 54 6F 77 65 72 20 ronomer's Tower +06E0: 50 6F 72 74 61 6C 07 44 00 00 00 14 00 4D 61 6A Portal.D.....Maj +06F0: 27 44 75 6C 20 53 75 69 74 65 20 50 6F 72 74 61 'Dul Suite Porta +0700: 6C 07 45 00 00 00 17 00 4D 69 73 74 6D 6F 6F 72 l.E.....Mistmoor +0710: 65 20 43 72 61 67 73 20 45 73 74 61 74 65 73 07 e Crags Estates. +0720: 46 00 00 00 0D 00 4F 61 6B 6D 79 73 74 20 47 6C F.....Oakmyst Gl +0730: 61 64 65 07 47 00 00 00 12 00 4F 70 65 72 61 20 ade.G.....Opera +0740: 48 6F 75 73 65 20 50 6F 72 74 61 6C 07 48 00 00 House Portal.H.. +0750: 00 16 00 50 65 72 73 6F 6E 61 6C 20 47 72 6F 74 ...Personal Grot +0760: 74 6F 20 50 6F 72 74 61 6C 07 49 00 00 00 17 00 to Portal.I..... +0770: 52 75 6D 20 52 75 6E 6E 65 72 73 20 43 6F 76 65 Rum Runners Cove +0780: 20 50 6F 72 74 61 6C 07 4A 00 00 00 12 00 50 6C Portal.J.....Pl +0790: 61 6E 65 74 61 72 69 75 6D 20 50 6F 72 74 61 6C anetarium Portal +07A0: 07 4B 00 00 00 14 00 52 65 73 65 61 72 63 68 65 .K.....Researche +07B0: 72 27 73 20 53 61 6E 63 74 75 6D 07 4C 00 00 00 r's Sanctum.L... +07C0: 1E 00 52 65 73 69 64 65 6E 63 65 20 6F 66 20 74 ..Residence of t +07D0: 68 65 20 42 6C 61 64 65 73 20 50 6F 72 74 61 6C he Blades Portal +07E0: 07 4D 00 00 00 16 00 53 61 6E 63 74 75 73 20 53 .M.....Sanctus S +07F0: 65 72 75 20 50 72 6F 6D 65 6E 61 64 65 07 4E 00 eru Promenade.N. +0800: 00 00 22 00 53 61 6E 74 61 20 47 6C 75 67 27 73 ..".Santa Glug's +0810: 20 43 68 65 65 72 66 75 6C 20 48 6F 6C 69 64 61 Cheerful Holida +0820: 79 20 48 6F 6D 65 07 4F 00 00 00 17 00 53 65 63 y Home.O.....Sec +0830: 6C 75 64 65 64 20 53 61 6E 63 74 75 6D 20 50 6F luded Sanctum Po +0840: 72 74 61 6C 07 50 00 00 00 18 00 53 6B 79 62 6C rtal.P.....Skybl +0850: 61 64 65 20 53 6B 69 66 66 20 4C 61 75 6E 63 68 ade Skiff Launch +0860: 70 61 64 07 51 00 00 00 0E 00 53 6E 6F 77 79 20 pad.Q.....Snowy +0870: 44 77 65 6C 6C 69 6E 67 07 52 00 00 00 1D 00 53 Dwelling.R.....S +0880: 70 72 6F 63 6B 65 74 27 73 20 49 6E 74 65 72 6C procket's Interl +0890: 6F 63 6B 69 6E 67 20 50 6C 61 6E 65 07 53 00 00 ocking Plane.S.. +08A0: 00 17 00 53 74 6F 72 6D 20 54 6F 77 65 72 20 49 ...Storm Tower I +08B0: 73 6C 65 20 50 6F 72 74 61 6C 07 54 00 00 00 21 sle Portal.T...! +08C0: 00 52 65 6C 69 63 20 54 69 6E 6B 65 72 20 50 72 .Relic Tinker Pr +08D0: 65 73 74 69 67 65 20 48 6F 6D 65 20 50 6F 72 74 estige Home Port +08E0: 61 6C 07 55 00 00 00 10 00 54 65 6E 65 62 72 6F al.U.....Tenebro +08F0: 75 73 20 50 6F 72 74 61 6C 07 56 00 00 00 10 00 us Portal.V..... +0900: 54 68 65 20 42 61 75 62 62 6C 65 73 68 69 72 65 The Baubbleshire +0910: 07 57 00 00 00 0F 00 54 69 6E 6B 65 72 65 72 27 .W.....Tinkerer' +0920: 73 20 49 73 6C 65 07 58 00 00 00 12 00 54 6F 77 s Isle.X.....Tow +0930: 65 72 20 6F 66 20 4B 6E 6F 77 6C 65 64 67 65 07 er of Knowledge. +0940: 59 00 00 00 15 00 55 6E 63 61 6E 6E 79 20 45 73 Y.....Uncanny Es +0950: 74 61 74 65 20 50 6F 72 74 61 6C 07 5A 00 00 00 tate Portal.Z... +0960: 1E 00 56 61 63 61 6E 74 20 45 73 74 61 74 65 20 ..Vacant Estate +0970: 6F 66 20 55 6E 72 65 73 74 20 50 6F 72 74 61 6C of Unrest Portal +0980: 07 5B 00 00 00 18 00 56 61 6C 65 20 6F 66 20 48 .[.....Vale of H +0990: 61 6C 66 70 69 6E 74 20 44 65 6C 69 67 68 74 07 alfpint Delight. +09A0: 5C 00 00 00 26 00 4C 69 6F 6E 27 73 20 4D 61 6E \...&.Lion's Man +09B0: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room - +09C0: 20 4E 65 74 74 6C 65 76 69 6C 6C 65 07 5D 00 00 Nettleville.].. +09D0: 00 2C 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 56 .,.Lion's Mane V +09E0: 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D 20 53 74 estige Room - St +09F0: 61 72 63 72 65 73 74 20 43 6F 6D 6D 75 6E 65 07 arcrest Commune. +0A00: 5E 00 00 00 29 00 4C 69 6F 6E 27 73 20 4D 61 6E ^...).Lion's Man +0A10: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room - +0A20: 20 47 72 61 79 73 74 6F 6E 65 20 59 61 72 64 07 Graystone Yard. +0A30: 5F 00 00 00 2C 00 4C 69 6F 6E 27 73 20 4D 61 6E _...,.Lion's Man +0A40: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room - +0A50: 20 43 61 73 74 6C 65 76 69 65 77 20 48 61 6D 6C Castleview Haml +0A60: 65 74 07 60 00 00 00 2A 00 4C 69 6F 6E 27 73 20 et.`...*.Lion's +0A70: 4D 61 6E 65 20 56 65 73 74 69 67 65 20 52 6F 6F Mane Vestige Roo +0A80: 6D 20 2D 20 54 68 65 20 57 69 6C 6C 6F 77 20 57 m - The Willow W +0A90: 6F 6F 64 07 61 00 00 00 2B 00 4C 69 6F 6E 27 73 ood.a...+.Lion's +0AA0: 20 4D 61 6E 65 20 56 65 73 74 69 67 65 20 52 6F Mane Vestige Ro +0AB0: 6F 6D 20 2D 20 54 68 65 20 42 61 75 62 62 6C 65 om - The Baubble +0AC0: 73 68 69 72 65 07 62 00 00 00 FF FF FF FF shire.b....... +*/ +} \ No newline at end of file diff --git a/source/WorldServer/Items/Items.cpp b/source/WorldServer/Items/Items.cpp new file mode 100644 index 0000000..a7b87fe --- /dev/null +++ b/source/WorldServer/Items/Items.cpp @@ -0,0 +1,4648 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Items.h" +#include "../Spells.h" +#include "../Quests.h" +#include "../Player.h" +#include "../classes.h" +#include "math.h" +#include "../World.h" +#include "../LuaInterface.h" +#include "../../common/Log.h" +#include "../Entity.h" +#include "../Recipes/Recipe.h" +#include +#include +#include +#include "../Rules/Rules.h" + +extern World world; +extern MasterSpellList master_spell_list; +extern MasterQuestList master_quest_list; +extern MasterRecipeList master_recipe_list; +extern ConfigReader configReader; +extern LuaInterface* lua_interface; +extern RuleManager rule_manager; +extern Classes classes; + +MasterItemList::MasterItemList(){ + AddMappedItemStat(ITEM_STAT_ADORNING, std::string("adorning")); + AddMappedItemStat(ITEM_STAT_AGGRESSION, std::string("aggression")); + AddMappedItemStat(ITEM_STAT_ARTIFICING, std::string("artificing")); + AddMappedItemStat(ITEM_STAT_ARTISTRY, std::string("artistry")); + AddMappedItemStat(ITEM_STAT_CHEMISTRY, std::string("chemistry")); + AddMappedItemStat(ITEM_STAT_CRUSHING, std::string("crushing")); + AddMappedItemStat(ITEM_STAT_DEFENSE, std::string("defense")); + AddMappedItemStat(ITEM_STAT_DEFLECTION, std::string("deflection")); + AddMappedItemStat(ITEM_STAT_DISRUPTION, std::string("disruption")); + AddMappedItemStat(ITEM_STAT_FISHING, std::string("fishing")); + AddMappedItemStat(ITEM_STAT_FLETCHING, std::string("fletching")); + AddMappedItemStat(ITEM_STAT_FOCUS, std::string("focus")); + AddMappedItemStat(ITEM_STAT_FORESTING, std::string("foresting")); + AddMappedItemStat(ITEM_STAT_GATHERING, std::string("gathering")); + AddMappedItemStat(ITEM_STAT_METAL_SHAPING, std::string("metal shaping")); + AddMappedItemStat(ITEM_STAT_METALWORKING, std::string("metalworking")); + AddMappedItemStat(ITEM_STAT_MINING, std::string("mining")); + AddMappedItemStat(ITEM_STAT_MINISTRATION, std::string("ministration")); + AddMappedItemStat(ITEM_STAT_ORDINATION, std::string("ordination")); + AddMappedItemStat(ITEM_STAT_ADORNING, std::string("adorning")); + AddMappedItemStat(ITEM_STAT_PARRY, std::string("parry")); + AddMappedItemStat(ITEM_STAT_PIERCING, std::string("piercing")); + AddMappedItemStat(ITEM_STAT_RANGED, std::string("ranged")); + AddMappedItemStat(ITEM_STAT_SAFE_FALL, std::string("safe fall")); + AddMappedItemStat(ITEM_STAT_SCRIBING, std::string("scribing")); + AddMappedItemStat(ITEM_STAT_SCULPTING, std::string("sculpting")); + AddMappedItemStat(ITEM_STAT_SLASHING, std::string("slashing")); + AddMappedItemStat(ITEM_STAT_SUBJUGATION, std::string("subjugation")); + AddMappedItemStat(ITEM_STAT_SWIMMING, std::string("swimming")); + AddMappedItemStat(ITEM_STAT_TAILORING, std::string("tailoring")); + AddMappedItemStat(ITEM_STAT_TINKERING, std::string("tinkering")); + AddMappedItemStat(ITEM_STAT_TRANSMUTING, std::string("transmuting")); + AddMappedItemStat(ITEM_STAT_TRAPPING, std::string("trapping")); + AddMappedItemStat(ITEM_STAT_WEAPON_SKILLS, std::string("weapon skills")); + AddMappedItemStat(ITEM_STAT_POWER_COST_REDUCTION, std::string("power cost reduction")); + AddMappedItemStat(ITEM_STAT_SPELL_AVOIDANCE, std::string("spell avoidance")); +} + +void MasterItemList::AddMappedItemStat(int32 id, std::string lower_case_name) +{ + mappedItemStatsStrings[lower_case_name] = id; + mappedItemStatTypeIDs[id] = lower_case_name; +} + +MasterItemList::~MasterItemList(){ + RemoveAll(); + + map>::iterator itr; + for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++) + { + VersionRange* range = itr->first; + delete range; + } + + broker_item_map.clear(); +} + + +void MasterItemList::AddBrokerItemMapRange(int32 min_version, int32 max_version, + int64 client_bitmask, int64 server_bitmask) +{ + map>::iterator itr = FindBrokerItemMapVersionRange(min_version, max_version); + if (itr != broker_item_map.end()) { + itr->second.insert(make_pair(client_bitmask, server_bitmask)); + return; + } + + VersionRange* range = new VersionRange(min_version, max_version); + broker_item_map[range][client_bitmask] = server_bitmask; +} + +map>::iterator MasterItemList::FindBrokerItemMapVersionRange(int32 min_version, int32 max_version) +{ + map>::iterator itr; + for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion()) + return itr; + // if the min version is in range, but max range is 0 + else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0) + return itr; + // if min version is 0 and max_version has a cap + else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion()) + return itr; + } + + return broker_item_map.end(); +} + +map>::iterator MasterItemList::FindBrokerItemMapByVersion(int32 version) +{ + map>::iterator enditr = broker_item_map.end(); + map>::iterator itr; + for (itr = broker_item_map.begin(); itr != broker_item_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0) + enditr = itr; + else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion()) + return itr; + } + + return enditr; +} + +vector* MasterItemList::GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass){ + vector* ret = new vector; + map::iterator iter; + Item* item = 0; + const char* chkname = 0; + //const char* chkseller = 0; + //const char* chkadornment = 0; + if(name.length() > 0) + chkname = name.c_str(); + //if(seller.length() > 0) + // chkseller = seller.c_str(); + //if(adornment.length() > 0) + // chkadornment = adornment.c_str(); + LogWrite(ITEM__WARNING, 0, "Item", "Get Items: %s (itype: %llu, ltype: %llu, btype: %llu, minskill: %u, maxskill: %u, mintier: %u, maxtier: %u, minlevel: %u, maxlevel: %u itemclass %i)", name.c_str(), itype, ltype, btype, minskill, maxskill, mintier, maxtier, minlevel, maxlevel, itemclass); + bool should_add = true; + for(iter = items.begin();iter != items.end(); iter++){ + item = iter->second; + if(item){ + if(itype != ITEM_BROKER_TYPE_ANY && itype != ITEM_BROKER_TYPE_ANY64BIT){ + should_add = false; + switch(itype){ + case ITEM_BROKER_TYPE_ADORNMENT:{ + if(item->IsAdornment()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_AMMO:{ + if(item->IsAmmo()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_ATTUNEABLE:{ + if(item->CheckFlag(ATTUNEABLE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_BAG:{ + if(item->IsBag()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_BAUBLE:{ + if(item->IsBauble()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_BOOK:{ + if(item->IsBook()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_CHAINARMOR:{ + if(item->IsChainArmor()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_CLOAK:{ + if(item->IsCloak()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_CLOTHARMOR:{ + if(item->IsClothArmor()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_COLLECTABLE:{ + if(item->IsCollectable()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_CRUSHWEAPON:{ + if(item->IsCrushWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_DRINK:{ + if(item->IsFoodDrink()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_FOOD:{ + if(item->IsFoodFood()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_HOUSEITEM:{ + if(item->IsHouseItem()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_JEWELRY:{ + if(item->IsJewelry()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_LEATHERARMOR:{ + if(item->IsLeatherArmor()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_LORE:{ + if(item->CheckFlag(LORE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_MISC:{ + if(item->IsMisc()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_PIERCEWEAPON:{ + if(item->IsPierceWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_PLATEARMOR:{ + if(item->IsPlateArmor()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_POISON:{ + if(item->IsPoison()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_POTION:{ + if(item->IsPotion()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_RECIPEBOOK:{ + if(item->IsRecipeBook()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_SALESDISPLAY:{ + if(item->IsSalesDisplay()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_SHIELD:{ + if(item->IsShield()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_SLASHWEAPON:{ + if(item->IsSlashWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE)) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_SPELLSCROLL:{ + if(item->IsSpellScroll()) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_TINKERED:{ + if(item->tinkered == 1) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_TRADESKILL:{ + if(item->crafted == 1) + should_add = true; + break; + } + case ITEM_BROKER_TYPE_2H_CRUSH:{ + should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_STAFF; + break; + } + case ITEM_BROKER_TYPE_2H_PIERCE:{ + should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_GREATSPEAR; + break; + } + case ITEM_BROKER_TYPE_2H_SLASH:{ + should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_GREATSWORD; + break; + } + } + if(!should_add) + continue; + } + if(ltype != ITEM_BROKER_SLOT_ANY){ + should_add = false; + switch(ltype){ + case ITEM_BROKER_SLOT_AMMO:{ + should_add = item->HasSlot(EQ2_AMMO_SLOT); + break; + } + case ITEM_BROKER_SLOT_CHARM:{ + should_add = item->HasSlot(EQ2_CHARM_SLOT_1, EQ2_CHARM_SLOT_2); + break; + } + case ITEM_BROKER_SLOT_CHEST:{ + should_add = item->HasSlot(EQ2_CHEST_SLOT); + break; + } + case ITEM_BROKER_SLOT_CLOAK:{ + should_add = item->HasSlot(EQ2_CLOAK_SLOT); + break; + } + case ITEM_BROKER_SLOT_DRINK:{ + should_add = item->HasSlot(EQ2_DRINK_SLOT); + break; + } + case ITEM_BROKER_SLOT_EARS:{ + should_add = item->HasSlot(EQ2_EARS_SLOT_1, EQ2_EARS_SLOT_2); + break; + } + case ITEM_BROKER_SLOT_FEET:{ + should_add = item->HasSlot(EQ2_FEET_SLOT); + break; + } + case ITEM_BROKER_SLOT_FOOD:{ + should_add = item->HasSlot(EQ2_FOOD_SLOT); + break; + } + case ITEM_BROKER_SLOT_FOREARMS:{ + should_add = item->HasSlot(EQ2_FOREARMS_SLOT); + break; + } + case ITEM_BROKER_SLOT_HANDS:{ + should_add = item->HasSlot(EQ2_HANDS_SLOT); + break; + } + case ITEM_BROKER_SLOT_HEAD:{ + should_add = item->HasSlot(EQ2_HEAD_SLOT); + break; + } + case ITEM_BROKER_SLOT_LEGS:{ + should_add = item->HasSlot(EQ2_LEGS_SLOT); + break; + } + case ITEM_BROKER_SLOT_NECK:{ + should_add = item->HasSlot(EQ2_NECK_SLOT); + break; + } + case ITEM_BROKER_SLOT_PRIMARY:{ + should_add = item->HasSlot(EQ2_PRIMARY_SLOT); + break; + } + case ITEM_BROKER_SLOT_PRIMARY_2H:{ + should_add = item->HasSlot(EQ2_PRIMARY_SLOT) && item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND; + break; + } + case ITEM_BROKER_SLOT_RANGE_WEAPON:{ + should_add = item->HasSlot(EQ2_RANGE_SLOT); + break; + } + case ITEM_BROKER_SLOT_RING:{ + should_add = item->HasSlot(EQ2_LRING_SLOT, EQ2_RRING_SLOT); + break; + } + case ITEM_BROKER_SLOT_SECONDARY:{ + should_add = item->HasSlot(EQ2_SECONDARY_SLOT); + break; + } + case ITEM_BROKER_SLOT_SHOULDERS:{ + should_add = item->HasSlot(EQ2_SHOULDERS_SLOT); + break; + } + case ITEM_BROKER_SLOT_WAIST:{ + should_add = item->HasSlot(EQ2_WAIST_SLOT); + break; + } + case ITEM_BROKER_SLOT_WRIST:{ + should_add = item->HasSlot(EQ2_LWRIST_SLOT, EQ2_RWRIST_SLOT); + break; + } + } + if(!should_add) + continue; + } + + if(btype != 0xFFFFFFFF){ + vector::iterator itr; + bool stat_found = false; + should_add = false; + switch(btype){ + case ITEM_BROKER_STAT_TYPE_NONE:{ + if (item->item_stats.size() == 0) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DEF:{ + stat_found = item->HasStat(ITEM_STAT_DEFENSE, GetItemStatNameByID(ITEM_STAT_DEFENSE)); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_STR:{ + stat_found = item->HasStat(ITEM_STAT_STR); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_STA:{ + stat_found = item->HasStat(ITEM_STAT_STA); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_AGI:{ + stat_found = item->HasStat(ITEM_STAT_AGI); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_WIS:{ + stat_found = item->HasStat(ITEM_STAT_WIS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_INT:{ + stat_found = item->HasStat(ITEM_STAT_INT); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_HEALTH:{ + stat_found = item->HasStat(ITEM_STAT_HEALTH); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_POWER:{ + stat_found = item->HasStat(ITEM_STAT_POWER); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_HEAT:{ + stat_found = item->HasStat(ITEM_STAT_VS_HEAT); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_COLD:{ + stat_found = item->HasStat(ITEM_STAT_VS_COLD); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_MAGIC:{ + stat_found = item->HasStat(ITEM_STAT_VS_MAGIC); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_MENTAL:{ + stat_found = item->HasStat(ITEM_STAT_VS_MENTAL); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DIVINE:{ + stat_found = item->HasStat(ITEM_STAT_VS_DIVINE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_POISON:{ + stat_found = item->HasStat(ITEM_STAT_VS_POISON); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DISEASE:{ + stat_found = item->HasStat(ITEM_STAT_VS_DISEASE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CRUSH:{ + stat_found = item->HasStat(ITEM_STAT_DMG_CRUSH); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_SLASH:{ + stat_found = item->HasStat(ITEM_STAT_DMG_SLASH); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_PIERCE:{ + stat_found = item->HasStat(ITEM_STAT_DMG_PIERCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CRITICAL: { + stat_found = item->HasStat(ITEM_STAT_CRITICALMITIGATION); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DBL_ATTACK:{ + stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_ABILITY_MOD:{ + stat_found = item->HasStat(ITEM_STAT_ABILITY_MODIFIER); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_POTENCY:{ + stat_found = item->HasStat(ITEM_STAT_POTENCY); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_AEAUTOATTACK:{ + stat_found = item->HasStat(ITEM_STAT_AEAUTOATTACKCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_ATTACKSPEED:{ + stat_found = item->HasStat(ITEM_STAT_ATTACKSPEED); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_BLOCKCHANCE:{ + stat_found = item->HasStat(ITEM_STAT_EXTRASHIELDBLOCKCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CASTINGSPEED:{ + stat_found = item->HasStat(ITEM_STAT_ABILITYCASTINGSPEED); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CRITBONUS:{ + stat_found = item->HasStat(ITEM_STAT_CRITBONUS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_CRITCHANCE:{ + stat_found = item->HasStat(ITEM_STAT_MELEECRITCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_DPS:{ + stat_found = item->HasStat(ITEM_STAT_DPS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_FLURRYCHANCE:{ + stat_found = item->HasStat(ITEM_STAT_FLURRY); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_HATEGAIN:{ + stat_found = item->HasStat(ITEM_STAT_HATEGAINMOD); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_MITIGATION:{ + stat_found = item->HasStat(ITEM_STAT_ARMORMITIGATIONINCREASE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_MULTI_ATTACK:{ + stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_RECOVERY:{ + stat_found = item->HasStat(ITEM_STAT_ABILITYRECOVERYSPEED); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_REUSE_SPEED:{ + stat_found = item->HasStat(ITEM_STAT_ABILITYREUSESPEED); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_SPELL_WPNDMG:{ + stat_found = item->HasStat(ITEM_STAT_SPELLWEAPONDAMAGEBONUS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_STRIKETHROUGH:{ + stat_found = item->HasStat(ITEM_STAT_STRIKETHROUGH); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_TOUGHNESS:{ + stat_found = item->HasStat(ITEM_STAT_PVPTOUGHNESS); + if (stat_found) + should_add = true; + break; + } + case ITEM_BROKER_STAT_TYPE_WEAPONDMG:{ + stat_found = item->HasStat(ITEM_STAT_WEAPONDAMAGEBONUS); + if (stat_found) + should_add = true; + break; + } + default: { + LogWrite(ITEM__DEBUG, 0, "Item", "Unknown item broker stat type %u", btype); + LogWrite(ITEM__DEBUG, 0, "Item", "If you have a client before the new expansion this may be the reason. Please be patient while we update items to support the new client.", btype); + break; + } + } + if (!should_add) + continue; + } + + if(itemclass > 0){ + int64 tmpVal = ((int64)2) << (itemclass-1); + should_add = (item->generic_info.adventure_classes & tmpVal); + if(!should_add && !(item->generic_info.tradeskill_classes & tmpVal)) + continue; + } + if(chkname && item->lowername.find(chkname) >= 0xFFFFFFFF) + continue; + if(item->generic_info.adventure_default_level == 0 && item->generic_info.tradeskill_default_level == 0 && minlevel > 0 && maxlevel > 0){ + if(item->details.recommended_level < minlevel) + continue; + if(item->details.recommended_level > maxlevel) + continue; + } + else{ + if(minlevel > 0 && ((item->generic_info.adventure_default_level == 0 && item->generic_info.tradeskill_default_level == 0) || (item->generic_info.adventure_default_level > 0 && item->generic_info.adventure_default_level < minlevel) || (item->generic_info.tradeskill_default_level > 0 && item->generic_info.tradeskill_default_level < minlevel))) + continue; + if(maxlevel > 0 && ((item->generic_info.adventure_default_level > 0 && item->generic_info.adventure_default_level > maxlevel) || (item->generic_info.tradeskill_default_level > 0 && item->generic_info.tradeskill_default_level > maxlevel))) + continue; + } + // mintier of 1 is 'ANY' + if(mintier > 1 && item->details.tier < mintier) + continue; + if(maxtier > 0 && item->details.tier > maxtier) + continue; + + /* these skill values are not fields provided in the UI beyond CLASSIC + ** They are also not in line with skill_min, they provide a scale of 0-6, obselete is 0, 6 is red (cannot be used) + if(minskill > 0 && item->generic_info.skill_min < minskill) + continue; + if(maxskill > 0 && item->generic_info.skill_min > maxskill) + continue; + */ + ret->push_back(item); + } + } + return ret; +} + +vector* MasterItemList::GetItems(map criteria, Client* client_to_map){ + string name, seller, adornment; + int64 itype = ITEM_BROKER_TYPE_ANY64BIT; + int64 ltype = ITEM_BROKER_TYPE_ANY64BIT; + int64 btype = ITEM_BROKER_TYPE_ANY64BIT; + int64 minprice = 0; + int64 maxprice = 0; + int8 minskill = 0; + int8 maxskill = 0; + int8 mintier = 0; + int8 maxtier = 0; + int16 minlevel = 0; + int16 maxlevel = 0; + sint8 itemclass = 0; + int32 itemID = 0; + if (criteria.count("ITEM") > 0) + { + if (IsNumber(criteria["ITEM"].c_str())) + { + itemID = atoul(criteria["ITEM"].c_str()); + Item* itm = GetItem(itemID); + vector* ret = new vector; + if (itm) + ret->push_back(itm); + return ret; + } + else + name = criteria["ITEM"]; + } + if(criteria.count("MINSKILL") > 0) + minskill = (int8)ParseIntValue(criteria["MINSKILL"]); + if(criteria.count("MAXSKILL") > 0) + maxskill = (int8)ParseIntValue(criteria["MAXSKILL"]); + if(criteria.count("MINTIER") > 0) + mintier = (int8)ParseIntValue(criteria["MINTIER"]); + if(criteria.count("MAXTIER") > 0) + maxtier = (int8)ParseIntValue(criteria["MAXTIER"]); + if(criteria.count("MINLEVEL") > 0) + minlevel = (int16)ParseIntValue(criteria["MINLEVEL"]); + if(criteria.count("MAXLEVEL") > 0) + maxlevel = (int16)ParseIntValue(criteria["MAXLEVEL"]); + if(criteria.count("ITYPE") > 0) + itype = ParseLongLongValue(criteria["ITYPE"]); + if(criteria.count("LTYPE") > 0) + ltype = ParseLongLongValue(criteria["LTYPE"]); + if(criteria.count("BTYPE") > 0) + btype = ParseLongLongValue(criteria["BTYPE"]); + if(criteria.count("SKILLNAME") > 0) + itemclass = world.GetClassID(criteria["SKILLNAME"].c_str()); + + if(client_to_map) { + map>::iterator itr = FindBrokerItemMapByVersion(client_to_map->GetVersion()); + if(itr != broker_item_map.end() && itr->second.find(btype) != itr->second.end()) { + LogWrite(ITEM__DEBUG, 0, "Item", "Found broker mapping, btype %u becomes %llu", btype, itr->second[btype]); + btype = itr->second[btype]; + } + } + return GetItems(name, itype, ltype, btype, minprice, maxprice, minskill, maxskill, seller, adornment, mintier, maxtier, minlevel, maxlevel, itemclass); +} + +void MasterItemList::ResetUniqueID(int32 new_id){ + next_unique_id = new_id; +} + +int32 MasterItemList::NextUniqueID(){ + next_unique_id++; + if(next_unique_id >= 0xFFFFFFF0) + next_unique_id = 1; + return next_unique_id; +} + +bool MasterItemList::IsBag(int32 item_id){ + Item* item = GetItem(item_id); + if(item && item->details.num_slots > 0) + return true; + else + return false; +} + + +Item* MasterItemList::GetItem(int32 id){ + Item* item = 0; + if(items.count(id) > 0) + item = items[id]; + return item; +} + +Item* MasterItemList::GetItemByName(const char* name) { + Item* item = 0; + map::iterator itr; + for (itr = items.begin(); itr != items.end(); itr++) { + Item* current_item = itr->second; + if (::ToLower(string(current_item->name.c_str())) == ::ToLower(string(name))) { + item = current_item; + break; + } + } + return item; +} + +ItemStatsValues* MasterItemList::CalculateItemBonuses(int32 item_id, Entity* entity){ + return CalculateItemBonuses(items[item_id], entity); +} + +ItemStatsValues* MasterItemList::CalculateItemBonuses(Item* item, Entity* entity, ItemStatsValues* values){ + if(item){ + if(!values){ + values = new ItemStatsValues; + memset(values, 0, sizeof(ItemStatsValues)); + } + for(int32 i=0;iitem_stats.size();i++){ + ItemStat* stat = item->item_stats[i]; + int multiplier = 100; + if(stat->stat_subtype > 99) + multiplier = 1000; + + int32 id = 0; + sint32 value = stat->value; + if(stat->stat_type != 1) + id = stat->stat_type*multiplier + stat->stat_subtype; + else + { + int32 tmp_id = master_item_list.GetItemStatIDByName(::ToLower(stat->stat_name)); + if(tmp_id != 0xFFFFFFFF) + { + id = tmp_id; + value = stat->stat_subtype; + } + else + id = stat->stat_type*multiplier + stat->stat_subtype; + } + + if(entity->IsPlayer()) { + int32 effective_level = entity->GetInfoStructUInt("effective_level"); + if(effective_level && effective_level < entity->GetLevel() && item->details.recommended_level > effective_level) + { + int32 diff = item->details.recommended_level - effective_level; + float tmpValue = (float)value; + value = (sint32)(float)(tmpValue / (1.0f + ((float)diff * rule_manager.GetGlobalRule(R_Player, MentorItemDecayRate)->GetFloat()))); + } + } + + world.AddBonuses(item, values, id, value, entity); + } + return values; + } + return 0; +} + +void MasterItemList::RemoveAll(){ + map::iterator iter; + for(iter = items.begin();iter != items.end(); iter++){ + safe_delete(iter->second); + } + items.clear(); + if(lua_interface) + lua_interface->DestroyItemScripts(); +} + +void MasterItemList::AddItem(Item* item){ + map::iterator iter; + if((iter = items.find(item->details.item_id)) != items.end()) { + Item* tmpItem = items[item->details.item_id]; + items.erase(iter); + safe_delete(tmpItem); + } + items[item->details.item_id] = item; +} + +Item::Item(){ + item_script = ""; + sell_price = 0; + sell_status = 0; + max_sell_value = 0; + save_needed = true; + needs_deletion = false; + weapon_info = 0; + ranged_info = 0; + adornment_info = 0; + bag_info = 0; + food_info = 0; + bauble_info = 0; + thrown_info = 0; + skill_info = 0; + recipebook_info = 0; + itemset_info = 0; + armor_info = 0; + book_info = 0; + book_info_pages = 0; + houseitem_info = 0; + housecontainer_info = 0; + memset(&details, 0, sizeof(ItemCore)); + memset(&generic_info, 0, sizeof(Generic_Info)); + generic_info.condition = 100; + no_buy_back = false; + no_sale = false; + created = std::time(nullptr); + effect_type = NO_EFFECT_TYPE; + book_language = 0; +} + +Item::Item(Item* in_item){ + needs_deletion = false; + sell_price = in_item->sell_price; + sell_status = in_item->sell_status; + max_sell_value = in_item->max_sell_value; + save_needed = true; + SetItem(in_item); + details.unique_id = master_item_list.NextUniqueID(); + if (IsBag()) + details.bag_id = details.unique_id; + generic_info.condition = 100; + spell_id = in_item->spell_id; + spell_tier = in_item->spell_tier; + no_buy_back = in_item->no_buy_back; + no_sale = in_item->no_sale; + created = in_item->created; + grouped_char_ids.insert(in_item->grouped_char_ids.begin(), in_item->grouped_char_ids.end()); + effect_type = in_item->effect_type; + book_language = in_item->book_language; +} + +Item::~Item(){ + for(int32 i=0;iGetItemScript()) + SetItemScript(old_item->GetItemScript()); + name = old_item->name; + lowername = old_item->lowername; + description = old_item->description; + memcpy(&generic_info, &old_item->generic_info, sizeof(Generic_Info)); + weapon_info = 0; + ranged_info = 0; + adornment_info = 0; + adorn0 = 0; + adorn1 = 0; + adorn2 = 0; + bag_info = 0; + food_info = 0; + bauble_info = 0; + thrown_info = 0; + skill_info = 0; + recipebook_info = 0; + itemset_info = 0; + armor_info = 0; + book_info = 0; + book_info_pages = 0; + houseitem_info = 0; + housecontainer_info = 0; + stack_count = old_item->stack_count; + generic_info.skill_req1 = old_item->generic_info.skill_req1; + generic_info.skill_req2 = old_item->generic_info.skill_req2; + memcpy(&details, &old_item->details, sizeof(ItemCore)); + weapon_type = old_item->GetWeaponType(); + switch(old_item->generic_info.item_type){ + case ITEM_TYPE_WEAPON:{ + weapon_info = new Weapon_Info; + memcpy(weapon_info, old_item->weapon_info, sizeof(Weapon_Info)); + break; + } + case ITEM_TYPE_RANGED:{ + ranged_info = new Ranged_Info; + memcpy(ranged_info, old_item->ranged_info, sizeof(Ranged_Info)); + break; + } + case ITEM_TYPE_SHIELD: + case ITEM_TYPE_ARMOR:{ + armor_info = new Armor_Info; + memcpy(armor_info, old_item->armor_info, sizeof(Armor_Info)); + break; + } + case ITEM_TYPE_BAG:{ + bag_info = new Bag_Info; + memcpy(bag_info, old_item->bag_info, sizeof(Bag_Info)); + break; + } + case ITEM_TYPE_FOOD:{ + food_info = new Food_Info; + memcpy(food_info, old_item->food_info, sizeof(Food_Info)); + break; + } + case ITEM_TYPE_BAUBLE:{ + bauble_info = new Bauble_Info; + memcpy(bauble_info, old_item->bauble_info, sizeof(Bauble_Info)); + break; + } + case ITEM_TYPE_SKILL:{ + skill_info = new Skill_Info; + memcpy(skill_info, old_item->skill_info, sizeof(Skill_Info)); + break; + } + case ITEM_TYPE_THROWN:{ + thrown_info = new Thrown_Info; + memcpy(thrown_info, old_item->thrown_info, sizeof(Thrown_Info)); + break; + } + case ITEM_TYPE_BOOK:{ + book_info = new Book_Info; + book_info->language = old_item->book_info->language; + book_info->author.data = old_item->book_info->author.data; + book_info->author.size = old_item->book_info->author.size; + book_info->title.data = old_item->book_info->title.data; + book_info->title.size = old_item->book_info->title.size; + + break; + } + case ITEM_TYPE_HOUSE:{ + houseitem_info = new HouseItem_Info; + memcpy(houseitem_info, old_item->houseitem_info, sizeof(HouseItem_Info)); + break; + } + case ITEM_TYPE_RECIPE:{ + // Recipe Book + recipebook_info = new RecipeBook_Info; + if (old_item->recipebook_info) { + recipebook_info->recipe_id = old_item->recipebook_info->recipe_id; + recipebook_info->uses = old_item->recipebook_info->uses; + for (int32 i = 0; i < old_item->recipebook_info->recipes.size(); i++) + recipebook_info->recipes.push_back(old_item->recipebook_info->recipes.at(i)); + } + break; + } + + case ITEM_TYPE_ADORNMENT:{ + adornment_info = new Adornment_Info; + memcpy(adornment_info, old_item->adornment_info, sizeof(Adornment_Info)); + break; + } + case ITEM_TYPE_HOUSE_CONTAINER:{ + // House Containers + housecontainer_info = new HouseContainer_Info; + if (old_item->housecontainer_info) { + housecontainer_info->broker_commission = old_item->housecontainer_info->broker_commission; + housecontainer_info->fence_commission = old_item->housecontainer_info->fence_commission; + housecontainer_info->allowed_types = old_item->housecontainer_info->allowed_types; + housecontainer_info->num_slots = old_item->housecontainer_info->num_slots; + } + break; + } + } + creator = old_item->creator; + adornment = old_item->adornment; + DeleteItemSets(); + for (int32 i = 0; iitem_sets.size(); i++){ + ItemSet* set = old_item->item_sets[i]; + if (set){ + ItemSet* set2 = new ItemSet; + set2->item_id = set->item_id; + set2->item_crc = set->item_crc; + set2->item_icon = set->item_icon; + set2->item_stack_size = set->item_stack_size; + set2->item_list_color = set->item_list_color; + item_sets.push_back(set2); + } + } + item_stats.clear(); + for(int32 i=0;iitem_stats.size();i++){ + ItemStat* stat = old_item->item_stats[i]; + if(stat){ + ItemStat* stat2 = new ItemStat; + stat2->stat_name = stat->stat_name; + stat2->stat_type = stat->stat_type; + stat2->stat_subtype = stat->stat_subtype; + stat2->value = stat->value; + stat2->stat_type_combined = stat->stat_type_combined; + item_stats.push_back(stat2); + } + } + item_string_stats.clear(); + for(int32 i=0;iitem_string_stats.size();i++){ + ItemStatString* stat = old_item->item_string_stats[i]; + if(stat){ + ItemStatString* stat2 = new ItemStatString; + stat2->stat_string.data = stat->stat_string.data; + stat2->stat_string.size = stat->stat_string.size; + item_string_stats.push_back(stat2); + } + } + item_level_overrides.clear(); + for(int32 i=0;iitem_level_overrides.size();i++){ + ItemLevelOverride* item_override = old_item->item_level_overrides[i]; + if(item_override){ + ItemLevelOverride* item_override2 = new ItemLevelOverride; + memcpy(item_override2, item_override, sizeof(ItemLevelOverride)); + item_level_overrides.push_back(item_override2); + } + } + item_effects.clear(); + for(int32 i=0;iitem_effects.size();i++){ + ItemEffect* effect = old_item->item_effects[i]; + if(effect){ + ItemEffect* effect_2 = new ItemEffect; + effect_2->effect = effect->effect; + effect_2->percentage = effect->percentage; + effect_2->subbulletflag = effect->subbulletflag; + item_effects.push_back(effect_2); + } + } + book_pages.clear(); + for (int32 i = 0; i < old_item->book_pages.size(); i++) { + BookPage* bookpage = old_item->book_pages[i]; + if (bookpage) { + BookPage* bookpage_2 = new BookPage; + bookpage_2->page = bookpage->page; + bookpage_2->page_text.data = bookpage->page_text.data; + bookpage_2->page_text.size = bookpage->page_text.size; + bookpage_2->valign = bookpage->valign; + bookpage_2->halign = bookpage->halign; + + + + + book_pages.push_back(bookpage_2); + } + } + slot_data.clear(); + slot_data = old_item->slot_data; + spell_id = old_item->spell_id; + spell_tier = old_item->spell_tier; + book_language = old_item->book_language; +} + +bool Item::CheckArchetypeAdvSubclass(int8 adventure_class, map* adv_class_levels) { + if (adventure_class > FIGHTER && adventure_class < ANIMALIST) { + int8 check = adventure_class % 10; + if (check == 2 || check == 5 || check == 8) { + int64 adv_classes = 0; + int16 level = 0; + for (int i = adventure_class + 1; i < adventure_class + 3; i++) { + if (adv_class_levels) { //need to match levels + if (level == 0) { + if (adv_class_levels->count(i) > 0) + level = adv_class_levels->at(i); + else + return false; + } + else{ + if (adv_class_levels->count(i) > 0 && adv_class_levels->at(i) != level) + return false; + } + } + else { + adv_classes = ((int64)2) << (i - 1); + if (!(generic_info.adventure_classes & adv_classes)) + return false; + } + } + return true; + } + } + return false; +} + +bool Item::CheckArchetypeAdvClass(int8 adventure_class, map* adv_class_levels) { + if (adventure_class == 1 || adventure_class == 11 || adventure_class == 21 || adventure_class == 31) { + //if the class is an archetype class and the subclasses have access, then allow + if (CheckArchetypeAdvSubclass(adventure_class + 1, adv_class_levels) && CheckArchetypeAdvSubclass(adventure_class + 4, adv_class_levels) && CheckArchetypeAdvSubclass(adventure_class + 7, adv_class_levels)) { + if (adv_class_levels) { + int16 level = 0; + for (int i = adventure_class + 1; i <= adventure_class + 7; i += 3) { + if (adv_class_levels->count(i+1) == 0 || adv_class_levels->count(i + 2) == 0) + return false; + if(level == 0) + level = adv_class_levels->at(i+1); + if (adv_class_levels->at(i+1) != level) //already verified the classes, just need to verify the subclasses have the same levels + return false; + } + + } + return true; + } + } + else if (CheckArchetypeAdvSubclass(adventure_class, adv_class_levels)) {//check archetype subclass + return true; + } + return false; +} + +bool Item::CheckClass(int8 adventure_class, int8 tradeskill_class) { + int64 adv_classes = ((int64)2) << (adventure_class - 1); + int64 ts_classes = ((int64)2) << (tradeskill_class - 1); + if( ((generic_info.adventure_classes & adv_classes) || generic_info.adventure_classes == 0) && ((generic_info.tradeskill_classes & ts_classes) || generic_info.tradeskill_classes == 0) ) + return true; + //check arechtype classes as last resort + return CheckArchetypeAdvClass(adventure_class); +} + +bool Item::CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level) { + if ((level >= generic_info.adventure_default_level && adventure_class < 255) && (level >= generic_info.tradeskill_default_level && tradeskill_class < 255)) + return true; + return false; +} + +void Item::AddStat(ItemStat* in_stat){ + item_stats.push_back(in_stat); +} + +bool Item::HasStat(uint32 statID, std::string statNameLower) +{ + vector::iterator itr; + for (itr = item_stats.begin(); itr != item_stats.end(); itr++) { + if (statID > 99 && statID < 200 && + (*itr)->stat_type == 1 && ::ToLower((*itr)->stat_name) == statNameLower) { + return true; + break; + } + else if((*itr)->stat_type_combined == statID && (statNameLower.length() < 1 || + (::ToLower((*itr)->stat_name) == statNameLower))) { + return true; + break; + } + } + + return false; +} + +void Item::DeleteItemSets() +{ + for (int32 i = 0; i < item_sets.size(); i++){ + ItemSet* set = item_sets[i]; + safe_delete(set); + } + + item_sets.clear(); +} + +void Item::AddSet(ItemSet* in_set){ + item_sets.push_back(in_set); +} +void Item::AddStatString(ItemStatString* in_stat){ + item_string_stats.push_back(in_stat); +} + +bool Item::IsNormal(){ + return generic_info.item_type == ITEM_TYPE_NORMAL; +} + +bool Item::IsWeapon(){ + return generic_info.item_type == ITEM_TYPE_WEAPON; +} + +bool Item::IsDualWieldAble(Client* client, Item* item, int8 slot) { + + if (!item || !client || slot < 0) { + LogWrite(ITEM__DEBUG, 0, "Items", "Error in IsDualWieldAble. No Item, Client, or slot Passed"); + return 0; + } + + Player* player = client->GetPlayer(); + int8 base_class = classes.GetBaseClass(player->GetAdventureClass()); + + //map out classes that can dw vs those that cant (did it this way so its easier to expand should we need to add classes later + int8 can_dw; + switch ((int)base_class) { + case 1: + can_dw = 1; + break; + case 5: + can_dw = 1; + break; + case 31: + can_dw = 1; + break; + case 35: + can_dw = 1; + break; + case 41: + can_dw = 1; + break; + + default : + can_dw = 0; + } + + //if mage, item is dw, and they are trying to put offhand. Not sure this will ever happen but figured I should cover it. + if (base_class == 21 && item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL && slot == 1) { + return 0; + } + + //if the item is main hand (single) and they are trying to put in in offhand. + //exceptions are classes 1, 5, 31, 35, 42 (fighter/brawler/rogue/bard/beastlord) + if (item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE && slot == 1 && can_dw != 1) { + return 0; + } +//assume its safe if the above 2 if's arent hit. +return 1; +} + +bool Item::IsArmor(){ + return generic_info.item_type == ITEM_TYPE_ARMOR || generic_info.item_type == ITEM_TYPE_SHIELD; +} + +bool Item::IsRanged(){ + return generic_info.item_type == ITEM_TYPE_RANGED; +} + +bool Item::IsBag(){ + return generic_info.item_type == ITEM_TYPE_BAG; +} + +bool Item::IsFood(){ + return generic_info.item_type == ITEM_TYPE_FOOD; +} + +bool Item::IsBauble(){ + return generic_info.item_type == ITEM_TYPE_BAUBLE; +} + +bool Item::IsSkill(){ + return generic_info.item_type == ITEM_TYPE_SKILL; +} + +bool Item::IsHouseItem(){ + return generic_info.item_type == ITEM_TYPE_HOUSE; +} + +bool Item::IsHouseContainer(){ + return generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER; +} + +bool Item::IsShield(){ + return generic_info.item_type == ITEM_TYPE_SHIELD; +} + +bool Item::IsAdornment(){ + return generic_info.item_type == ITEM_TYPE_ADORNMENT && !CheckFlag2(ORNATE); +} + +bool Item::IsAmmo(){ + return HasSlot(EQ2_AMMO_SLOT); +} + +bool Item::HasAdorn0(){ + if (adorn0 > 0) + return true; + + return false; +} + +bool Item::HasAdorn1(){ + if (adorn1 > 0) + return true; + + return false; +} + +bool Item::HasAdorn2(){ + if (adorn2 > 0) + return true; + + return false; +} + + + + +bool Item::IsBook(){ + return generic_info.item_type == ITEM_TYPE_BOOK; +} + +bool Item::IsChainArmor(){ + return generic_info.item_type == ITEM_TYPE_ARMOR && (generic_info.skill_req1 == 2246237129UL || generic_info.skill_req2 == 2246237129UL); +} + +bool Item::IsClothArmor(){ + return generic_info.item_type == ITEM_TYPE_ARMOR && (generic_info.skill_req1 == 3539032716UL || generic_info.skill_req2 == 3539032716UL); +} + +bool Item::IsCollectable(){ + return generic_info.collectable == 1; +} + +bool Item::HasSlot(int8 slot, int8 slot2){ + for(int32 i=0;itype == 1; +} + +bool Item::IsFoodDrink(){ + return generic_info.item_type == ITEM_TYPE_FOOD && food_info && food_info->type == 0; +} + +bool Item::IsJewelry(){ + if(generic_info.item_type != ITEM_TYPE_ARMOR || (generic_info.skill_req1 != 2072844078 && generic_info.skill_req2 != 2072844078)) + return false; + for(int32 i=0;itinkered. +/* +bool Item::IsTinkered(){ + LogWrite(MISC__TODO, 1, "TODO", "Item Is Tinkered\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + return false; +} +*/ + +bool Item::IsThrown(){ + return generic_info.item_type == ITEM_TYPE_THROWN; +} + +bool Item::IsHarvest() { + return generic_info.harvest == 1; +} + +bool Item::IsBodyDrop() { + return generic_info.body_drop == 1; +} +//item->crafted +/*bool Item::IsTradeskill(){ + LogWrite(MISC__TODO, 1, "TODO", "Item Is Crafted\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + return false; +}*/ + +void Item::SetItemType(int8 in_type){ + generic_info.item_type = in_type; + if(IsArmor() && !armor_info){ + armor_info = new Armor_Info; + memset(armor_info, 0, sizeof(Armor_Info)); + } + else if (IsWeapon() && !weapon_info){ + weapon_info = new Weapon_Info; + memset(weapon_info, 0, sizeof(Weapon_Info)); + } + else if (IsAdornment() && !adornment_info){ + adornment_info = new Adornment_Info; + memset(adornment_info, 0, sizeof(Adornment_Info)); + } + else if(IsRanged() && !ranged_info){ + ranged_info = new Ranged_Info; + memset(ranged_info, 0, sizeof(Ranged_Info)); + } + else if(IsBag() && !bag_info){ + bag_info = new Bag_Info; + memset(bag_info, 0, sizeof(Bag_Info)); + } + else if(IsFood() && !food_info){ + food_info = new Food_Info; + memset(food_info, 0, sizeof(Food_Info)); + } + else if(IsBauble() && !bauble_info){ + bauble_info = new Bauble_Info; + memset(bauble_info, 0, sizeof(Bauble_Info)); + } + else if(IsThrown() && !thrown_info){ + thrown_info = new Thrown_Info; + memset(thrown_info, 0, sizeof(Thrown_Info)); + } + else if(IsSkill() && !skill_info){ + skill_info = new Skill_Info; + memset(skill_info, 0, sizeof(Skill_Info)); + } + else if(IsRecipeBook() && !recipebook_info){ + recipebook_info = new RecipeBook_Info; + recipebook_info->recipe_id = 0; + recipebook_info->uses = 0; + } + else if(IsBook() && !book_info){ + book_info = new Book_Info; + book_info->language = 0; + book_info->author.size = 0; + book_info->title.size = 0; + } + else if(IsHouseItem() && !houseitem_info){ + houseitem_info = new HouseItem_Info; + memset(houseitem_info, 0, sizeof(HouseItem_Info)); + } + else if(IsHouseContainer() && !housecontainer_info){ + housecontainer_info = new HouseContainer_Info; + housecontainer_info->allowed_types = 0; + housecontainer_info->broker_commission = 0; + housecontainer_info->fence_commission = 0; + housecontainer_info->num_slots = 0; + } +} +bool Item::CheckFlag2(int32 flag){ + int32 value = 0; + int32 flag_val = generic_info.item_flags2; + while (flag_val > 0){ + if (flag_val >= FLAGS2_32768) + value = FLAGS2_32768; + else if (flag_val >= FREE_REFORGE) + value = FREE_REFORGE; + else if (flag_val >= BUILDING_BLOCK) + value = BUILDING_BLOCK; + else if (flag_val >= FLAGS2_4096) + value = FLAGS2_4096; + else if (flag_val >= HOUSE_LORE) + value = HOUSE_LORE; + else if (flag_val >= NO_EXPERIMENT) + value = NO_EXPERIMENT; + else if (flag_val >= INDESTRUCTABLE) + value = INDESTRUCTABLE; + else if (flag_val >= NO_SALVAGE) + value = NO_SALVAGE; + else if (flag_val >= REFINED) + value = REFINED; + else if (flag_val >= ETHERAL) + value = ETHERAL; + else if (flag_val >= NO_REPAIR) + value = NO_REPAIR; + else if (flag_val >= REFORGED) + value = REFORGED; + else if (flag_val >= UNLOCKED) + value = UNLOCKED; + else if (flag_val >= APPEARANCE_ONLY) + value = APPEARANCE_ONLY; + else if (flag_val >= HEIRLOOM) + value = HEIRLOOM; + else if (flag_val >= ORNATE) + value = ORNATE; + if (value == flag) + return true; + else + flag_val -= value; + } + + return false; +} + +bool Item::CheckFlag(int32 flag){ + int32 value = 0; + int32 flag_val = generic_info.item_flags; + while(flag_val>0){ + if (flag_val >= CURSED) //change this + value = CURSED; + else if (flag_val >= NO_TRANSMUTE) //change this + value = NO_TRANSMUTE; + else if (flag_val >= LORE_EQUIP) //change this + value = LORE_EQUIP; + else if (flag_val >= STACK_LORE) //change this + value = STACK_LORE; + else if(flag_val >= EVIL_ONLY) + value = EVIL_ONLY; + else if(flag_val >= GOOD_ONLY) + value = GOOD_ONLY; + else if(flag_val >= CRAFTED) + value = CRAFTED; + else if(flag_val >= NO_DESTROY) + value = NO_DESTROY; + else if(flag_val >= NO_ZONE) + value = NO_ZONE; + else if(flag_val >= NO_VALUE) + value = NO_VALUE; + else if(flag_val >= NO_TRADE) + value = NO_TRADE; + else if(flag_val >= TEMPORARY) + value = TEMPORARY; + else if(flag_val >= LORE) + value = LORE; + else if(flag_val >= ARTIFACT) + value = ARTIFACT; + else if(flag_val >= ATTUNEABLE) + value = ATTUNEABLE; + else if(flag_val >= ATTUNED) + value = ATTUNED; + if(value == flag) + return true; + else + flag_val -= value; + } + return false; +} + +void Item::SetSlots(int32 slots){ + if(slots & PRIMARY_SLOT) + AddSlot(EQ2_PRIMARY_SLOT); + if(slots & SECONDARY_SLOT) + AddSlot(EQ2_SECONDARY_SLOT); + if(slots & HEAD_SLOT) + AddSlot(EQ2_HEAD_SLOT); + if(slots & CHEST_SLOT) + AddSlot(EQ2_CHEST_SLOT); + if(slots & SHOULDERS_SLOT) + AddSlot(EQ2_SHOULDERS_SLOT); + if(slots & FOREARMS_SLOT) + AddSlot(EQ2_FOREARMS_SLOT); + if(slots & HANDS_SLOT) + AddSlot(EQ2_HANDS_SLOT); + if(slots & LEGS_SLOT) + AddSlot(EQ2_LEGS_SLOT); + if(slots & FEET_SLOT) + AddSlot(EQ2_FEET_SLOT); + if(slots & LRING_SLOT) + AddSlot(EQ2_LRING_SLOT); + if(slots & RRING_SLOT) + AddSlot(EQ2_RRING_SLOT); + if(slots & EARS_SLOT_1) + AddSlot(EQ2_EARS_SLOT_1); + if(slots & EARS_SLOT_2) + AddSlot(EQ2_EARS_SLOT_2); + if(slots & NECK_SLOT) + AddSlot(EQ2_NECK_SLOT); + if(slots & LWRIST_SLOT) + AddSlot(EQ2_LWRIST_SLOT); + if(slots & RWRIST_SLOT) + AddSlot(EQ2_RWRIST_SLOT); + if(slots & RANGE_SLOT) + AddSlot(EQ2_RANGE_SLOT); + if(slots & AMMO_SLOT) + AddSlot(EQ2_AMMO_SLOT); + if(slots & WAIST_SLOT) + AddSlot(EQ2_WAIST_SLOT); + if(slots & CLOAK_SLOT) + AddSlot(EQ2_CLOAK_SLOT); + if(slots & CHARM_SLOT_1) + AddSlot(EQ2_CHARM_SLOT_1); + if(slots & CHARM_SLOT_2) + AddSlot(EQ2_CHARM_SLOT_2); + if(slots & FOOD_SLOT) + AddSlot(EQ2_FOOD_SLOT); + if(slots & DRINK_SLOT) + AddSlot(EQ2_DRINK_SLOT); + if(slots & TEXTURES_SLOT) + AddSlot(EQ2_TEXTURES_SLOT); +} + +void Item::AddStat(int8 type, int16 subtype, float value, int8 level, char* name){ + char item_stat_combined_string[8] = {0}; + if(name && strlen(name) > 0 && type != 1){ + ItemStatString* stat = new ItemStatString; + stat->stat_string.data = string(name); + stat->stat_string.size = stat->stat_string.data.length(); + AddStatString(stat); + } + else{ + ItemStat* stat = new ItemStat; + if(name && strlen(name) > 0) + stat->stat_name = string(name); + stat->stat_type = type; + stat->stat_subtype = subtype; + stat->value = value; + stat->level = level; + snprintf(item_stat_combined_string, 7, "%u%02u", type, subtype); + stat->stat_type_combined = atoi(item_stat_combined_string); + AddStat(stat); + } +} +void Item::AddSet(int32 item_id, int32 item_crc, int16 item_icon, int32 item_stack_size, int32 item_list_color, std::string name, int8 language){ + ItemSet* set = new ItemSet; + set->item_id = item_id; + set->item_icon = item_icon; + set->item_crc = item_crc; + set->item_stack_size = item_stack_size; + set->item_list_color = item_list_color; + set->name = string(name); + set->language = language; + + AddSet(set); +} + +int16 Item::GetOverrideLevel(int8 adventure_class, int8 tradeskill_class){ + int16 ret = 0; + int8 tmp_class = 0; + bool found_class = false; + for(int32 i=0;iadventure_class; + if(tmp_class == PRIEST && (adventure_class >= CLERIC && adventure_class <= DEFILER)) + found_class = true; + else if(tmp_class == MAGE && (adventure_class >= SORCERER && adventure_class <= NECROMANCER)) + found_class = true; + else if(tmp_class == SCOUT && (adventure_class >= ROGUE && adventure_class <= ASSASSIN)) + found_class = true; + else if(tmp_class == adventure_class || tmp_class == COMMONER || (tmp_class == FIGHTER && (adventure_class >= WARRIOR && adventure_class <= PALADIN))) + found_class = true; + } + else if(tradeskill_class != 255){ + tmp_class = item_level_overrides[i]->tradeskill_class; + if(tmp_class == CRAFTSMAN && (tradeskill_class >= PROVISIONER && adventure_class <= CARPENTER)) + found_class = true; + else if(tmp_class == OUTFITTER && (tradeskill_class >= ARMORER && tradeskill_class <= TAILOR)) + found_class = true; + else if(tmp_class == SCHOLAR && (tradeskill_class >= JEWELER && tradeskill_class <= ALCHEMIST)) + found_class = true; + else if(tmp_class == tradeskill_class || tmp_class == ARTISAN) + found_class = true; + } + if(found_class){ + ret = item_level_overrides[i]->level; + break; + } + } + return ret; +} + +void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16 packet_type, int8 subtype, bool loot_item, bool inspect){ + int64 classes = 0; + Client *client; + int8 tmp_subtype = 0; + if (!packet || !player) + return; + client = ((Player*)player)->GetClient(); + if (!client) + return; + if(creator.length() > 0){ + packet->setSubstructSubstructDataByName("header", "info_header", "creator_flag", 1); + packet->setSubstructSubstructDataByName("header", "info_header", "creator", creator.c_str()); + } + if(show_name) + packet->setSubstructSubstructDataByName("header", "info_header", "show_name", show_name); + + if(packet_type == 0) + packet->setSubstructSubstructDataByName("header", "info_header", "packettype", GetItemPacketType(packet->GetVersion())); + else + packet->setSubstructSubstructDataByName("header", "info_header", "packettype", packet_type); + packet->setSubstructSubstructDataByName("header", "info_header", "packetsubtype", subtype); // should be substype + + /* +0 red +1 orange +2 yellow +3 white +4 blue +5 green +6 grey +7 purple*/ + int32 color = 3; + + if(player) + { + int32 effective_level = player->GetInfoStructUInt("effective_level"); + if(effective_level && effective_level < player->GetLevel() && details.recommended_level > effective_level) + color = 7; + } + + packet->setSubstructDataByName("header_info", "footer_type", color); + packet->setSubstructDataByName("header_info", "item_id", details.item_id); + + if (!loot_item) + packet->setSubstructDataByName("header_info", "broker_item_id", details.item_id); + else + packet->setSubstructDataByName("header_info", "broker_item_id", 0xFFFFFFFFFFFFFFFF); + + if(details.unique_id == 0) + packet->setSubstructDataByName("header_info", "unique_id", details.item_id); + else + packet->setSubstructDataByName("header_info", "unique_id", details.unique_id); + packet->setSubstructDataByName("header_info", "icon", GetIcon(packet->GetVersion())); + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setSubstructDataByName("header_info", "tier", details.tier); + } + packet->setSubstructDataByName("header_info", "flags", generic_info.item_flags); + packet->setSubstructDataByName("header_info", "flags2", generic_info.item_flags2); + if(item_stats.size() > 0){ + //packet->setSubstructArrayLengthByName("header_info", "stat_count", item_stats.size()); + int8 dropstat = 0; + int8 bluemod = 0; + for (int32 i = 0; i < item_stats.size(); i++){ + ItemStat* stat = item_stats[i]; + + if(!stat) + { + LogWrite(ITEM__ERROR, 0, "Item", "%s: %s (itemid: %u) Error Serializing Item: Invalid item in item_stats position %u", client->GetPlayer()->GetName(), this->name.c_str(), this->details.item_id, i); + continue; + } + + if (stat->stat_type == 9){ + bluemod += 1; + } + + tmp_subtype = world.TranslateSlotSubTypeToClient(client, stat->stat_type, stat->stat_subtype); + + if (tmp_subtype == 255 ){ + dropstat += 1; + } + + } + packet->setSubstructArrayLengthByName("header_info", "stat_count", item_stats.size() - dropstat); + dropstat = 0; + for (int32 i = 0; i < item_stats.size(); i++) { + ItemStat* stat = item_stats[i]; + tmp_subtype = world.TranslateSlotSubTypeToClient(client, stat->stat_type, stat->stat_subtype); + int16 stat_type = stat->stat_type; + + float statValue = stat->value; + if(player) + { + int32 effective_level = player->GetInfoStructUInt("effective_level"); + if(effective_level && effective_level < player->GetLevel() && details.recommended_level > effective_level) + { + int32 diff = details.recommended_level - effective_level; + float tmpValue = (float)statValue; + statValue = (sint32)(float)(tmpValue / (1.0f + ((float)diff * rule_manager.GetGlobalRule(R_Player, MentorItemDecayRate)->GetFloat()))); + } + } + + bool valueSet = false; + if (tmp_subtype == 255 ){ + + dropstat += 1; + //packet->setSubstructArrayLengthByName("header_info", "stat_count", item_stats.size()-dropstat); + } + else { + packet->setArrayDataByName("stat_type", stat_type, i-dropstat); + + if(client->GetVersion() <= 561 && stat_type == 5) { + valueSet = true; + // DoF client has to be goofy about this junk, stat_subtype is the stat value, value is always "9" and we set the stat_name to the appropriate stat (but power=mana) + packet->setArrayDataByName("stat_subtype", (sint16)statValue , i - dropstat); + packet->setArrayDataByName("value", (sint16)9 , i - dropstat); + switch(tmp_subtype) { + case 0: { + packet->setArrayDataByName("stat_name", "health", i - dropstat); + break; + } + case 1: { + packet->setArrayDataByName("stat_name", "mana", i - dropstat); + break; + } + case 2: { + packet->setArrayDataByName("stat_name", "concentration", i - dropstat); + break; + } + } + } + else { + packet->setArrayDataByName("stat_subtype", tmp_subtype, i-dropstat); + } + } + if (stat->stat_name.length() > 0) + packet->setArrayDataByName("stat_name", stat->stat_name.c_str(), i-dropstat); + /* SF client */ + + if(!valueSet) { + if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331) { + if (stat->stat_type == 6){ + packet->setArrayDataByName("value", statValue , i - dropstat);//63119 or when diety started (this is actually the modified stat + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value (this is the unmodified stat + } + else { + packet->setArrayDataByName("value", (sint16)statValue , i - dropstat, 0U, true); + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value + } + } + else if (client->GetVersion() >= 1028) { + if (stat->stat_type == 6){ + packet->setArrayDataByName("value", statValue , i - dropstat);//63119 or when diety started (this is actually the infused modified stat + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value (this is the unmodified stat + } + else { + packet->setArrayDataByName("value", (sint16)statValue , i - dropstat, 0U, true); + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value + } + + } + else{ + packet->setArrayDataByName("value", (sint16)statValue , i - dropstat); + packet->setArrayDataByName("value2", stat->value, i - dropstat);//63119 temp will be replace by modified value + } + } + } + } + if (item_string_stats.size() > 0 && !loot_item){ + if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331) { + packet->setSubstructArrayLengthByName("header_info", "mod_count", item_string_stats.size()); + for (int32 i = 0; i < item_string_stats.size(); i++){ + ItemStatString* stat = item_string_stats[i]; + packet->setArrayDataByName("mod_string", &(stat->stat_string), i); + packet->setArrayDataByName("mod_need", 0, i); + } + } + + else if (client->GetVersion() >= 1096) { + packet->setSubstructArrayLengthByName("header_info", "stat_string_count", item_string_stats.size()); + for (int32 i = 0; i < item_string_stats.size(); i++){ + ItemStatString* stat = item_string_stats[i]; + packet->setArrayDataByName("stat_string", &(stat->stat_string), i); + + } + } + } + if (item_sets.size() > 0){ + packet->setArrayLengthByName("num_pieces", item_sets.size()); + for (int32 i = 0; i < item_sets.size(); i++){ + ItemSet* set = item_sets[i]; + packet->setArrayDataByName("item_id", set->item_id, i); + packet->setArrayDataByName("item_crc", set->item_crc, i); + packet->setArrayDataByName("item_icon", set->item_icon, i); + packet->setArrayDataByName("item_unknown1", set->item_stack_size, i); + + Item* item2 = master_item_list.GetItem(set->item_id); + if (item2) + packet->setArrayDataByName("item_name", item2->name.c_str(), i); + + packet->setArrayDataByName("item_unknown2", set->item_list_color, i); + + } + + + } + + + + + + if(!loot_item && item_effects.size() > 0){ + packet->setSubstructArrayLengthByName("footer", "num_effects", item_effects.size()); + for(int32 i=0;isetArrayDataByName("subbulletflag", effect->subbulletflag, i); + packet->setArrayDataByName("effect", &(effect->effect), i); + packet->setArrayDataByName("percentage", effect->percentage, i); + } + } + + if (packet->GetVersion() < 1096) { + packet->setSubstructDataByName("header_info", "adornment_id", 0xFFFFFFFF); // Send no ID for now + packet->setSubstructDataByName("header_info", "unknown3", 0xFFFFFFFF); + } + packet->setSubstructDataByName("header_info", "unknown21", 0x00000000); + packet->setSubstructDataByName("header_info", "condition", generic_info.condition); + packet->setSubstructDataByName("header_info", "weight", generic_info.weight); + if (packet->GetVersion() <= 373) { //orig client only has one skill + if (generic_info.skill_req1 == 0 || generic_info.skill_req1 == 0xFFFFFFFF) { + if (generic_info.skill_req2 != 0 && generic_info.skill_req2 != 0xFFFFFFFF) { + packet->setSubstructDataByName("header_info", "skill_req1", generic_info.skill_req2); + } + else { + packet->setSubstructDataByName("header_info", "skill_req1", 0xFFFFFFFF); + } + } + else { + packet->setSubstructDataByName("header_info", "skill_req1", generic_info.skill_req1); + } + } + else { + if (generic_info.skill_req1 == 0) + packet->setSubstructDataByName("header_info", "skill_req1", 0xFFFFFFFF); + else + packet->setSubstructDataByName("header_info", "skill_req1", generic_info.skill_req1); + if (generic_info.skill_req2 == 0) + packet->setSubstructDataByName("header_info", "skill_req2", 0xFFFFFFFF); + else + packet->setSubstructDataByName("header_info", "skill_req2", generic_info.skill_req2); + } + if(generic_info.skill_min != 0) + packet->setSubstructDataByName("header_info", "skill_min", generic_info.skill_min); + if (client->GetVersion() <= 373) { + string flags; + if (CheckFlag(NO_TRADE)) + flags += "NO-TRADE "; + if (CheckFlag(NO_VALUE)) + flags += "NO-VALUE "; + if(flags.length() > 0) + packet->setSubstructDataByName("header_info", "flag_names", flags.c_str()); + } + if (generic_info.adventure_classes > 0 || generic_info.tradeskill_classes > 0 || item_level_overrides.size() > 0) { + //int64 classes = 0; + int16 tmp_level = 0; + map adv_class_levels; + map tradeskill_class_levels; + map::iterator itr; + int64 tmpVal = 0; + int8 temp = ASSASSIN; + // AoD + clients with beastlords + if (packet->GetVersion() >= 1142) + temp += 2; + + // Chaneler class, get a more accurate version + if (packet->GetVersion() >= 60000) + temp += 2; + + for (int32 i = 0; i <= temp; i++) { + tmpVal = (int64)pow(2.0, (double)i); + if ((generic_info.adventure_classes & tmpVal)) { + //classes += 2 << (i - 1); + classes += tmpVal; + tmp_level = GetOverrideLevel(i, 255); + if (tmp_level == 0) + adv_class_levels[i] = generic_info.adventure_default_level; + else + adv_class_levels[i] = tmp_level; + } + if (tmpVal == 0) { + if (packet->GetVersion() >= 60000) + classes = 576379072454289112; + else if (packet->GetVersion() >= 57048) + classes = 6281081087704; + else if (packet->GetVersion() >= 1142) + classes = 144095080877876952; + else + classes = 36024082983773912; + } + } + for (int i = ALCHEMIST + 1 - ARTISAN; i >= 0; i--) { + //tmpVal = 2 << i; + tmpVal = (int64)pow(2.0, (double)i); + if ((generic_info.tradeskill_classes & tmpVal)) { + classes += pow(2, (i + ARTISAN)); + //classes += 2 << (i+ARTISAN-1); + tmp_level = GetOverrideLevel(i, 255); + if (tmp_level == 0) + tradeskill_class_levels[i] = generic_info.tradeskill_default_level; + else + tradeskill_class_levels[i] = tmp_level; + } + } + if (client->GetVersion() <= 561) { //simplify display (if possible) + map new_adv_class_levels; + for (int i = 1; i <= 31; i += 10) { + bool add_archetype = CheckArchetypeAdvClass(i, &adv_class_levels); + if (add_archetype) { + new_adv_class_levels[i] = 0; + } + else { + for (int x = 1; x <= 7; x += 3) { + if (CheckArchetypeAdvSubclass(i+x, &adv_class_levels)) { + new_adv_class_levels[i+x] = 0; + } + } + } + } + if (new_adv_class_levels.size() > 0) { + int8 i = 0; + for (itr = new_adv_class_levels.begin(); itr != new_adv_class_levels.end(); itr++) { + i = itr->first; + if ((i % 10) == 1) { + int16 level = 0; + for (int x = i; x < i+10; x++) { + if (adv_class_levels.count(x) > 0) { + if(level == 0) + level = adv_class_levels.at(x); + adv_class_levels.erase(x); + } + } + adv_class_levels[i] = level; + } + else { + int16 level = 0; + for (int x = i+1; x < i + 3; x++) { + if (adv_class_levels.count(x) > 0) { + if (level == 0) + level = adv_class_levels.at(x); + adv_class_levels.erase(x); + } + } + adv_class_levels[i] = level; + } + } + } + } + packet->setSubstructArrayLengthByName("header_info", "class_count", adv_class_levels.size() + tradeskill_class_levels.size()); + int i = 0; + for (itr = adv_class_levels.begin(); itr != adv_class_levels.end(); itr++, i++) { + packet->setArrayDataByName("adventure_class", itr->first, i); + packet->setArrayDataByName("tradeskill_class", 255, i); + packet->setArrayDataByName("level", itr->second * 10, i); + } + for (itr = tradeskill_class_levels.begin(); itr != tradeskill_class_levels.end(); itr++, i++) { + packet->setArrayDataByName("adventure_class", 255, i); + packet->setArrayDataByName("tradeskill_class", itr->first, i); + packet->setArrayDataByName("level", itr->second * 10, i); + } + packet->setSubstructDataByName("footer", "required_classes", classes); + } + else { + if (packet->GetVersion() >= 60000) + classes = 576379072454289112; + else if (packet->GetVersion() >= 57048) + classes = 6281081087704; + else if (packet->GetVersion() >= 1142) + classes = 144095080877876952; + else + classes = 36024082983773912; + packet->setSubstructDataByName("footer", "required_classes", classes); + } + + // Is this a copy and paste error??? + + + packet->setSubstructDataByName("footer", "required_classes2", classes); + + { + if (packet->GetVersion() >= 60000) + classes = 576379072454289112; + else if (packet->GetVersion() >= 57048) + classes = 6281081087704; + else if (packet->GetVersion() >= 1142) + classes = 144095080877876952; + else + classes = 36024082983773912; + + } + if (client->GetVersion() <= 373 && generic_info.adventure_default_level > 0) { + packet->setSubstructDataByName("header_info", "skill_min", (generic_info.adventure_default_level-1)*5+1); + packet->setSubstructDataByName("header_info", "skill_recommended", details.recommended_level * 5); + } + packet->setSubstructDataByName("footer", "recommended_level", details.recommended_level); + if(generic_info.adventure_default_level > 0){ + packet->setSubstructDataByName("footer", "required_level", generic_info.adventure_default_level); + packet->setSubstructDataByName("footer", "footer_unknown2", 0);// remove defualt + } + else{ + packet->setSubstructDataByName("footer", "required_level", generic_info.tradeskill_default_level * 10); + packet->setSubstructDataByName("footer", "footer_unknown2", 0);//remove default + } + if(slot_data.size() > 0){ + packet->setSubstructArrayLengthByName("header_info", "slot_count", slot_data.size()); + for(int32 i=0;iGetVersion() <= 373) { + if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) //they added a second ear slot later, adjust for only 1 original slot + slot -= 1; + else if (slot == EQ2_FOOD_SLOT) + slot = EQ2_ORIG_FOOD_SLOT; + else if(slot == EQ2_DRINK_SLOT) + slot = EQ2_ORIG_DRINK_SLOT; + } + else if (client->GetVersion() <= 561) { + if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) //they added a second ear slot later, adjust for only 1 original slot + slot -= 1; + else if (slot == EQ2_FOOD_SLOT) + slot = EQ2_DOF_FOOD_SLOT; + else if (slot == EQ2_DRINK_SLOT) + slot = EQ2_DOF_DRINK_SLOT; + } + packet->setArrayDataByName("slot", slot, i); + } + } + if(!loot_item && !inspect){ + if (adornment_info) + LogWrite(ITEM__DEBUG, 0, "Items", "\ttype: %i, Duration: %i, item_types_: %i, slot_type: %i", generic_info.item_type, adornment_info->duration, adornment_info->item_types, adornment_info->slot_type); + + int8 tmpType = generic_info.item_type; + if (client->GetVersion() <= 373 && generic_info.item_type > ITEM_TYPE_RECIPE) + tmpType = 0; + else if(client->GetVersion() <= 561 && (generic_info.item_type > ITEM_TYPE_HOUSE || generic_info.item_type == ITEM_TYPE_BAUBLE)) + tmpType = 0; + + packet->setSubstructDataByName("header", "item_type", tmpType); + switch(generic_info.item_type){ + case ITEM_TYPE_WEAPON:{ + if(weapon_info){ + if (client->GetVersion() < 373) { + packet->setSubstructDataByName("details", "wield_type", weapon_info->wield_type); + packet->setSubstructDataByName("details", "damage_low1", weapon_info->damage_low1); + packet->setSubstructDataByName("details", "damage_high1", weapon_info->damage_high1); + packet->setSubstructDataByName("details", "damage_low2", weapon_info->damage_low2); + packet->setSubstructDataByName("details", "damage_high2", weapon_info->damage_high2); + packet->setSubstructDataByName("details", "damage_type", weapon_type); + packet->setSubstructDataByName("details", "delay", weapon_info->delay); + } + else { + packet->setDataByName("wield_type", weapon_info->wield_type); + packet->setDataByName("damage_low1", weapon_info->damage_low1); + packet->setDataByName("damage_high1", weapon_info->damage_high1); + packet->setDataByName("damage_low2", weapon_info->damage_low2); + packet->setDataByName("damage_high2", weapon_info->damage_high2); + packet->setDataByName("damage_low3", weapon_info->damage_low3); + packet->setDataByName("damage_high3", weapon_info->damage_high3); + packet->setDataByName("damage_type", weapon_type); + packet->setDataByName("delay", weapon_info->delay); + packet->setDataByName("rating", weapon_info->rating); + } + } + break; + } + case ITEM_TYPE_RANGED:{ + if(ranged_info){ + if (client->GetVersion() < 373) { + packet->setSubstructDataByName("details", "damage_low1", ranged_info->weapon_info.damage_low1); + packet->setSubstructDataByName("details", "damage_high1", ranged_info->weapon_info.damage_high1); + packet->setSubstructDataByName("details", "damage_low2", ranged_info->weapon_info.damage_low2); + packet->setSubstructDataByName("details", "damage_high2", ranged_info->weapon_info.damage_high2); + packet->setSubstructDataByName("details", "delay", ranged_info->weapon_info.delay); + packet->setSubstructDataByName("details", "range_low", ranged_info->range_low); + packet->setSubstructDataByName("details", "range_high", ranged_info->range_high); + } + else { + packet->setDataByName("damage_low1", ranged_info->weapon_info.damage_low1); + packet->setDataByName("damage_high1", ranged_info->weapon_info.damage_high1); + packet->setDataByName("damage_low2", ranged_info->weapon_info.damage_low2); + packet->setDataByName("damage_high2", ranged_info->weapon_info.damage_high2); + packet->setDataByName("damage_low3", ranged_info->weapon_info.damage_low3); + packet->setDataByName("damage_high3", ranged_info->weapon_info.damage_high3); + packet->setDataByName("delay", ranged_info->weapon_info.delay); + packet->setDataByName("range_low", ranged_info->range_low); + packet->setDataByName("range_high", ranged_info->range_high); + packet->setDataByName("rating", ranged_info->weapon_info.rating); + } + } + break; + } + case ITEM_TYPE_SHIELD: + case ITEM_TYPE_ARMOR:{ + if(armor_info){ + if (client->GetVersion() < 373) { + packet->setSubstructDataByName("details", "mitigation_low", armor_info->mitigation_low); + packet->setSubstructDataByName("details", "mitigation_high", armor_info->mitigation_high); + } + else { + packet->setDataByName("mitigation_low", armor_info->mitigation_low); + packet->setDataByName("mitigation_high", armor_info->mitigation_high); + } + } + break; + } + case ITEM_TYPE_BAG:{ + if(bag_info){ + + int8 max_slots = player->GetMaxBagSlots(client->GetVersion()); + if (bag_info->num_slots > max_slots) + bag_info->num_slots = max_slots; + + int16 free_slots = bag_info->num_slots; + if (player) { + Item* bag = player->GetPlayerItemList()->GetItemFromUniqueID(details.unique_id, true); + if (bag && bag->IsBag()) { + vector* bag_items = player->GetPlayerItemList()->GetItemsInBag(bag); + if (bag_items->size() > bag->bag_info->num_slots) { + free_slots = 0; + packet->setArrayLengthByName("num_names", bag->bag_info->num_slots); + } + else { + free_slots = bag->bag_info->num_slots - bag_items->size(); + packet->setArrayLengthByName("num_names", bag_items->size()); + } + vector::iterator itr; + int16 i = 0; + Item* tmp_bag_item = 0; + for (itr = bag_items->begin(); itr != bag_items->end(); itr++) { + tmp_bag_item = *itr; + if (tmp_bag_item && tmp_bag_item->details.slot_id < bag->bag_info->num_slots) { + packet->setArrayDataByName("item_name", tmp_bag_item->name.c_str(), i); + i++; + } + } + safe_delete(bag_items); + } + } + packet->setDataByName("num_slots", bag_info->num_slots); + packet->setDataByName("num_empty", free_slots); + packet->setDataByName("weight_reduction", bag_info->weight_reduction); + packet->setDataByName("item_score", 2); + //packet->setDataByName("unknown5", 0x1e50a86f); + //packet->setDataByName("unknown6", 0x2c17f61d); + //1 armorer + //2 weaponsmith + //4 tailor + //16 jeweler + //32 sage + //64 alchemist + //120 all scholars + //250 all craftsman + //int8 blah[] = {0x00,0x00,0x01,0x01,0xb6,0x01,0x01}; + //int8 blah[] = {0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + int8 blah[] = { 0xd8,0x66,0x9b,0x6d,0xb6,0xfb,0x7f }; + for (int8 i = 0; i < sizeof(blah); i++) + packet->setSubstructDataByName("footer", "footer_unknown_0", blah[i], 0, i); + } + break; + } + case ITEM_TYPE_FOOD:{ + if(food_info && client->GetVersion() >=374){ + packet->setDataByName("food_type", food_info->type); + packet->setDataByName("level", food_info->level); + packet->setDataByName("duration", food_info->duration); + } + break; + } + case ITEM_TYPE_SKILL:{ + //Spell Books + if(skill_info->spell_id > 0){ + Spell* spell = master_spell_list.GetSpell(skill_info->spell_id, skill_info->spell_tier); + if(spell){ + if(player && client->GetVersion() >= 374) { + packet->setSubstructDataByName("header_info", "footer_type", 0); + + spell->SetPacketInformation(packet, client); + if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) { + packet->setDataByName("scribed", 1); + } + + if (packet->GetVersion() >= 927){ + if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) { + packet->setAddToPacketByName("scribed_better_version", 1);// need to confirm + } + } + else + packet->setAddToPacketByName("scribed_better_version", 0); //if not scribed + + // either don't require previous tier or check that we have the lower tier spells potentially + int32 tier_up = player->GetTierUp(skill_info->spell_tier); + if (!rule_manager.GetGlobalRule(R_Spells, RequirePreviousTierScribe)->GetInt8() || player->HasSpell(skill_info->spell_id, tier_up, false, true)) + packet->setDataByName("require_previous", 1, 0); + // membership required + //packet->setDataByName("unknown_1188_2_MJ", 1, 1); + + } + else { + spell->SetPacketInformation(packet, client); + } + //packet->setDataByName("unknown26", 0); + } + } + break; + } + case ITEM_TYPE_BAUBLE:{ + if(bauble_info && client->GetVersion() >= 546){ + packet->setDataByName("cast", bauble_info->cast); + packet->setDataByName("recovery", bauble_info->recovery); + packet->setDataByName("duration", bauble_info->duration); + packet->setDataByName("recast", bauble_info->recast); + packet->setDataByName("display_slot_optional", bauble_info->display_slot_optional); + packet->setDataByName("display_cast_time", bauble_info->display_cast_time); + packet->setDataByName("display_bauble_type", bauble_info->display_bauble_type); + packet->setDataByName("effect_radius", bauble_info->effect_radius); + packet->setDataByName("max_aoe_targets", bauble_info->max_aoe_targets); + packet->setDataByName("display_until_cancelled", bauble_info->display_until_cancelled); + //packet->setDataByName("item_score", 1); + } + break; + } + case ITEM_TYPE_THROWN:{ + if(thrown_info && client->GetVersion() >= 374){ + packet->setDataByName("range", thrown_info->range); + packet->setDataByName("damage_modifier", thrown_info->damage_modifier); + packet->setDataByName("hit_bonus", thrown_info->hit_bonus); + packet->setDataByName("damage_type", thrown_info->damage_type); + } + break; + } + case ITEM_TYPE_HOUSE:{ + if(houseitem_info && client->GetVersion() >= 374){ + packet->setDataByName("status_rent_reduction", houseitem_info->status_rent_reduction); + packet->setDataByName("coin_rent_reduction", houseitem_info->coin_rent_reduction); + packet->setDataByName("house_only", houseitem_info->house_only); + } + break; + } + case ITEM_TYPE_BOOK:{ + if(book_info && client->GetVersion() >= 374){ + packet->setDataByName("language", book_info->language); + packet->setMediumStringByName("author", book_info->author.data.c_str()); + packet->setMediumStringByName("title", book_info->title.data.c_str()); + } + if (packet->GetVersion() <= 1096) packet->setDataByName("item_type", 13); + + break; + } + case ITEM_TYPE_RECIPE:{ + // Recipe Books + if(recipebook_info){ + packet->setArrayLengthByName("num_recipes", recipebook_info->recipes.size()); + for (int32 i = 0; i < recipebook_info->recipes.size(); i++) { + Recipe* recipe = master_recipe_list.GetRecipeByCRC(recipebook_info->recipes.at(i)); + if (recipe) { + packet->setArrayDataByName("recipe_name", recipe->GetName(), i); + packet->setArrayDataByName("recipe_id", recipe->GetID(), i); + packet->setArrayDataByName("recipe_icon", recipe->GetIcon(), i); + } + } + packet->setDataByName("uses", recipebook_info->uses); + if(player->GetRecipeBookList()->HasRecipeBook(recipebook_info->recipe_id)) + packet->setDataByName("scribed", 1); + else + packet->setDataByName("scribed", 0); + } + break; + } + case ITEM_TYPE_ADORNMENT:{ + //Adornements + if (client->GetVersion() >= 374) { + packet->setDataByName("item_types", adornment_info->item_types); + packet->setDataByName("duration", adornment_info->duration); // need to calcualte for remaining duration + packet->setDataByName("slot_type", adornment_info->slot_type); + packet->setDataByName("footer_set_name", "test footer set name"); + packet->setArrayLengthByName("footer_set_bonus_list_count", 1);// list of the bonus items + packet->setArrayDataByName("footer_set_bonus_items_needed", 2, 0); //this is nember of items needed for granteing that stat //name,value,array + packet->setSubArrayLengthByName("footer_set_bonus_stats_count", 2, 0);//name,value,array,subarray + packet->setSubArrayDataByName("set_stat_type", 5, 0, 0); + packet->setSubArrayDataByName("set_stat_subtype", 1, 0, 0); + packet->setSubArrayDataByName("set_value", 25000, 0, 0); + } + + } + case ITEM_TYPE_HOUSE_CONTAINER:{ + //House Containers + if(housecontainer_info && client->GetVersion() >= 374){ + packet->setDataByName("allowed_types", housecontainer_info->allowed_types); + packet->setDataByName("num_slots", housecontainer_info->num_slots); + packet->setDataByName("broker_commission", housecontainer_info->broker_commission); + packet->setDataByName("fence_commission", housecontainer_info->fence_commission); + } + } + } + } + + LogWrite(MISC__TODO, 1, "TODO", "Item Set information\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + if (IsBauble()) { + packet->setSubstructDataByName("footer", "stack_size", stack_count); + } + else { + packet->setSubstructDataByName("footer", "stack_size", stack_count); + } + packet->setSubstructDataByName("footer", "collectable", generic_info.collectable); + + + + + + packet->setSubstructDataByName("footer", "status_item", sell_status); + + + LogWrite(MISC__TODO, 1, "TODO", "Set collection_needed information properly\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + packet->setSubstructDataByName("footer", "collection_needed", player->GetCollectionList()->NeedsItem(this) ? 1 : 0); + + if(generic_info.offers_quest_id > 0){ + Quest* quest = master_quest_list.GetQuest(generic_info.offers_quest_id, false); + if(quest){ + packet->setSubstructDataByName("footer", "offers_quest", strlen(generic_info.offers_quest_name) ? generic_info.offers_quest_name : quest->GetName()); + packet->setSubstructDataByName("footer", "offers_quest_color", player->GetArrowColor(quest->GetQuestLevel())); + } + } + if(generic_info.part_of_quest_id > 0){ + Quest* quest = master_quest_list.GetQuest(generic_info.part_of_quest_id, false); + if(quest){ + packet->setSubstructDataByName("footer", "part_of_quest", strlen(generic_info.required_by_quest_name) ? generic_info.required_by_quest_name : quest->GetName()); + packet->setSubstructDataByName("footer", "part_of_quest_color", player->GetArrowColor(quest->GetQuestLevel())); + } + } + if(generic_info.max_charges > 0){ + packet->setSubstructDataByName("footer", "charges", 1); + packet->setSubstructDataByName("footer", "total_charges", generic_info.max_charges); + packet->setSubstructDataByName("footer", "charges_left", details.count); + packet->setSubstructDataByName("footer", "display_charges", generic_info.display_charges); + } + if ((packet->GetVersion() >= 63119) || packet->GetVersion() == 61331){ + if (sell_status > 0){ + + } + } + //packet->setSubstructDataByName("footer", "status_item", 0); + + if (IsHarvest()){ + packet->setSubstructDataByName("footer", "crafting_flag", 1); + + + + } + + // Set these to 0 for now + if(packet->GetVersion() >= 1188){ + packet->setSubstructDataByName("footer", "locked_flag", 0); + packet->setSubstructDataByName("footer", "account_retricted", 0); + } + + // Adorns, set all to FF for now + if (packet->GetVersion() >= 1096) {// changed to 1096 for dov from 1188 + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 0); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 1); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 2); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 3); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 4); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 5); + + } + if (packet->GetVersion() >= 1289) {// at some point after this there are 10 adornment slots all FF for now but will skip this if not needed for a version + + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 6); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 7); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 8); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 9); + packet->setSubstructDataByName("footer", "adorn_slots", 0xFF, 0, 10); + } + + + packet->setSubstructDataByName("footer", "name", name.c_str()); + packet->setSubstructDataByName("footer", "description", description.c_str()); + + LogWrite(ITEM__PACKET, 0, "Items", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + +} + +PacketStruct* Item::PrepareItem(int16 version, bool merchant_item, bool loot_item, bool inspection){ + PacketStruct* packet = 0; + + if(loot_item && version > 561) + packet = configReader.getStruct("WS_LootItemGeneric", version); + else if(!inspection && loot_item && version <= 561) { + packet = configReader.getStruct("WS_ItemGeneric", version); + packet->AddFlag("loot"); + } + else if(inspection && version <= 373) { + packet = configReader.getStruct("WS_ItemInspect", version); + } + else if(version <= 561 && (generic_info.item_type > ITEM_TYPE_HOUSE || generic_info.item_type == ITEM_TYPE_BAUBLE)) { + packet = configReader.getStruct("WS_ItemGeneric", version); + } + else{ + int8 tmpType = generic_info.item_type; + if (version <= 373 && generic_info.item_type > ITEM_TYPE_RECIPE) + tmpType = 0; + else if(version <= 561 && (generic_info.item_type > ITEM_TYPE_HOUSE || generic_info.item_type == ITEM_TYPE_BAUBLE)) + tmpType = 0; + + switch(tmpType){ + case ITEM_TYPE_WEAPON:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemWeapon", version); + else + packet = configReader.getStruct("WS_ItemWeapon", version); + break; + } + case ITEM_TYPE_RANGED:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemRange", version); + else + packet = configReader.getStruct("WS_ItemRange", version); + break; + } + case ITEM_TYPE_SHIELD:{ + if (merchant_item) + packet = configReader.getStruct("WS_MerchantItemShield", version); + else + packet = configReader.getStruct("WS_ItemShield", version); + break; + } + case ITEM_TYPE_ARMOR:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemArmor", version); + else + packet = configReader.getStruct("WS_ItemArmor", version); + break; + } + case ITEM_TYPE_BAG:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemBag", version); + else + packet = configReader.getStruct("WS_ItemBag", version); + break; + } + case ITEM_TYPE_BOOK:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemBook", version); + else + packet = configReader.getStruct("WS_ItemBook", version); + break; + } + case ITEM_TYPE_SKILL:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemSkill", version); + else + packet = configReader.getStruct("WS_ItemSkill", version); + break; + } + case ITEM_TYPE_RECIPE:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemRecipeBook", version); + else + packet = configReader.getStruct("WS_ItemRecipeBook", version); + break; + } + case ITEM_TYPE_FOOD:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemFood", version); + else + packet = configReader.getStruct("WS_ItemFood", version); + break; + } + case ITEM_TYPE_BAUBLE:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemBauble", version); + else + packet = configReader.getStruct("WS_ItemBauble", version); + break; + } + case ITEM_TYPE_ITEMCRATE:{ + if (merchant_item) + packet = configReader.getStruct("WS_MerchantItemSet", version); + else + packet = configReader.getStruct("WS_ItemSet", version); + break; + } + case ITEM_TYPE_HOUSE:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemHouse", version); + else + packet = configReader.getStruct("WS_ItemHouse", version); + break; + } + case ITEM_TYPE_THROWN:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemThrown", version); + else + packet = configReader.getStruct("WS_ItemThrown", version); + break; + } + case ITEM_TYPE_HOUSE_CONTAINER:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemHouseContainer", version); + else + packet = configReader.getStruct("WS_ItemHouseContainer", version); + break; + } + case ITEM_TYPE_ADORNMENT:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantAdornment", version); + else + packet = configReader.getStruct("WS_ItemAdornment", version); + break; + } + default:{ + if(merchant_item) + packet = configReader.getStruct("WS_MerchantItemGeneric", version); + else + packet = configReader.getStruct("WS_ItemGeneric", version); + } + } + if (packet && loot_item) + packet->AddFlag("loot"); + } + if(!packet){ + LogWrite(ITEM__ERROR, 0, "Item", "Unhandled Item type: %i", (int)generic_info.item_type); + return 0; + } + return packet; +} + +EQ2Packet* Item::serialize(int16 version, bool show_name, Player* player, bool include_twice, int16 packet_type, int8 subtype, bool merchant_item, bool loot_item, bool inspect){ + PacketStruct* packet = PrepareItem(version, merchant_item, loot_item, inspect); + if(!packet) + return 0; + if (version <= 561) { + include_twice = false; + packet_type = 0; + } + if(include_twice && IsBag() == false && IsBauble() == false && IsFood() == false) + serialize(packet, show_name, player, packet_type, 0x80, loot_item, inspect); + else + serialize(packet, show_name, player, packet_type, 0, loot_item, inspect); + if(merchant_item) + packet->setSubstructDataByName("header_info", "unique_id", 0xFFFFFFFF); + string* generic_string_data = packet->serializeString(); + + //packet->PrintPacket(); + //LogWrite(ITEM__DEBUG, 9, "Items", "generic_string_data:"); + //DumpPacket((uchar*)generic_string_data->c_str(), generic_string_data->length()); + + int32 size = generic_string_data->length(); + if(include_twice && IsBag() == false && IsBauble() == false && IsFood() == false) + size = (size*2)-13; + uchar* out_data = new uchar[size+1]; + uchar* out_ptr = out_data; + memcpy(out_ptr, (uchar*)generic_string_data->c_str(), generic_string_data->length()); + out_ptr += generic_string_data->length(); + if(include_twice && IsBag() == false && IsBauble() == false && IsFood() == false){ + memcpy(out_ptr, (uchar*)generic_string_data->c_str() + 13, generic_string_data->length() -13); + } + int32 size2 = size; + if (version <= 373) { + uchar* out_ptr2 = out_data; + if (size2 >= 0xFF) { + size2 -= 3; + out_ptr2[0] = 0xFF; + out_ptr2 += sizeof(int8); + memcpy(out_ptr2, &size2, sizeof(int16)); + } + else { + size2 -= 1; + out_ptr2[0] = size2; + } + } + else { + size2 -= 4; + memcpy(out_data, &size2, sizeof(int32)); + } + EQ2Packet* outapp = new EQ2Packet(OP_ClientCmdMsg, out_data, size); + //DumpPacket(outapp); + safe_delete(packet); + safe_delete_array(out_data); + return outapp; +} + +void Item::SetAppearance(ItemAppearance* appearance){ + SetAppearance(appearance->type, appearance->red, appearance->green, appearance->blue, appearance->highlight_red, appearance->highlight_green, appearance->highlight_blue); +} + +void Item::SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue){ + generic_info.appearance_id = type; + generic_info.appearance_red = red; + generic_info.appearance_green = green; + generic_info.appearance_blue = blue; + generic_info.appearance_highlight_red = highlight_red; + generic_info.appearance_highlight_green = highlight_green; + generic_info.appearance_highlight_blue = highlight_blue; +} + +void Item::AddEffect(string effect, int8 percentage, int8 subbulletflag){ + ItemEffect* item_effect = new ItemEffect; + item_effect->subbulletflag = subbulletflag; + item_effect->effect.data = effect; + item_effect->effect.size = effect.length(); + item_effect->percentage = percentage; + item_effects.push_back(item_effect); +} +void Item::AddBookPage(int8 page, string page_text, int8 valign, int8 halign) { + BookPage * bookpage = new BookPage; + bookpage->page = page; + bookpage->page_text.data = page_text; + bookpage->page_text.size = page_text.length(); + bookpage->valign = valign; + bookpage->halign = halign; + book_pages.push_back(bookpage); +} +void Item::AddLevelOverride(ItemLevelOverride* level_override){ + AddLevelOverride(level_override->adventure_class, level_override->tradeskill_class, level_override->level); +} + +void Item::AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level){ + ItemLevelOverride* item_override = new ItemLevelOverride; + item_override->adventure_class = adventure_class; + item_override->tradeskill_class = tradeskill_class; + item_override->level = level; + item_level_overrides.push_back(item_override); +} + +void Item::AddSlot(int8 slot_id){ + slot_data.push_back(slot_id); +} + +void Item::SetWeaponType(int8 type){ + weapon_type = type; +} + +int8 Item::GetWeaponType(){ + return weapon_type; +} + +int32 Item::GetMaxSellValue(){ + return max_sell_value; +} + +void Item::SetMaxSellValue(int32 val){ + max_sell_value = val; +} + +void Item::SetItemScript(string name){ + item_script = name; +} + +const char* Item::GetItemScript(){ + if(item_script.length() > 0) + return item_script.c_str(); + return 0; +} + +int32 Item::CalculateRepairCost() { + if (generic_info.condition == 100) + return 0; + float repair_cost = (float)generic_info.adventure_default_level * (10.0 - ((float)generic_info.condition * 0.1)); + if (details.tier == ITEM_TAG_LEGENDARY) + repair_cost *= 4; + else if (details.tier == ITEM_TAG_FABLED) + repair_cost *= 8; + else if (details.tier == ITEM_TAG_MYTHICAL) + repair_cost *= 12; + return (int32)repair_cost; +} + +PlayerItemList::PlayerItemList(){ + packet_count = 0; + xor_packet = 0; + orig_packet = 0; + max_saved_index = 0; + MPlayerItems.SetName("PlayerItemList::MPlayerItems"); +} + +PlayerItemList::~PlayerItemList(){ + safe_delete_array(xor_packet); + safe_delete_array(orig_packet); + map> >::iterator bag_iter; + map::iterator itr; + for(bag_iter = items.begin(); bag_iter != items.end(); bag_iter++){ + for(itr = bag_iter->second[0].begin(); itr != bag_iter->second[0].end(); itr++){ + safe_delete(itr->second); + } + for(itr = bag_iter->second[1].begin(); itr != bag_iter->second[1].end(); itr++){ + safe_delete(itr->second); + } + bag_iter->second.clear(); + } + items.clear(); + while (!overflowItems.empty()){ + safe_delete(overflowItems.back()); + overflowItems.pop_back(); + } +} + +map* PlayerItemList::GetAllItems(){ + map* ret = new map; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + ret->insert(indexed_items.begin(), indexed_items.end()); + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +Item* PlayerItemList::GetItemFromIndex(int32 index){ + Item* ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(indexed_items.count(index) > 0) + ret = indexed_items[index]; + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +Item* PlayerItemList::GetItem(sint32 bag_slot, int16 slot, int8 appearance_type){ + Item* ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(bag_slot) > 0 && items[bag_slot][appearance_type].count(slot) > 0) + ret = items[bag_slot][appearance_type][slot]; + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int32 PlayerItemList::SetMaxItemIndex() { + int32 max_index = indexed_items.size(); + int32 new_index = 0; + map::iterator itr; + MPlayerItems.writelock(__FUNCTION__, __LINE__); + for(itr = indexed_items.begin();itr != indexed_items.end(); itr++){ + if(itr->first > max_index) //just grab the highest index val for next loop + max_index = itr->first; + } + max_saved_index = max_index; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + + return max_index; +} + +bool PlayerItemList::AddItem(Item* item){ //is called with a slot already set + //quick check to verify item + if(!item) + return false; + else{ + if(item->details.inv_slot_id != 0){ + Item* bag = GetItemFromUniqueID(item->details.inv_slot_id, true); + if(bag && bag->IsBag()){ + if(item->details.slot_id > bag->details.num_slots){ + LogWrite(ITEM__ERROR, 0, "Item", "Error Adding Item: Invalid slot for item unique id: %u (%s - %i), InvSlotID: %u, slotid: %u, numslots: %u", item->details.unique_id, item->name.c_str(), + item->details.item_id, item->details.inv_slot_id, item->details.slot_id, bag->details.num_slots); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + return false; + } + } + } + } + int32 max_index = indexed_items.size(); + int32 new_index = 0; + map::iterator itr; + MPlayerItems.writelock(__FUNCTION__, __LINE__); + for(itr = indexed_items.begin();itr != indexed_items.end(); itr++){ + if(itr->first > max_index) //just grab the highest index val for next loop + max_index = itr->first; + } + + bool doNotOverrideIndex = false; + int32 i=0; + for(i=0;iname.c_str(), i); + item->details.new_item = false; + item->details.new_index = 0; + doNotOverrideIndex = true; + break; + } + } + + if(doNotOverrideIndex) { + if(i < max_saved_index) { + item->details.new_item = false; + } else { + item->details.new_item = true; + } + } + + // may break non DoF clients + if(!doNotOverrideIndex && new_index == 0 && max_index > 0) + new_index = max_index; + + indexed_items[new_index] = item; + item->details.index = new_index; + items[item->details.inv_slot_id][item->details.appearance_type][item->details.slot_id] = item; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + + return true; +} + +Item* PlayerItemList::GetBag(int8 inventory_slot, bool lock){ + Item* bag = 0; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(0) > 0 && items[0][BASE_EQUIPMENT].count(inventory_slot) > 0 && items[0][BASE_EQUIPMENT][inventory_slot]->IsBag()) + bag = items[0][BASE_EQUIPMENT][inventory_slot]; + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return bag; +} + +Item* PlayerItemList::GetBankBag(int8 inventory_slot, bool lock){ + Item* bag = 0; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(-3) > 0 && items[-3][BASE_EQUIPMENT].count(inventory_slot) > 0 && items[-3][BASE_EQUIPMENT][inventory_slot]->IsBag()) + bag = items[-3][BASE_EQUIPMENT][inventory_slot]; + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return bag; +} + +int16 PlayerItemList::GetNumberOfFreeSlots(){ + int16 count = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(int8 i=0;idetails.num_slots > 0){ + if(items.count(bag->details.bag_id) > 0){ + for(int16 x=0;xdetails.num_slots;x++){ + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0) + count++; + } + } + else + count += bag->bag_info->num_slots; //if the bag hasnt been used yet, add all the free slots + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return count; +} + +bool PlayerItemList::HasFreeBagSlot(){ + bool ret = false; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(0) > 0){ + for(int8 i=0;i 0){ + for(int8 i=0;idetails.num_slots > 0){ + if(items.count(bag->details.bag_id) > 0){ + for(int16 x=0;xdetails.num_slots;x++){ + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0){ + ret = true; + break; + } + } + } + else{ //if the bag hasnt been used yet, then all slots are free + ret = true; + break; + } + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool PlayerItemList::GetFirstFreeBankSlot(sint32* bag_id, sint16* slot) { + bool ret = false; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (items.count(-3) > 0) { + for (int8 i = 0; i < NUM_BANK_SLOTS; i++) { + if (items[-3][BASE_EQUIPMENT].count(i) == 0) { + *bag_id = -3; + *slot = i; + ret = true; + break; + } + } + } + else { + *bag_id = -3; + *slot = 0; + ret = true; + } + + if(!ret) { + // Inventory slots were full so check bags + Item* bag = 0; + for(int8 i = 0; !ret && i < NUM_BANK_SLOTS; i++) { + // Check to see if the item in the inventory slot is a bag and it has slots + bag = GetBankBag(i, false); + if(bag && bag->details.num_slots > 0) { + // Item was a bag so lets loop through the slots and try to find an empty one + if(items.count(bag->details.bag_id) > 0) { + for(int16 x = 0; x < bag->details.num_slots; x++) { + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0) { + // Found a free slot, get the bag id of this bag + *bag_id = bag->details.bag_id; + // Get the slot + *slot = x; + ret = true; + break; + } + } + } + else { + //if the bag hasnt been used yet, then all slots are free, so set the bag_id to this bag + // and the slot to 0 (the first slot) + *bag_id = bag->details.bag_id; + *slot = 0; + ret = true; + break; + } + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool PlayerItemList::GetFirstFreeSlot(sint32* bag_id, sint16* slot) { + // Mostly copy and paste from the above function + bool ret = false; + // Try to place the item in the normal inventory slots first + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.count(0) > 0){ + for(int8 i=0; i < NUM_INV_SLOTS; i++) { + if(items[0][BASE_EQUIPMENT].count(i) == 0) { + // Found an empty slot, store the slot id and set the return value + *bag_id = 0; + *slot = i; + ret = true; + break; + } + } + } + else { + // no items in the players inventory, set it to the first slot + *bag_id = 0; + *slot = 0; + ret = true; + } + + if(!ret) { + // Inventory slots were full so check bags + Item* bag = 0; + for(int8 i = 0; !ret && i < NUM_INV_SLOTS; i++) { + // Check to see if the item in the inventory slot is a bag and it has slots + bag = GetBag(i, false); + if(bag && bag->details.num_slots > 0) { + // Item was a bag so lets loop through the slots and try to find an empty one + if(items.count(bag->details.bag_id) > 0) { + for(int16 x = 0; x < bag->details.num_slots; x++) { + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0) { + // Found a free slot, get the bag id of this bag + *bag_id = bag->details.bag_id; + // Get the slot + *slot = x; + ret = true; + break; + } + } + } + else { + //if the bag hasnt been used yet, then all slots are free, so set the bag_id to this bag + // and the slot to 0 (the first slot) + *bag_id = bag->details.bag_id; + *slot = 0; + ret = true; + break; + } + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} +vector PlayerItemList::GetAllItemsFromID(int32 id, bool include_bank, bool lock) { + //first check for an exact count match + map> >::iterator itr; + map::iterator slot_itr; + vector ret ; + if (lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for (itr = items.begin(); itr != items.end(); itr++) { + if (include_bank || (!include_bank && itr->first >= 0)) { + for (int8 i = 0; i < MAX_EQUIPMENT; i++) + { + for (slot_itr = itr->second[i].begin(); slot_itr != itr->second[i].end(); slot_itr++) { + if (slot_itr->second && slot_itr->second->details.item_id == id) { + if (lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + ret.push_back(slot_itr->second); + } + } + + } + + } + } + return ret; +} +Item* PlayerItemList::CanStack(Item* item, bool include_bank){ + if(!item || item->stack_count < 2) + return 0; + + Item* ret = 0; + map> >::iterator itr; + map::iterator slot_itr; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && (((slot_itr->second->details.count ? slot_itr->second->details.count : 1) + (item->details.count > 0 ? item->details.count : 1)) <= slot_itr->second->stack_count)){ + ret = slot_itr->second; + break; + } + } + for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && (((slot_itr->second->details.count ? slot_itr->second->details.count : 1) + (item->details.count > 0 ? item->details.count : 1)) <= slot_itr->second->stack_count)){ + ret = slot_itr->second; + break; + } + } + } + if(ret) + break; + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void PlayerItemList::Stack(Item* orig_item, Item* item){ + if(!orig_item || !item) + return; + orig_item->details.count += item->details.count; + orig_item->save_needed = true; +} + +bool PlayerItemList::AssignItemToFreeSlot(Item* item){ + if(item){ + Item* orig_item = CanStack(item); + if(orig_item){ + Stack(orig_item, item); + return true; + } + bool use_bag_freeslot = false; + if(item->IsBag()) + use_bag_freeslot = HasFreeBagSlot(); + MPlayerItems.writelock(__FUNCTION__, __LINE__); + if(!use_bag_freeslot){ + Item* bag = 0; + for(int8 i=0;iIsBag() && bag->details.num_slots > 0){ + for(int16 x=0;xdetails.num_slots;x++){ + if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0){ + item->details.inv_slot_id = bag->details.bag_id; + item->details.slot_id = x; + item->details.new_item = true; + item->details.new_index = 0; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + bool ret = AddItem(item); + return ret; + } + } + } + } + } + //bags full, check inventory slots + for(int8 i=0;idetails.inv_slot_id = 0; + item->details.slot_id = i; + item->details.new_item = true; + item->details.new_index = 0; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + bool ret = AddItem(item); + return ret; + } + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + } + return false; +} + + +void PlayerItemList::RemoveItem(Item* item, bool delete_item){ + MPlayerItems.writelock(__FUNCTION__, __LINE__); + if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][item->details.appearance_type].count(item->details.slot_id) > 0){ + items[item->details.inv_slot_id][item->details.appearance_type].erase(item->details.slot_id); + indexed_items[item->details.index] = 0; + } + if(item->IsBag() && item->details.inv_slot_id == 0 && item->details.slot_id < NUM_INV_SLOTS && items.count(item->details.bag_id) > 0){ + map::iterator itr; + for(itr = items[item->details.bag_id][item->details.appearance_type].begin(); itr != items[item->details.bag_id][item->details.appearance_type].end(); itr++){ + indexed_items[itr->second->details.index] = 0; + if(delete_item){ + if(itr->second == item) { + item = nullptr; + } + lua_interface->SetLuaUserDataStale(itr->second); + safe_delete(itr->second); + } + } + items.erase(item->details.bag_id); + } + if(item && delete_item){ + map::iterator itr = indexed_items.find(item->details.index); + if(itr != indexed_items.end() && item == indexed_items[item->details.index]) + indexed_items[item->details.index] = 0; + + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); +} + +void PlayerItemList::DestroyItem(int16 index){ + MPlayerItems.writelock(__FUNCTION__, __LINE__); + Item* item = indexed_items[index]; + map::iterator itr; + if(item && item->IsBag() && item->details.inv_slot_id == 0 && item->details.slot_id < NUM_INV_SLOTS && items.count((sint32)item->details.bag_id) > 0){ //inventory + map* tmp_map = &(items[(sint32)item->details.bag_id][item->details.appearance_type]); + for(itr = tmp_map->begin(); itr != tmp_map->end(); itr++){ + indexed_items[itr->second->details.index] = 0; + if(itr->second != item){ + lua_interface->SetLuaUserDataStale(itr->second); + safe_delete(itr->second); + } + } + items.erase(item->details.bag_id); + } + if(item) { + if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][item->details.appearance_type].count(item->details.slot_id) > 0) + items[item->details.inv_slot_id][item->details.appearance_type].erase(item->details.slot_id); + indexed_items[index] = 0; + + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); +} + +void PlayerItemList::MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old){ + if(erase_old && items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][BASE_EQUIPMENT].count(item->details.slot_id)) + items[item->details.inv_slot_id][BASE_EQUIPMENT].erase(item->details.slot_id); + items[inv_slot][BASE_EQUIPMENT][slot] = item; + item->details.inv_slot_id = inv_slot; + item->details.slot_id = slot; + item->details.appearance_type = 0; + item->save_needed = true; +} + +void PlayerItemList::EraseItem(Item* item){ + if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][BASE_EQUIPMENT].count(item->details.slot_id)) + items[item->details.inv_slot_id][BASE_EQUIPMENT].erase(item->details.slot_id); +} + +int16 PlayerItemList::GetNumberOfItems(){ + int16 ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(items.size() > 0){ + map> >::iterator itr; + sint32 bag_id = 0; + for(itr = items.begin(); itr != items.end(); itr++){ + bag_id = itr->first; + if(items[bag_id].count(0)) + ret += items[bag_id][BASE_EQUIPMENT].size(); + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int32 PlayerItemList::GetWeight(){ + int32 ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(int16 i = 0; i < indexed_items.size(); i++){ + Item* item = indexed_items[i]; + if (item) { + ret += item->generic_info.weight; + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + + +bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges){ + MPlayerItems.writelock(__FUNCTION__, __LINE__); + Item* item_from = indexed_items[from_index]; + Item* item_to = 0; + if(item_from){ + if(to_bag_id > 0){ //bag item + Item* bag = GetItemFromUniqueID(to_bag_id, true, false); + if(bag && bag->details.num_slots > to && (!item_from || !item_from->IsBag())) + item_to = items[to_bag_id][BASE_EQUIPMENT][to]; + else{ + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + } + else { + item_to = items[to_bag_id][BASE_EQUIPMENT][to]; + if(item_to && item_to->IsBag() && item_from && item_from->IsBag()) { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + } + if(charges > 0) { + if (item_to && item_from->details.item_id == item_to->details.item_id){ + if(item_to->details.count > 0 && item_to->details.count < item_to->stack_count){ + int32 total_tmp_price = 0; + if((item_to->details.count + item_from->details.count) <= item_to->stack_count){ + total_tmp_price = (item_to->GetMaxSellValue()*item_to->details.count) + (item_from->GetMaxSellValue()*item_from->details.count); + item_to->details.count += item_from->details.count; + indexed_items[from_index] = 0; + items[item_from->details.inv_slot_id][BASE_EQUIPMENT].erase(item_from->details.slot_id); + item_from->needs_deletion = true; + item_to->save_needed = true; + } + else{ + int8 diff = item_to->stack_count - item_to->details.count; + total_tmp_price = (item_to->GetMaxSellValue()*item_to->details.count) + (item_from->GetMaxSellValue()*diff); + item_to->details.count = item_to->stack_count; + item_from->details.count -= diff; + item_to->save_needed = true; + } + item_to->SetMaxSellValue(total_tmp_price/item_to->details.count); + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return true; + } + } + else { + if (item_from->details.count == charges) { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + if (item_to) + MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id, BASE_EQUIPMENT, true); + + MoveItem(item_from, to_bag_id, to, BASE_EQUIPMENT, item_to ? false:true); + } + else { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + if (item_to) { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + item_from->details.count -= charges; + Item* new_item = new Item(master_item_list.GetItem(item_from->details.item_id)); + new_item->details.count = charges; + new_item->details.slot_id = to; + new_item->details.inv_slot_id = to_bag_id; + new_item->details.appearance_type = 0; + new_item->save_needed = true; + AddItem(new_item); + if (item_from->details.count == 0) + RemoveItem(item_from); + } + return true; + } + } + else if(item_to && item_to->IsBag() && item_to->details.num_slots > 0){ + // if item we are moving is a bag + if (item_from->IsBag() && item_from->details.num_slots > 0) { + for (int8 i = 0; i < item_from->details.num_slots; i++) { + // if there is something in the bag return, can't put bags with items into other bags + if (items[item_from->details.bag_id][BASE_EQUIPMENT].count(i) != 0) { + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + } + } + if(items.count(item_to->details.bag_id) > 0){ + for(int8 i=0;idetails.num_slots;i++){ + if(items[item_to->details.bag_id][BASE_EQUIPMENT].count(i) == 0){ + MoveItem(item_from, item_to->details.bag_id, i, 0, true); + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return true; + } + } + } + else{ + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + MoveItem(item_from, item_to->details.bag_id, 0, BASE_EQUIPMENT, true); + return true; + } + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + + if (item_to) + MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id, BASE_EQUIPMENT, true); + + MoveItem(item_from, to_bag_id, to, BASE_EQUIPMENT, item_to ? false:true); + + return true; + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return false; +} + +EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){ + bool firstRun = false; + if(version <= 561 && !packet_count) { + firstRun = true; + } + EQ2Packet* app = 0; + PacketStruct* packet = configReader.getStruct("WS_UpdateInventory",version); + Item* item = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(packet && indexed_items.size() > 0){ + int8 packet_size = 0; + int16 size = indexed_items.size(); + int16 actual_size = 0; + for(int16 i = 0; i < indexed_items.size(); i++){ + item = indexed_items[i]; + if(item) + actual_size++; + } + size = actual_size; + + if (!firstRun && overflowItems.size() > 0) + size++; + + if(size > 20 && firstRun) { + size = 20; + } + PacketStruct* packet2 = configReader.getStruct("Substruct_Item", version); + packet_size = packet2->GetTotalPacketSize(); + safe_delete(packet2); + packet->setArrayLengthByName("item_count", size); + if(packet_count < size){ + if(!orig_packet){ + xor_packet = new uchar[packet_size * size]; + orig_packet = new uchar[packet_size * size]; + memset(xor_packet, 0, packet_size * size); + memset(orig_packet, 0, packet_size * size); + } + else{ + uchar* tmp = new uchar[packet_size * size]; + memset(tmp, 0, packet_size * size); + memcpy(tmp, orig_packet, packet_size * packet_count); + safe_delete_array(orig_packet); + orig_packet = tmp; + safe_delete_array(xor_packet); + xor_packet = new uchar[packet_size * size]; + } + } + + packet_count = size; + + int16 new_index = 0; + for(int16 i = 0; i < indexed_items.size(); i++){ + item = indexed_items[i]; + if(item && item->details.new_item) + new_index++; + + if(item) { + printf("%u: %s\n", i, item->name.c_str()); + } + else{ + printf("%u: empty\n", i); + } + + if(item && firstRun && i > 19) { + item->details.new_item = true; + continue; + } + + if (item && item->details.item_id > 0) + AddItemToPacket(packet, player, item, i, false, new_index); + + } + + if (!firstRun && overflowItems.size() > 0) { + // We have overflow items, lets get the first one + item = overflowItems.at(0); + // Lets make sure the item is valid + if (item && item->details.item_id > 0) { + // Set the slot to 6 as that is what overflow requires to work + item->details.slot_id = 6; + // now add it to the packet + AddItemToPacket(packet, player, item, size - 1, true); + } + } + + LogWrite(ITEM__PACKET, 0, "Items", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + packet->setDataByName("equip_flag",0); + app = packet->serializeCountPacket(version, 1, orig_packet, xor_packet); + safe_delete(packet); + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return app; +} + +int16 PlayerItemList::GetFirstNewItem() { + int16 new_item_slot = 0; + for(int16 i = 0; i < indexed_items.size(); i++){ + Item* item = indexed_items[i]; + if(item && item->details.new_item) { + return i; + } + } + return 0xFFFF; +} + +int16 PlayerItemList::GetNewItemByIndex(int16 in_index) { + int16 new_item_slot = 0; + for(int16 i = 0; i < indexed_items.size(); i++){ + Item* item = indexed_items[i]; + if(item && item->details.new_item) { + new_item_slot++; + int16 actual_index = in_index - new_item_slot; + // this isn't compiling right + //printf("In index: %u new index %u actual %u and %u, new slot num %u\n", in_index, item->details.new_index, actual_index, i, new_item_slot); + if(actual_index == i) { + return i; + } + } + } + return 0xFFFF; +} + +void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow, int16 new_index){ + Client *client; + if (!packet || !player) + return; + client = ((Player*)player)->GetClient(); + if (!client) + return; + + int32 menu_data = 3; + if(item->slot_data.size() > 0) + menu_data -= ITEM_MENU_TYPE_GENERIC; + + if (item->details.num_slots > 0) { + int8 max_slots = player->GetMaxBagSlots(client->GetVersion()); + if (item->details.num_slots > max_slots) + item->details.num_slots = max_slots; + + menu_data += ITEM_MENU_TYPE_BAG; + + if (item->details.num_free_slots == item->details.num_slots) + menu_data += ITEM_MENU_TYPE_EMPTY_BAG; + } + if (item->details.item_id == 21355) { + //menu_data += ITEM_MENU_TYPE_GENERIC; + //menu_data += ITEM_MENU_TYPE_EQUIP; + menu_data += ITEM_MENU_TYPE_BOOK; + //menu_data += ITEM_MENU_TYPE_BAG; + //menu_data += ITEM_MENU_TYPE_HOUSE; + //menu_data += ITEM_MENU_TYPE_TEST12; + //menu_data += ITEM_MENU_TYPE_SCRIBE; + //menu_data += ITEM_MENU_TYPE_TEST13; + //menu_data += ITEM_MENU_TYPE_INVALID; + //menu_data += ITEM_MENU_TYPE_TEST14; + //menu_data += ITEM_MENU_TYPE_BROKEN; + } + if (item->details.item_id == 21356) { + //menu_data += ITEM_MENU_TYPE_TEST15; + menu_data += ITEM_MENU_TYPE_ATTUNED; + menu_data += ITEM_MENU_TYPE_ATTUNEABLE; + menu_data += ITEM_MENU_TYPE_BOOK; + menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES; + menu_data += ITEM_MENU_TYPE_TEST1; + menu_data += ITEM_MENU_TYPE_NAMEPET; + menu_data += ITEM_MENU_TYPE_MENTORED; + menu_data += ITEM_MENU_TYPE_CONSUME; + menu_data += ITEM_MENU_TYPE_USE; + } + if (item->details.item_id == 21357) { + menu_data += ITEM_MENU_TYPE_CONSUME_OFF ; + menu_data += ITEM_MENU_TYPE_TEST3 ; + menu_data += ITEM_MENU_TYPE_TEST4 ; + menu_data += ITEM_MENU_TYPE_TEST5 ; + menu_data += ITEM_MENU_TYPE_TEST6 ; + menu_data += ITEM_MENU_TYPE_TEST7 ; + menu_data += ITEM_MENU_TYPE_TEST8 ; + menu_data += ITEM_MENU_TYPE_TEST9 ; + menu_data += ITEM_MENU_TYPE_DAMAGED ; + menu_data += ITEM_MENU_TYPE_BROKEN2 ; + menu_data += ITEM_MENU_TYPE_REDEEM ; + menu_data += ITEM_MENU_TYPE_TEST10 ; + menu_data += ITEM_MENU_TYPE_UNPACK ; + } + if(item->IsSkill()){ + Spell* spell = master_spell_list.GetSpell(item->skill_info->spell_id, item->skill_info->spell_tier); + if (spell && spell->ScribeAllowed(player)) + menu_data += ITEM_MENU_TYPE_SCRIBE; + else + menu_data += ITEM_MENU_TYPE_INSUFFICIENT_KNOWLEDGE; + } + if(item->IsRecipeBook()){ + //TODO: Add check to allow scribe + menu_data += ITEM_MENU_TYPE_SCRIBE; + } + if (item->generic_info.item_type == 10){ + menu_data += ITEM_MENU_TYPE_TEST1; + menu_data += ITEM_MENU_TYPE_HOUSE; + } + if (item->generic_info.item_type == 18){ + menu_data += ITEM_MENU_TYPE_UNPACK; + packet->setSubstructArrayDataByName("items", "unknown3", ITEM_MENU_TYPE2_UNPACK, 0, i); + } + + if(item->generic_info.condition == 0) + menu_data += ITEM_MENU_TYPE_BROKEN; + if (client->GetVersion() <= 373){ + string flags; + if (item->CheckFlag(NO_TRADE)) + flags += "NO-TRADE "; + if (item->CheckFlag(NO_VALUE)) + flags += "NO-VALUE "; + if (flags.length() > 0) + packet->setSubstructArrayDataByName("items", "flag_names", flags.c_str(), 0, i); + } + + if (item->CheckFlag(ATTUNED) || item->CheckFlag(NO_TRADE)) { + if (client->GetVersion() <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_ATTUNED; + else + menu_data += ITEM_MENU_TYPE_ATTUNED; + } + else if (item->CheckFlag(ATTUNEABLE)) { + if (client->GetVersion() <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_ATTUNEABLE; + else + menu_data += ITEM_MENU_TYPE_ATTUNEABLE; + } + if (item->generic_info.usable == 1) + menu_data += ITEM_MENU_TYPE_USE; + if (item->details.count > 0 && item->stack_count > 1) { + if (client->GetVersion() <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_STACKABLE; + else + menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES; + } + if(item->IsFood()) { + if (client->GetVersion() <= 373) { + if (item->IsFoodDrink()) + menu_data += ORIG_ITEM_MENU_TYPE_DRINK; + else if(item->IsFoodFood()) + menu_data += ORIG_ITEM_MENU_TYPE_FOOD; + } + } + if(item->details.item_locked) { + menu_data += ITEM_MENU_TYPE_BROKEN; // broken is also used to lock item during crafting + } + // Added the if (overflow) so mouseover examines work properly + if (overflow) + packet->setSubstructArrayDataByName("items", "unique_id", item->details.item_id, 0, i); + else + packet->setSubstructArrayDataByName("items", "unique_id", item->details.unique_id, 0, i); + packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id, 0, i); + packet->setSubstructArrayDataByName("items", "inv_slot_id", item->details.inv_slot_id, 0, i); + packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i); + if (overflow) + packet->setSubstructArrayDataByName("items", "index", 0xFFFF, 0, i); + else { + if(packet->GetVersion() <= 561) { + /* DoF client and earlier side automatically assigns indexes + ** we have to send 0xFF or else all index is set to 255 on client + ** and then examine inventory won't work */ + LogWrite(ITEM__DEBUG, 0, "%s Offset index %u bag id %u (new index %u, set index %u)",item->name.c_str(),i, item->details.bag_id, new_index, item->details.new_index); + if(item->details.new_item) { + item->details.new_index = new_index + i; // we have to offset in this way to get consistent indexes for the client to send back + packet->setSubstructArrayDataByName("items", "index", 0xFF+item->details.new_index, 0, i); + } + else { + packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i); + } + } + else { + packet->setSubstructArrayDataByName("items", "index", i, 0, i); + } + } + item->details.index = i; + + packet->setSubstructArrayDataByName("items", "icon", item->GetIcon(client->GetVersion()), 0, i); + packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i); // inventory doesn't convert slots + if (client->GetVersion() <= 1208) { + packet->setSubstructArrayDataByName("items", "count", (std::min)(item->details.count, (int16)255), 0, i); + } + else + packet->setSubstructArrayDataByName("items", "count", item->details.count, 0, i); + //packet->setSubstructArrayDataByName("items", "unknown4", 5, 0, i); + // need item level + packet->setSubstructArrayDataByName("items", "item_level", item->details.recommended_level , 0, i); + + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setSubstructArrayDataByName("items", "tier", item->details.tier, 0, i); + } + + packet->setSubstructArrayDataByName("items", "num_slots", item->details.num_slots, 0, i); + // need empty slots + packet->setSubstructArrayDataByName("items", "item_id", item->details.item_id, 0, i); + //need broker id + packet->setSubstructArrayDataByName("items", "name", item->name.c_str(), 0, i); + +} + +bool PlayerItemList::AddOverflowItem(Item* item) { + bool ret = false; + MPlayerItems.writelock(__FUNCTION__, __LINE__); + if (item && item->details.item_id > 0 && overflowItems.size() < 255) { + item->details.slot_id = 6; + item->details.inv_slot_id = -2; + overflowItems.push_back(item); + ret = true; + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +Item* PlayerItemList::GetOverflowItem() { + if(overflowItems.empty()) { + return nullptr; + } + + return overflowItems.at(0); +} + +void PlayerItemList::RemoveOverflowItem(Item* item) { + MPlayerItems.writelock(__FUNCTION__, __LINE__); + vector::iterator itr = std::find(overflowItems.begin(), overflowItems.end(), item); + if(itr != overflowItems.end()) { + overflowItems.erase(itr); + } + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); +} + +vector* PlayerItemList::GetOverflowItemList() { + vector* ret = new vector; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + vector::iterator itr= ret->begin(); + ret->insert(itr, overflowItems.begin(), overflowItems.end()); + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool PlayerItemList::HasItem(int32 id, bool include_bank){ + map> >::iterator itr; + map::iterator slot_itr; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(slot_itr=itr->second[BASE_EQUIPMENT].begin();slot_itr!=itr->second[BASE_EQUIPMENT].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id){ + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return true; + } + } + for(slot_itr=itr->second[APPEARANCE_EQUIPMENT].begin();slot_itr!=itr->second[APPEARANCE_EQUIPMENT].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id){ + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return true; + } + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return false; +} + +bool PlayerItemList::SharedBankAddAllowed(Item* item){ + if(!item || (item->CheckFlag(NO_TRADE) && (item->CheckFlag2(HEIRLOOM) == 0))) + return false; + + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(item->IsBag() && items.count(item->details.bag_id) > 0){ + map::iterator itr; + for(itr = items[item->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[item->details.bag_id][BASE_EQUIPMENT].end(); itr++){ + if(itr->second->CheckFlag(NO_TRADE) && itr->second->CheckFlag2(HEIRLOOM) == 0){ + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return false; + } + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return true; +} + +vector* PlayerItemList::GetItemsFromBagID(sint32 bag_id){ + vector* ret = new vector; + if(items.count(bag_id) > 0){ + MPlayerItems.readlock(__FUNCTION__, __LINE__); + map::iterator itr; + map::iterator itr2; + Item* item = 0; + for(itr = items[bag_id][BASE_EQUIPMENT].begin(); itr != items[bag_id][BASE_EQUIPMENT].end(); itr++){ + item = itr->second; + if(item) + ret->push_back(item); + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} +int32 PlayerItemList::GetItemCountInBag(Item* bag){ + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(bag && bag->IsBag() && items.count(bag->details.bag_id) > 0){ + int32 bagitems = items.count(bag->details.bag_id); + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return bagitems; + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return 0; +} +vector* PlayerItemList::GetItemsInBag(Item* bag){ + vector* ret_items = new vector; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if(bag && bag->IsBag() && items.count(bag->details.bag_id) > 0){ + map::iterator itr; + for(itr = items[bag->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[bag->details.bag_id][BASE_EQUIPMENT].end(); itr++){ + ret_items->push_back(itr->second); + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret_items; +} + +Item* PlayerItemList::GetItemFromID(int32 id, int8 count, bool include_bank, bool lock){ + //first check for an exact count match + map> >::iterator itr; + map::iterator slot_itr; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(int8 i=0;isecond[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id && (count == 0 || slot_itr->second->details.count == count)){ + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return slot_itr->second; + } + } + } + } + } + + //couldn't find an exact match, look for closest + Item* closest = 0; + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(int8 i=0;isecond[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id && slot_itr->second->details.count > count && (closest == 0 || slot_itr->second->details.count < closest->details.count)) + closest = slot_itr->second; + } + } + } + } + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return closest; +} + +sint32 PlayerItemList::GetAllStackCountItemFromID(int32 id, int8 count, bool include_bank, bool lock){ + sint32 stack_count = 0; + //first check for an exact count match + map> >::iterator itr; + map::iterator slot_itr; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(int8 i=0;isecond[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id && (count == 0 || slot_itr->second->details.count == count)){ + stack_count += slot_itr->second->details.count; + } + } + } + } + } + + //couldn't find an exact match, look for closest + Item* closest = 0; + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(int8 i=0;isecond[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == id && slot_itr->second->details.count > count && (closest == 0 || slot_itr->second->details.count < closest->details.count)) + stack_count += slot_itr->second->details.count; + } + } + } + } + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return stack_count; +} + +Item* PlayerItemList::GetItemFromUniqueID(int32 id, bool include_bank, bool lock){ + map> >::iterator itr; + map::iterator slot_itr; + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for(itr = items.begin(); itr != items.end(); itr++){ + if(include_bank || (!include_bank && itr->first >= 0)){ + for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.unique_id == id){ + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return slot_itr->second; + } + } + for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.unique_id == id){ + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return slot_itr->second; + } + } + } + } + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return 0; +} + +bool PlayerItemList::HasFreeBankSlot() { + bool ret = false; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (items[-3][BASE_EQUIPMENT].size() < 12) //12 slots in the bank + ret = true; + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int8 PlayerItemList::FindFreeBankSlot() { + int8 ret = 0; + MPlayerItems.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < 12; i++) { //12 slots in the bank + if (items[-3][BASE_EQUIPMENT].count(i) == 0) { + ret = i; + break; + } + } + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void PlayerItemList::ResetPackets() { + MPlayerItems.writelock(__FUNCTION__, __LINE__); + safe_delete_array(orig_packet); + safe_delete_array(xor_packet); + orig_packet = 0; + xor_packet = 0; + packet_count = 0; + MPlayerItems.releasewritelock(__FUNCTION__, __LINE__); +} + + +int32 PlayerItemList::CheckSlotConflict(Item* item, bool check_lore_only, bool lock, int16* lore_stack_count) { + bool is_lore = false; + bool is_stack_lore = false; + if(!(is_lore = item->CheckFlag(LORE)) && !(is_stack_lore = item->CheckFlag(STACK_LORE)) && check_lore_only) { + return 0; + } + + if(!check_lore_only && !is_lore && !is_stack_lore && !item->CheckFlag(LORE_EQUIP)) { + return 0; + } + + + int32 conflict = 0; + + if(lock) + MPlayerItems.readlock(__FUNCTION__, __LINE__); + + map> >::iterator itr; + map::iterator slot_itr; + + for(itr = items.begin(); itr != items.end(); itr++){ + for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id){ + if(lore_stack_count) { + *lore_stack_count += slot_itr->second->details.count; + } + if(!is_stack_lore && slot_itr->second->CheckFlag(LORE)) { + conflict = LORE; + break; + } + else if(is_stack_lore && (*lore_stack_count + item->details.count) > slot_itr->second->stack_count) { + conflict = STACK_LORE; + break; + } + else if(!check_lore_only && slot_itr->second->CheckFlag(LORE_EQUIP)) { + conflict = LORE_EQUIP; + break; + } + } + } + + if(conflict > 0) + break; + + for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){ + if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id){ + if(lore_stack_count) { + *lore_stack_count += slot_itr->second->details.count; + } + if(!is_stack_lore && slot_itr->second->CheckFlag(LORE)) { + conflict = LORE; + break; + } + else if(is_stack_lore && (*lore_stack_count + item->details.count) > slot_itr->second->stack_count) { + conflict = STACK_LORE; + break; + } + else if(!check_lore_only && slot_itr->second->CheckFlag(LORE_EQUIP)) { + conflict = LORE_EQUIP; + break; + } + } + } + + if(conflict > 0) + break; + } + + if(lock) + MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + + return conflict; +} + + +EquipmentItemList::EquipmentItemList(){ + orig_packet = 0; + xor_packet = 0; + for(int8 i=0;iname.c_str(), slot, item->name.c_str()); + return false; + } + + SetItem(slot, item, true); + if (item->details.unique_id == 0) { + GetItem(slot)->details.unique_id = MasterItemList::NextUniqueID(); + if (item->IsBag()) + item->details.bag_id = item->details.unique_id; + } + MEquipmentItems.unlock(); + return true; + } + return false; +} + +int8 EquipmentItemList::GetNumberOfItems(){ + int8 ret = 0; + MEquipmentItems.lock(); + for(int8 i=0;igeneric_info.weight; + } + } + MEquipmentItems.unlock(); + return ret; +} + +void EquipmentItemList::SetItem(int8 slot_id, Item* item, bool locked){ + if(!locked) + MEquipmentItems.lock(); + item->details.bag_id = item->details.unique_id; + if(!item->IsBag()) { + item->details.inv_slot_id = 0; + } + else { + item->details.equip_slot_id = slot_id; + } + + if(!item->IsBag()) { + item->details.slot_id = slot_id; + item->details.index = slot_id; + } + item->details.appearance_type = GetAppearanceType(); + items[slot_id] = item; + + if(!locked) + MEquipmentItems.unlock(); +} + +vector* EquipmentItemList::GetAllEquippedItems(){ + vector* ret = new vector; + MEquipmentItems.lock(); + for(int8 i=0;ipush_back(items[i]); + } + MEquipmentItems.unlock(); + return ret; +} + +Item* EquipmentItemList::GetItem(int8 slot_id){ + return items[slot_id]; +} + +void EquipmentItemList::SendEquippedItems(Player* player){ + if(!player->GetClient()) { + return; + } + + for(int16 i=0;idetails.item_id > 0) + player->GetClient()->QueuePacket(item->serialize(player->GetClient()->GetVersion(), false, player)); + } +} + +EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){ + EQ2Packet* app = 0; + Item* item = 0; + PacketStruct* packet = configReader.getStruct("WS_UpdateInventory", version); + MEquipmentItems.lock(); + if(packet){ + int8 packet_size = 0; + PacketStruct* packet2 = configReader.getStruct("Substruct_Item", version); + packet_size = packet2->GetTotalPacketSize(); + safe_delete(packet2); + int8 num_slots = player->GetNumSlotsEquip(version); + packet->setArrayLengthByName("item_count", num_slots); + if(!orig_packet){ + xor_packet = new uchar[packet_size* num_slots]; + orig_packet = new uchar[packet_size* num_slots]; + memset(xor_packet, 0, packet_size* num_slots); + memset(orig_packet, 0, packet_size* num_slots); + } + int32 menu_data = 3; + int32 effective_level = player->GetInfoStructUInt("effective_level"); + + int32 levelsLowered = (effective_level > 0 && effective_level < player->GetLevel()) ? player->GetLevel() - effective_level : 0; + + for(int16 i=0;iConvertSlotFromClient(i, version); + + menu_data = 3; + item = items[itemIdx]; + if(item && item->details.item_id > 0){ + if(item->slot_data.size() > 0) + menu_data -= ITEM_MENU_TYPE_GENERIC; + if (item->details.num_slots > 0) { + int8 max_slots = player->GetMaxBagSlots(version); + if (item->details.num_slots > max_slots) + item->details.num_slots = max_slots; + + menu_data += ITEM_MENU_TYPE_BAG; + + if (item->details.num_free_slots == item->details.num_slots) + menu_data += ITEM_MENU_TYPE_EMPTY_BAG; + } + if(item->IsSkill()) + menu_data += ITEM_MENU_TYPE_SCRIBE; + if(item->generic_info.condition == 0) + menu_data += ITEM_MENU_TYPE_BROKEN2; + else if (item->generic_info.condition <= 20) + menu_data += ITEM_MENU_TYPE_DAMAGED; + if (item->CheckFlag(ATTUNED) || item->CheckFlag(NO_TRADE)) { + if (version <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_ATTUNED; + else + menu_data += ITEM_MENU_TYPE_ATTUNED; + } + else if (item->CheckFlag(ATTUNEABLE)) { + if (version <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_ATTUNEABLE; + else + menu_data += ITEM_MENU_TYPE_ATTUNEABLE; + } + if (item->generic_info.usable == 1) + menu_data += ITEM_MENU_TYPE_USE; + if (item->IsFood()) + { + if (version <= 373) { + if (item->IsFoodDrink()) + menu_data += ORIG_ITEM_MENU_TYPE_DRINK; + else + menu_data += ORIG_ITEM_MENU_TYPE_FOOD; + } + else { + menu_data += ITEM_MENU_TYPE_CONSUME; + if (player && ((item->IsFoodFood() && player->get_character_flag(CF_FOOD_AUTO_CONSUME)) || (item->IsFoodDrink() && player->get_character_flag(CF_DRINK_AUTO_CONSUME)))) + { + // needs all 3 to display 'auto consume' off option as well as set the yellowish tint in the background + menu_data += ITEM_MENU_TYPE_CONSUME_OFF; + menu_data += ORIG_ITEM_MENU_TYPE_DRINK; + menu_data += ORIG_ITEM_MENU_TYPE_FOOD; + } + } + } + packet->setSubstructArrayDataByName("items", "unique_id", item->details.unique_id, 0, i); + packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id, 0, i); + packet->setSubstructArrayDataByName("items", "inv_slot_id", item->details.inv_slot_id, 0, i); + if (item->details.count > 0 && item->stack_count > 1) { + if (version <= 373) + menu_data += ORIG_ITEM_MENU_TYPE_STACKABLE; + else + menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES; + } + if(levelsLowered && item->details.recommended_level > effective_level) + menu_data += ITEM_MENU_TYPE_MENTORED; + packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i); + packet->setSubstructArrayDataByName("items", "icon", item->GetIcon(version), 0, i); + packet->setSubstructArrayDataByName("items", "slot_id", player->ConvertSlotToClient(item->details.equip_slot_id > 0 ? item->details.equip_slot_id : item->details.slot_id, version), 0, i); + packet->setSubstructArrayDataByName("items", "count", item->details.count, 0, i); + // item level needed here + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setSubstructArrayDataByName("items", "tier", item->details.tier, 0, i); + } + packet->setSubstructArrayDataByName("items", "num_slots", item->details.num_slots, 0, i); + //empty slots needed here + packet->setSubstructArrayDataByName("items", "item_id", item->details.item_id, 0, i); + //broker id needed here + packet->setSubstructArrayDataByName("items", "name", item->name.c_str(), 0, i); + + //packet->setSubstructArrayDataByName("items", "unknown4", 10, 0, i); + + item->details.index = i; + } + packet->setSubstructArrayDataByName("items", "index", i, 0, i); + } + packet->setDataByName("equip_flag", GetAppearanceType() ? 2 : 1); + app = packet->serializeCountPacket(version, 1, orig_packet, xor_packet); + safe_delete(packet); + } + MEquipmentItems.unlock(); + return app; +} +ItemStatsValues* EquipmentItemList::CalculateEquipmentBonuses(Entity* entity){ + ItemStatsValues* stats = new ItemStatsValues; + memset(stats, 0, sizeof(ItemStatsValues)); + entity->GetInfoStruct()->set_mitigation_base(0); + MEquipmentItems.lock(); + for(int8 i=0;idetails.item_id > 0){ + master_item_list.CalculateItemBonuses(items[i], entity, stats); + if (items[i]->armor_info && !items[i]->IsShield()) + entity->GetInfoStruct()->add_mitigation_base(items[i]->armor_info->mitigation_high); + } + } + MEquipmentItems.unlock(); + return stats; +} +bool EquipmentItemList::HasItem(int32 id){ + MEquipmentItems.lock(); + for(int8 i=0;idetails.item_id == id){ + MEquipmentItems.unlock(); + return true; + } + } + MEquipmentItems.unlock(); + return false; +} +void EquipmentItemList::RemoveItem(int8 slot, bool delete_item){ + if(slot < NUM_SLOTS){ + MEquipmentItems.lock(); + if(items[slot] && items[slot]->details.appearance_type) + items[slot]->details.appearance_type = 0; + + if(delete_item){ + safe_delete(items[slot]); + } + items[slot] = 0; + MEquipmentItems.unlock(); + } +} + +Item* EquipmentItemList::GetItemFromUniqueID(int32 item_id){ + MEquipmentItems.lock(); + for(int8 i=0;idetails.unique_id == item_id){ + MEquipmentItems.unlock(); + return items[i]; + } + } + MEquipmentItems.unlock(); + return 0; +} + +Item* EquipmentItemList::GetItemFromItemID(int32 item_id) { + Item* item = 0; + MEquipmentItems.lock(); + for(int8 i = 0; i < NUM_SLOTS; i++) { + if(items[i] && items[i]->details.item_id == item_id) { + item = items[i]; + break; + } + } + MEquipmentItems.unlock(); + return item; +} + +bool EquipmentItemList::CanItemBeEquippedInSlot(Item* tmp, int8 slot){ + MEquipmentItems.lock(); + for(int8 i=0;tmp && islot_data.size();i++){ + if(tmp->slot_data[i] == slot){ + MEquipmentItems.unlock(); + return true; + } + } + MEquipmentItems.unlock(); + return false; +} +bool EquipmentItemList::CheckEquipSlot(Item* tmp, int8 slot){ + MEquipmentItems.lock(); + for(int8 i=0;tmp && islot_data.size();i++){ + if(tmp->slot_data[i] == slot){ + Item* tmp_item = GetItem(tmp->slot_data[i]); + if(!tmp_item || tmp_item->details.item_id == 0){ + if(slot == EQ2_SECONDARY_SLOT) + { + Item* primary = GetItem(EQ2_PRIMARY_SLOT); + if(primary && primary->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND) + continue; + } + MEquipmentItems.unlock(); + return true; + } + } + } + MEquipmentItems.unlock(); + return false; +} + +int8 EquipmentItemList::GetFreeSlot(Item* tmp, int8 slot_id, int16 version){ + int8 slot = 0; + MEquipmentItems.lock(); + for(int8 i=0;tmp && islot_data.size();i++){ + slot = tmp->slot_data[i]; + if(slot_id == 255 || slot == slot_id){ + Item* tmp_item = GetItem(slot); + if(!tmp_item || tmp_item->details.item_id == 0){ + if(slot == EQ2_SECONDARY_SLOT) + { + Item* primary = GetItem(EQ2_PRIMARY_SLOT); + if(primary && primary->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND) + continue; + } + MEquipmentItems.unlock(); + return slot; + } + else if ( slot == EQ2_LRING_SLOT || slot == EQ2_EARS_SLOT_1 || slot == EQ2_LWRIST_SLOT || slot == EQ2_CHARM_SLOT_1) + { + if(version <= 561 && slot == EQ2_EARS_SLOT_1) + continue; + + Item* rslot = GetItem(slot+1); + if(!rslot) + { + MEquipmentItems.unlock(); + return slot+1; + } + } + } + } + MEquipmentItems.unlock(); + return 255; +} + +int32 EquipmentItemList::CheckSlotConflict(Item* item, bool check_lore_only, int16* lore_stack_count) { + bool is_lore = false; + bool is_stack_lore = false; + if(!(is_lore = item->CheckFlag(LORE)) && !(is_stack_lore = item->CheckFlag(STACK_LORE)) && check_lore_only) { + return 0; + } + + if(!check_lore_only && !is_lore && !is_stack_lore && !item->CheckFlag(LORE_EQUIP)) { + return 0; + } + + int32 conflict = 0; + MEquipmentItems.lock(); + for(int8 i=0;idetails.item_id == item->details.item_id) { + if(lore_stack_count) + *lore_stack_count += items[i]->details.count; + if(!is_stack_lore && items[i]->CheckFlag(LORE)) { + conflict = LORE; + break; + } + else if(is_stack_lore && (*lore_stack_count + item->details.count) > items[i]->stack_count) { + conflict = STACK_LORE; + break; + } + else if(!check_lore_only && items[i]->CheckFlag(LORE_EQUIP)) { + conflict = LORE_EQUIP; + break; + } + } + } + + MEquipmentItems.unlock(); + return conflict; +} + +int8 EquipmentItemList::GetSlotByItem(Item* item) { + int8 slot = 255; + for (int8 i = 0; i < NUM_SLOTS; i++) { + if (items[i] && items[i] == item) { + slot = i; + break; + } + } + return slot; +} + +string Item::CreateItemLink(int16 client_Version, bool bUseUniqueID) { + ostringstream ss; + if(client_Version > 561) + ss << "\\aITEM " << details.item_id << ' ' << (bUseUniqueID ? details.unique_id : 0) << ':' << name << "\\/a"; + else { + if(bUseUniqueID) + ss << "\\aITEM " << details.item_id << ' ' << details.unique_id << ':' << name << "\\/a"; + else + ss << "\\aITEM " << details.item_id << ' ' << name << ':' << name << "\\/a"; + } + return ss.str(); +} + +int16 Item::GetIcon(int16 version) { + if(version <= 561 && details.classic_icon) { + return details.classic_icon; + } + + return details.icon; +} + +int32 MasterItemList::GetItemStatIDByName(std::string name) +{ + boost::to_lower(name); + map::iterator itr = mappedItemStatsStrings.find(name.c_str()); + if(itr != mappedItemStatsStrings.end()) + return itr->second; + + return 0xFFFFFFFF; +} + +std::string MasterItemList::GetItemStatNameByID(int32 id) +{ + map::iterator itr = mappedItemStatTypeIDs.find(id); + if(itr != mappedItemStatTypeIDs.end()) + return itr->second; + + return std::string(""); +} diff --git a/source/WorldServer/Items/Items.h b/source/WorldServer/Items/Items.h new file mode 100644 index 0000000..c098f50 --- /dev/null +++ b/source/WorldServer/Items/Items.h @@ -0,0 +1,1223 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_ITEMS__ +#define __EQ2_ITEMS__ +#include +#include +#include +#include "../../common/types.h" +#include "../../common/DataBuffer.h" +#include "../Commands/Commands.h" +#include "../../common/ConfigReader.h" + +using namespace std; +class MasterItemList; +class Player; +class Entity; +extern MasterItemList master_item_list; + +#define BASE_EQUIPMENT 0 +#define APPEARANCE_EQUIPMENT 1 +#define MAX_EQUIPMENT 2 // max iterations for equipment (base is 0, appearance is 1, so this is 2) + +#define EQ2_PRIMARY_SLOT 0 +#define EQ2_SECONDARY_SLOT 1 +#define EQ2_HEAD_SLOT 2 +#define EQ2_CHEST_SLOT 3 +#define EQ2_SHOULDERS_SLOT 4 +#define EQ2_FOREARMS_SLOT 5 +#define EQ2_HANDS_SLOT 6 +#define EQ2_LEGS_SLOT 7 +#define EQ2_FEET_SLOT 8 +#define EQ2_LRING_SLOT 9 +#define EQ2_RRING_SLOT 10 +#define EQ2_EARS_SLOT_1 11 +#define EQ2_EARS_SLOT_2 12 +#define EQ2_NECK_SLOT 13 +#define EQ2_LWRIST_SLOT 14 +#define EQ2_RWRIST_SLOT 15 +#define EQ2_RANGE_SLOT 16 +#define EQ2_AMMO_SLOT 17 +#define EQ2_WAIST_SLOT 18 +#define EQ2_CLOAK_SLOT 19 +#define EQ2_CHARM_SLOT_1 20 +#define EQ2_CHARM_SLOT_2 21 +#define EQ2_FOOD_SLOT 22 +#define EQ2_DRINK_SLOT 23 +#define EQ2_TEXTURES_SLOT 24 +#define EQ2_HAIR_SLOT 25 +#define EQ2_BEARD_SLOT 26 +#define EQ2_WINGS_SLOT 27 +#define EQ2_NAKED_CHEST_SLOT 28 +#define EQ2_NAKED_LEGS_SLOT 29 +#define EQ2_BACK_SLOT 30 +#define EQ2_ORIG_FOOD_SLOT 18 +#define EQ2_ORIG_DRINK_SLOT 19 +#define EQ2_DOF_FOOD_SLOT 20 +#define EQ2_DOF_DRINK_SLOT 21 + +#define PRIMARY_SLOT 1 +#define SECONDARY_SLOT 2 +#define HEAD_SLOT 4 +#define CHEST_SLOT 8 +#define SHOULDERS_SLOT 16 +#define FOREARMS_SLOT 32 +#define HANDS_SLOT 64 +#define LEGS_SLOT 128 +#define FEET_SLOT 256 +#define LRING_SLOT 512 +#define RRING_SLOT 1024 +#define EARS_SLOT_1 2048 +#define EARS_SLOT_2 4096 +#define NECK_SLOT 8192 +#define LWRIST_SLOT 16384 +#define RWRIST_SLOT 32768 +#define RANGE_SLOT 65536 +#define AMMO_SLOT 131072 +#define WAIST_SLOT 262144 +#define CLOAK_SLOT 524288 +#define CHARM_SLOT_1 1048576 +#define CHARM_SLOT_2 2097152 +#define FOOD_SLOT 4194304 +#define DRINK_SLOT 8388608 +#define TEXTURES_SLOT 16777216 +#define HAIR_SLOT 33554432 +#define BEARD_SLOT 67108864 +#define WINGS_SLOT 134217728 +#define NAKED_CHEST_SLOT 268435456 +#define NAKED_LEGS_SLOT 536870912 +#define BACK_SLOT 1073741824 +#define ORIG_FOOD_SLOT 524288 +#define ORIG_DRINK_SLOT 1048576 +#define DOF_FOOD_SLOT 1048576 +#define DOF_DRINK_SLOT 2097152 + +#define CLASSIC_EQ_MAX_BAG_SLOTS 20 +#define DOF_EQ_MAX_BAG_SLOTS 36 +#define NUM_BANK_SLOTS 12 +#define NUM_SHARED_BANK_SLOTS 8 +#define CLASSIC_NUM_SLOTS 22 +#define NUM_SLOTS 25 +#define NUM_INV_SLOTS 6 +#define INV_SLOT1 0 +#define INV_SLOT2 50 +#define INV_SLOT3 100 +#define INV_SLOT4 150 +#define INV_SLOT5 200 +#define INV_SLOT6 250 +#define BANK_SLOT1 1000 +#define BANK_SLOT2 1100 +#define BANK_SLOT3 1200 +#define BANK_SLOT4 1300 +#define BANK_SLOT5 1400 +#define BANK_SLOT6 1500 +#define BANK_SLOT7 1600 +#define BANK_SLOT8 1700 + +// FLAGS +#define ATTUNED 1 +#define ATTUNEABLE 2 +#define ARTIFACT 4 +#define LORE 8 +#define TEMPORARY 16 +#define NO_TRADE 32 +#define NO_VALUE 64 +#define NO_ZONE 128 +#define NO_DESTROY 256 +#define CRAFTED 512 +#define GOOD_ONLY 1024 +#define EVIL_ONLY 2048 +#define STACK_LORE 4096 +#define LORE_EQUIP 8192 +#define NO_TRANSMUTE 16384 +#define CURSED 32768 + +// FLAGS2 +#define ORNATE 1 +#define HEIRLOOM 2 +#define APPEARANCE_ONLY 4 +#define UNLOCKED 8 +#define REFORGED 16 +#define NO_REPAIR 32 +#define ETHERAL 64 +#define REFINED 128 +#define NO_SALVAGE 256 +#define INDESTRUCTABLE 512 +#define NO_EXPERIMENT 1024 +#define HOUSE_LORE 2048 +#define FLAGS2_4096 4096//AoM: not used at this time +#define BUILDING_BLOCK 8192 +#define FREE_REFORGE 16384 +#define FLAGS2_32768 32768//AoM: not used at this time + + +#define ITEM_WIELD_TYPE_DUAL 1 +#define ITEM_WIELD_TYPE_SINGLE 2 +#define ITEM_WIELD_TYPE_TWO_HAND 4 + +#define ITEM_TYPE_NORMAL 0 +#define ITEM_TYPE_WEAPON 1 +#define ITEM_TYPE_RANGED 2 +#define ITEM_TYPE_ARMOR 3 +#define ITEM_TYPE_SHIELD 4 +#define ITEM_TYPE_BAG 5 +#define ITEM_TYPE_SKILL 6 +#define ITEM_TYPE_RECIPE 7 +#define ITEM_TYPE_FOOD 8 +#define ITEM_TYPE_BAUBLE 9 +#define ITEM_TYPE_HOUSE 10 +#define ITEM_TYPE_THROWN 11 +#define ITEM_TYPE_HOUSE_CONTAINER 12 +#define ITEM_TYPE_ADORNMENT 13 +#define ITEM_TYPE_GENERIC_ADORNMENT 14 +#define ITEM_TYPE_PROFILE 16 +#define ITEM_TYPE_PATTERN 17 +#define ITEM_TYPE_ARMORSET 18 +#define ITEM_TYPE_ITEMCRATE 18 +#define ITEM_TYPE_BOOK 19 +#define ITEM_TYPE_DECORATION 20 +#define ITEM_TYPE_DUNGEON_MAKER 21 +#define ITEM_TYPE_MARKETPLACE 22 + + +//DOV defines everything till 13 is the same +//#define ITEM_TYPE_BOOK 13 +//#define ITEM_TYPE_ADORNMENT 14 +//#define ITEM_TYPE_PATTERN 15 +//#define ITEM_TYPE_ARMORSET 16 + + + +#define ITEM_MENU_TYPE_GENERIC 1 //0 (NON_EQUIPABLE) +#define ITEM_MENU_TYPE_EQUIP 2 //1 (This is SLOT_FULL for classic) +#define ITEM_MENU_TYPE_BAG 4//2 +#define ITEM_MENU_TYPE_HOUSE 8 //3 Place +#define ITEM_MENU_TYPE_EMPTY_BAG 16 //4 +#define ITEM_MENU_TYPE_SCRIBE 32//5 +#define ITEM_MENU_TYPE_BANK_BAG 64//6 +#define ITEM_MENU_TYPE_INSUFFICIENT_KNOWLEDGE 128//7 +#define ITEM_MENU_TYPE_ACTIVATE 256//8 +#define ITEM_MENU_TYPE_BROKEN 512//9 +#define ITEM_MENU_TYPE_TWO_HANDED 1024//10 +#define ITEM_MENU_TYPE_ATTUNED 2048//11 +#define ITEM_MENU_TYPE_ATTUNEABLE 4096//12 +#define ITEM_MENU_TYPE_BOOK 8192//13 +#define ITEM_MENU_TYPE_DISPLAY_CHARGES 16384//14 +#define ITEM_MENU_TYPE_TEST1 32768//15 Possibly toogle decorator mode +#define ITEM_MENU_TYPE_NAMEPET 65536 //16 Right CLick Menu +#define ITEM_MENU_TYPE_MENTORED 131072 //sets a purple background on item +#define ITEM_MENU_TYPE_CONSUME 262144//18 +#define ITEM_MENU_TYPE_USE 524288//19 +#define ITEM_MENU_TYPE_CONSUME_OFF 1048576//20 +#define ITEM_MENU_TYPE_TEST3 1310720// bad number combo of 2 bits +#define ITEM_MENU_TYPE_TEST4 2097152//21 +#define ITEM_MENU_TYPE_TEST5 4194304//22 infusable +#define ITEM_MENU_TYPE_TEST6 8388608 //drink option on menu +#define ITEM_MENU_TYPE_TEST7 16777216//24 +#define ITEM_MENU_TYPE_TEST8 33554432 // bit 25 use option in bags +#define ITEM_MENU_TYPE_TEST9 67108864//26 +#define ITEM_MENU_TYPE_DAMAGED 134217728 //27 +#define ITEM_MENU_TYPE_BROKEN2 268435456 //28 +#define ITEM_MENU_TYPE_REDEEM 536870912 //29 //READ?? +#define ITEM_MENU_TYPE_TEST10 1073741824 //30 +#define ITEM_MENU_TYPE_UNPACK 2147483648//31 * on items i found this unpack is used at same time as UNPACK below +#define ORIG_ITEM_MENU_TYPE_FOOD 2048 +#define ORIG_ITEM_MENU_TYPE_DRINK 4096 +#define ORIG_ITEM_MENU_TYPE_ATTUNED 8192 +#define ORIG_ITEM_MENU_TYPE_ATTUNEABLE 16384 +#define ORIG_ITEM_MENU_TYPE_BOOK 32768 +#define ORIG_ITEM_MENU_TYPE_STACKABLE 65536 +#define ORIG_ITEM_MENU_TYPE_NAMEPET 262144 + +#define ITEM_MENU_TYPE2_TEST1 1 //0 auto consume on +#define ITEM_MENU_TYPE2_TEST2 2 //1 +#define ITEM_MENU_TYPE2_UNPACK 4//2 +#define ITEM_MENU_TYPE2_TEST4 8 //3 +#define ITEM_MENU_TYPE2_TEST5 16 //4 +#define ITEM_MENU_TYPE2_TEST6 32//5 +#define ITEM_MENU_TYPE2_TEST7 64//6 +#define ITEM_MENU_TYPE2_TEST8 128//7 +#define ITEM_MENU_TYPE2_TEST9 256//8 +#define ITEM_MENU_TYPE2_TEST10 512//9 +#define ITEM_MENU_TYPE2_TEST11 1024//10 +#define ITEM_MENU_TYPE2_TEST12 2048//11 +#define ITEM_MENU_TYPE2_TEST13 4096//12 +#define ITEM_MENU_TYPE2_TEST14 8192//13 +#define ITEM_MENU_TYPE2_TEST15 16384//14 +#define ITEM_MENU_TYPE2_TEST16 32768//15 + +#define ITEM_TAG_COMMON 2 +#define ITEM_TAG_UNCOMMON 3 //tier tags +#define ITEM_TAG_TREASURED 4 +#define ITEM_TAG_LEGENDARY 7 +#define ITEM_TAG_FABLED 9 +#define ITEM_TAG_MYTHICAL 12 + +#define ITEM_BROKER_TYPE_ANY 0xFFFFFFFF +#define ITEM_BROKER_TYPE_ANY64BIT 0xFFFFFFFFFFFFFFFF +#define ITEM_BROKER_TYPE_ADORNMENT 134217728 +#define ITEM_BROKER_TYPE_AMMO 1024 +#define ITEM_BROKER_TYPE_ATTUNEABLE 16384 +#define ITEM_BROKER_TYPE_BAG 2048 +#define ITEM_BROKER_TYPE_BAUBLE 16777216 +#define ITEM_BROKER_TYPE_BOOK 128 +#define ITEM_BROKER_TYPE_CHAINARMOR 2097152 +#define ITEM_BROKER_TYPE_CLOAK 1073741824 +#define ITEM_BROKER_TYPE_CLOTHARMOR 524288 +#define ITEM_BROKER_TYPE_COLLECTABLE 67108864 +#define ITEM_BROKER_TYPE_CRUSHWEAPON 4 +#define ITEM_BROKER_TYPE_DRINK 131072 +#define ITEM_BROKER_TYPE_FOOD 4096 +#define ITEM_BROKER_TYPE_HOUSEITEM 512 +#define ITEM_BROKER_TYPE_JEWELRY 262144 +#define ITEM_BROKER_TYPE_LEATHERARMOR 1048576 +#define ITEM_BROKER_TYPE_LORE 8192 +#define ITEM_BROKER_TYPE_MISC 1 +#define ITEM_BROKER_TYPE_PIERCEWEAPON 8 +#define ITEM_BROKER_TYPE_PLATEARMOR 4194304 +#define ITEM_BROKER_TYPE_POISON 65536 +#define ITEM_BROKER_TYPE_POTION 32768 +#define ITEM_BROKER_TYPE_RECIPEBOOK 8388608 +#define ITEM_BROKER_TYPE_SALESDISPLAY 33554432 +#define ITEM_BROKER_TYPE_SHIELD 32 +#define ITEM_BROKER_TYPE_SLASHWEAPON 2 +#define ITEM_BROKER_TYPE_SPELLSCROLL 64 +#define ITEM_BROKER_TYPE_TINKERED 268435456 +#define ITEM_BROKER_TYPE_TRADESKILL 256 + +#define ITEM_BROKER_TYPE_2H_CRUSH 17179869184 +#define ITEM_BROKER_TYPE_2H_PIERCE 34359738368 +#define ITEM_BROKER_TYPE_2H_SLASH 8589934592 + +#define ITEM_BROKER_SLOT_ANY 0xFFFFFFFF +#define ITEM_BROKER_SLOT_AMMO 65536 +#define ITEM_BROKER_SLOT_CHARM 524288 +#define ITEM_BROKER_SLOT_CHEST 32 +#define ITEM_BROKER_SLOT_CLOAK 262144 +#define ITEM_BROKER_SLOT_DRINK 2097152 +#define ITEM_BROKER_SLOT_EARS 4096 +#define ITEM_BROKER_SLOT_FEET 1024 +#define ITEM_BROKER_SLOT_FOOD 1048576 +#define ITEM_BROKER_SLOT_FOREARMS 128 +#define ITEM_BROKER_SLOT_HANDS 256 +#define ITEM_BROKER_SLOT_HEAD 16 +#define ITEM_BROKER_SLOT_LEGS 512 +#define ITEM_BROKER_SLOT_NECK 8192 +#define ITEM_BROKER_SLOT_PRIMARY 1 +#define ITEM_BROKER_SLOT_PRIMARY_2H 2 +#define ITEM_BROKER_SLOT_RANGE_WEAPON 32768 +#define ITEM_BROKER_SLOT_RING 2048 +#define ITEM_BROKER_SLOT_SECONDARY 8 +#define ITEM_BROKER_SLOT_SHOULDERS 64 +#define ITEM_BROKER_SLOT_WAIST 131072 +#define ITEM_BROKER_SLOT_WRIST 16384 + +#define ITEM_BROKER_STAT_TYPE_NONE 0 +#define ITEM_BROKER_STAT_TYPE_DEF 2 +#define ITEM_BROKER_STAT_TYPE_STR 4 +#define ITEM_BROKER_STAT_TYPE_STA 8 +#define ITEM_BROKER_STAT_TYPE_AGI 16 +#define ITEM_BROKER_STAT_TYPE_WIS 32 +#define ITEM_BROKER_STAT_TYPE_INT 64 +#define ITEM_BROKER_STAT_TYPE_HEALTH 128 +#define ITEM_BROKER_STAT_TYPE_POWER 256 +#define ITEM_BROKER_STAT_TYPE_HEAT 512 +#define ITEM_BROKER_STAT_TYPE_COLD 1024 +#define ITEM_BROKER_STAT_TYPE_MAGIC 2048 +#define ITEM_BROKER_STAT_TYPE_MENTAL 4096 +#define ITEM_BROKER_STAT_TYPE_DIVINE 8192 +#define ITEM_BROKER_STAT_TYPE_POISON 16384 +#define ITEM_BROKER_STAT_TYPE_DISEASE 32768 +#define ITEM_BROKER_STAT_TYPE_CRUSH 65536 +#define ITEM_BROKER_STAT_TYPE_SLASH 131072 +#define ITEM_BROKER_STAT_TYPE_PIERCE 262144 +#define ITEM_BROKER_STAT_TYPE_CRITICAL 524288 +#define ITEM_BROKER_STAT_TYPE_DBL_ATTACK 1048576 +#define ITEM_BROKER_STAT_TYPE_ABILITY_MOD 2097152 +#define ITEM_BROKER_STAT_TYPE_POTENCY 4194304 +#define ITEM_BROKER_STAT_TYPE_AEAUTOATTACK 8388608 +#define ITEM_BROKER_STAT_TYPE_ATTACKSPEED 16777216 +#define ITEM_BROKER_STAT_TYPE_BLOCKCHANCE 33554432 +#define ITEM_BROKER_STAT_TYPE_CASTINGSPEED 67108864 +#define ITEM_BROKER_STAT_TYPE_CRITBONUS 134217728 +#define ITEM_BROKER_STAT_TYPE_CRITCHANCE 268435456 +#define ITEM_BROKER_STAT_TYPE_DPS 536870912 +#define ITEM_BROKER_STAT_TYPE_FLURRYCHANCE 1073741824 +#define ITEM_BROKER_STAT_TYPE_HATEGAIN 2147483648 +#define ITEM_BROKER_STAT_TYPE_MITIGATION 4294967296 +#define ITEM_BROKER_STAT_TYPE_MULTI_ATTACK 8589934592 +#define ITEM_BROKER_STAT_TYPE_RECOVERY 17179869184 +#define ITEM_BROKER_STAT_TYPE_REUSE_SPEED 34359738368 +#define ITEM_BROKER_STAT_TYPE_SPELL_WPNDMG 68719476736 +#define ITEM_BROKER_STAT_TYPE_STRIKETHROUGH 137438953472 +#define ITEM_BROKER_STAT_TYPE_TOUGHNESS 274877906944 +#define ITEM_BROKER_STAT_TYPE_WEAPONDMG 549755813888 + + +#define OVERFLOW_SLOT 0xFFFFFFFE +#define SLOT_INVALID 0xFFFF + +#define ITEM_STAT_STR 0 +#define ITEM_STAT_STA 1 +#define ITEM_STAT_AGI 2 +#define ITEM_STAT_WIS 3 +#define ITEM_STAT_INT 4 + +#define ITEM_STAT_ADORNING 100 +#define ITEM_STAT_AGGRESSION 101 +#define ITEM_STAT_ARTIFICING 102 +#define ITEM_STAT_ARTISTRY 103 +#define ITEM_STAT_CHEMISTRY 104 +#define ITEM_STAT_CRUSHING 105 +#define ITEM_STAT_DEFENSE 106 +#define ITEM_STAT_DEFLECTION 107 +#define ITEM_STAT_DISRUPTION 108 +#define ITEM_STAT_FISHING 109 +#define ITEM_STAT_FLETCHING 110 +#define ITEM_STAT_FOCUS 111 +#define ITEM_STAT_FORESTING 112 +#define ITEM_STAT_GATHERING 113 +#define ITEM_STAT_METAL_SHAPING 114 +#define ITEM_STAT_METALWORKING 115 +#define ITEM_STAT_MINING 116 +#define ITEM_STAT_MINISTRATION 117 +#define ITEM_STAT_ORDINATION 118 +#define ITEM_STAT_PARRY 119 +#define ITEM_STAT_PIERCING 120 +#define ITEM_STAT_RANGED 121 +#define ITEM_STAT_SAFE_FALL 122 +#define ITEM_STAT_SCRIBING 123 +#define ITEM_STAT_SCULPTING 124 +#define ITEM_STAT_SLASHING 125 +#define ITEM_STAT_SUBJUGATION 126 +#define ITEM_STAT_SWIMMING 127 +#define ITEM_STAT_TAILORING 128 +#define ITEM_STAT_TINKERING 129 +#define ITEM_STAT_TRANSMUTING 130 +#define ITEM_STAT_TRAPPING 131 +#define ITEM_STAT_WEAPON_SKILLS 132 +#define ITEM_STAT_POWER_COST_REDUCTION 133 +#define ITEM_STAT_SPELL_AVOIDANCE 134 + +#define ITEM_STAT_VS_PHYSICAL 200 +#define ITEM_STAT_VS_HEAT 201 //elemental +#define ITEM_STAT_VS_POISON 202 //noxious +#define ITEM_STAT_VS_MAGIC 203 //arcane +#define ITEM_STAT_VS_DROWNING 210 +#define ITEM_STAT_VS_FALLING 211 +#define ITEM_STAT_VS_PAIN 212 +#define ITEM_STAT_VS_MELEE 213 + +#define ITEM_STAT_VS_SLASH 204 +#define ITEM_STAT_VS_CRUSH 205 +#define ITEM_STAT_VS_PIERCE 206 +//#define ITEM_STAT_VS_HEAT 203 //just so no build error +#define ITEM_STAT_VS_COLD 207 +//#define ITEM_STAT_VS_MAGIC 205 //just so no build error +#define ITEM_STAT_VS_MENTAL 208 +#define ITEM_STAT_VS_DIVINE 209 +#define ITEM_STAT_VS_DISEASE 214 +//#define ITEM_STAT_VS_POISON 209 //just so no build error +//#define ITEM_STAT_VS_DROWNING 210 //just so no build error +//#define ITEM_STAT_VS_FALLING 211 //just so no build error +//#define ITEM_STAT_VS_PAIN 212 //just so no build error +//#define ITEM_STAT_VS_MELEE 213 //just so no build error + +#define ITEM_STAT_DMG_SLASH 300 +#define ITEM_STAT_DMG_CRUSH 301 +#define ITEM_STAT_DMG_PIERCE 302 +#define ITEM_STAT_DMG_HEAT 303 +#define ITEM_STAT_DMG_COLD 304 +#define ITEM_STAT_DMG_MAGIC 305 +#define ITEM_STAT_DMG_MENTAL 306 +#define ITEM_STAT_DMG_DIVINE 307 +#define ITEM_STAT_DMG_DISEASE 308 +#define ITEM_STAT_DMG_POISON 309 +#define ITEM_STAT_DMG_DROWNING 310 +#define ITEM_STAT_DMG_FALLING 311 +#define ITEM_STAT_DMG_PAIN 312 +#define ITEM_STAT_DMG_MELEE 313 + +#define ITEM_STAT_DEFLECTIONCHANCE 400 //just so no build error + +#define ITEM_STAT_HEALTH 500 +#define ITEM_STAT_POWER 501 +#define ITEM_STAT_CONCENTRATION 502 +#define ITEM_STAT_SAVAGERY 503 + +//this is the master stat list you should be using and names match what is in census. it is based off of DoV. the comment is what is displayed on items when examining +//the itemstats table will maintain the custom lists per expansion +// emu # is digits after the 6 + +#define ITEM_STAT_HPREGEN 600 //Health Regeneration +#define ITEM_STAT_MANAREGEN 601 //Power Regeneration +#define ITEM_STAT_HPREGENPPT 602 //Out-of-Combat Health Regeneration %%? +#define ITEM_STAT_MPREGENPPT 603 //Out-of-Combat Power Regeneration %%? +#define ITEM_STAT_COMBATHPREGENPPT 604 //In-Combat Health Regeneration %%? +#define ITEM_STAT_COMBATMPREGENPPT 605 //In-Combat Power Regeneration %%? +#define ITEM_STAT_MAXHP 606 //Max Health +#define ITEM_STAT_MAXHPPERC 607 +#define ITEM_STAT_MAXHPPERCFINAL 608 //% Max Mealth +#define ITEM_STAT_SPEED 609 //Out of Combat Run Speed +#define ITEM_STAT_SLOW 610 //Slow +#define ITEM_STAT_MOUNTSPEED 611 //Ground Mount Speed +#define ITEM_STAT_MOUNTAIRSPEED 612 //Mount Air Speed +#define ITEM_STAT_LEAPSPEED 613 +#define ITEM_STAT_LEAPTIME 614 +#define ITEM_STAT_GLIDEEFFICIENCY 615 +#define ITEM_STAT_OFFENSIVESPEED 616 //In Combat Run Speed +#define ITEM_STAT_ATTACKSPEED 617 //% Attack Speed +#define ITEM_STAT_SPELLWEAPONATTACKSPEED 618 +#define ITEM_STAT_MAXMANA 619 //Max Power +#define ITEM_STAT_MAXMANAPERC 620 //% Max Power +#define ITEM_STAT_MAXATTPERC 621 //All Attributes //is this a percent or is it a stat change +#define ITEM_STAT_BLURVISION 622 //Blurs Vision +#define ITEM_STAT_MAGICLEVELIMMUNITY 623 //Magic Level Immunity +#define ITEM_STAT_HATEGAINMOD 624 //% Hate Gain +#define ITEM_STAT_COMBATEXPMOD 625 //Combat XP Gain +#define ITEM_STAT_TRADESKILLEXPMOD 626 //Tradeskill XP Gain +#define ITEM_STAT_ACHIEVEMENTEXPMOD 627 //AA XP Gain +#define ITEM_STAT_SIZEMOD 628 //Size +#define ITEM_STAT_DPS 629 //%Damage Per Second +#define ITEM_STAT_SPELLWEAPONDPS 630 //%Damage Per Second +#define ITEM_STAT_STEALTH 631 //Stealth +#define ITEM_STAT_INVIS 632 //Invisibility +#define ITEM_STAT_SEESTEALTH 633 //See Stealth +#define ITEM_STAT_SEEINVIS 634 //See Invisible +#define ITEM_STAT_EFFECTIVELEVELMOD 635 //Effective Level +#define ITEM_STAT_RIPOSTECHANCE 636 //%Extra Riposte Chance +#define ITEM_STAT_PARRYCHANCE 637 //%Extra Parry Chance +#define ITEM_STAT_DODGECHANCE 638 //%Extra Dodge Chance +#define ITEM_STAT_AEAUTOATTACKCHANCE 639 //% AE Autoattck Chance +#define ITEM_STAT_SPELLWEAPONAEAUTOATTACKCHANCE 640 // +#define ITEM_STAT_MULTIATTACKCHANCE 641 //% Multi Attack Chance // inconsistant with db +#define ITEM_STAT_PVPDOUBLEATTACKCHANCE 642 +#define ITEM_STAT_SPELLWEAPONDOUBLEATTACKCHANCE 643 // missing in db +#define ITEM_STAT_PVPSPELLWEAPONDOUBLEATTACKCHANCE 644 +#define ITEM_STAT_SPELLMULTIATTACKCHANCE 645 //% Spell Multi Atttack Chance +#define ITEM_STAT_PVPSPELLDOUBLEATTACKCHANCE 646 +#define ITEM_STAT_FLURRY 647 //%Flurry +#define ITEM_STAT_SPELLWEAPONFLURRY 648 +#define ITEM_STAT_MELEEDAMAGEMULTIPLIER 649 //Melee Damage Multiplier +#define ITEM_STAT_EXTRAHARVESTCHANCE 650 //Extra Harvest Chance +#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 651 //Block Chance +#define ITEM_STAT_ITEMHPREGENPPT 652 //In-Combat Health Regeneration +#define ITEM_STAT_ITEMPPREGENPPT 653 //In-Combat Power Regeneration +#define ITEM_STAT_MELEECRITCHANCE 654 //% Crit Chance +#define ITEM_STAT_CRITAVOIDANCE 655 //% Crit Avoidance +#define ITEM_STAT_BENEFICIALCRITCHANCE 656 //% Beneficial Crit Chance +#define ITEM_STAT_CRITBONUS 657 //% Crit Bonus +#define ITEM_STAT_PVPCRITBONUS 658 +#define ITEM_STAT_POTENCY 659 //% Potency +#define ITEM_STAT_PVPPOTENCY 660 +#define ITEM_STAT_UNCONSCIOUSHPMOD 661 //Unconcious Health +#define ITEM_STAT_ABILITYREUSESPEED 662 //% Ability Reuse Speed +#define ITEM_STAT_ABILITYRECOVERYSPEED 663 //% Ability Recovery Speed +#define ITEM_STAT_ABILITYCASTINGSPEED 664 //% Ability Casting Speed +#define ITEM_STAT_SPELLREUSESPEED 665 //% Spell Reuse Speed +#define ITEM_STAT_MELEEWEAPONRANGE 666 //% Melee Weapon Range Increase +#define ITEM_STAT_RANGEDWEAPONRANGE 667 //% Ranged Weapon Range Increase +#define ITEM_STAT_FALLINGDAMAGEREDUCTION 668 //Fallling Damage Reduction +#define ITEM_STAT_RIPOSTEDAMAGE 669 //% Riposte Damage +#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 670 //% Minimum Block Chance +#define ITEM_STAT_MOVEMENTWEAVE 671 //Movement Weave +#define ITEM_STAT_COMBATHPREGEN 672 //Combat HP Regen +#define ITEM_STAT_COMBATMANAREGEN 673 //Combat Mana Regen +#define ITEM_STAT_CONTESTSPEEDBOOST 674 //Contest Only Speed +#define ITEM_STAT_TRACKINGAVOIDANCE 675 //Tracking avoidance +#define ITEM_STAT_STEALTHINVISSPEEDMOD 676 //Movement Bonus whie Stealthed or Invisible +#define ITEM_STAT_LOOT_COIN 677 //Loot Coin +#define ITEM_STAT_ARMORMITIGATIONINCREASE 678 //% Mitigation Increase +#define ITEM_STAT_AMMOCONSERVATION 679 // Ammo Conservation +#define ITEM_STAT_STRIKETHROUGH 680 //Strikethrough +#define ITEM_STAT_STATUSBONUS 681 //Status Bonus +#define ITEM_STAT_ACCURACY 682 //% Accuracy +#define ITEM_STAT_COUNTERSTRIKE 683 //CounterStrike +#define ITEM_STAT_SHIELDBASH 684 //Shield Bash +#define ITEM_STAT_WEAPONDAMAGEBONUS 685 //Weapon Damage Bonus +#define ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY 686 //additional chance to Riposte +#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 687 //additional chance to Riposte +#define ITEM_STAT_CRITICALMITIGATION 688 //Critical Mitigation +#define ITEM_STAT_PVPTOUGHNESS 689 //Toughness +#define ITEM_STAT_PVPLETHALITY 690 // +#define ITEM_STAT_STAMINABONUS 691 //Stamina Bonus +#define ITEM_STAT_WISDOMMITBONUS 692 //Wisdom Mitigation Bonus +#define ITEM_STAT_HEALRECEIVE 693 //Applied Heals +#define ITEM_STAT_HEALRECEIVEPERC 694 //% Applied Heals +#define ITEM_STAT_PVPCRITICALMITIGATION 695 //PvP Critical Mitigation +#define ITEM_STAT_BASEAVOIDANCEBONUS 696 +#define ITEM_STAT_INCOMBATSAVAGERYREGEN 697 +#define ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN 698 +#define ITEM_STAT_SAVAGERYREGEN 699 +#define ITEM_STAT_SAVAGERYGAINMOD 6100 +#define ITEM_STAT_MAXSAVAGERYLEVEL 6101 +#define ITEM_STAT_SPELLWEAPONDAMAGEBONUS 6102 +#define ITEM_STAT_INCOMBATDISSONANCEREGEN 6103 +#define ITEM_STAT_OUTOFCOMBATDISSONANCEREGEN 6104 +#define ITEM_STAT_DISSONANCEREGEN 6105 +#define ITEM_STAT_DISSONANCEGAINMOD 6106 +#define ITEM_STAT_AEAUTOATTACKAVOID 6107 +#define ITEM_STAT_AGNOSTICDAMAGEBONUS 6108 +#define ITEM_STAT_AGNOSTICHEALBONUS 6109 +#define ITEM_STAT_TITHEGAIN 6110 +#define ITEM_STAT_FERVER 6111 +#define ITEM_STAT_RESOLVE 6112 +#define ITEM_STAT_COMBATMITIGATION 6113 +#define ITEM_STAT_ABILITYMITIGATION 6114 +#define ITEM_STAT_MULTIATTACKAVOIDANCE 6115 +#define ITEM_STAT_DOUBLECASTAVOIDANCE 6116 +#define ITEM_STAT_ABILITYDOUBLECASTAVOIDANCE 6117 +#define ITEM_STAT_DAMAGEPERSECONDMITIGATION 6118 +#define ITEM_STAT_FERVERMITIGATION 6119 +#define ITEM_STAT_FLURRYAVOIDANCE 6120 +#define ITEM_STAT_WEAPONDAMAGEBONUSMITIGATION 6121 +#define ITEM_STAT_ABILITYDOUBLECASTCHANCE 6122 +#define ITEM_STAT_ABILITYMODIFIERMITIGATATION 6123 +#define ITEM_STAT_STATUSEARNED 6124 + + + + +#define ITEM_STAT_SPELL_DAMAGE 700 +#define ITEM_STAT_HEAL_AMOUNT 701 +#define ITEM_STAT_SPELL_AND_HEAL 702 +#define ITEM_STAT_COMBAT_ART_DAMAGE 703 +#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE 704 +#define ITEM_STAT_TAUNT_AMOUNT 705 +#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE 706 +#define ITEM_STAT_ABILITY_MODIFIER 707 + +// Other stats not listed above (not sent from the server), never send these to the client +// using type 8 as it is not used by the client as far as we know +#define ITEM_STAT_DURABILITY_MOD 800 +#define ITEM_STAT_DURABILITY_ADD 801 +#define ITEM_STAT_PROGRESS_ADD 802 +#define ITEM_STAT_PROGRESS_MOD 803 +#define ITEM_STAT_SUCCESS_MOD 804 +#define ITEM_STAT_CRIT_SUCCESS_MOD 805 +#define ITEM_STAT_EX_DURABILITY_MOD 806 +#define ITEM_STAT_EX_DURABILITY_ADD 807 +#define ITEM_STAT_EX_PROGRESS_MOD 808 +#define ITEM_STAT_EX_PROGRESS_ADD 809 +#define ITEM_STAT_EX_SUCCESS_MOD 810 +#define ITEM_STAT_EX_CRIT_SUCCESS_MOD 811 +#define ITEM_STAT_EX_CRIT_FAILURE_MOD 812 +#define ITEM_STAT_RARE_HARVEST_CHANCE 813 +#define ITEM_STAT_MAX_CRAFTING 814 +#define ITEM_STAT_COMPONENT_REFUND 815 +#define ITEM_STAT_BOUNTIFUL_HARVEST 816 + +#define ITEM_STAT_UNCONTESTED_PARRY 850 +#define ITEM_STAT_UNCONTESTED_BLOCK 851 +#define ITEM_STAT_UNCONTESTED_DODGE 852 +#define ITEM_STAT_UNCONTESTED_RIPOSTE 853 + +#define DISPLAY_FLAG_RED_TEXT 1 // old clients +#define DISPLAY_FLAG_NO_GUILD_STATUS 8 +#define DISPLAY_FLAG_NO_BUYBACK 16 +#define DISPLAY_FLAG_NOT_FOR_SALE 64 +#define DISPLAY_FLAG_NO_BUY 128 // disables buying on merchant 'buy' list + +enum ItemEffectType { + NO_EFFECT_TYPE=0, + EFFECT_CURE_TYPE_TRAUMA=1, + EFFECT_CURE_TYPE_ARCANE=2, + EFFECT_CURE_TYPE_NOXIOUS=3, + EFFECT_CURE_TYPE_ELEMENTAL=4, + EFFECT_CURE_TYPE_CURSE=5, + EFFECT_CURE_TYPE_MAGIC=6, + EFFECT_CURE_TYPE_ALL=7 +}; +#pragma pack(1) +struct ItemStatsValues{ + sint16 str; + sint16 sta; + sint16 agi; + sint16 wis; + sint16 int_; + sint16 vs_slash; + sint16 vs_crush; + sint16 vs_pierce; + sint16 vs_heat; + sint16 vs_cold; + sint16 vs_magic; + sint16 vs_mental; + sint16 vs_divine; + sint16 vs_disease; + sint16 vs_poison; + sint16 health; + sint16 power; + sint8 concentration; + sint16 ability_modifier; + sint16 criticalmitigation; + sint16 extrashieldblockchance; + sint16 beneficialcritchance; + sint16 critbonus; + sint16 potency; + sint16 hategainmod; + sint16 abilityreusespeed; + sint16 abilitycastingspeed; + sint16 abilityrecoveryspeed; + sint16 spellreusespeed; + sint16 spellmultiattackchance; + sint16 dps; + sint16 attackspeed; + sint16 multiattackchance; + sint16 flurry; + sint16 aeautoattackchance; + sint16 strikethrough; + sint16 accuracy; + sint16 offensivespeed; + float uncontested_parry; + float uncontested_block; + float uncontested_dodge; + float uncontested_riposte; + + +}; +struct ItemCore{ + int32 item_id; + sint32 soe_id; + int32 bag_id; + sint32 inv_slot_id; + sint16 slot_id; + sint16 equip_slot_id; // used for when a bag is equipped + sint16 appearance_type; // 0 for combat armor, 1 for appearance armor + int8 index; + int16 icon; + int16 classic_icon; + int16 count; + int8 tier; + int8 num_slots; + int32 unique_id; + int8 num_free_slots; + int16 recommended_level; + bool item_locked; + bool new_item; + int16 new_index; +}; +#pragma pack() +struct ItemStat{ + string stat_name; + int8 stat_type; + sint16 stat_subtype; + int16 stat_type_combined; + float value; + int8 level; +}; +struct ItemSet{ + int32 item_id; + int32 item_crc; + int16 item_icon; + int16 item_stack_size; + int32 item_list_color; + std::string name; + int8 language; +}; +struct Classifications{ + int32 classification_id; //classifications MJ + string classification_name; +}; +struct ItemLevelOverride{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemClass{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemAppearance{ + int16 type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; +}; + +enum AddItemType { + NOT_SET = 0, + BUY_FROM_BROKER = 1, + GM_COMMAND = 2 +}; + +struct QuestRewardData { + int32 quest_id; + bool is_temporary; + std::string description; + bool is_collection; + bool has_displayed; + int64 tmp_coin; + int32 tmp_status; + bool db_saved; + int32 db_index; +}; + +class PlayerItemList; +class Item{ +public: + #pragma pack(1) + struct ItemStatString{ + EQ2_8BitString stat_string; + }; + struct Generic_Info{ + int8 show_name; + int8 creator_flag; + int16 item_flags; + int16 item_flags2; + int8 condition; + int32 weight; // num/10 + int32 skill_req1; + int32 skill_req2; + int16 skill_min; + int8 item_type; //0=normal, 1=weapon, 2=range, 3=armor, 4=shield, 5=bag, 6=scroll, 7=recipe, 8=food, 9=bauble, 10=house item, 11=thrown, 12=house container, 13=adormnet, 14=??, 16=profile, 17=patter set, 18=item set, 19=book, 20=decoration, 21=dungeon maker, 22=marketplace + int16 appearance_id; + int8 appearance_red; + int8 appearance_green; + int8 appearance_blue; + int8 appearance_highlight_red; + int8 appearance_highlight_green; + int8 appearance_highlight_blue; + int8 collectable; + int32 offers_quest_id; + int32 part_of_quest_id; + int16 max_charges; + int8 display_charges; + int64 adventure_classes; + int64 tradeskill_classes; + int16 adventure_default_level; + int16 tradeskill_default_level; + int8 usable; + int8 harvest; + int8 body_drop; + int8 pvp_description; + int8 merc_only; + int8 mount_only; + int32 set_id; + int8 collectable_unk; + char offers_quest_name[255]; + char required_by_quest_name[255]; + int8 transmuted_material; + }; + struct Armor_Info { + int16 mitigation_low; + int16 mitigation_high; + }; + struct Adornment_Info { + float duration; + int16 item_types; + int16 slot_type; + }; + struct Weapon_Info { + int16 wield_type; + int16 damage_low1; + int16 damage_high1; + int16 damage_low2; + int16 damage_high2; + int16 damage_low3; + int16 damage_high3; + int16 delay; + float rating; + }; + struct Shield_Info { + Armor_Info armor_info; + }; + struct Ranged_Info { + Weapon_Info weapon_info; + int16 range_low; + int16 range_high; + }; + struct Bag_Info { + int8 num_slots; + int16 weight_reduction; + }; + struct Food_Info{ + int8 type; //0=water, 1=food + int8 level; + float duration; + int8 satiation; + }; + struct Bauble_Info{ + int16 cast; + int16 recovery; + int32 duration; + float recast; + int8 display_slot_optional; + int8 display_cast_time; + int8 display_bauble_type; + float effect_radius; + int32 max_aoe_targets; + int8 display_until_cancelled; + }; + struct Book_Info{ + int8 language; + EQ2_16BitString author; + EQ2_16BitString title; + }; + struct Book_Info_Pages { + int8 page; + EQ2_16BitString page_text; + int8 page_text_valign; + int8 page_text_halign; + }; + struct Skill_Info{ + int32 spell_id; + int32 spell_tier; + }; + struct HouseItem_Info{ + int32 status_rent_reduction; + float coin_rent_reduction; + int8 house_only; + }; + struct HouseContainer_Info{ + int64 allowed_types; + int8 num_slots; + int8 broker_commission; + int8 fence_commission; + }; + struct RecipeBook_Info{ + vector recipes; + int32 recipe_id; + int8 uses; + }; + struct ItemSet_Info{ + int32 item_id; + int32 item_crc; + int16 item_icon; + int32 item_stack_size; + int32 item_list_color; + int32 soe_item_id_unsigned; + int32 soe_item_crc_unsigned; + }; + struct Thrown_Info{ + sint32 range; + sint32 damage_modifier; + float hit_bonus; + int32 damage_type; + }; + struct ItemEffect{ + EQ2_16BitString effect; + int8 percentage; + int8 subbulletflag; + }; + struct BookPage { + int8 page; + EQ2_16BitString page_text; + int8 valign; + int8 halign; + }; + #pragma pack() + Item(); + Item(Item* in_item); + ~Item(); + string lowername; + string name; + string description; + int16 stack_count; + int32 sell_price; + int32 sell_status; + int32 max_sell_value; + bool save_needed; + int8 weapon_type; + string adornment; + string creator; + int32 adorn0; + int32 adorn1; + int32 adorn2; + vectorclassifications; //classifications MJ + vector item_stats; + vector item_sets; + vector item_string_stats; + vector item_level_overrides; + vector item_effects; + vector book_pages; + Generic_Info generic_info; + Weapon_Info* weapon_info; + Ranged_Info* ranged_info; + Armor_Info* armor_info; + Adornment_Info* adornment_info; + Bag_Info* bag_info; + Food_Info* food_info; + Bauble_Info* bauble_info; + Book_Info* book_info; + Book_Info_Pages* book_info_pages; + HouseItem_Info* houseitem_info; + HouseContainer_Info* housecontainer_info; + Skill_Info* skill_info; + RecipeBook_Info* recipebook_info; + ItemSet_Info* itemset_info; + Thrown_Info* thrown_info; + vector slot_data; + ItemCore details; + int32 spell_id; + int8 spell_tier; + string item_script; + bool no_buy_back; + bool no_sale; + bool needs_deletion; + std::time_t created; + std::map grouped_char_ids; + ItemEffectType effect_type; + bool crafted; + bool tinkered; + int8 book_language; + + void AddEffect(string effect, int8 percentage, int8 subbulletflag); + void AddBookPage(int8 page, string page_text,int8 valign, int8 halign); + int32 GetMaxSellValue(); + void SetMaxSellValue(int32 val); + void SetItem(Item* old_item); + int16 GetOverrideLevel(int8 adventure_class, int8 tradeskill_class); + void AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level); + void AddLevelOverride(ItemLevelOverride* class_); + bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + bool CheckClass(int8 adventure_class, int8 tradeskill_class); + bool CheckArchetypeAdvClass(int8 adventure_class, map* adv_class_levels = 0); + bool CheckArchetypeAdvSubclass(int8 adventure_class, map* adv_class_levels = 0); + bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue); + void SetAppearance(ItemAppearance* appearance); + void AddStat(ItemStat* in_stat); + bool HasStat(uint32 statID, std::string statName = std::string("")); + void DeleteItemSets(); + void AddSet(ItemSet* in_set); + void AddStatString(ItemStatString* in_stat); + void AddStat(int8 type, int16 subtype, float value, int8 level, char* name = 0); + void AddSet(int32 item_id, int32 item_crc, int16 item_icon, int32 item_stack_size, int32 item_list_color, std::string name, int8 language); + void SetWeaponType(int8 type); + int8 GetWeaponType(); + bool HasSlot(int8 slot, int8 slot2 = 255); + bool HasAdorn0(); + bool HasAdorn1(); + bool HasAdorn2(); + bool IsNormal(); + bool IsWeapon(); + bool IsArmor(); + bool IsDualWieldAble(Client* client, Item* item, int8 slot = -1); + bool IsRanged(); + bool IsBag(); + bool IsFood(); + bool IsBauble(); + bool IsSkill(); + bool IsHouseItem(); + bool IsHouseContainer(); + bool IsShield(); + bool IsAdornment(); + bool IsAmmo(); + bool IsBook(); + bool IsChainArmor(); + bool IsClothArmor(); + bool IsCollectable(); + bool IsCloak(); + bool IsCrushWeapon(); + bool IsFoodFood(); + bool IsFoodDrink(); + bool IsJewelry(); + bool IsLeatherArmor(); + bool IsMisc(); + bool IsPierceWeapon(); + bool IsPlateArmor(); + bool IsPoison(); + bool IsPotion(); + bool IsRecipeBook(); + bool IsSalesDisplay(); + bool IsSlashWeapon(); + bool IsSpellScroll(); + bool IsTinkered(); + bool IsTradeskill(); + bool IsThrown(); + bool IsHarvest(); + bool IsBodyDrop(); + void SetItemScript(string name); + const char* GetItemScript(); + int32 CalculateRepairCost(); + string CreateItemLink(int16 client_Version, bool bUseUniqueID=false); + + void SetItemType(int8 in_type); + void serialize(PacketStruct* packet, bool show_name = false, Player* player = 0, int16 packet_type = 0, int8 subtype = 0, bool loot_item = false, bool inspect = false); + EQ2Packet* serialize(int16 version, bool show_name = false, Player* player = 0, bool include_twice = true, int16 packet_type = 0, int8 subtype = 0, bool merchant_item = false, bool loot_item = false, bool inspect = false); + PacketStruct* PrepareItem(int16 version, bool merchant_item = false, bool loot_item = false, bool inspection = false); + bool CheckFlag(int32 flag); + bool CheckFlag2(int32 flag); + void AddSlot(int8 slot_id); + void SetSlots(int32 slots); + int16 GetIcon(int16 version); +}; +class MasterItemList{ +public: + MasterItemList(); + ~MasterItemList(); + map items; + + Item* GetItem(int32 id); + Item* GetItemByName(const char *name); + Item* GetAllItemsByClassification(const char* name); + ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0); + ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0); + vector* GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass); + vector* GetItems(map criteria, Client* client_to_map); + void AddItem(Item* item); + bool IsBag(int32 item_id); + void RemoveAll(); + static int32 NextUniqueID(); + static void ResetUniqueID(int32 new_id); + static int32 next_unique_id; + int32 GetItemStatIDByName(std::string name); + std::string GetItemStatNameByID(int32 id); + void AddMappedItemStat(int32 id, std::string lower_case_name); + + + void AddBrokerItemMapRange(int32 min_version, int32 max_version, int64 client_bitmask, int64 server_bitmask); + map>::iterator FindBrokerItemMapVersionRange(int32 min_version, int32 max_version); + map>::iterator FindBrokerItemMapByVersion(int32 version); + + map mappedItemStatsStrings; + map mappedItemStatTypeIDs; + std::map> broker_item_map; +}; +class PlayerItemList { +public: + PlayerItemList(); + ~PlayerItemList(); +// int16 number; + int32 max_saved_index; + map indexed_items; + map> > items; +// map< int8, Item* > inv_items; +// map< int8, Item* > bank_items; + int32 SetMaxItemIndex(); + bool SharedBankAddAllowed(Item* item); + vector* GetItemsFromBagID(sint32 bag_id); + vector* GetItemsInBag(Item* bag); + Item* GetBag(int8 inventory_slot, bool lock = true); + bool HasItem(int32 id, bool include_bank = false); + Item* GetItemFromIndex(int32 index); + void MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old); // erase old was true + bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges); + void EraseItem(Item* item); + Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true); + Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); + sint32 GetAllStackCountItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); + bool AssignItemToFreeSlot(Item* item); + int16 GetNumberOfFreeSlots(); + int16 GetNumberOfItems(); + int32 GetWeight(); + bool HasFreeSlot(); + bool HasFreeBagSlot(); + void DestroyItem(int16 index); + Item* CanStack(Item* item, bool include_bank = false); + vector GetAllItemsFromID(int32 item, bool include_bank = false, bool lock = false); + void RemoveItem(Item* item, bool delete_item = false); + bool AddItem(Item* item); + + Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0); + + EQ2Packet* serialize(Player* player, int16 version); + uchar* xor_packet; + uchar* orig_packet; + map* GetAllItems(); + bool HasFreeBankSlot(); + int8 FindFreeBankSlot(); + + ///Get the first free slot and store them in the provided variables + ///Will contain the bag id of the first free spot + ///Will contain the slot id of the first free slot + ///True if a free slot was found + bool GetFirstFreeSlot(sint32* bag_id, sint16* slot); + + /// Get the first free slot in the bank and store it in the provided variables + /// Will contain the bag id of the first free bank slot + /// Will contain the slot id of the first free bank slot + /// True if a free bank slot was found + bool GetFirstFreeBankSlot(sint32* bag_id, sint16* slot); + + /// + Item* GetBankBag(int8 inventory_slot, bool lock = true); + + /// + bool AddOverflowItem(Item* item); + + Item* GetOverflowItem(); + + void RemoveOverflowItem(Item* item); + + vector* GetOverflowItemList(); + + void ResetPackets(); + + int32 CheckSlotConflict(Item* tmp, bool check_lore_only = false, bool lock_mutex = true, int16* lore_stack_count = 0); + + int32 GetItemCountInBag(Item* bag); + + int16 GetFirstNewItem(); + int16 GetNewItemByIndex(int16 in_index); + + Mutex MPlayerItems; +private: + void AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow = false, int16 new_index = 0); + void Stack(Item* orig_item, Item* item); + int16 packet_count; + vector overflowItems; +}; + +class EquipmentItemList{ +public: + EquipmentItemList(); + EquipmentItemList(const EquipmentItemList& list); + ~EquipmentItemList(); + Item* items[NUM_SLOTS]; + Mutex MEquipmentItems; + + vector* GetAllEquippedItems(); + + void ResetPackets(); + + bool HasItem(int32 id); + int8 GetNumberOfItems(); + int32 GetWeight(); + Item* GetItemFromUniqueID(int32 item_id); + Item* GetItemFromItemID(int32 item_id); + void SetItem(int8 slot_id, Item* item, bool locked = false); + void RemoveItem(int8 slot, bool delete_item = false); + Item* GetItem(int8 slot_id); + bool AddItem(int8 slot, Item* item); + bool CheckEquipSlot(Item* tmp, int8 slot); + bool CanItemBeEquippedInSlot(Item* tmp, int8 slot); + int8 GetFreeSlot(Item* tmp, int8 slot_id = 255, int16 version = 0); + int32 CheckSlotConflict(Item* tmp, bool check_lore_only = false, int16* lore_stack_count = 0); + + int8 GetSlotByItem(Item* item); + ItemStatsValues* CalculateEquipmentBonuses(Entity* entity = 0); + EQ2Packet* serialize(int16 version, Player* player); + void SendEquippedItems(Player* player); + uchar* xor_packet; + uchar* orig_packet; + + void SetAppearanceType(int8 type) { AppearanceType = type; } + int8 GetAppearanceType() { return AppearanceType; } +private: + int8 AppearanceType; // 0 for normal equip, 1 for appearance +}; + +#endif + diff --git a/source/WorldServer/Items/ItemsDB.cpp b/source/WorldServer/Items/ItemsDB.cpp new file mode 100644 index 0000000..62888b8 --- /dev/null +++ b/source/WorldServer/Items/ItemsDB.cpp @@ -0,0 +1,1419 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Items.h" +#include "../World.h" +#include "../Rules/Rules.h" +#include "../LuaInterface.h" + +extern World world; +extern RuleManager rule_manager; +extern LuaInterface* lua_interface; + +// handle new database class til all functions are converted +void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item) +{ +// this is too much on top of already having the top level load item debug msg +// LogWrite(ITEM__DEBUG, 5, "Items", "\tSetting details for item ID: %i", result->GetInt32Str("id")); + + item->details.item_id = result->GetInt32Str("id"); + int8 size = strlen(result->GetStringStr("name")); + + if(size > 63) + size = 63; + + item->name = string(result->GetStringStr("name")); + item->lowername = ToLower(item->name); + item->details.icon = result->GetInt16Str("icon"); + item->details.classic_icon = result->GetInt16Str("classic_icon"); + item->details.count = result->GetInt16Str("count"); + item->details.tier = result->GetInt8Str("tier"); + item->generic_info.weight = result->GetInt32Str("weight"); + + if( strlen(result->GetStringStr("description")) > 0 ) + item->description = string(result->GetStringStr("description")); + + item->generic_info.show_name = result->GetInt8Str("show_name"); + + if( result->GetInt8Str("attuneable") == 1 ) + item->generic_info.item_flags += ATTUNEABLE; + + if( result->GetInt8Str("artifact") == 1 ) + item->generic_info.item_flags += ARTIFACT; + + if( result->GetInt8Str("lore") == 1 ) + item->generic_info.item_flags += LORE; + + if( result->GetInt8Str("temporary") == 1 ) + item->generic_info.item_flags += TEMPORARY; + + if( result->GetInt8Str("notrade") == 1 ) + item->generic_info.item_flags += NO_TRADE; + + if( result->GetInt8Str("novalue") == 1 ) + item->generic_info.item_flags += NO_VALUE; + + if( result->GetInt8Str("nozone") == 1 ) + item->generic_info.item_flags += NO_ZONE; + + if( result->GetInt8Str("nodestroy") == 1 ) + item->generic_info.item_flags += NO_DESTROY; + + if( result->GetInt8Str("crafted") == 1 ) + item->generic_info.item_flags += CRAFTED; + + if( result->GetInt8Str("good_only") == 1 ) + item->generic_info.item_flags += GOOD_ONLY; + + if( result->GetInt8Str("evil_only") == 1 ) + item->generic_info.item_flags += EVIL_ONLY; + + if( result->GetInt8Str("stacklore") == 1 ) + item->generic_info.item_flags += STACK_LORE; + + // add more Flags/Flags2 here + + if (result->GetInt8Str("lore_equip") == 1) + item->generic_info.item_flags += LORE_EQUIP; + + if (result->GetInt8Str("no_transmute") == 1) + item->generic_info.item_flags += NO_TRANSMUTE; + + if (result->GetInt8Str("CURSED_flags_32768") == 1) + item->generic_info.item_flags += CURSED; + + if (result->GetInt8Str("ornate") == 1) + item->generic_info.item_flags2 += ORNATE; + + if (result->GetInt8Str("heirloom") == 1) + item->generic_info.item_flags2 += HEIRLOOM; + + if (result->GetInt8Str("appearance_only") == 1) + item->generic_info.item_flags2 += APPEARANCE_ONLY; + + if (result->GetInt8Str("unlocked") == 1) + item->generic_info.item_flags2 += UNLOCKED; + + if (result->GetInt8Str("reforged") == 1) + item->generic_info.item_flags2 += REFORGED; + + if (result->GetInt8Str("norepair") == 1) + item->generic_info.item_flags2 += NO_REPAIR; + + if (result->GetInt8Str("etheral") == 1) + item->generic_info.item_flags2 += ETHERAL; + + if (result->GetInt8Str("refined") == 1) + item->generic_info.item_flags2 += REFINED; + + if (result->GetInt8Str("no_salvage") == 1) + item->generic_info.item_flags2 += NO_SALVAGE; + + if (result->GetInt8Str("indestructable") == 1) + item->generic_info.item_flags2 += INDESTRUCTABLE; + + if (result->GetInt8Str("no_experiment") == 1) + item->generic_info.item_flags2 += NO_EXPERIMENT; + + if (result->GetInt8Str("house_lore") == 1) + item->generic_info.item_flags2 += HOUSE_LORE; + + if (result->GetInt8Str("building_block") == 1) + item->generic_info.item_flags2 += BUILDING_BLOCK; + + if (result->GetInt8Str("free_reforge") == 1) + item->generic_info.item_flags2 += FREE_REFORGE; + + + if( result->GetInt32Str("skill_id_req") == 0 ) + item->generic_info.skill_req1 = 0xFFFFFFFF; + else + item->generic_info.skill_req1 = result->GetInt32Str("skill_id_req"); + + if( result->GetInt32Str("skill_id_req2") == 0 ) + item->generic_info.skill_req2 = 0xFFFFFFFF; + else + item->generic_info.skill_req2 = result->GetInt32Str("skill_id_req2"); + + item->generic_info.skill_min = result->GetInt16Str("skill_min"); + + if( result->GetInt32Str("slots") > 0) + item->SetSlots(result->GetInt32Str("slots")); + + item->sell_price = result->GetInt32Str("sell_price"); + item->sell_status = result->GetInt32Str("sell_status_amount"); + item->stack_count = result->GetInt16Str("stack_count"); + item->generic_info.collectable = result->GetInt8Str("collectable"); + item->generic_info.offers_quest_id = result->GetInt32Str("offers_quest_id"); + item->generic_info.part_of_quest_id = result->GetInt32Str("part_of_quest_id"); + item->details.recommended_level = result->GetInt16Str("recommended_level"); + item->details.item_locked = false; + item->generic_info.adventure_default_level = result->GetInt16Str("adventure_default_level"); + item->generic_info.max_charges = result->GetInt16Str("max_charges"); + item->generic_info.display_charges = result->GetInt8Str("display_charges"); + item->generic_info.tradeskill_default_level = result->GetInt16Str("tradeskill_default_level"); + + item->generic_info.adventure_classes = result->GetInt64Str("adventure_classes"); + item->generic_info.tradeskill_classes = result->GetInt64Str("tradeskill_classes"); + + if( !result->IsNullStr("lua_script") && strlen(result->GetStringStr("lua_script")) > 0 ) + { + item->SetItemScript(string(result->GetStringStr("lua_script"))); + LogWrite(ITEM__DEBUG, 5, "LUA", "--Loading LUA Item Script: '%s'", item->item_script.c_str()); + } + + item->effect_type = (ItemEffectType)result->GetInt32Str("effect_type"); + + if(item->generic_info.max_charges > 0) + item->details.count = item->generic_info.max_charges; + + if(item->details.count == 0) + item->details.count = 1; + + item->generic_info.usable = result->GetInt8Str("usable"); + item->details.soe_id = result->GetSInt32Str("soe_item_id"); + + item->generic_info.harvest = result->GetInt8Str("harvest"); + item->generic_info.body_drop = result->GetInt8Str("body_drop"); + + item->no_buy_back = (result->GetInt8Str("no_buy_back") == 1); + + item->generic_info.pvp_description = result->GetInt8Str("bPvpDesc"); + + item->generic_info.merc_only = (result->GetInt8Str("merc_only") == 1); + item->generic_info.mount_only = (result->GetInt8Str("mount_only") == 1); + + item->generic_info.set_id = result->GetInt32Str("set_id"); + + item->generic_info.collectable_unk = result->GetInt8Str("collectable_unk"); + + const char* offerQuestName = result->GetFieldValueStr("offers_quest_name"); + if(offerQuestName) + strncpy(item->generic_info.offers_quest_name,offerQuestName,255); + else + strcpy(item->generic_info.offers_quest_name,""); + + const char* requiredQuestName = result->GetFieldValueStr("required_by_quest_name"); + if(requiredQuestName) + strncpy(item->generic_info.required_by_quest_name,requiredQuestName,255); + else + strcpy(item->generic_info.required_by_quest_name,""); + + item->generic_info.transmuted_material = result->GetInt8Str("transmuted_material"); + + item->crafted = result->GetInt8Str("crafted"); + item->tinkered = result->GetInt8Str("tinkered"); + item->book_language = result->GetInt8Str("book_language"); + +} + +int32 WorldDatabase::LoadSkillItems(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, spell_id, spell_tier FROM item_details_skill%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = atoul(row[0]); + Item* item = master_item_list.GetItem(id); + + if(!row[1] || !row[2]) + continue; + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tLoading Skill for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, spell: %i, tier: %i", ITEM_TYPE_SKILL, atoi(row[1]), atoi(row[2])); + item->SetItemType(ITEM_TYPE_SKILL); + item->skill_info->spell_id = atoul(row[1]); + item->skill_info->spell_tier = atoi(row[2]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_skill`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadShields(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, mitigation_low, mitigation_high FROM item_details_shield%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Shield for item_id: %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, mit_low: %i, mit_high: %i", ITEM_TYPE_SHIELD, atoi(row[1]), atoi(row[2])); + item->SetItemType(ITEM_TYPE_SHIELD); + item->armor_info->mitigation_low = atoi(row[1]); + item->armor_info->mitigation_high = atoi(row[2]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_shield`, ID: %i", id); + } + } + return total; +} +int32 WorldDatabase::LoadAdornments(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, duration, item_types,slot_type FROM item_details_adornments%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if (result) + { + while (result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if (item) + { + //LogWrite(ITEM__DEBUG, 0, "Items", "\tItem Adornment for item_id: %u", id); + //LogWrite(ITEM__DEBUG, 0, "Items", "\ttype: %i, Duration: %i, item_types_: %i, slot_type: %i", ITEM_TYPE_ADORNMENT, atoi(row[1]), atoi(row[2]), atoi(row[3])); + item->SetItemType(ITEM_TYPE_ADORNMENT); + item->adornment_info->duration = atof(row[1]); + item->adornment_info->item_types = atoi(row[2]); + item->adornment_info->slot_type = atoi(row[3]); + //LogWrite(ITEM__DEBUG, 0, "Items", "\ttype: %i, Duration: %i, item_types_: %i, slot_type: %i",item->generic_info.item_type, item->adornment_info->duration, item->adornment_info->item_types, item->adornment_info->slot_type); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_shield`, ID: %i", id); + } + } + return total; +} +int32 WorldDatabase::LoadClassifications() +{ + int32 total = 0; + int32 id = 0; + return total; +} + +int32 WorldDatabase::LoadBaubles(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, cast, recovery, duration, recast, display_slot_optional, display_cast_time, display_bauble_type, effect_radius, max_aoe_targets, display_until_cancelled FROM item_details_bauble%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Bauble for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i", ITEM_TYPE_BAUBLE, atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atof(row[7]), atoi(row[8]), atoi(row[9]), atoi(row[10])); + item->SetItemType(ITEM_TYPE_BAUBLE); + item->bauble_info->cast = atoi(row[1]); + item->bauble_info->recovery = atoi(row[2]); + item->bauble_info->duration = atoi(row[3]); + item->bauble_info->recast = atoi(row[4]); + item->bauble_info->display_slot_optional = atoi(row[5]); + item->bauble_info->display_cast_time = atoi(row[6]); + item->bauble_info->display_bauble_type = atoi(row[7]); + item->bauble_info->effect_radius = atof(row[8]); + item->bauble_info->max_aoe_targets = atoi(row[9]); + item->bauble_info->display_until_cancelled = atoi(row[10]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_bauble`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadBooks(int32 item_id) +{ + DatabaseResult result; + int32 total = 0; + int32 id = 0; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + if( database_new.Select(&result, "SELECT item_id, language, author, title FROM item_details_book%s", (item_id == 0) ? "" : select_query_addition.c_str()) ) + { + while( result.Next() ) + { + id = result.GetInt32Str("item_id"); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Book for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %s, %s", + ITEM_TYPE_BOOK, + result.GetInt8Str("language"), + result.GetStringStr("author"), + result.GetStringStr("title")); + + item->SetItemType(ITEM_TYPE_BOOK); + item->book_info->language = result.GetInt8Str("language"); + item->book_info->author.data = result.GetStringStr("author"); + item->book_info->author.size = item->book_info->author.data.length(); + item->book_info->title.data = result.GetStringStr("title"); + item->book_info->title.size = item->book_info->title.data.length(); + + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_book`, ID: %i", id); + } + } + + return total; +} +int32 WorldDatabase::LoadItemsets(int32 item_id) +{ + DatabaseResult result; + int32 total = 0; + int32 id = 0; + + std::string select_query_addition = std::string(" and crate.item_id = ") + std::to_string(item_id); + //if (database_new.Select(&result, "SELECT id, itemset_item_id, item_id, item_icon,item_stack_size,item_list_color,language_type FROM item_details_itemset")) + if (database_new.Select(&result, "select crate.item_id, crateitem.reward_item_id, crateitem.icon, crateitem.stack_size, crateitem.name_color, crateitem.name, crateitem.language_type from item_details_reward_crate crate, item_details_reward_crate_item crateitem where crateitem.crate_item_id = crate.item_id%s", (item_id == 0) ? "" : select_query_addition.c_str())) + { + while (result.Next()) + { + id = result.GetInt32(0); + Item* item = master_item_list.GetItem(id); + + if (item) + { + item->SetItemType(ITEM_TYPE_ITEMCRATE); + //int32 item_id = result.GetInt32Str("item_id"); + const char* setName = result.GetString(5); + item->AddSet(result.GetInt32(1),0, result.GetInt16(2), result.GetInt16(3), result.GetInt32(4), setName ? string(setName) : string(""), result.GetInt8(6)); + + + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Item Set Crate Items", "Error loading `item_details_Items`, ID: %i", id); + } + } + + return total; +} +int32 WorldDatabase::LoadHouseItem(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, rent_reduction, status_rent_reduction, coin_rent_reduction, house_only FROM item_details_house%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem HouseItem for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %u, %.2f, %u", ITEM_TYPE_HOUSE, atoul(row[1]), atoi(row[2]), atof(row[3]), atoul(row[4])); + item->SetItemType(ITEM_TYPE_HOUSE); + item->houseitem_info->status_rent_reduction = atoi(row[2]); + item->houseitem_info->coin_rent_reduction = atof(row[3]); + item->houseitem_info->house_only = atoi(row[4]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_house`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadRecipeBookItems(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + //std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + //MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, name FROM item_details_recipe_items%s", (item_id == 0) ? "" : select_query_addition.c_str()); + std::string select_query_addition = std::string(" and r.item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT r.item_id, ri.recipe_id ,ri.`name`,ri.soe_recipe_crc FROM item_details_recipe r LEFT JOIN item_details_recipe_items ri ON ri.recipe_id = r.recipe_id where ri.recipe_id is not null%s", (item_id == 0) ? "" : select_query_addition.c_str()); + + int32 total = 0; + int32 id = 0; + uint32 soe_id = 0; + if (result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + soe_id = strtoul(row[3], NULL, 0); + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tRecipe Book for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tType: %i, '%s'", ITEM_TYPE_RECIPE, row[2]); + item->SetItemType(ITEM_TYPE_RECIPE); + item->recipebook_info->recipe_id = (atoi(row[1])); + item->recipebook_info->recipes.push_back(soe_id); + //item->recipebook_info->recipe_id(row[1]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_recipe_items`, ID: %u", id); + } + } + return total; +} + +int32 WorldDatabase::LoadHouseContainers(int32 item_id){ + DatabaseResult result; + int32 total = 0; + int32 id = 0; + + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + if( database_new.Select(&result, "SELECT item_id, num_slots, allowed_types, broker_commission, fence_commission FROM item_details_house_container%s", (item_id == 0) ? "" : select_query_addition.c_str()) ) + { + while (result.Next() ) + { + id = result.GetInt32Str("item_id"); + Item* item = master_item_list.GetItem(id); + + if (item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tHouse Container for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tType: %i, '%i', '%u', '%i', '%i'", ITEM_TYPE_RECIPE, result.GetInt8Str("num_slots"), result.GetInt64Str("allowed_types"), result.GetInt8Str("broker_commission"), result.GetInt8Str("fence_commission")); + + item->SetItemType(ITEM_TYPE_HOUSE_CONTAINER); + item->housecontainer_info->num_slots = result.GetInt8Str("num_slots"); + item->housecontainer_info->allowed_types = result.GetInt64Str("allowed_types"); + item->housecontainer_info->broker_commission = result.GetInt8Str("broker_commission"); + item->housecontainer_info->fence_commission = result.GetInt8Str("fence_commission"); + + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_house_container`, ID: %u", id); + } + } + return total; +} + +int32 WorldDatabase::LoadArmor(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, mitigation_low, mitigation_high FROM item_details_armor%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Armor for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, mit_low: %i, mit_high: %i", ITEM_TYPE_ARMOR, atoi(row[1]), atoi(row[2])); + item->SetItemType(ITEM_TYPE_ARMOR); + item->armor_info->mitigation_low = atoi(row[1]); + item->armor_info->mitigation_high = atoi(row[2]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_armor`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadBags(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, num_slots, weight_reduction FROM item_details_bag%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Bag for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, slots: %i, wt_red: %i", id, ITEM_TYPE_BAG, atoi(row[1]), atoi(row[2])); + item->SetItemType(ITEM_TYPE_BAG); + item->details.num_slots = atoi(row[1]); + item->details.num_free_slots = item->details.num_slots; + item->bag_info->num_slots = item->details.num_slots; + item->bag_info->weight_reduction = atoi(row[2]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_bag`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadFoods(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, type, level, duration, satiation FROM item_details_food%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Food for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, lvl: %i, dur: %i, sat: %.2f, tier: %i", ITEM_TYPE_FOOD, atoi(row[1]), atoi(row[2]), atof(row[3]), atoi(row[4])); + item->SetItemType(ITEM_TYPE_FOOD); + item->food_info->type = atoi(row[1]); + item->food_info->level = atoi(row[2]); + item->food_info->duration = atof(row[3]); + item->food_info->satiation = atoi(row[4]); + item->details.tier = atoi(row[4]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_food`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadRangeWeapons(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, dmg_low, dmg_high, dmg_mastery_low, dmg_mastery_high, dmg_base_low, dmg_base_high, delay, damage_rating, range_low, range_high, damage_type FROM item_details_range%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Ranged for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %i, %i, %i, %i, %i, %i, %.2f, %i, %i, %i", ITEM_TYPE_RANGED, atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7]), atof(row[8]), atoi(row[9]), atoi(row[10]), atoi(row[11])); + item->SetItemType(ITEM_TYPE_RANGED); + item->ranged_info->weapon_info.damage_low1 = atoi(row[1]); + item->ranged_info->weapon_info.damage_high1 = atoi(row[2]); + item->ranged_info->weapon_info.damage_low2 = atoi(row[3]); + item->ranged_info->weapon_info.damage_high2 = atoi(row[4]); + item->ranged_info->weapon_info.damage_low3 = atoi(row[5]); + item->ranged_info->weapon_info.damage_high3 = atoi(row[6]); + item->ranged_info->weapon_info.delay = atoi(row[7]); + item->ranged_info->weapon_info.rating = atof(row[8]); + item->ranged_info->range_low = atoi(row[9]); + item->ranged_info->range_high = atoi(row[10]); + item->SetWeaponType(atoi(row[11])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_range`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadThrownWeapons(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, range_bonus, damage_bonus, hit_bonus, damage_type FROM item_details_thrown%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Thrown for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %u, %.2f, %u", ITEM_TYPE_THROWN, atoul(row[1]), atoi(row[2]), atof(row[3]), atoul(row[4])); + item->SetItemType(ITEM_TYPE_THROWN); + item->thrown_info->range = atoul(row[1]); + item->thrown_info->damage_modifier = atoul(row[2]); + item->thrown_info->hit_bonus = atof(row[3]); + item->thrown_info->damage_type = atoul(row[4]); + item->SetWeaponType(item->thrown_info->damage_type); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_details_thrown`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadWeapons(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, wield_style, dmg_low, dmg_high, dmg_mastery_low, dmg_mastery_high, dmg_base_low, dmg_base_high, delay, damage_rating, damage_type FROM item_details_weapon%s", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 total = 0; + int32 id = 0; + + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + id = strtoul(row[0], NULL, 0); + Item* item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Weapon for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, %i, %i, %i, %i, %i, %i, %i, %i, %.2f, %i", ITEM_TYPE_WEAPON, atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7]), atoi(row[8]), atof(row[9]), atoi(row[10])); + item->SetItemType(ITEM_TYPE_WEAPON); + item->weapon_info->wield_type = atoi(row[1]); + item->weapon_info->damage_low1 = atoi(row[2]); + item->weapon_info->damage_high1 = atoi(row[3]); + item->weapon_info->damage_low2 = atoi(row[4]); + item->weapon_info->damage_high2 = atoi(row[5]); + item->weapon_info->damage_low3 = atoi(row[6]); + item->weapon_info->damage_high3 = atoi(row[7]); + item->weapon_info->delay = atoi(row[8]); + item->weapon_info->rating = atof(row[9]); + item->SetWeaponType(atoi(row[10])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_weapons`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadItemAppearances(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue FROM item_appearances %sORDER BY item_id asc", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + if(id != strtoul(row[0], NULL, 0)) + { + id = strtoul(row[0], NULL, 0); + item = master_item_list.GetItem(id); + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Appearance for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tequip_type: %i, R: %i, G: %i, B: %i, HR: %i, HG: %i, HB: %i", atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7])); + item->SetAppearance(atoi(row[1]), atoi(row[2]), atoi(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error Loading item_appearances, ID: %i", id); + } + } + } + return total; +} + +int32 WorldDatabase::LoadItemEffects(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, effect, percentage, bullet FROM item_effects %sORDER BY item_id, id", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + if(id != atoul(row[0])) + { + id = atoul(row[0]); + item = master_item_list.GetItem(id); + } + + if(item && row[1]) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Effects for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tEffect: '%s', Percent: %i, Sub: %i", row[1], atoi(row[2]), atoi(row[3])); + item->AddEffect(row[1], atoi(row[2]), atoi(row[3])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error Loading item_effects, ID: %i", id); + } + } + return total; +} +int32 WorldDatabase::LoadBookPages(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, page, page_text, page_text_valign, page_text_halign FROM item_details_book_pages %sORDER BY item_id, id", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if (result && mysql_num_rows(result) > 0) + { + while (result && (row = mysql_fetch_row(result))) + { + if (id != atoul(row[0])) + { + id = atoul(row[0]); + item = master_item_list.GetItem(id); + } + + if (item && row[1]) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tBook Pages for item_id %u", id); + //LogWrite(ITEM__DEBUG, 5, "Items", "\tPages: '%s', Percent: %i, Sub: %i", row[1], atoi(row[2]), atoi(row[3])); + item->AddBookPage(atoi(row[1]), row[2], atoi(row[3]), atoi(row[4])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error Loading item_details_book_pages, ID: %i", id); + } + } + return total; +} +int32 WorldDatabase::LoadItemLevelOverride(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, adventure_class_id, tradeskill_class_id, level FROM item_levels_override %sORDER BY item_id asc", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + if(id != strtoul(row[0], NULL, 0)) + { + id = strtoul(row[0], NULL, 0); + item = master_item_list.GetItem(id); + } + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tLevel Override for item_id %u", id); + LogWrite(ITEM__DEBUG, 5, "Items", "\tAdv: %i, TS: %i, Lvl: %i", atoi(row[1]), atoi(row[2]), atoi(row[3])); + item->AddLevelOverride(atoi(row[1]), atoi(row[2]), atoi(row[3])); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_levels_override`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadItemStats(int32 item_id) +{ + Query query; + MYSQL_ROW row; + + std::string select_query_addition = std::string("where item_id = ") + std::to_string(item_id) + " "; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, type, subtype, iValue, fValue, sValue, level FROM item_mod_stats %sORDER BY stats_order asc", (item_id == 0) ? "" : select_query_addition.c_str()); + int32 id = 0; + Item* item = 0; + int32 total = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + if(id != strtoul(row[0], NULL, 0)) + { + id = strtoul(row[0], NULL, 0); + item = master_item_list.GetItem(id); + } + + if(item) + { + LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Stats for item_id %u", id); + + float fValue = 0.0f; + if(row[3]) + fValue = atof(row[3]); + else if(row[4]) + fValue = atof(row[4]); + + //LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, sub: %i, val: %.2f, name: %s", atoi(row[1]), atoi(row[2]), atof(row[3]), row[4]); + item->AddStat(atoi(row[1]), atoi(row[2]), fValue, atoul(row[6]), row[5]); + total++; + } + else + LogWrite(ITEM__ERROR, 0, "Items", "Error loading `item_stats`, ID: %i", id); + } + } + return total; +} + +int32 WorldDatabase::LoadItemModStrings(int32 item_id) +{ + DatabaseResult result; + + int32 id = 0; + Item* item = 0; + int32 total = 0; + + std::string select_query_addition = std::string(" where item_id = ") + std::to_string(item_id); + if( !database_new.Select(&result, "SELECT * FROM item_mod_strings%s", (item_id == 0) ? "" : select_query_addition.c_str()) ) { + LogWrite(ITEM__ERROR, 0, "Items", "Cannot load WorldDatabase::LoadItemModStrings in %s, line: %i", __FUNCTION__, __LINE__); + return 0; + } + else { + while( result.Next() ) + { + int32 item_id = result.GetInt32Str("item_id"); + if(id != item_id) + { + item = master_item_list.GetItem(item_id); + id = item_id; + } + + const char* modName = result.GetFieldValueStr("mod"); + if(item && modName) + { + Item::ItemStatString* stat_ = new Item::ItemStatString; + stat_->stat_string.data = string(modName); + stat_->stat_string.size = stat_->stat_string.data.length(); + item->AddStatString(stat_); + } + total++; + } + } + return total; +} + +void WorldDatabase::LoadBrokerItemStats() +{ + DatabaseResult result; + + if( !database_new.Select(&result, "SELECT * FROM broker_item_map") ) { + LogWrite(ITEM__ERROR, 0, "Items", "Cannot load WorldDatabase::LoadBrokerItemStats in %s, line: %i", __FUNCTION__, __LINE__); + } + else { + while( result.Next() ) + { + int32 version_range1 = result.GetInt32Str("version_range1"); + int32 version_range2 = result.GetInt32Str("version_range2"); + int64 client_bitmask = result.GetInt64Str("client_bitmask"); + int64 server_bitmask = result.GetInt64Str("server_bitmask"); + master_item_list.AddBrokerItemMapRange(version_range1, version_range2, client_bitmask, server_bitmask); + } + } +} +void WorldDatabase::ReloadItemList(int32 item_id) +{ + LogWrite(ITEM__DEBUG, 0, "Items", "Unloading Item List..."); + if(!item_id) { + master_item_list.RemoveAll(); + } + LoadItemList(item_id); +} + +void WorldDatabase::LoadItemList(int32 item_id) +{ + DatabaseResult result; + + int32 t_now = Timer::GetUnixTimeStamp(); + int32 total = 0; + int32 normal_items = 0; + string item_type; + std::string select_query_addition = std::string(" where id = ") + std::to_string(item_id); + if( !database_new.Select(&result, "SELECT * FROM items%s", (item_id == 0) ? "" : select_query_addition.c_str()) ) + LogWrite(ITEM__ERROR, 0, "Items", "Cannot load items in %s, line: %i", __FUNCTION__, __LINE__); + else + { + while( result.Next() ) + { + item_type = result.GetStringStr("item_type"); + LogWrite(ITEM__DEBUG, 5, "Items", "\tLoading: %s (ID: %i, Type: %s)...", result.GetStringStr("name"), result.GetInt32Str("id"), item_type.c_str()); + + Item* item = new Item; + LoadDataFromRow(&result, item); + master_item_list.AddItem(item); + + if( strcmp(item_type.c_str(), "Normal") == 0 ) + { + item->SetItemType(ITEM_TYPE_NORMAL); + normal_items++; + } + total++; + } + } + + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Normal Items", normal_items); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Baubles", LoadBaubles(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Bags", LoadBags(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Books", LoadBooks(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Sets", LoadItemsets(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u House Items", LoadHouseItem(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Food Items", LoadFoods(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Weapons", LoadWeapons(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Ranged Weapons", LoadRangeWeapons(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Thrown Weapons", LoadThrownWeapons(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Armor Pieces", LoadArmor(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Shields", LoadShields(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Skill Items", LoadSkillItems(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Adornment Items", LoadAdornments(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Recipe Book Items", LoadRecipeBookItems(item_id)); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u House Containers", LoadHouseContainers(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Appearances..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Appearances", LoadItemAppearances(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Stats..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Stats", LoadItemStats(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Stats Mods (Strings)..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Stats", LoadItemModStrings(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Effects..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Effects", LoadItemEffects(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Book Pages..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Book Pages", LoadBookPages(item_id)); + + LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Level Overrides..."); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Level Overrides", LoadItemLevelOverride(item_id)); + + if(!item_id) { + LoadBrokerItemStats(); + LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded Broker Item Stat Map Versioning"); + } + + LogWrite(ITEM__INFO, 0, "Items", "Loaded %u Total Item%s (took %u seconds)", total, ( total == 1 ) ? "" : "s", Timer::GetUnixTimeStamp() - t_now); +} + +int32 WorldDatabase::LoadNextUniqueItemID() +{ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM character_items"); + + if(result && (row = mysql_fetch_row(result))) + { + if(row[0]) + { + LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, atoul(row[0])); + return strtoul(row[0], NULL, 0); + } + else + return 0; + } + else if(!result) + LogWrite(ITEM__ERROR, 0, "Items", "%s: Unable to load next unique item ID.", __FUNCTION__); + + return 0; +} + +void WorldDatabase::SaveItems(Client* client) +{ + LogWrite(ITEM__DEBUG, 3, "Items", "Save Items for Player %i", client->GetCharacterID()); + + map* items = client->GetPlayer()->GetItemList(); + map::iterator item_iter; + Item* item = 0; + + for(item_iter = items->begin(); item_iter != items->end(); item_iter++) + { + item = item_iter->second; + + if(item) { + if(item->CheckFlag(TEMPORARY)) { + item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire + } + if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) { + DeleteItem(client->GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(item->details.index); + if(!client->IsZoning()) { + client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion())); + } + } + else if(item->save_needed) + { + LogWrite(ITEM__DEBUG, 5, "Items", "SaveItems: Acct: %u, Char: %u, Item: %u, NOT-EQUIPPED", client->GetAccountID(), client->GetCharacterID(), item); + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "NOT-EQUIPPED"); + item->save_needed = false; + } + } + } + safe_delete(items); + + vector* equipped_list = client->GetPlayer()->GetEquippedItemList(); + + for(int32 i=0;isize();i++) + { + item = equipped_list->at(i); + + if(item) + { + if(item->CheckFlag(TEMPORARY)) { + item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire + } + if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) { + DeleteItem(client->GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(item->details.index); + if(!client->IsZoning()) { + client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion())); + } + } + else if(item->save_needed) { + if(item->details.appearance_type) + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "APPEARANCE"); + else + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "EQUIPPED"); + } + item->save_needed = false; + } + } + safe_delete(equipped_list); + + + vector* appearance_equipped_list = client->GetPlayer()->GetAppearanceEquippedItemList(); + + for(int32 i=0;isize();i++) + { + item = appearance_equipped_list->at(i); + + if(item) + { + if(item->CheckFlag(TEMPORARY)) { + item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire + } + if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) { + DeleteItem(client->GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(item->details.index); + if(!client->IsZoning()) { + client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion())); + } + } + else if(item->save_needed) { + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "APPEARANCE"); + item->save_needed = false; + } + } + } + safe_delete(appearance_equipped_list); + + vector* overflow = client->GetPlayer()->item_list.GetOverflowItemList(); + for (int32 i = 0; i < overflow->size(); i++){ + item = overflow->at(i); + if (item) { + if(item->CheckFlag(TEMPORARY)) { + item->save_needed = true; // we need to keep updating the timestamp so it doesn't expire + } + if(item->needs_deletion || (client->IsZoning() && item->CheckFlag(NO_ZONE))) { + DeleteItem(client->GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(item->details.index); + if(!client->IsZoning()) { + client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion())); + } + } + else { + sint16 slot = item->details.slot_id; + item->details.slot_id = i; + SaveItem(client->GetAccountID(), client->GetCharacterID(), item, "NOT-EQUIPPED"); + item->details.slot_id = slot; + } + } + } + safe_delete(overflow); +} + +void WorldDatabase::SaveItem(int32 account_id, int32 char_id, Item* item, const char* type) +{ + LogWrite(ITEM__DEBUG, 1, "Items", "Saving ItemID: %u (Type: %s) for account: %u, player: %u", item->details.item_id, type, account_id, char_id); + + Query query; + string update_item = string("REPLACE INTO character_items (id, type, char_id, slot, equip_slot, item_id, creator,adorn0,adorn1,adorn2, condition_, attuned, bag_id, count, max_sell_value, no_sale, account_id, login_checksum) VALUES (%u, '%s', %u, %i, %i, %u, '%s', %i, %i, %i, %i, %i, %i, %i, %u, %u, %u, 0)"); + query.AddQueryAsync(char_id, this, Q_REPLACE, update_item.c_str(), item->details.unique_id, type, char_id, item->details.slot_id, item->details.equip_slot_id, item->details.item_id, + getSafeEscapeString(item->creator.c_str()).c_str(),item->adorn0,item->adorn1,item->adorn2, item->generic_info.condition, item->CheckFlag(ATTUNED) ? 1 : 0, item->details.inv_slot_id, item->details.count, item->GetMaxSellValue(), item->no_sale, account_id); + if(item->CheckFlag2(HEIRLOOM)) { + std::map::iterator itr; + for(itr = item->grouped_char_ids.begin(); itr != item->grouped_char_ids.end(); itr++) { + string addmembers_query = string("REPLACE INTO character_items_group_members (unique_id, character_id) VALUES (%u, %u)"); + query.AddQueryAsync(char_id, this, Q_REPLACE, addmembers_query.c_str(), item->details.unique_id, itr->first); + } + } +} + +void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type) +{ + string delete_item; + + if(type) + { + LogWrite(ITEM__DEBUG, 1, "Items", "Deleting item_id %u (Type: %s) for player %u", item->details.item_id, type, char_id); + + delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u) AND type='%s'"); + Query query; + query.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id, type); + } + else + { + LogWrite(ITEM__DEBUG, 0, "Items", "Deleting item_id %u for player %u", item->details.item_id, char_id); + + delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u)"); + Query query2; + query2.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id); + } + + if(item->CheckFlag2(HEIRLOOM)) { + delete_item = string("DELETE FROM character_items_group_members WHERE unique_id = %u"); + Query query3; + query3.RunQuery2(Q_DELETE, delete_item.c_str(), item->details.unique_id); + } +} + +void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16 version) +{ + LogWrite(ITEM__DEBUG, 0, "Items", "Loading items for character '%s' (%u)", player->GetName(), char_id); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type, id, slot, equip_slot, item_id, creator,adorn0,adorn1,adorn2, condition_, attuned, bag_id, count, max_sell_value, no_sale, UNIX_TIMESTAMP(last_saved), UNIX_TIMESTAMP(created) FROM character_items where char_id = %u or (bag_id = -4 and account_id = %u) ORDER BY bag_id, slot asc", char_id, account_id); + + if(result) + { + bool ret = true; + + while(result && (row = mysql_fetch_row(result))) + { + LogWrite(ITEM__DEBUG, 5, "Items", "Loading character item: %u, slot: %i", strtoul(row[1], NULL, 0), atoi(row[2])); + Item* master_item = master_item_list.GetItem(strtoul(row[4], NULL, 0)); + if(master_item) + { + Item* item = new Item(master_item); + int32 xxx = 0; + if(master_item->recipebook_info) + item->recipebook_info->recipe_id = master_item->recipebook_info->recipe_id; + item->details.unique_id = strtoul(row[1], NULL, 0); + item->details.slot_id = atoi(row[2]); + if(item->IsBag()) { + item->details.equip_slot_id = atoi(row[3]); + } + + if(item->details.num_slots > 0) + item->details.bag_id = item->details.unique_id; + + item->save_needed = false; + + // we need the items basics (unique id slot id bag id) to continue this temporary check + if(item->CheckFlag(TEMPORARY)) { + std::time_t last_saved = static_cast(atoul(row[15])); + double timeInSeconds = std::difftime(std::time(nullptr), last_saved); + LogWrite(ITEM__INFO, 0, "Items", "Character ID %u has a temporary item %s time in seconds %f last saved.", char_id, item->name.c_str(), timeInSeconds); + if(timeInSeconds >= rule_manager.GetGlobalRule(R_Player, TemporaryItemLogoutTime)->GetFloat()) { + DeleteItem(char_id, item, 0); + LogWrite(ITEM__INFO, 0, "Items", "\tCharacter ID %u had a temporary item %s which was removed due to time limit.", char_id, item->name.c_str()); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + continue; + } + } + + if(row[5]) + item->creator = string(row[5]);//creator + item->adorn0 = atoi(row[6]); //adorn0 + item->adorn1 = atoi(row[7]); //adorn1 + item->adorn2 = atoi(row[8]); //adorn2 + item->generic_info.condition = atoi(row[9]); //condition + + if(row[10] && atoi(row[10])>0) //attuned + { + if(item->CheckFlag(ATTUNEABLE)) + item->generic_info.item_flags -= ATTUNEABLE; + + if(!item->CheckFlag(NO_TRADE)) + item->generic_info.item_flags += NO_TRADE; + + item->generic_info.item_flags += ATTUNED; + } + + if(item->CheckFlag2(HEIRLOOM)) { + MYSQL_ROW row2; + MYSQL_RES* result2 = query.RunQuery2(Q_SELECT, "SELECT character_id from character_items_group_members where unique_id = %u", item->details.unique_id); + + if(result2) + { + bool ret = true; + + while(result2 && (row2 = mysql_fetch_row(result2))) + { + item->grouped_char_ids.insert(std::make_pair(atoul(row2[0]),true)); + } + } + } + + item->details.inv_slot_id = atol(row[11]); //bag_id + item->details.new_item = false; + item->details.new_index = 0; + item->details.count = atoi(row[12]); //count + item->SetMaxSellValue(atoul(row[13])); //max sell value + item->no_sale = (atoul(row[14]) == 1); + item->details.appearance_type = 0; + + // position 14 is used for the last_saved timestamp (primarily for checking temporary items on login) + item->created = static_cast(atoul(row[16])); + + if(strncasecmp(row[0], "EQUIPPED", 8)==0) + ret = player->GetEquipmentList()->AddItem(item->details.slot_id, item); + else if (strncasecmp(row[0], "APPEARANCE", 10) == 0) + { + item->details.appearance_type = 1; + ret = player->GetAppearanceEquipmentList()->AddItem(item->details.slot_id, item); + } + else { + if (version < 1209 && item->details.count > 255) { + int stacks = item->details.count / 255; + int8 remainder = item->details.count % 255; + item->details.count = remainder; + + if (item->details.inv_slot_id == -2) + player->item_list.AddOverflowItem(item); + else { + if(!player->item_list.AddItem(item)) + item = nullptr; + } + + if(item) { + for (int stack = 1; stack <= stacks; stack++) { + item->details.count = 255; + item->details.inv_slot_id = -2; + player->item_list.AddOverflowItem(item); + } + } + } + else { + if (item->details.inv_slot_id == -2) + player->item_list.AddOverflowItem(item); + else + player->item_list.AddItem(item); + } + + if(item->details.equip_slot_id) { + player->GetEquipmentList()->AddItem(item->details.equip_slot_id, item); + } + } + } + else + ret = false; + } + + if(!ret) + LogWrite(ITEM__ERROR, 0, "Items", "%s: Error Loading item(s) for Char ID: %u (%s)", __FUNCTION__, char_id, player->GetName()); + } +} + diff --git a/source/WorldServer/Items/Items_CoE.h b/source/WorldServer/Items/Items_CoE.h new file mode 100644 index 0000000..5786b7f --- /dev/null +++ b/source/WorldServer/Items/Items_CoE.h @@ -0,0 +1,817 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_ITEMS__ +#define __EQ2_ITEMS__ +#include +#include +#include "../../common/types.h" +#include "../../common/DataBuffer.h" +#include "../../common/MiscFunctions.h" +#include "../Commands/Commands.h" +#include "../../common/ConfigReader.h" + +using namespace std; +class MasterItemList; +class Player; +extern MasterItemList master_item_list; +#define EQ2_PRIMARY_SLOT 0 +#define EQ2_SECONDARY_SLOT 1 +#define EQ2_HEAD_SLOT 2 +#define EQ2_CHEST_SLOT 3 +#define EQ2_SHOULDERS_SLOT 4 +#define EQ2_FOREARMS_SLOT 5 +#define EQ2_HANDS_SLOT 6 +#define EQ2_LEGS_SLOT 7 +#define EQ2_FEET_SLOT 8 +#define EQ2_LRING_SLOT 9 +#define EQ2_RRING_SLOT 10 +#define EQ2_EARS_SLOT_1 11 +#define EQ2_EARS_SLOT_2 12 +#define EQ2_NECK_SLOT 13 +#define EQ2_LWRIST_SLOT 14 +#define EQ2_RWRIST_SLOT 15 +#define EQ2_RANGE_SLOT 16 +#define EQ2_AMMO_SLOT 17 +#define EQ2_WAIST_SLOT 18 +#define EQ2_CLOAK_SLOT 19 +#define EQ2_CHARM_SLOT_1 20 +#define EQ2_CHARM_SLOT_2 21 +#define EQ2_FOOD_SLOT 22 +#define EQ2_DRINK_SLOT 23 +#define EQ2_TEXTURES_SLOT 24 +#define EQ2_UNKNOWN_SLOT 25 + +#define PRIMARY_SLOT 1 +#define SECONDARY_SLOT 2 +#define HEAD_SLOT 4 +#define CHEST_SLOT 8 +#define SHOULDERS_SLOT 16 +#define FOREARMS_SLOT 32 +#define HANDS_SLOT 64 +#define LEGS_SLOT 128 +#define FEET_SLOT 256 +#define LRING_SLOT 512 +#define RRING_SLOT 1024 +#define EARS_SLOT_1 2048 +#define EARS_SLOT_2 4096 +#define NECK_SLOT 8192 +#define LWRIST_SLOT 16384 +#define RWRIST_SLOT 32768 +#define RANGE_SLOT 65536 +#define AMMO_SLOT 131072 +#define WAIST_SLOT 262144 +#define CLOAK_SLOT 524288 +#define CHARM_SLOT_1 1048576 +#define CHARM_SLOT_2 2097152 +#define FOOD_SLOT 4194304 +#define DRINK_SLOT 8388608 +#define TEXTURES_SLOT 16777216 + +#define NUM_BANK_SLOTS 12 +#define NUM_SHARED_BANK_SLOTS 8 +#define NUM_SLOTS 25 +#define NUM_INV_SLOTS 6 +#define INV_SLOT1 0 +#define INV_SLOT2 50 +#define INV_SLOT3 100 +#define INV_SLOT4 150 +#define INV_SLOT5 200 +#define INV_SLOT6 250 +#define BANK_SLOT1 1000 +#define BANK_SLOT2 1100 +#define BANK_SLOT3 1200 +#define BANK_SLOT4 1300 +#define BANK_SLOT5 1400 +#define BANK_SLOT6 1500 +#define BANK_SLOT7 1600 +#define BANK_SLOT8 1700 +#define ATTUNED 1 +#define ATTUNEABLE 2 +#define ARTIFACT 4 +#define LORE 8 +#define TEMPORARY 16 +#define NO_TRADE 32 +#define NO_VALUE 64 +#define NO_ZONE 128 +#define NO_DESTROY 256 +#define CRAFTED 512 +#define GOOD_ONLY 1024 +#define EVIL_ONLY 2048 + +#define ITEM_WIELD_TYPE_DUAL 1 +#define ITEM_WIELD_TYPE_SINGLE 2 +#define ITEM_WIELD_TYPE_TWO_HAND 4 + +#define ITEM_TYPE_NORMAL 0 +#define ITEM_TYPE_WEAPON 1 +#define ITEM_TYPE_RANGED 2 +#define ITEM_TYPE_ARMOR 3 +#define ITEM_TYPE_SHIELD 4 +#define ITEM_TYPE_BAG 5 +#define ITEM_TYPE_SKILL 6 +#define ITEM_TYPE_RECIPE 7 +#define ITEM_TYPE_FOOD 8 +#define ITEM_TYPE_BAUBLE 9 +#define ITEM_TYPE_HOUSE 10 +#define ITEM_TYPE_THROWN 11 +#define ITEM_TYPE_HOUSE_CONTAINER 12 +#define ITEM_TYPE_BOOK 13 +#define ITEM_TYPE_ADORNMENT 14 +#define ITEM_TYPE_PATTERN 15 +#define ITEM_TYPE_ARMORSET 16 + +#define ITEM_MENU_TYPE_GENERIC 1 +#define ITEM_MENU_TYPE_EQUIP 2 +#define ITEM_MENU_TYPE_BAG 4 +#define ITEM_MENU_TYPE_HOUSE 8 +#define ITEM_MENU_TYPE_SCRIBE 32 +#define ITEM_MENU_TYPE_INVALID 128 +#define ITEM_MENU_TYPE_BROKEN 512 +#define ITEM_MENU_TYPE_ATTUNED 2048 +#define ITEM_MENU_TYPE_ATTUNEABLE 4096 +#define ITEM_MENU_TYPE_BOOK 8192 +#define ITEM_MENU_TYPE_DISPLAY_CHARGES 16384 +#define ITEM_MENU_TYPE_NAMEPET 65536 +#define ITEM_MENU_TYPE_USE 524288 +#define ITEM_MENU_TYPE_DRINK 8388608 +#define ITEM_MENU_TYPE_REDEEM 536870912 + +#define ITEM_TAG_UNCOMMON 3 //tier tags +#define ITEM_TAG_TREASURED 4 +#define ITEM_TAG_LEGENDARY 7 +#define ITEM_TAG_FABLED 9 +#define ITEM_TAG_MYTHICAL 12 + +#define ITEM_BROKER_TYPE_ANY 0xFFFFFFFF +#define ITEM_BROKER_TYPE_ADORNMENT 134217728 +#define ITEM_BROKER_TYPE_AMMO 1024 +#define ITEM_BROKER_TYPE_ATTUNEABLE 16384 +#define ITEM_BROKER_TYPE_BAG 2048 +#define ITEM_BROKER_TYPE_BAUBLE 16777216 +#define ITEM_BROKER_TYPE_BOOK 128 +#define ITEM_BROKER_TYPE_CHAINARMOR 2097152 +#define ITEM_BROKER_TYPE_CLOAK 1073741824 +#define ITEM_BROKER_TYPE_CLOTHARMOR 524288 +#define ITEM_BROKER_TYPE_COLLECTABLE 67108864 +#define ITEM_BROKER_TYPE_CRUSHWEAPON 4 +#define ITEM_BROKER_TYPE_DRINK 131072 +#define ITEM_BROKER_TYPE_FOOD 4096 +#define ITEM_BROKER_TYPE_HOUSEITEM 512 +#define ITEM_BROKER_TYPE_JEWELRY 262144 +#define ITEM_BROKER_TYPE_LEATHERARMOR 1048576 +#define ITEM_BROKER_TYPE_LORE 8192 +#define ITEM_BROKER_TYPE_MISC 1 +#define ITEM_BROKER_TYPE_PIERCEWEAPON 8 +#define ITEM_BROKER_TYPE_PLATEARMOR 4194304 +#define ITEM_BROKER_TYPE_POISON 65536 +#define ITEM_BROKER_TYPE_POTION 32768 +#define ITEM_BROKER_TYPE_RECIPEBOOK 8388608 +#define ITEM_BROKER_TYPE_SALESDISPLAY 33554432 +#define ITEM_BROKER_TYPE_SHIELD 32 +#define ITEM_BROKER_TYPE_SLASHWEAPON 2 +#define ITEM_BROKER_TYPE_SPELLSCROLL 64 +#define ITEM_BROKER_TYPE_TINKERED 268435456 +#define ITEM_BROKER_TYPE_TRADESKILL 256 + +#define ITEM_BROKER_SLOT_ANY 0xFFFFFFFF +#define ITEM_BROKER_SLOT_AMMO 65536 +#define ITEM_BROKER_SLOT_CHARM 524288 +#define ITEM_BROKER_SLOT_CHEST 32 +#define ITEM_BROKER_SLOT_CLOAK 262144 +#define ITEM_BROKER_SLOT_DRINK 2097152 +#define ITEM_BROKER_SLOT_EARS 4096 +#define ITEM_BROKER_SLOT_FEET 1024 +#define ITEM_BROKER_SLOT_FOOD 1048576 +#define ITEM_BROKER_SLOT_FOREARMS 128 +#define ITEM_BROKER_SLOT_HANDS 256 +#define ITEM_BROKER_SLOT_HEAD 16 +#define ITEM_BROKER_SLOT_LEGS 512 +#define ITEM_BROKER_SLOT_NECK 8192 +#define ITEM_BROKER_SLOT_PRIMARY 1 +#define ITEM_BROKER_SLOT_PRIMARY_2H 2 +#define ITEM_BROKER_SLOT_RANGE_WEAPON 32768 +#define ITEM_BROKER_SLOT_RING 2048 +#define ITEM_BROKER_SLOT_SECONDARY 8 +#define ITEM_BROKER_SLOT_SHOULDERS 64 +#define ITEM_BROKER_SLOT_WAIST 131072 +#define ITEM_BROKER_SLOT_WRIST 16384 + +#define ITEM_BROKER_STAT_TYPE_NONE 0 +#define ITEM_BROKER_STAT_TYPE_DEF 2 +#define ITEM_BROKER_STAT_TYPE_STR 4 +#define ITEM_BROKER_STAT_TYPE_STA 8 +#define ITEM_BROKER_STAT_TYPE_AGI 16 +#define ITEM_BROKER_STAT_TYPE_WIS 32 +#define ITEM_BROKER_STAT_TYPE_INT 64 +#define ITEM_BROKER_STAT_TYPE_HEALTH 128 +#define ITEM_BROKER_STAT_TYPE_POWER 256 +#define ITEM_BROKER_STAT_TYPE_HEAT 512 +#define ITEM_BROKER_STAT_TYPE_COLD 1024 +#define ITEM_BROKER_STAT_TYPE_MAGIC 2048 +#define ITEM_BROKER_STAT_TYPE_MENTAL 4096 +#define ITEM_BROKER_STAT_TYPE_DIVINE 8192 +#define ITEM_BROKER_STAT_TYPE_POISON 16384 +#define ITEM_BROKER_STAT_TYPE_DISEASE 32768 +#define ITEM_BROKER_STAT_TYPE_CRUSH 65536 +#define ITEM_BROKER_STAT_TYPE_SLASH 131072 +#define ITEM_BROKER_STAT_TYPE_PIERCE 262144 +#define ITEM_BROKER_STAT_TYPE_CRITICAL 524288 +#define ITEM_BROKER_STAT_TYPE_DBL_ATTACK 1048576 +#define ITEM_BROKER_STAT_TYPE_ABILITY_MOD 2097152 +#define ITEM_BROKER_STAT_TYPE_POTENCY 4194304 + + + +#define OVERFLOW_SLOT 0xFFFFFFFE +#define SLOT_INVALID 0xFFFF + +#define ITEM_STAT_STR 0 +#define ITEM_STAT_STA 1 +#define ITEM_STAT_AGI 2 +#define ITEM_STAT_WIS 3 +#define ITEM_STAT_INT 4 + +#define ITEM_STAT_ADORNING 100 +#define ITEM_STAT_AGGRESSION 101 +#define ITEM_STAT_ARTIFICING 102 +#define ITEM_STAT_ARTISTRY 103 +#define ITEM_STAT_CHEMISTRY 104 +#define ITEM_STAT_CRUSHING 105 +#define ITEM_STAT_DEFENSE 106 +#define ITEM_STAT_DEFLECTION 107 +#define ITEM_STAT_DISRUPTION 108 +#define ITEM_STAT_FISHING 109 +#define ITEM_STAT_FLETCHING 110 +#define ITEM_STAT_FOCUS 111 +#define ITEM_STAT_FORESTING 112 +#define ITEM_STAT_GATHERING 113 +#define ITEM_STAT_METAL_SHAPING 114 +#define ITEM_STAT_METALWORKING 115 +#define ITEM_STAT_MINING 116 +#define ITEM_STAT_MINISTRATION 117 +#define ITEM_STAT_ORDINATION 118 +#define ITEM_STAT_PARRY 119 +#define ITEM_STAT_PIERCING 120 +#define ITEM_STAT_RANGED 121 +#define ITEM_STAT_SAFE_FALL 122 +#define ITEM_STAT_SCRIBING 123 +#define ITEM_STAT_SCULPTING 124 +#define ITEM_STAT_SLASHING 125 +#define ITEM_STAT_SUBJUGATION 126 +#define ITEM_STAT_SWIMMING 127 +#define ITEM_STAT_TAILORING 128 +#define ITEM_STAT_TINKERING 129 +#define ITEM_STAT_TRANSMUTING 130 +#define ITEM_STAT_TRAPPING 131 +#define ITEM_STAT_WEAPON_SKILLS 132 + +#define ITEM_STAT_VS_PHYSICAL 200 +#define ITEM_STAT_VS_ELEMENTAL 201 +#define ITEM_STAT_VS_NOXIOUS 202 +#define ITEM_STAT_VS_ARCANE 203 + +//#define ITEM_STAT_VS_SLASH 200 +//#define ITEM_STAT_VS_CRUSH 201 +//#define ITEM_STAT_VS_PIERCE 202 +//#define ITEM_STAT_VS_HEAT 203 +//#define ITEM_STAT_VS_COLD 204 +//#define ITEM_STAT_VS_MAGIC 205 +//#define ITEM_STAT_VS_MENTAL 206 +//#define ITEM_STAT_VS_DIVINE 207 +//#define ITEM_STAT_VS_DISEASE 208 +//#define ITEM_STAT_VS_POISON 209 +//#define ITEM_STAT_VS_DROWNING 210 +//#define ITEM_STAT_VS_FALLING 211 +//#define ITEM_STAT_VS_PAIN 212 +//#define ITEM_STAT_VS_MELEE 213 + + +#define ITEM_STAT_DMG_SLASH 300 +#define ITEM_STAT_DMG_CRUSH 301 +#define ITEM_STAT_DMG_PIERCE 302 +#define ITEM_STAT_DMG_HEAT 303 +#define ITEM_STAT_DMG_COLD 304 +#define ITEM_STAT_DMG_MAGIC 305 +#define ITEM_STAT_DMG_MENTAL 306 +#define ITEM_STAT_DMG_DIVINE 307 +#define ITEM_STAT_DMG_DISEASE 308 +#define ITEM_STAT_DMG_POISON 309 +#define ITEM_STAT_DMG_DROWNING 310 +#define ITEM_STAT_DMG_FALLING 311 +#define ITEM_STAT_DMG_PAIN 312 +#define ITEM_STAT_DMG_MELEE 313 + + +#define ITEM_STAT_HEALTH 500 +#define ITEM_STAT_POWER 501 +#define ITEM_STAT_CONCENTRATION 502 +#define ITEM_STAT_SAVAGERY 503 + + +#define ITEM_STAT_HPREGEN 600 +#define ITEM_STAT_MANAREGEN 601 +#define ITEM_STAT_HPREGENPPT 602 +#define ITEM_STAT_MPREGENPPT 603 +#define ITEM_STAT_COMBATHPREGENPPT 604 +#define ITEM_STAT_COMBATMPREGENPPT 605 +#define ITEM_STAT_MAXHP 606 +#define ITEM_STAT_MAXHPPERC 607 +#define ITEM_STAT_MAXHPPERCFINAL 608 +#define ITEM_STAT_SPEED 609 +#define ITEM_STAT_SLOW 610 +#define ITEM_STAT_MOUNTSPEED 611 +#define ITEM_STAT_MOUNTAIRSPEED 612 +#define ITEM_STAT_LEAPSPEED 613 +#define ITEM_STAT_LEAPTIME 614 +#define ITEM_STAT_GLIDEEFFICIENCY 615 +#define ITEM_STAT_OFFENSIVESPEED 616 +#define ITEM_STAT_ATTACKSPEED 617 +#define ITEM_STAT_SPELLWEAPONATTACKSPEED 618 +#define ITEM_STAT_MAXMANA 619 +#define ITEM_STAT_MAXMANAPERC 620 +#define ITEM_STAT_MAXATTPERC 621 +#define ITEM_STAT_BLURVISION 622 +#define ITEM_STAT_MAGICLEVELIMMUNITY 623 +#define ITEM_STAT_HATEGAINMOD 624 +#define ITEM_STAT_COMBATEXPMOD 625 +#define ITEM_STAT_TRADESKILLEXPMOD 626 +#define ITEM_STAT_ACHIEVEMENTEXPMOD 627 +#define ITEM_STAT_SIZEMOD 628 +#define ITEM_STAT_DPS 629 +#define ITEM_STAT_SPELLWEAPONDPS 630 +#define ITEM_STAT_STEALTH 631 +#define ITEM_STAT_INVIS 632 +#define ITEM_STAT_SEESTEALTH 633 +#define ITEM_STAT_SEEINVIS 634 +#define ITEM_STAT_EFFECTIVELEVELMOD 635 +#define ITEM_STAT_RIPOSTECHANCE 636 +#define ITEM_STAT_PARRYCHANCE 637 +#define ITEM_STAT_DODGECHANCE 638 +#define ITEM_STAT_AEAUTOATTACKCHANCE 639 +#define ITEM_STAT_SPELLWEAPONAEAUTOATTACKCHANCE 640 +#define ITEM_STAT_DOUBLEATTACKCHANCE 641 +#define ITEM_STAT_PVPDOUBLEATTACKCHANCE 642 +#define ITEM_STAT_SPELLWEAPONAEAUTOATTACKCHANCE 643 +#define ITEM_STAT_PVPSPELLWEAPONDOUBLEATTACKCHANCE 644 +#define ITEM_STAT_SPELLDOUBLEATTACKCHANCE 645 +#define ITEM_STAT_PVPSPELLDOUBLEATTACKCHANCE 646 +#define ITEM_STAT_FLURRY 647 +#define ITEM_STAT_SPELLWEAPONFLURRY 648 +#define ITEM_STAT_MELEEDAMAGEMULTIPLIER 649 +#define ITEM_STAT_EXTRAHARVESTCHANCE 650 +#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 651 +#define ITEM_STAT_ITEMHPREGENPPT 652 +#define ITEM_STAT_ITEMPPREGENPPT 653 +#define ITEM_STAT_MELEECRITCHANCE 654 +#define ITEM_STAT_CRITAVOIDANCE 655 +#define ITEM_STAT_BENEFICIALCRITCHANCE 656 +#define ITEM_STAT_CRITBONUS 657 +#define ITEM_STAT_PVPCRITBONUS 658 +#define ITEM_STAT_BASEMODIFIER 659 +#define ITEM_STAT_PVPBASEMODIFIER 660 +#define ITEM_STAT_UNCONSCIOUSHPMOD 661 +#define ITEM_STAT_SPELLTIMEREUSEPCT 662 +#define ITEM_STAT_SPELLTIMERECOVERYPCT 663 +#define ITEM_STAT_SPELLTIMECASTPCT 664 +#define ITEM_STAT_SPELLTIMEREUSESPELLONLY 665 +#define ITEM_STAT_MELEEWEAPONRANGE 666 +#define ITEM_STAT_RANGEDWEAPONRANGE 667 +#define ITEM_STAT_FALLINGDAMAGEREDUCTION 668 +#define ITEM_STAT_RIPOSTEDAMAGE 669 +#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 670 +#define ITEM_STAT_MOVEMENTWEAVE 671 +#define ITEM_STAT_COMBATHPREGEN 672 +#define ITEM_STAT_COMBATMANAREGEN 673 +#define ITEM_STAT_CONTESTSPEEDBOOST 674 +#define ITEM_STAT_TRACKINGAVOIDANCE 675 +#define ITEM_STAT_STEALTHINVISSPEEDMOD 676 +#define ITEM_STAT_LOOT_COIN 677 +#define ITEM_STAT_ARMORMITIGATIONINCREASE 678 +#define ITEM_STAT_AMMOCONSERVATION 679 +#define ITEM_STAT_STRIKETHROUGH 680 +#define ITEM_STAT_STATUSBONUS 681 +#define ITEM_STAT_ACCURACY 682 +#define ITEM_STAT_COUNTERSTRIKE 683 +#define ITEM_STAT_SHIELDBASH 684 +#define ITEM_STAT_WEAPONDAMAGEBONUS 685 +#define ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY 686 +#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 687 +#define ITEM_STAT_CRITICALMITIGATION 688 +#define ITEM_STAT_PVPTOUGHNESS 689 +#define ITEM_STAT_PVPLETHALITY 690 +#define ITEM_STAT_STAMINABONUS 691 +#define ITEM_STAT_WISDOMMITBONUS 692 +#define ITEM_STAT_HEALRECEIVE 693 +#define ITEM_STAT_HEALRECEIVEPERC 694 +#define ITEM_STAT_PVPCRITICALMITIGATION 695 +#define ITEM_STAT_BASEAVOIDANCEBONUS 696 +#define ITEM_STAT_INCOMBATSAVAGERYREGEN 697 +#define ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN 698 +#define ITEM_STAT_SAVAGERYREGEN 699 +#define ITEM_STAT_SAVAGERYGAINMOD 6100 +#define ITEM_STAT_MAXSAVAGERYLEVEL 6101 + +#define ITEM_STAT_SPELL_DAMAGE 700 +#define ITEM_STAT_HEAL_AMOUNT 701 +#define ITEM_STAT_SPELL_AND_HEAL 702 +#define ITEM_STAT_COMBAT_ART_DAMAGE 703 +#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE 704 +#define ITEM_STAT_TAUNT_AMOUNT 705 +#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE 706 +#define ITEM_STAT_ABILITY_MODIFIER 707 + + + +#pragma pack(1) +struct ItemStatsValues{ + sint16 str; + sint16 sta; + sint16 agi; + sint16 wis; + sint16 int_; + sint16 vs_slash; + sint16 vs_crush; + sint16 vs_pierce; + sint16 vs_heat; + sint16 vs_cold; + sint16 vs_magic; + sint16 vs_mental; + sint16 vs_divine; + sint16 vs_disease; + sint16 vs_poison; + sint16 health; + sint16 power; + sint8 concentration; +}; +struct ItemCore{ + int32 item_id; + sint32 soe_id; + int32 bag_id; + sint32 inv_slot_id; + sint16 slot_id; + int8 index; + int16 icon; + int16 count; + int8 tier; + int8 num_slots; + int32 unique_id; + int8 num_free_slots; + int16 recommended_level; + bool item_locked; +}; +#pragma pack() +struct ItemStat{ + string stat_name; + int8 stat_type; + sint16 stat_subtype; + int16 stat_type_combined; + float value; +}; +struct ItemLevelOverride{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemClass{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemAppearance{ + int16 type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; +}; +class PlayerItemList; +class Item{ +public: + #pragma pack(1) + struct ItemStatString{ + EQ2_8BitString stat_string; + }; + struct Generic_Info{ + int8 show_name; + int8 creator_flag; + int16 item_flags; + int8 condition; + int32 weight; // num/10 + int32 skill_req1; + int32 skill_req2; + int16 skill_min; + int8 item_type; //0=normal, 1=weapon, 2=range, 3=armor, 4=shield, 5=container, 6=spell scroll, 7=recipe book, 8=food/drink, 9=bauble, 10=house item, 11=ammo, 12=house container + int16 appearance_id; + int8 appearance_red; + int8 appearance_green; + int8 appearance_blue; + int8 appearance_highlight_red; + int8 appearance_highlight_green; + int8 appearance_highlight_blue; + int8 collectable; + int32 offers_quest_id; + int32 part_of_quest_id; + int16 max_charges; + int8 display_charges; + int64 adventure_classes; + int64 tradeskill_classes; + int16 adventure_default_level; + int16 tradeskill_default_level; + int8 usable; + }; + struct Armor_Info { + int16 mitigation_low; + int16 mitigation_high; + }; + struct Weapon_Info { + int16 wield_type; + int16 damage_low1; + int16 damage_high1; + int16 damage_low2; + int16 damage_high2; + int16 damage_low3; + int16 damage_high3; + int16 delay; + float rating; + }; + struct Shield_Info { + Armor_Info armor_info; + }; + struct Ranged_Info { + Weapon_Info weapon_info; + int16 range_low; + int16 range_high; + }; + struct Bag_Info { + int8 num_slots; + int16 weight_reduction; + }; + struct Food_Info{ + int8 type; //0=water, 1=food + int8 level; + float duration; + int8 satiation; + }; + struct Bauble_Info{ + int16 cast; + int16 recovery; + int32 duration; + float recast; + int8 display_slot_optional; + int8 display_cast_time; + int8 display_bauble_type; + float effect_radius; + int32 max_aoe_targets; + int8 display_until_cancelled; + }; + struct Book_Info{ + int8 language; + EQ2_16BitString author; + EQ2_16BitString title; + }; + struct Skill_Info{ + int32 spell_id; + int32 spell_tier; + }; + struct House_Info{ + int32 status_rent_reduction; + }; + struct HouseContainer_Info{ + int16 disallowed_types; + int16 allowed_types; + int8 num_slots; + }; + struct RecipeBook_Info{ + vector recipes; + int8 uses; + }; + struct Thrown_Info{ + sint32 range; + sint32 damage_modifier; + float hit_bonus; + int32 damage_type; + }; + struct ItemEffect{ + EQ2_16BitString effect; + int8 percentage; + int8 subbulletflag; + }; + #pragma pack() + Item(); + Item(Item* in_item); + ~Item(); + string lowername; + string name; + string description; + int8 stack_count; + int32 sell_price; + int32 max_sell_value; + bool save_needed; + int8 weapon_type; + string adornment; + string creator; + vector item_stats; + vector item_string_stats; + vector item_level_overrides; + vector item_effects; + Generic_Info generic_info; + Weapon_Info* weapon_info; + Ranged_Info* ranged_info; + Armor_Info* armor_info; + Bag_Info* bag_info; + Food_Info* food_info; + Bauble_Info* bauble_info; + Book_Info* book_info; + Skill_Info* skill_info; + RecipeBook_Info* recipebook_info; + Thrown_Info* thrown_info; + vector slot_data; + ItemCore details; + int32 spell_id; + int8 spell_tier; + string item_script; + + void AddEffect(string effect, int8 percentage, int8 subbulletflag); + int32 GetMaxSellValue(); + void SetMaxSellValue(int32 val); + void SetItem(Item* old_item); + int16 GetOverrideLevel(int8 adventure_class, int8 tradeskill_class); + void AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level); + void AddLevelOverride(ItemLevelOverride* class_); + bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + bool CheckClass(int8 adventure_class, int8 tradeskill_class); + bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue); + void SetAppearance(ItemAppearance* appearance); + void AddStat(ItemStat* in_stat); + void AddStatString(ItemStatString* in_stat); + void AddStat(int8 type, int16 subtype, float value, char* name = 0); + void SetWeaponType(int8 type); + int8 GetWeaponType(); + bool HasSlot(int8 slot, int8 slot2 = 255); + bool IsNormal(); + bool IsWeapon(); + bool IsArmor(); + bool IsRanged(); + bool IsBag(); + bool IsFood(); + bool IsBauble(); + bool IsSkill(); + bool IsHouseItem(); + bool IsHouseContainer(); + bool IsShield(); + bool IsAdornment(); + bool IsAmmo(); + bool IsBook(); + bool IsChainArmor(); + bool IsClothArmor(); + bool IsCollectable(); + bool IsCloak(); + bool IsCrushWeapon(); + bool IsFoodFood(); + bool IsFoodDrink(); + bool IsJewelry(); + bool IsLeatherArmor(); + bool IsMisc(); + bool IsPierceWeapon(); + bool IsPlateArmor(); + bool IsPoison(); + bool IsPotion(); + bool IsRecipeBook(); + bool IsSalesDisplay(); + bool IsSlashWeapon(); + bool IsSpellScroll(); + bool IsTinkered(); + bool IsTradeskill(); + bool IsThrown(); + void SetItemScript(string name); + const char* GetItemScript(); + int32 CalculateRepairCost(); + + void SetItemType(int8 in_type); + void serialize(PacketStruct* packet, bool show_name = false, Player* player = 0, int16 packet_type = 0, int8 subtype = 0, bool loot_item = false); + EQ2Packet* serialize(int16 version, bool show_name = false, Player* player = 0, bool include_twice = true, int16 packet_type = 0, int8 subtype = 0, bool merchant_item = false, bool loot_item = false); + PacketStruct* PrepareItem(int16 version, bool merchant_item = false, bool loot_item = false); + bool CheckFlag(int32 flag); + void AddSlot(int8 slot_id); + void SetSlots(int32 slots); + bool needs_deletion; +}; +class MasterItemList{ +public: + ~MasterItemList(); + map items; + + Item* GetItem(int32 id); + Item* GetItemByName(const char *name); + ItemStatsValues* CalculateItemBonuses(int32 item_id); + ItemStatsValues* CalculateItemBonuses(Item* desc, ItemStatsValues* values = 0); + vector* GetItems(string name, int32 itype, int32 ltype, int32 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass); + vector* GetItems(map criteria); + void AddItem(Item* item); + bool IsBag(int32 item_id); + void RemoveAll(); + static int32 NextUniqueID(); + static void ResetUniqueID(int32 new_id); + static int32 next_unique_id; +}; +class PlayerItemList { +public: + PlayerItemList(); + ~PlayerItemList(); +// int16 number; + map indexed_items; + map > items; +// map< int8, Item* > inv_items; +// map< int8, Item* > bank_items; + bool SharedBankAddAllowed(Item* item); + vector* GetItemsFromBagID(sint32 bag_id); + vector* GetItemsInBag(Item* bag); + Item* GetBag(int8 inventory_slot, bool lock = true); + bool HasItem(int32 id, bool include_bank = false); + Item* GetItemFromIndex(int32 index); + void MoveItem(Item* item, sint32 inv_slot, int16 slot, bool erase_old = true); + bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 charges); + Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true); + Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); + bool AssignItemToFreeSlot(Item* item); + int16 GetNumberOfFreeSlots(); + int16 GetNumberOfItems(); + bool HasFreeSlot(); + bool HasFreeBagSlot(); + void DestroyItem(int16 index); + Item* CanStack(Item* item, bool include_bank = false); + + void RemoveItem(Item* item, bool delete_item = false); + void AddItem(Item* item); + + Item* GetItem(sint32 bag_slot, int16 slot); + + EQ2Packet* serialize(Player* player, int16 version); + uchar* xor_packet; + uchar* orig_packet; + map* GetAllItems(); + bool HasFreeBankSlot(); + int8 FindFreeBankSlot(); + +private: + void Stack(Item* orig_item, Item* item); + Mutex MPlayerItems; + int16 packet_count; +}; +class OverFlowItemList : public PlayerItemList { +public: + bool OverFlowSlotFull(); + int8 GetNextOverFlowSlot(); + bool AddItem(Item* item); + Item* GetOverFlowItem(); +}; +class EquipmentItemList{ +public: + EquipmentItemList(); + EquipmentItemList(const EquipmentItemList& list); + ~EquipmentItemList(); + Item* items[NUM_SLOTS]; + + vector* GetAllEquippedItems(); + + bool HasItem(int32 id); + int8 GetNumberOfItems(); + Item* GetItemFromUniqueID(int32 item_id); + Item* GetItemFromItemID(int32 item_id); + void SetItem(int8 slot_id, Item* item); + void RemoveItem(int8 slot, bool delete_item = false); + Item* GetItem(int8 slot_id); + bool AddItem(int8 slot, Item* item); + bool CheckEquipSlot(Item* tmp, int8 slot); + bool CanItemBeEquippedInSlot(Item* tmp, int8 slot); + int8 GetFreeSlot(Item* tmp, int8 slot_id = 255); + ItemStatsValues* CalculateEquipmentBonuses(); + EQ2Packet* serialize(int16 version); + uchar* xor_packet; + uchar* orig_packet; +private: + Mutex MEquipmentItems; +}; + +#endif + diff --git a/source/WorldServer/Items/Items_DoV.h b/source/WorldServer/Items/Items_DoV.h new file mode 100644 index 0000000..9bfc87e --- /dev/null +++ b/source/WorldServer/Items/Items_DoV.h @@ -0,0 +1,916 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_ITEMS__ +#define __EQ2_ITEMS__ +#include +#include +#include "../../common/types.h" +#include "../../common/DataBuffer.h" +#include "../../common/MiscFunctions.h" +#include "../Commands/Commands.h" +#include "../../common/ConfigReader.h" + +using namespace std; +class MasterItemList; +class Player; +class Entity; +extern MasterItemList master_item_list; +#define EQ2_PRIMARY_SLOT 0 +#define EQ2_SECONDARY_SLOT 1 +#define EQ2_HEAD_SLOT 2 +#define EQ2_CHEST_SLOT 3 +#define EQ2_SHOULDERS_SLOT 4 +#define EQ2_FOREARMS_SLOT 5 +#define EQ2_HANDS_SLOT 6 +#define EQ2_LEGS_SLOT 7 +#define EQ2_FEET_SLOT 8 +#define EQ2_LRING_SLOT 9 +#define EQ2_RRING_SLOT 10 +#define EQ2_EARS_SLOT_1 11 +#define EQ2_EARS_SLOT_2 12 +#define EQ2_NECK_SLOT 13 +#define EQ2_LWRIST_SLOT 14 +#define EQ2_RWRIST_SLOT 15 +#define EQ2_RANGE_SLOT 16 +#define EQ2_AMMO_SLOT 17 +#define EQ2_WAIST_SLOT 18 +#define EQ2_CLOAK_SLOT 19 +#define EQ2_CHARM_SLOT_1 20 +#define EQ2_CHARM_SLOT_2 21 +#define EQ2_FOOD_SLOT 22 +#define EQ2_DRINK_SLOT 23 +#define EQ2_TEXTURES_SLOT 24 +#define EQ2_HAIR_SLOT 25 +#define EQ2_BEARD_SLOT 26 +#define EQ2_WINGS_SLOT 27 +#define EQ2_NAKED_CHEST_SLOT 28 +#define EQ2_NAKED_LEGS_SLOT 29 +#define EQ2_BACK_SLOT 30 + +#define PRIMARY_SLOT 1 +#define SECONDARY_SLOT 2 +#define HEAD_SLOT 4 +#define CHEST_SLOT 8 +#define SHOULDERS_SLOT 16 +#define FOREARMS_SLOT 32 +#define HANDS_SLOT 64 +#define LEGS_SLOT 128 +#define FEET_SLOT 256 +#define LRING_SLOT 512 +#define RRING_SLOT 1024 +#define EARS_SLOT_1 2048 +#define EARS_SLOT_2 4096 +#define NECK_SLOT 8192 +#define LWRIST_SLOT 16384 +#define RWRIST_SLOT 32768 +#define RANGE_SLOT 65536 +#define AMMO_SLOT 131072 +#define WAIST_SLOT 262144 +#define CLOAK_SLOT 524288 +#define CHARM_SLOT_1 1048576 +#define CHARM_SLOT_2 2097152 +#define FOOD_SLOT 4194304 +#define DRINK_SLOT 8388608 +#define TEXTURES_SLOT 16777216 +#define HAIR_SLOT 33554432 +#define BEARD_SLOT 67108864 +#define WINGS_SLOT 134217728 +#define NAKED_CHEST_SLOT 268435456 +#define NAKED_LEGS_SLOT 536870912 +#define BACK_SLOT 1073741824 + +#define NUM_BANK_SLOTS 12 +#define NUM_SHARED_BANK_SLOTS 8 +#define NUM_SLOTS 25 +#define NUM_INV_SLOTS 6 +#define INV_SLOT1 0 +#define INV_SLOT2 50 +#define INV_SLOT3 100 +#define INV_SLOT4 150 +#define INV_SLOT5 200 +#define INV_SLOT6 250 +#define BANK_SLOT1 1000 +#define BANK_SLOT2 1100 +#define BANK_SLOT3 1200 +#define BANK_SLOT4 1300 +#define BANK_SLOT5 1400 +#define BANK_SLOT6 1500 +#define BANK_SLOT7 1600 +#define BANK_SLOT8 1700 +#define ATTUNED 1 +#define ATTUNEABLE 2 +#define ARTIFACT 4 +#define LORE 8 +#define TEMPORARY 16 +#define NO_TRADE 32 +#define NO_VALUE 64 +#define NO_ZONE 128 +#define NO_DESTROY 256 +#define CRAFTED 512 +#define GOOD_ONLY 1024 +#define EVIL_ONLY 2048 + +#define ITEM_WIELD_TYPE_DUAL 1 +#define ITEM_WIELD_TYPE_SINGLE 2 +#define ITEM_WIELD_TYPE_TWO_HAND 4 + +#define ITEM_TYPE_NORMAL 0 +#define ITEM_TYPE_WEAPON 1 +#define ITEM_TYPE_RANGED 2 +#define ITEM_TYPE_ARMOR 3 +#define ITEM_TYPE_SHIELD 4 +#define ITEM_TYPE_BAG 5 +#define ITEM_TYPE_SKILL 6 +#define ITEM_TYPE_RECIPE 7 +#define ITEM_TYPE_FOOD 8 +#define ITEM_TYPE_BAUBLE 9 +#define ITEM_TYPE_HOUSE 10 +#define ITEM_TYPE_THROWN 11 +#define ITEM_TYPE_HOUSE_CONTAINER 12 +#define ITEM_TYPE_BOOK 13 +#define ITEM_TYPE_ADORNMENT 14 +#define ITEM_TYPE_PATTERN 15 +#define ITEM_TYPE_ARMORSET 16 + +#define ITEM_MENU_TYPE_GENERIC 1 +#define ITEM_MENU_TYPE_EQUIP 2 +#define ITEM_MENU_TYPE_BAG 4 +#define ITEM_MENU_TYPE_HOUSE 8 +#define ITEM_MENU_TYPE_SCRIBE 32 +#define ITEM_MENU_TYPE_INVALID 128 +#define ITEM_MENU_TYPE_BROKEN 512 +#define ITEM_MENU_TYPE_ATTUNED 2048 +#define ITEM_MENU_TYPE_ATTUNEABLE 4096 +#define ITEM_MENU_TYPE_BOOK 8192 +#define ITEM_MENU_TYPE_DISPLAY_CHARGES 16384 +#define ITEM_MENU_TYPE_NAMEPET 65536 +#define ITEM_MENU_TYPE_CONSUME 262144 +#define ITEM_MENU_TYPE_USE 524288 +#define ITEM_MENU_TYPE_DRINK 8388608 +#define ITEM_MENU_TYPE_REDEEM 536870912 + +#define ITEM_TAG_UNCOMMON 3 //tier tags +#define ITEM_TAG_TREASURED 4 +#define ITEM_TAG_LEGENDARY 7 +#define ITEM_TAG_FABLED 9 +#define ITEM_TAG_MYTHICAL 12 + +#define ITEM_BROKER_TYPE_ANY 0xFFFFFFFF +#define ITEM_BROKER_TYPE_ADORNMENT 134217728 +#define ITEM_BROKER_TYPE_AMMO 1024 +#define ITEM_BROKER_TYPE_ATTUNEABLE 16384 +#define ITEM_BROKER_TYPE_BAG 2048 +#define ITEM_BROKER_TYPE_BAUBLE 16777216 +#define ITEM_BROKER_TYPE_BOOK 128 +#define ITEM_BROKER_TYPE_CHAINARMOR 2097152 +#define ITEM_BROKER_TYPE_CLOAK 1073741824 +#define ITEM_BROKER_TYPE_CLOTHARMOR 524288 +#define ITEM_BROKER_TYPE_COLLECTABLE 67108864 +#define ITEM_BROKER_TYPE_CRUSHWEAPON 4 +#define ITEM_BROKER_TYPE_DRINK 131072 +#define ITEM_BROKER_TYPE_FOOD 4096 +#define ITEM_BROKER_TYPE_HOUSEITEM 512 +#define ITEM_BROKER_TYPE_JEWELRY 262144 +#define ITEM_BROKER_TYPE_LEATHERARMOR 1048576 +#define ITEM_BROKER_TYPE_LORE 8192 +#define ITEM_BROKER_TYPE_MISC 1 +#define ITEM_BROKER_TYPE_PIERCEWEAPON 8 +#define ITEM_BROKER_TYPE_PLATEARMOR 4194304 +#define ITEM_BROKER_TYPE_POISON 65536 +#define ITEM_BROKER_TYPE_POTION 32768 +#define ITEM_BROKER_TYPE_RECIPEBOOK 8388608 +#define ITEM_BROKER_TYPE_SALESDISPLAY 33554432 +#define ITEM_BROKER_TYPE_SHIELD 32 +#define ITEM_BROKER_TYPE_SLASHWEAPON 2 +#define ITEM_BROKER_TYPE_SPELLSCROLL 64 +#define ITEM_BROKER_TYPE_TINKERED 268435456 +#define ITEM_BROKER_TYPE_TRADESKILL 256 + +#define ITEM_BROKER_SLOT_ANY 0xFFFFFFFF +#define ITEM_BROKER_SLOT_AMMO 65536 +#define ITEM_BROKER_SLOT_CHARM 524288 +#define ITEM_BROKER_SLOT_CHEST 32 +#define ITEM_BROKER_SLOT_CLOAK 262144 +#define ITEM_BROKER_SLOT_DRINK 2097152 +#define ITEM_BROKER_SLOT_EARS 4096 +#define ITEM_BROKER_SLOT_FEET 1024 +#define ITEM_BROKER_SLOT_FOOD 1048576 +#define ITEM_BROKER_SLOT_FOREARMS 128 +#define ITEM_BROKER_SLOT_HANDS 256 +#define ITEM_BROKER_SLOT_HEAD 16 +#define ITEM_BROKER_SLOT_LEGS 512 +#define ITEM_BROKER_SLOT_NECK 8192 +#define ITEM_BROKER_SLOT_PRIMARY 1 +#define ITEM_BROKER_SLOT_PRIMARY_2H 2 +#define ITEM_BROKER_SLOT_RANGE_WEAPON 32768 +#define ITEM_BROKER_SLOT_RING 2048 +#define ITEM_BROKER_SLOT_SECONDARY 8 +#define ITEM_BROKER_SLOT_SHOULDERS 64 +#define ITEM_BROKER_SLOT_WAIST 131072 +#define ITEM_BROKER_SLOT_WRIST 16384 + +#define ITEM_BROKER_STAT_TYPE_NONE 0 +#define ITEM_BROKER_STAT_TYPE_DEF 2 +#define ITEM_BROKER_STAT_TYPE_STR 4 +#define ITEM_BROKER_STAT_TYPE_STA 8 +#define ITEM_BROKER_STAT_TYPE_AGI 16 +#define ITEM_BROKER_STAT_TYPE_WIS 32 +#define ITEM_BROKER_STAT_TYPE_INT 64 +#define ITEM_BROKER_STAT_TYPE_HEALTH 128 +#define ITEM_BROKER_STAT_TYPE_POWER 256 +#define ITEM_BROKER_STAT_TYPE_HEAT 512 +#define ITEM_BROKER_STAT_TYPE_COLD 1024 +#define ITEM_BROKER_STAT_TYPE_MAGIC 2048 +#define ITEM_BROKER_STAT_TYPE_MENTAL 4096 +#define ITEM_BROKER_STAT_TYPE_DIVINE 8192 +#define ITEM_BROKER_STAT_TYPE_POISON 16384 +#define ITEM_BROKER_STAT_TYPE_DISEASE 32768 +#define ITEM_BROKER_STAT_TYPE_CRUSH 65536 +#define ITEM_BROKER_STAT_TYPE_SLASH 131072 +#define ITEM_BROKER_STAT_TYPE_PIERCE 262144 +#define ITEM_BROKER_STAT_TYPE_CRITICAL 524288 +#define ITEM_BROKER_STAT_TYPE_DBL_ATTACK 1048576 +#define ITEM_BROKER_STAT_TYPE_ABILITY_MOD 2097152 +#define ITEM_BROKER_STAT_TYPE_POTENCY 4194304 + + + +#define OVERFLOW_SLOT 0xFFFFFFFE +#define SLOT_INVALID 0xFFFF + +#define ITEM_STAT_STR 0 +#define ITEM_STAT_STA 1 +#define ITEM_STAT_AGI 2 +#define ITEM_STAT_WIS 3 +#define ITEM_STAT_INT 4 + +#define ITEM_STAT_ADORNING 100 +#define ITEM_STAT_AGGRESSION 101 +#define ITEM_STAT_ARTIFICING 102 +#define ITEM_STAT_ARTISTRY 103 +#define ITEM_STAT_CHEMISTRY 104 +#define ITEM_STAT_CRUSHING 105 +#define ITEM_STAT_DEFENSE 106 +#define ITEM_STAT_DEFLECTION 107 +#define ITEM_STAT_DISRUPTION 108 +#define ITEM_STAT_FISHING 109 +#define ITEM_STAT_FLETCHING 110 +#define ITEM_STAT_FOCUS 111 +#define ITEM_STAT_FORESTING 112 +#define ITEM_STAT_GATHERING 113 +#define ITEM_STAT_METAL_SHAPING 114 +#define ITEM_STAT_METALWORKING 115 +#define ITEM_STAT_MINING 116 +#define ITEM_STAT_MINISTRATION 117 +#define ITEM_STAT_ORDINATION 118 +#define ITEM_STAT_PARRY 119 +#define ITEM_STAT_PIERCING 120 +#define ITEM_STAT_RANGED 121 +#define ITEM_STAT_SAFE_FALL 122 +#define ITEM_STAT_SCRIBING 123 +#define ITEM_STAT_SCULPTING 124 +#define ITEM_STAT_SLASHING 125 +#define ITEM_STAT_SUBJUGATION 126 +#define ITEM_STAT_SWIMMING 127 +#define ITEM_STAT_TAILORING 128 +#define ITEM_STAT_TINKERING 129 +#define ITEM_STAT_TRANSMUTING 130 +#define ITEM_STAT_TRAPPING 131 +#define ITEM_STAT_WEAPON_SKILLS 132 + +#define ITEM_STAT_VS_PHYSICAL 200 +#define ITEM_STAT_VS_HEAT 201 //elemental +#define ITEM_STAT_VS_POISON 202 //noxious +#define ITEM_STAT_VS_MAGIC 203 //arcane +#define ITEM_STAT_VS_DROWNING 210 +#define ITEM_STAT_VS_FALLING 211 +#define ITEM_STAT_VS_PAIN 212 +#define ITEM_STAT_VS_MELEE 213 + +#define ITEM_STAT_VS_SLASH 204 +#define ITEM_STAT_VS_CRUSH 205 +#define ITEM_STAT_VS_PIERCE 206 +//#define ITEM_STAT_VS_HEAT 203 //just so no build error +#define ITEM_STAT_VS_COLD 207 +//#define ITEM_STAT_VS_MAGIC 205 //just so no build error +#define ITEM_STAT_VS_MENTAL 208 +#define ITEM_STAT_VS_DIVINE 209 +#define ITEM_STAT_VS_DISEASE 214 +//#define ITEM_STAT_VS_POISON 209 //just so no build error +//#define ITEM_STAT_VS_DROWNING 210 //just so no build error +//#define ITEM_STAT_VS_FALLING 211 //just so no build error +//#define ITEM_STAT_VS_PAIN 212 //just so no build error +//#define ITEM_STAT_VS_MELEE 213 //just so no build error + +#define ITEM_STAT_DMG_SLASH 300 +#define ITEM_STAT_DMG_CRUSH 301 +#define ITEM_STAT_DMG_PIERCE 302 +#define ITEM_STAT_DMG_HEAT 303 +#define ITEM_STAT_DMG_COLD 304 +#define ITEM_STAT_DMG_MAGIC 305 +#define ITEM_STAT_DMG_MENTAL 306 +#define ITEM_STAT_DMG_DIVINE 307 +#define ITEM_STAT_DMG_DISEASE 308 +#define ITEM_STAT_DMG_POISON 309 +#define ITEM_STAT_DMG_DROWNING 310 +#define ITEM_STAT_DMG_FALLING 311 +#define ITEM_STAT_DMG_PAIN 312 +#define ITEM_STAT_DMG_MELEE 313 + +#define ITEM_STAT_HEALTH 500 +#define ITEM_STAT_POWER 501 +#define ITEM_STAT_CONCENTRATION 502 + +#define ITEM_STAT_HPREGEN 600 +#define ITEM_STAT_MANAREGEN 601 +#define ITEM_STAT_HPREGENPPT 602 +#define ITEM_STAT_MPREGENPPT 603 +#define ITEM_STAT_COMBATHPREGENPPT 604 +#define ITEM_STAT_COMBATMPREGENPPT 605 +#define ITEM_STAT_MAXHP 606 +#define ITEM_STAT_MAXHPPERC 607 +#define ITEM_STAT_SPEED 608 +#define ITEM_STAT_SLOW 609 +#define ITEM_STAT_MOUNTSPEED 610 +#define ITEM_STAT_MOUNTAIRSPEED 611 +#define ITEM_STAT_OFFENSIVESPEED 612 +#define ITEM_STAT_ATTACKSPEED 613 +#define ITEM_STAT_MAXMANA 614 +#define ITEM_STAT_MAXMANAPERC 615 +#define ITEM_STAT_MAXATTPERC 616 +#define ITEM_STAT_BLURVISION 617 +#define ITEM_STAT_MAGICLEVELIMMUNITY 618 +#define ITEM_STAT_HATEGAINMOD 619 +#define ITEM_STAT_COMBATEXPMOD 620 +#define ITEM_STAT_TRADESKILLEXPMOD 621 +#define ITEM_STAT_ACHIEVEMENTEXPMOD 622 +#define ITEM_STAT_SIZEMOD 623 +#define ITEM_STAT_DPS 624 +#define ITEM_STAT_STEALTH 625 +#define ITEM_STAT_INVIS 626 +#define ITEM_STAT_SEESTEALTH 627 +#define ITEM_STAT_SEEINVIS 628 +#define ITEM_STAT_EFFECTIVELEVELMOD 629 +#define ITEM_STAT_RIPOSTECHANCE 630 +#define ITEM_STAT_PARRYCHANCE 631 +#define ITEM_STAT_DODGECHANCE 632 +#define ITEM_STAT_AEAUTOATTACKCHANCE 633 +#define ITEM_STAT_MULTIATTACKCHANCE 634 +#define ITEM_STAT_SPELLMULTIATTACKCHANCE 635 +#define ITEM_STAT_FLURRY 636 +#define ITEM_STAT_MELEEDAMAGEMULTIPLIER 637 +#define ITEM_STAT_EXTRAHARVESTCHANCE 638 +#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 639 +#define ITEM_STAT_ITEMHPREGENPPT 640 +#define ITEM_STAT_ITEMPPREGENPPT 641 +#define ITEM_STAT_MELEECRITCHANCE 642 +#define ITEM_STAT_CRITAVOIDANCE 643 +#define ITEM_STAT_BENEFICIALCRITCHANCE 644 +#define ITEM_STAT_CRITBONUS 645 +#define ITEM_STAT_POTENCY 646 +#define ITEM_STAT_UNCONSCIOUSHPMOD 647 +#define ITEM_STAT_ABILITYREUSESPEED 648 +#define ITEM_STAT_ABILITYRECOVERYSPEED 649 +#define ITEM_STAT_ABILITYCASTINGSPEED 650 +#define ITEM_STAT_SPELLREUSESPEED 651 +#define ITEM_STAT_MELEEWEAPONRANGE 652 +#define ITEM_STAT_RANGEDWEAPONRANGE 653 +#define ITEM_STAT_FALLINGDAMAGEREDUCTION 654 +#define ITEM_STAT_RIPOSTEDAMAGE 655 +#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 656 +#define ITEM_STAT_MOVEMENTWEAVE 657 +#define ITEM_STAT_COMBATHPREGEN 658 +#define ITEM_STAT_COMBATMANAREGEN 659 +#define ITEM_STAT_CONTESTSPEEDBOOST 660 +#define ITEM_STAT_TRACKINGAVOIDANCE 661 +#define ITEM_STAT_STEALTHINVISSPEEDMOD 662 +#define ITEM_STAT_LOOT_COIN 663 +#define ITEM_STAT_ARMORMITIGATIONINCREASE 664 +#define ITEM_STAT_AMMOCONSERVATION 665 +#define ITEM_STAT_STRIKETHROUGH 666 +#define ITEM_STAT_STATUSBONUS 667 +#define ITEM_STAT_ACCURACY 668 +#define ITEM_STAT_COUNTERSTRIKE 669 +#define ITEM_STAT_SHIELDBASH 670 +#define ITEM_STAT_WEAPONDAMAGEBONUS 671 +#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 672 +#define ITEM_STAT_CRITICALMITIGATION 673 +#define ITEM_STAT_PVPTOUGHNESS 674 +#define ITEM_STAT_STAMINABONUS 675 +#define ITEM_STAT_WISDOMITBONUS 676 +#define ITEM_STAT_HEALRECEIVE 677 +#define ITEM_STAT_HEALRECEIVEPERC 678 +#define ITEM_STAT_PVPCRITICALMITIGATION 679 +#define ITEM_STAT_BASEAVOIDANCEBONUS 680 + + +//#define ITEM_STAT_HPREGEN 600 +//#define ITEM_STAT_MANAREGEN 601 +//#define ITEM_STAT_HPREGENPPT 602 +//#define ITEM_STAT_MPREGENPPT 603 +//#define ITEM_STAT_COMBATHPREGENPPT 604 +//#define ITEM_STAT_COMBATMPREGENPPT 605 +//#define ITEM_STAT_MAXHP 606 +//#define ITEM_STAT_MAXHPPERC 607 +//#define ITEM_STAT_SPEED 608 +//#define ITEM_STAT_SLOW 609 +//#define ITEM_STAT_MOUNTSPEED 610 +//#define ITEM_STAT_OFFENSIVESPEED 611 +//#define ITEM_STAT_ATTACKSPEED 612 +//#define ITEM_STAT_MAXMANA 613 +//#define ITEM_STAT_MAXMANAPERC 614 +//#define ITEM_STAT_MAXATTPERC 615 +//#define ITEM_STAT_BLURVISION 616 +//#define ITEM_STAT_MAGICLEVELIMMUNITY 617 +//#define ITEM_STAT_HATEGAINMOD 618 +//#define ITEM_STAT_COMBATEXPMOD 619 +//#define ITEM_STAT_TRADESKILLEXPMOD 620 +//#define ITEM_STAT_ACHIEVEMENTEXPMOD 621 +//#define ITEM_STAT_SIZEMOD 622 +//#define ITEM_STAT_UNKNOWN 623 +//#define ITEM_STAT_STEALTH 624 +//#define ITEM_STAT_INVIS 625 +//#define ITEM_STAT_SEESTEALTH 626 +//#define ITEM_STAT_SEEINVIS 627 +//#define ITEM_STAT_EFFECTIVELEVELMOD 628 +//#define ITEM_STAT_RIPOSTECHANCE 629 +//#define ITEM_STAT_PARRYCHANCE 630 +//#define ITEM_STAT_DODGECHANCE 631 +//#define ITEM_STAT_AEAUTOATTACKCHANCE 632 +//#define ITEM_STAT_DOUBLEATTACKCHANCE 633 +//#define ITEM_STAT_RANGEDDOUBLEATTACKCHANCE 634 +//#define ITEM_STAT_SPELLDOUBLEATTACKCHANCE 635 +//#define ITEM_STAT_FLURRY 636 +//#define ITEM_STAT_EXTRAHARVESTCHANCE 637 +//#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 638 +#define ITEM_STAT_DEFLECTIONCHANCE 400 //just so no build error +//#define ITEM_STAT_ITEMHPREGENPPT 640 +//#define ITEM_STAT_ITEMPPREGENPPT 641 +//#define ITEM_STAT_MELEECRITCHANCE 642 +//#define ITEM_STAT_RANGEDCRITCHANCE 643 +//#define ITEM_STAT_DMGSPELLCRITCHANCE 644 +//#define ITEM_STAT_HEALSPELLCRITCHANCE 645 +//#define ITEM_STAT_MELEECRITBONUS 646 +//#define ITEM_STAT_RANGEDCRITBONUS 647 +//#define ITEM_STAT_DMGSPELLCRITBONUS 648 +//#define ITEM_STAT_HEALSPELLCRITBONUS 649 +//#define ITEM_STAT_UNCONSCIOUSHPMOD 650 +//#define ITEM_STAT_SPELLTIMEREUSEPCT 651 +//#define ITEM_STAT_SPELLTIMERECOVERYPCT 652 +//#define ITEM_STAT_SPELLTIMECASTPCT 653 +//#define ITEM_STAT_MELEEWEAPONRANGE 654 +//#define ITEM_STAT_RANGEDWEAPONRANGE 655 +//#define ITEM_STAT_FALLINGDAMAGEREDUCTION 656 +//#define ITEM_STAT_SHIELDEFFECTIVENESS 657 +//#define ITEM_STAT_RIPOSTEDAMAGE 658 +//#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 659 +//#define ITEM_STAT_MOVEMENTWEAVE 660 +//#define ITEM_STAT_COMBATHPREGEN 661 +//#define ITEM_STAT_COMBATMANAREGEN 662 +//#define ITEM_STAT_CONTESTSPEEDBOOST 663 +//#define ITEM_STAT_TRACKINGAVOIDANCE 664 +//#define ITEM_STAT_STEALTHINVISSPEEDMOD 665 +//#define ITEM_STAT_LOOT_COIN 666 +//#define ITEM_STAT_ARMORMITIGATIONINCREASE 667 +//#define ITEM_STAT_AMMOCONSERVATION 668 +//#define ITEM_STAT_STRIKETHROUGH 669 +//#define ITEM_STAT_STATUSBONUS 670 +//#define ITEM_STAT_ACCURACY 671 +//#define ITEM_STAT_COUNTERSTRIKE 672 +//#define ITEM_STAT_SHIELDBASH 673 +//#define ITEM_STAT_WEAPONDAMAGEBONUS 674 +//#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 675 +//#define ITEM_STAT_CRITICALMITIGATION 676 +//#define ITEM_STAT_COMBATARTDAMAGE 677 +//#define ITEM_STAT_SPELLDAMAGE 678 +//#define ITEM_STAT_HEALAMOUNT 679 +//#define ITEM_STAT_TAUNTAMOUNT 680 + +#define ITEM_STAT_SPELL_DAMAGE 700 +#define ITEM_STAT_HEAL_AMOUNT 701 +#define ITEM_STAT_SPELL_AND_HEAL 702 +#define ITEM_STAT_COMBAT_ART_DAMAGE 703 +#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE 704 +#define ITEM_STAT_TAUNT_AMOUNT 705 +#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE 706 +#define ITEM_STAT_ABILITY_MODIFIER 707 + + + +#pragma pack(1) +struct ItemStatsValues{ + sint16 str; + sint16 sta; + sint16 agi; + sint16 wis; + sint16 int_; + sint16 vs_slash; + sint16 vs_crush; + sint16 vs_pierce; + sint16 vs_heat; + sint16 vs_cold; + sint16 vs_magic; + sint16 vs_mental; + sint16 vs_divine; + sint16 vs_disease; + sint16 vs_poison; + sint16 health; + sint16 power; + sint8 concentration; + sint16 ability_modifier; + sint16 criticalmitigation; + sint16 extrashieldblockchance; + sint16 beneficialcritchance; + sint16 critbonus; + sint16 potency; + sint16 hategainmod; + sint16 abilityreusespeed; + sint16 abilitycastingspeed; + sint16 abilityrecoveryspeed; + sint16 spellreusespeed; + sint16 spellmultiattackchance; + sint16 dps; + sint16 attackspeed; + sint16 multiattackchance; + sint16 aeautoattackchance; + sint16 strikethrough; + sint16 accuracy; + sint16 offensivespeed; +}; +struct ItemCore{ + int32 item_id; + sint32 soe_id; + int32 bag_id; + sint32 inv_slot_id; + sint16 slot_id; + int8 index; + int16 icon; + int16 count; + int8 tier; + int8 num_slots; + int32 unique_id; + int8 num_free_slots; + int16 recommended_level; + bool item_locked; +}; +#pragma pack() +struct ItemStat{ + string stat_name; + int8 stat_type; + sint16 stat_subtype; + int16 stat_type_combined; + float value; +}; +struct ItemLevelOverride{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemClass{ + int8 adventure_class; + int8 tradeskill_class; + int16 level; +}; +struct ItemAppearance{ + int16 type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; +}; +class PlayerItemList; +class Item{ +public: + #pragma pack(1) + struct ItemStatString{ + EQ2_8BitString stat_string; + }; + struct Generic_Info{ + int8 show_name; + int8 creator_flag; + int16 item_flags; + int8 condition; + int32 weight; // num/10 + int32 skill_req1; + int32 skill_req2; + int16 skill_min; + int8 item_type; //0=normal, 1=weapon, 2=range, 3=armor, 4=shield, 5=bag, 6=scroll, 7=recipe, 8=food, 9=bauble, 10=house item, 11=thrown, 12=house container, 13=adormnet, 14=??, 16=profile, 17=patter set, 18=item set, 19=book, 20=decoration, 21=dungeon maker, 22=marketplace + int16 appearance_id; + int8 appearance_red; + int8 appearance_green; + int8 appearance_blue; + int8 appearance_highlight_red; + int8 appearance_highlight_green; + int8 appearance_highlight_blue; + int8 collectable; + int32 offers_quest_id; + int32 part_of_quest_id; + int16 max_charges; + int8 display_charges; + int64 adventure_classes; + int64 tradeskill_classes; + int16 adventure_default_level; + int16 tradeskill_default_level; + int8 usable; + }; + struct Armor_Info { + int16 mitigation_low; + int16 mitigation_high; + }; + struct Weapon_Info { + int16 wield_type; + int16 damage_low1; + int16 damage_high1; + int16 damage_low2; + int16 damage_high2; + int16 damage_low3; + int16 damage_high3; + int16 delay; + float rating; + }; + struct Shield_Info { + Armor_Info armor_info; + }; + struct Ranged_Info { + Weapon_Info weapon_info; + int16 range_low; + int16 range_high; + }; + struct Bag_Info { + int8 num_slots; + int16 weight_reduction; + }; + struct Food_Info{ + int8 type; //0=water, 1=food + int8 level; + float duration; + int8 satiation; + }; + struct Bauble_Info{ + int16 cast; + int16 recovery; + int32 duration; + float recast; + int8 display_slot_optional; + int8 display_cast_time; + int8 display_bauble_type; + float effect_radius; + int32 max_aoe_targets; + int8 display_until_cancelled; + }; + struct Book_Info{ + int8 language; + EQ2_16BitString author; + EQ2_16BitString title; + }; + struct Skill_Info{ + int32 spell_id; + int32 spell_tier; + }; + struct House_Info{ + int32 status_rent_reduction; + }; + struct HouseContainer_Info{ + int16 disallowed_types; + int16 allowed_types; + int8 num_slots; + }; + struct RecipeBook_Info{ + vector recipes; + int8 uses; + }; + struct Thrown_Info{ + sint32 range; + sint32 damage_modifier; + float hit_bonus; + int32 damage_type; + }; + struct ItemEffect{ + EQ2_16BitString effect; + int8 percentage; + int8 subbulletflag; + }; + #pragma pack() + Item(); + Item(Item* in_item); + ~Item(); + string lowername; + string name; + string description; + int8 stack_count; + int32 sell_price; + int32 max_sell_value; + bool save_needed; + int8 weapon_type; + string adornment; + string creator; + vector item_stats; + vector item_string_stats; + vector item_level_overrides; + vector item_effects; + Generic_Info generic_info; + Weapon_Info* weapon_info; + Ranged_Info* ranged_info; + Armor_Info* armor_info; + Bag_Info* bag_info; + Food_Info* food_info; + Bauble_Info* bauble_info; + Book_Info* book_info; + Skill_Info* skill_info; + RecipeBook_Info* recipebook_info; + Thrown_Info* thrown_info; + vector slot_data; + ItemCore details; + int32 spell_id; + int8 spell_tier; + string item_script; + + void AddEffect(string effect, int8 percentage, int8 subbulletflag); + int32 GetMaxSellValue(); + void SetMaxSellValue(int32 val); + void SetItem(Item* old_item); + int16 GetOverrideLevel(int8 adventure_class, int8 tradeskill_class); + void AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level); + void AddLevelOverride(ItemLevelOverride* class_); + bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + bool CheckClass(int8 adventure_class, int8 tradeskill_class); + bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue); + void SetAppearance(ItemAppearance* appearance); + void AddStat(ItemStat* in_stat); + void AddStatString(ItemStatString* in_stat); + void AddStat(int8 type, int16 subtype, float value, char* name = 0); + void SetWeaponType(int8 type); + int8 GetWeaponType(); + bool HasSlot(int8 slot, int8 slot2 = 255); + bool IsNormal(); + bool IsWeapon(); + bool IsArmor(); + bool IsRanged(); + bool IsBag(); + bool IsFood(); + bool IsBauble(); + bool IsSkill(); + bool IsHouseItem(); + bool IsHouseContainer(); + bool IsShield(); + bool IsAdornment(); + bool IsAmmo(); + bool IsBook(); + bool IsChainArmor(); + bool IsClothArmor(); + bool IsCollectable(); + bool IsCloak(); + bool IsCrushWeapon(); + bool IsFoodFood(); + bool IsFoodDrink(); + bool IsJewelry(); + bool IsLeatherArmor(); + bool IsMisc(); + bool IsPierceWeapon(); + bool IsPlateArmor(); + bool IsPoison(); + bool IsPotion(); + bool IsRecipeBook(); + bool IsSalesDisplay(); + bool IsSlashWeapon(); + bool IsSpellScroll(); + bool IsTinkered(); + bool IsTradeskill(); + bool IsThrown(); + void SetItemScript(string name); + const char* GetItemScript(); + int32 CalculateRepairCost(); + + void SetItemType(int8 in_type); + void serialize(PacketStruct* packet, bool show_name = false, Player* player = 0, int16 packet_type = 0, int8 subtype = 0, bool loot_item = false); + EQ2Packet* serialize(int16 version, bool show_name = false, Player* player = 0, bool include_twice = true, int16 packet_type = 0, int8 subtype = 0, bool merchant_item = false, bool loot_item = false); + PacketStruct* PrepareItem(int16 version, bool merchant_item = false, bool loot_item = false); + bool CheckFlag(int32 flag); + void AddSlot(int8 slot_id); + void SetSlots(int32 slots); + bool needs_deletion; +}; +class MasterItemList{ +public: + ~MasterItemList(); + map items; + + Item* GetItem(int32 id); + Item* GetItemByName(const char *name); + ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0); + ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0); + vector* GetItems(string name, int32 itype, int32 ltype, int32 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass); + vector* GetItems(map criteria); + void AddItem(Item* item); + bool IsBag(int32 item_id); + void RemoveAll(); + static int32 NextUniqueID(); + static void ResetUniqueID(int32 new_id); + static int32 next_unique_id; +}; +class PlayerItemList { +public: + PlayerItemList(); + ~PlayerItemList(); +// int16 number; + map indexed_items; + map > items; +// map< int8, Item* > inv_items; +// map< int8, Item* > bank_items; + bool SharedBankAddAllowed(Item* item); + vector* GetItemsFromBagID(sint32 bag_id); + vector* GetItemsInBag(Item* bag); + Item* GetBag(int8 inventory_slot, bool lock = true); + bool HasItem(int32 id, bool include_bank = false); + Item* GetItemFromIndex(int32 index); + void MoveItem(Item* item, sint32 inv_slot, int16 slot, bool erase_old = true); + bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 charges); + Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true); + Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true); + bool AssignItemToFreeSlot(Item* item); + int16 GetNumberOfFreeSlots(); + int16 GetNumberOfItems(); + bool HasFreeSlot(); + bool HasFreeBagSlot(); + void DestroyItem(int16 index); + Item* CanStack(Item* item, bool include_bank = false); + + void RemoveItem(Item* item, bool delete_item = false); + void AddItem(Item* item); + + Item* GetItem(sint32 bag_slot, int16 slot); + + EQ2Packet* serialize(Player* player, int16 version); + uchar* xor_packet; + uchar* orig_packet; + map* GetAllItems(); + bool HasFreeBankSlot(); + int8 FindFreeBankSlot(); + + ///Get the first free slot and stor them in the provided variables + ///Will contain the bag id of the first free spot + ///Will contain the slot id of the first free slot + ///True if a free slot was found + bool GetFirstFreeSlot(sint32* bag_id, sint16* slot); +private: + void Stack(Item* orig_item, Item* item); + Mutex MPlayerItems; + int16 packet_count; +}; +class OverFlowItemList : public PlayerItemList { +public: + bool OverFlowSlotFull(); + int8 GetNextOverFlowSlot(); + bool AddItem(Item* item); + Item* GetOverFlowItem(); +}; +class EquipmentItemList{ +public: + EquipmentItemList(); + EquipmentItemList(const EquipmentItemList& list); + ~EquipmentItemList(); + Item* items[NUM_SLOTS]; + + vector* GetAllEquippedItems(); + + bool HasItem(int32 id); + int8 GetNumberOfItems(); + Item* GetItemFromUniqueID(int32 item_id); + Item* GetItemFromItemID(int32 item_id); + void SetItem(int8 slot_id, Item* item); + void RemoveItem(int8 slot, bool delete_item = false); + Item* GetItem(int8 slot_id); + bool AddItem(int8 slot, Item* item); + bool CheckEquipSlot(Item* tmp, int8 slot); + bool CanItemBeEquippedInSlot(Item* tmp, int8 slot); + int8 GetFreeSlot(Item* tmp, int8 slot_id = 255); + ItemStatsValues* CalculateEquipmentBonuses(Entity* entity = 0); + EQ2Packet* serialize(int16 version); + uchar* xor_packet; + uchar* orig_packet; +private: + Mutex MEquipmentItems; +}; + +#endif + diff --git a/source/WorldServer/Items/Items_ToV.h b/source/WorldServer/Items/Items_ToV.h new file mode 100644 index 0000000..30bfbc2 --- /dev/null +++ b/source/WorldServer/Items/Items_ToV.h @@ -0,0 +1,211 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +//Item Stat defines for ToV Client + +//Stat type 6 (blue stats) +#define TOV_ITEM_STAT_HPREGEN 600 +#define TOV_ITEM_STAT_MANAREGEN 601 +#define TOV_ITEM_STAT_HPREGENPPT 602 +#define TOV_ITEM_STAT_MPREGENPPT 603 +#define TOV_ITEM_STAT_COMBATHPREGENPPT 604 +#define TOV_ITEM_STAT_COMBATMPREGENPPT 605 +#define TOV_ITEM_STAT_MAXHP 606 +#define TOV_ITEM_STAT_MAXHPPERC 607 +#define TOV_ITEM_STAT_MAXHPPERCFINAL 608 +#define TOV_ITEM_STAT_SPEED 609 +#define TOV_ITEM_STAT_SLOW 610 +#define TOV_ITEM_STAT_MOUNTSPEED 611 +#define TOV_ITEM_STAT_MOUNTAIRSPEED 612 +#define TOV_ITEM_STAT_LEAPSPEED 613 +#define TOV_ITEM_STAT_LEAPTIME 614 +#define TOV_ITEM_STAT_GLIDEEFFICIENCY 615 +#define TOV_ITEM_STAT_OFFENSIVESPEED 616 +#define TOV_ITEM_STAT_ATTACKSPEED 617 +#define TOV_ITEM_STAT_MAXMANA 618 +#define TOV_ITEM_STAT_MAXMANAPERC 619 +#define TOV_ITEM_STAT_MAXATTPERC 620 +#define TOV_ITEM_STAT_BLURVISION 621 +#define TOV_ITEM_STAT_MAGICLEVELIMMUNITY 622 +#define TOV_ITEM_STAT_HATEGAINMOD 623 +#define TOV_ITEM_STAT_COMBATEXPMOD 624 +#define TOV_ITEM_STAT_TRADESKILLEXPMOD 625 +#define TOV_ITEM_STAT_ACHIEVEMENTEXPMOD 626 +#define TOV_ITEM_STAT_SIZEMOD 627 +#define TOV_ITEM_STAT_DPS 628 +#define TOV_ITEM_STAT_STEALTH 629 +#define TOV_ITEM_STAT_INVIS 630 +#define TOV_ITEM_STAT_SEESTEALTH 631 +#define TOV_ITEM_STAT_SEEINVIS 632 +#define TOV_ITEM_STAT_EFFECTIVELEVELMOD 633 +#define TOV_ITEM_STAT_RIPOSTECHANCE 634 +#define TOV_ITEM_STAT_PARRYCHANCE 635 +#define TOV_ITEM_STAT_DODGECHANCE 636 +#define TOV_ITEM_STAT_AEAUTOATTACKCHANCE 637 +#define TOV_ITEM_STAT_MULTIATTACKCHANCE 638 //DOUBLEATTACKCHANCE +#define TOV_ITEM_STAT_SPELLMULTIATTACKCHANCE 639 +#define TOV_ITEM_STAT_FLURRY 640 +#define TOV_ITEM_STAT_MELEEDAMAGEMULTIPLIER 641 +#define TOV_ITEM_STAT_EXTRAHARVESTCHANCE 642 +#define TOV_ITEM_STAT_EXTRASHIELDBLOCKCHANCE 643 +#define TOV_ITEM_STAT_ITEMHPREGENPPT 644 +#define TOV_ITEM_STAT_ITEMPPREGENPPT 645 +#define TOV_ITEM_STAT_MELEECRITCHANCE 646 +#define TOV_ITEM_STAT_CRITAVOIDANCE 647 +#define TOV_ITEM_STAT_BENEFICIALCRITCHANCE 648 +#define TOV_ITEM_STAT_CRITBONUS 649 +#define TOV_ITEM_STAT_POTENCY 650 //BASEMODIFIER +#define TOV_ITEM_STAT_UNCONSCIOUSHPMOD 651 +#define TOV_ITEM_STAT_ABILITYREUSESPEED 652 //SPELLTIMEREUSEPCT +#define TOV_ITEM_STAT_ABILITYRECOVERYSPEED 653 //SPELLTIMERECOVERYPCT +#define TOV_ITEM_STAT_ABILITYCASTINGSPEED 654 //SPELLTIMECASTPCT +#define TOV_ITEM_STAT_SPELLREUSESPEED 655 //SPELLTIMEREUSESPELLONLY +#define TOV_ITEM_STAT_MELEEWEAPONRANGE 656 +#define TOV_ITEM_STAT_RANGEDWEAPONRANGE 657 +#define TOV_ITEM_STAT_FALLINGDAMAGEREDUCTION 658 +#define TOV_ITEM_STAT_RIPOSTEDAMAGE 659 +#define TOV_ITEM_STAT_MINIMUMDEFLECTIONCHANCE 660 +#define TOV_ITEM_STAT_MOVEMENTWEAVE 661 +#define TOV_ITEM_STAT_COMBATHPREGEN 662 +#define TOV_ITEM_STAT_COMBATMANAREGEN 663 +#define TOV_ITEM_STAT_CONTESTSPEEDBOOST 664 +#define TOV_ITEM_STAT_TRACKINGAVOIDANCE 665 +#define TOV_ITEM_STAT_STEALTHINVISSPEEDMOD 666 +#define TOV_ITEM_STAT_LOOT_COIN 667 +#define TOV_ITEM_STAT_ARMORMITIGATIONINCREASE 668 +#define TOV_ITEM_STAT_AMMOCONSERVATION 669 +#define TOV_ITEM_STAT_STRIKETHROUGH 670 +#define TOV_ITEM_STAT_STATUSBONUS 671 +#define TOV_ITEM_STAT_ACCURACY 672 +#define TOV_ITEM_STAT_COUNTERSTRIKE 673 +#define TOV_ITEM_STAT_SHIELDBASH 674 +#define TOV_ITEM_STAT_WEAPONDAMAGEBONUS 675 +#define TOV_ITEM_STAT_SPELLWEAPONDAMAGEBONUS 676 +#define TOV_ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY 677 +#define TOV_ITEM_STAT_ADDITIONALRIPOSTECHANCE 678 +#define TOV_ITEM_STAT_PVPTOUGHNESS 680 +#define TOV_ITEM_STAT_PVPLETHALITY 681 +#define TOV_ITEM_STAT_STAMINABONUS 682 +#define TOV_ITEM_STAT_WISDOMMITBONUS 683 +#define TOV_ITEM_STAT_HEALRECEIVE 684 +#define TOV_ITEM_STAT_HEALRECEIVEPERC 685 +#define TOV_ITEM_STAT_PVPCRITICALMITIGATION 686 +#define TOV_ITEM_STAT_BASEAVOIDANCEBONUS 687 +#define TOV_ITEM_STAT_INCOMBATSAVAGERYREGEN 688 +#define TOV_ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN 689 +#define TOV_ITEM_STAT_SAVAGERYREGEN 690 +#define TOV_ITEM_STAT_SAVAGERYGAINMOD 691 +#define TOV_ITEM_STAT_MAXSAVAGERYLEVEL 692 +#define TOV_ITEM_STAT_INCOMBATDISSONANCEREGEN 693 +#define TOV_ITEM_STAT_OUTOFCOMBATDISSONANCEREGEN 694 +#define TOV_ITEM_STAT_DISSONANCEREGEN 695 +#define TOV_ITEM_STAT_DISSONANCEGAINMOD 696 +#define TOV_ITEM_STAT_AEAUTOATTACKAVOID 697 +//End of stat type 6 (blue stats) + + +//Item stat type 5 (health,power,savagery,dissonance,concentration) +#define TOV_ITEM_STAT_HEALTH 500 +#define TOV_ITEM_STAT_POWER 501 +#define TOV_ITEM_STAT_CONCENTRATION 502 +#define TOV_ITEM_STAT_SAVAGERY 503 +#define TOV_ITEM_STAT_DISSONANCE 504 +//End of stat type 5 + +//Item stat type 3 (damage mods) +#define TOV_ITEM_STAT_DMG_SLASH 300 +#define TOV_ITEM_STAT_DMG_CRUSH 301 +#define TOV_ITEM_STAT_DMG_PIERCE 302 +#define TOV_ITEM_STAT_DMG_HEAT 303 +#define TOV_ITEM_STAT_DMG_COLD 304 +#define TOV_ITEM_STAT_DMG_MAGIC 305 +#define TOV_ITEM_STAT_DMG_MENTAL 306 +#define TOV_ITEM_STAT_DMG_DIVINE 307 +#define TOV_ITEM_STAT_DMG_DISEASE 308 +#define TOV_ITEM_STAT_DMG_POISON 309 +#define TOV_ITEM_STAT_DMG_DROWNING 310 +#define TOV_ITEM_STAT_DMG_FALLING 311 +#define TOV_ITEM_STAT_DMG_PAIN 312 +#define TOV_ITEM_STAT_DMG_MELEE 313 +//End of item stat 3 + +#define TOV_ITEM_STAT_DEFLECTIONCHANCE 400 //just so no build error + +// Other stats not listed above (not sent from the server), never send these to the client +// using type 8 as it is not used by the client as far as we know +#define TOV_ITEM_STAT_DURABILITY_MOD 800 +#define TOV_ITEM_STAT_DURABILITY_ADD 801 +#define TOV_ITEM_STAT_PROGRESS_ADD 802 +#define TOV_ITEM_STAT_PROGRESS_MOD 803 +#define TOV_ITEM_STAT_SUCCESS_MOD 804 +#define TOV_ITEM_STAT_CRIT_SUCCESS_MOD 805 +#define TOV_ITEM_STAT_EX_DURABILITY_MOD 806 +#define TOV_ITEM_STAT_EX_DURABILITY_ADD 807 +#define TOV_ITEM_STAT_EX_PROGRESS_MOD 808 +#define TOV_ITEM_STAT_EX_PROGRESS_ADD 809 +#define TOV_ITEM_STAT_EX_SUCCESS_MOD 810 +#define TOV_ITEM_STAT_EX_CRIT_SUCCESS_MOD 811 +#define TOV_ITEM_STAT_EX_CRIT_FAILURE_MOD 812 +#define TOV_ITEM_STAT_RARE_HARVEST_CHANCE 813 +#define TOV_ITEM_STAT_MAX_CRAFTING 814 +#define TOV_ITEM_STAT_COMPONENT_REFUND 815 +#define TOV_ITEM_STAT_BOUNTIFUL_HARVEST 816 + +#define TOV_ITEM_STAT_STR 0 +#define TOV_ITEM_STAT_STA 1 +#define TOV_ITEM_STAT_AGI 2 +#define TOV_ITEM_STAT_WIS 3 +#define TOV_ITEM_STAT_INT 4 + + + +#define TOV_ITEM_STAT_ADORNING 100 +#define TOV_ITEM_STAT_AGGRESSION 101 +#define TOV_ITEM_STAT_ARTIFICING 102 +#define TOV_ITEM_STAT_ARTISTRY 103 +#define TOV_ITEM_STAT_CHEMISTRY 104 +#define TOV_ITEM_STAT_CRUSHING 105 +#define TOV_ITEM_STAT_DEFENSE 106 +#define TOV_ITEM_STAT_DEFLECTION 107 +#define TOV_ITEM_STAT_DISRUPTION 108 +#define TOV_ITEM_STAT_FISHING 109 +#define TOV_ITEM_STAT_FLETCHING 110 +#define TOV_ITEM_STAT_FOCUS 111 +#define TOV_ITEM_STAT_FORESTING 112 +#define TOV_ITEM_STAT_GATHERING 113 +#define TOV_ITEM_STAT_METAL_SHAPING 114 +#define TOV_ITEM_STAT_METALWORKING 115 +#define TOV_ITEM_STAT_MINING 116 +#define TOV_ITEM_STAT_MINISTRATION 117 +#define TOV_ITEM_STAT_ORDINATION 118 +#define TOV_ITEM_STAT_PARRY 119 +#define TOV_ITEM_STAT_PIERCING 120 +#define TOV_ITEM_STAT_RANGED 121 +#define TOV_ITEM_STAT_SAFE_FALL 122 +#define TOV_ITEM_STAT_SCRIBING 123 +#define TOV_ITEM_STAT_SCULPTING 124 +#define TOV_ITEM_STAT_SLASHING 125 +#define TOV_ITEM_STAT_SUBJUGATION 126 +#define TOV_ITEM_STAT_SWIMMING 127 +#define TOV_ITEM_STAT_TAILORING 128 +#define TOV_ITEM_STAT_TINKERING 129 +#define TOV_ITEM_STAT_TRANSMUTING 130 +#define TOV_ITEM_STAT_TRAPPING 131 +#define TOV_ITEM_STAT_WEAPON_SKILLS 132 \ No newline at end of file diff --git a/source/WorldServer/Items/Loot.cpp b/source/WorldServer/Items/Loot.cpp new file mode 100644 index 0000000..112b714 --- /dev/null +++ b/source/WorldServer/Items/Loot.cpp @@ -0,0 +1,134 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Loot.h" +#include "../client.h" +#include "../../common/ConfigReader.h" +#include "../classes.h" +#include "../../common/debug.h" +#include "../zoneserver.h" +#include "../Skills.h" +#include "../classes.h" +#include "../World.h" +#include "../LuaInterface.h" +#include "../../common/Log.h" +#include "../Entity.h" +#include "../Rules/Rules.h" + +extern Classes classes; +extern ConfigReader configReader; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; + +// If we want to transfer functions to this file then we should just do it, but for now we don't need all this commented code here + +NPC* Entity::DropChest() { + // Check to see if treasure chests are disabled in the rules + if (rule_manager.GetGlobalRule(R_World, TreasureChestDisabled)->GetBool()) + return 0; + + if(GetChestDropTime()) { + return 0; // this is a chest! It doesn't drop itself! + } + + NPC* chest = 0; + + chest = new NPC(); + chest->SetAttackable(0); + chest->SetShowLevel(0); + chest->SetShowName(1); + chest->SetTargetable(1); + chest->SetLevel(GetLevel()); + chest->SetChestDropTime(); + chest->SetTotalHP(100); + chest->SetHP(100); + chest->SetAlive(false); + // Set the brain to a blank brain so it does nothing + chest->SetBrain(new BlankBrain(chest)); + // Set the x, y, z, heading, location (grid id) to that of the dead spawn + chest->SetZone(GetZone()); + // heading needs to be GetHeading() - 180 so the chest faces the proper way + chest->SetHeading(GetHeading() - 180); + // Set the primary command to loot and the secondary to disarm + chest->AddPrimaryEntityCommand("loot", rule_manager.GetGlobalRule(R_Loot, LootRadius)->GetFloat(), "loot", "", 0, 0); + chest->AddSecondaryEntityCommand("Disarm", rule_manager.GetGlobalRule(R_Loot, LootRadius)->GetFloat(), "Disarm", "", 0, 0); + // 32 = loot icon for the mouse + chest->SetIcon(32); + // 1 = show the right click menu + chest->SetShowCommandIcon(1); + chest->SetLootMethod(this->GetLootMethod(), this->GetLootRarity(), this->GetLootGroupID()); + chest->SetLootName(this->GetName()); + int8 highest_tier = 0; + vector::iterator itr; + for (itr = ((Spawn*)this)->GetLootItems()->begin(); itr != ((Spawn*)this)->GetLootItems()->end(); ) { + if ((*itr)->details.tier >= ITEM_TAG_COMMON && !(*itr)->IsBodyDrop()) { + if ((*itr)->details.tier > highest_tier) + highest_tier = (*itr)->details.tier; + + // Add the item to the chest + chest->AddLootItem((*itr)->details.item_id, (*itr)->details.count); + // Remove the item from the corpse + itr = ((Spawn*)this)->GetLootItems()->erase(itr); + } + else + itr++; + } + + /*4034 = small chest | 5864 = treasure chest | 5865 = ornate treasure chest | 4015 = exquisite chest*/ + if (highest_tier >= ITEM_TAG_FABLED) { + chest->SetModelType(4015); + chest->SetName("Exquisite Chest"); + } + else if (highest_tier >= ITEM_TAG_LEGENDARY) { + chest->SetModelType(5865); + chest->SetName("Ornate Chest"); + } + else if (highest_tier >= ITEM_TAG_TREASURED) { + chest->SetModelType(5864); + chest->SetName("Treasure Chest"); + } + else if (highest_tier >= ITEM_TAG_COMMON) { + chest->SetModelType(4034); + chest->SetName("Small Chest"); + } + else { + safe_delete(chest); + chest = nullptr; + } + + if (chest) { + chest->SetID(Spawn::NextID()); + chest->SetShowHandIcon(1); + chest->SetLocation(GetLocation()); + chest->SetX(GetX()); + chest->SetZ(GetZ()); + ((Entity*)chest)->GetInfoStruct()->set_flying_type(false); + chest->is_flying_creature = false; + if(GetMap()) { + auto loc = glm::vec3(GetX(), GetZ(), GetY()); + float new_z = FindBestZ(loc, nullptr); + chest->appearance.pos.Y = new_z; // don't use SetY here can cause a loop + } + else { + chest->appearance.pos.Y = GetY(); + } + } + + return chest; +} \ No newline at end of file diff --git a/source/WorldServer/Items/Loot.h b/source/WorldServer/Items/Loot.h new file mode 100644 index 0000000..de4f567 --- /dev/null +++ b/source/WorldServer/Items/Loot.h @@ -0,0 +1,23 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_LOOT__ +#define __EQ2_LOOT__ + +#endif diff --git a/source/WorldServer/Items/LootDB.cpp b/source/WorldServer/Items/LootDB.cpp new file mode 100644 index 0000000..374159a --- /dev/null +++ b/source/WorldServer/Items/LootDB.cpp @@ -0,0 +1,210 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "../World.h" + +extern World world; + +void WorldDatabase::LoadLoot(ZoneServer* zone) +{ + // First, clear previous loot tables... + zone->ClearLootTables(); + + DatabaseResult result; + int32 count = 0; + + if (database_new.Select(&result, "SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability FROM loottable")) { + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loading LootTables..."); + LootTable* table = 0; + // Load loottable from DB + while(result.Next()) { + int32 id = result.GetInt32Str("id"); + + table = new LootTable; + table->name = result.GetStringStr("name"); + table->mincoin = result.GetInt32Str("mincoin"); + table->maxcoin = result.GetInt32Str("maxcoin"); + table->maxlootitems = result.GetInt16Str("maxlootitems"); + table->lootdrop_probability = result.GetFloatStr("lootdrop_probability"); + table->coin_probability = result.GetFloatStr("coin_probability"); + zone->AddLootTable(id, table); + + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading LootTable '%s' (id: %u)", table->name.c_str(), id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---min_coin: %u, max_coin: %u, max_items: %i, prob: %.2f, coin_prob: %.2f", table->mincoin, table->maxcoin, table->maxlootitems, table->lootdrop_probability, table->coin_probability); + count++; + } + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u loot table%s.", count, count == 1 ? "" : "s"); + } + + // Now, load Loot Drops for configured loot tables + if (database_new.Select(&result, "SELECT loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed FROM lootdrop")) { + count = 0; + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loading LootDrops..."); + LootDrop* drop = 0; + + while(result.Next()) { + int32 id = result.GetInt32Str("loot_table_id"); + drop = new LootDrop; + drop->item_id = result.GetInt32Str("item_id"); + drop->item_charges = result.GetInt16Str("item_charges"); + drop->equip_item = (result.GetInt8Str("equip_item") == 1); + drop->probability = result.GetFloatStr("probability"); + drop->no_drop_quest_completed_id = result.GetInt32Str("no_drop_quest_completed"); + zone->AddLootDrop(id, drop); + + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading LootDrop item_id %u (tableID: %u", drop->item_id, id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---charges: %i, equip_item: %i, prob: %.2f", drop->item_charges, drop->equip_item, drop->probability); + count++; + } + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u loot drop%s.", count, count == 1 ? "" : "s"); + } + + // Finally, load loot tables into spawns that are set to use these loot tables + if (database_new.Select(&result, "SELECT spawn_id, loottable_id FROM spawn_loot")) { + count = 0; + LogWrite(LOOT__DEBUG, 0, "Loot", "--Assigning loot table(s) to spawn(s)..."); + + while(result.Next()) { + int32 spawn_id = result.GetInt32Str("spawn_id"); + int32 table_id = result.GetInt32Str("loottable_id"); + zone->AddSpawnLootList(spawn_id, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Adding loot table %u to spawn %u", table_id, spawn_id); + count++; + } + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u spawn loot list%s.", count, count == 1 ? "" : "s"); + } + + // Load global loot lists + LoadGlobalLoot(zone); +} + +void WorldDatabase::LoadGlobalLoot(ZoneServer* zone) { + LogWrite(LOOT__INFO, 0, "Loot", "-Loading Global loot data..."); + DatabaseResult result; + int32 count = 0; + if (database_new.Select(&result, "SELECT type, loot_table, value1, value2, value3, value4 FROM loot_global")) { + while(result.Next()) { + const char* type = result.GetStringStr("type"); + int32 table_id = result.GetInt32Str("loot_table"); + if (strcmp(type, "Level") == 0) { + GlobalLoot* loot = new GlobalLoot(); + loot->minLevel = result.GetInt8Str("value1"); + loot->maxLevel = result.GetInt8Str("value2"); + loot->table_id = table_id; + loot->loot_tier = result.GetInt32Str("value4"); + + if (loot->minLevel > loot->maxLevel) + loot->maxLevel = loot->minLevel; + + zone->AddLevelLootList(loot); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Level %i loot table (id: %u)", loot->minLevel, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel); + } + else if (strcmp(type, "Racial") == 0) { + GlobalLoot* loot = new GlobalLoot(); + int16 race_id = result.GetInt16Str("value1"); + loot->minLevel = result.GetInt8Str("value2"); + loot->maxLevel = result.GetInt8Str("value3"); + loot->table_id = table_id; + loot->loot_tier = result.GetInt32Str("value4"); + + if (loot->minLevel > loot->maxLevel) + loot->maxLevel = loot->minLevel; + + zone->AddRacialLootList(race_id, loot); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Racial %i loot table (id: %u)", race_id, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel); + } + else if (strcmp(type, "Zone") == 0) { + GlobalLoot* loot = new GlobalLoot(); + int32 zoneID = result.GetInt32Str("value1"); + loot->minLevel = result.GetInt8Str("value2"); + loot->maxLevel = result.GetInt8Str("value3"); + loot->table_id = table_id; + loot->loot_tier = result.GetInt32Str("value4"); + + if (loot->minLevel > loot->maxLevel) + loot->maxLevel = loot->minLevel; + + zone->AddZoneLootList(zoneID, loot); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Zone %i loot table (id: %u)", zoneID, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel); + } + count++; + } + + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u Global loot list%s.", count, count == 1 ? "" : "s"); + } +} + +bool WorldDatabase::LoadSpawnLoot(ZoneServer* zone, Spawn* spawn) +{ + if (!spawn->GetDatabaseID()) + return false; + + DatabaseResult result; + int32 count = 0; + + zone->ClearSpawnLootList(spawn->GetDatabaseID()); + // Finally, load loot tables into spawns that are set to use these loot tables + if (database_new.Select(&result, "SELECT spawn_id, loottable_id FROM spawn_loot where spawn_id=%u",spawn->GetDatabaseID())) { + count = 0; + LogWrite(LOOT__DEBUG, 0, "Loot", "--Assigning loot table(s) to spawn(s)..."); + + while (result.Next()) { + int32 spawn_id = result.GetInt32Str("spawn_id"); + int32 table_id = result.GetInt32Str("loottable_id"); + zone->AddSpawnLootList(spawn_id, table_id); + LogWrite(LOOT__DEBUG, 5, "Loot", "---Adding loot table %u to spawn %u", table_id, spawn_id); + count++; + } + LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u spawn loot list%s.", count, count == 1 ? "" : "s"); + return true; + } + return false; +} + +void WorldDatabase::AddLootTableToSpawn(Spawn* spawn, int32 loottable_id) { + Query query; + query.RunQuery2(Q_INSERT, "insert into spawn_loot set spawn_id=%u,loottable_id=%u", spawn->GetDatabaseID(), loottable_id); +} + +bool WorldDatabase::RemoveSpawnLootTable(Spawn* spawn, int32 loottable_id) { + Query query; + if (loottable_id) + { + string delete_char = string("delete from spawn_loot where spawn_id=%i and loottable_id=%i"); + query.RunQuery2(Q_DELETE, delete_char.c_str(), spawn->GetDatabaseID(), loottable_id); + } + else + { + string delete_char = string("delete from spawn_loot where spawn_id=%i"); + query.RunQuery2(Q_DELETE, delete_char.c_str(), spawn->GetDatabaseID()); + } + if (!query.GetAffectedRows()) + { + //No error just in case ppl try doing stupid stuff + return false; + } + + return true; +} \ No newline at end of file diff --git a/source/WorldServer/Languages.cpp b/source/WorldServer/Languages.cpp new file mode 100644 index 0000000..f2ed895 --- /dev/null +++ b/source/WorldServer/Languages.cpp @@ -0,0 +1,153 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include +#include +#include "Languages.h" + +Language::Language(){ + id = 0; + memset(name, 0, sizeof(name)); + save_needed = false; +} + +Language::Language(Language* language){ + id = language->id; + strncpy(name, language->GetName(), sizeof(name)); + save_needed = language->save_needed; +} + +MasterLanguagesList::MasterLanguagesList(){ +} + +MasterLanguagesList::~MasterLanguagesList(){ + Clear(); +} + + +// don't bother calling this beyond its deconstructor its not thread-safe +void MasterLanguagesList::Clear(){ + list::iterator itr; + Language* language = 0; + for(itr = languages_list.begin(); itr != languages_list.end(); itr++){ + language = *itr; + safe_delete(language); + } + languages_list.clear(); +} + +int32 MasterLanguagesList::Size(){ + return languages_list.size(); +} + +void MasterLanguagesList::AddLanguage(Language* language){ + assert(language); + languages_list.push_back(language); +} + +Language* MasterLanguagesList::GetLanguage(int32 id){ + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = languages_list.begin(); itr != languages_list.end(); itr++){ + language = *itr; + if(language->GetID() == id){ + ret = language; + break; + } + } + return ret; +} + +Language* MasterLanguagesList::GetLanguageByName(const char* name){ + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = languages_list.begin(); itr != languages_list.end(); itr++){ + language = *itr; + if(!language) + continue; + if(!strncmp(language->GetName(), name, strlen(name))){ + ret = language; + break; + } + } + return ret; +} + +list* MasterLanguagesList::GetAllLanguages(){ + return &languages_list; +} + +PlayerLanguagesList::PlayerLanguagesList(){ +} + +PlayerLanguagesList::~PlayerLanguagesList(){ +} + +void PlayerLanguagesList::Clear() { + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){ + language = *itr; + safe_delete(language); + } + + player_languages_list.clear(); +} + +void PlayerLanguagesList::Add(Language* language){ + player_languages_list.push_back(language); +} + +Language* PlayerLanguagesList::GetLanguage(int32 id){ + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){ + language = *itr; + if(language->GetID() == id){ + ret = language; + break; + } + } + return ret; +} + +Language* PlayerLanguagesList::GetLanguageByName(const char* name){ + list::iterator itr; + Language* language = 0; + Language* ret = 0; + for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){ + language = *itr; + if(!language) + continue; + if(!strncmp(language->GetName(), name, strlen(name))){ + ret = language; + break; + } + } + return ret; +} + +list* PlayerLanguagesList::GetAllLanguages(){ + return &player_languages_list; +} diff --git a/source/WorldServer/Languages.h b/source/WorldServer/Languages.h new file mode 100644 index 0000000..4d8a1e2 --- /dev/null +++ b/source/WorldServer/Languages.h @@ -0,0 +1,76 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifndef LANGUAGES_H_ +#define LANGUAGES_H_ + +#include +#include +#include "../common/types.h" + +using namespace std; + +class Language { +public: + Language(); + Language(Language* language); + void SetID(int32 id) {this->id = id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + void SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;} + + int32 GetID() {return id;} + const char* GetName() {return name;} + bool GetSaveNeeded() {return save_needed;} + +private: + int32 id; + char name[50]; + bool save_needed; +}; + +class MasterLanguagesList { +public: + MasterLanguagesList(); + ~MasterLanguagesList(); + void Clear(); + int32 Size(); + void AddLanguage(Language* language); + Language* GetLanguage(int32 id); + Language* GetLanguageByName(const char* name); + list* GetAllLanguages(); + +private: + list languages_list; +}; + +class PlayerLanguagesList { +public: + PlayerLanguagesList(); + ~PlayerLanguagesList(); + void Clear(); + void Add(Language* language); + Language* GetLanguage(int32 id); + Language* GetLanguageByName(const char* name); + list* GetAllLanguages(); + +private: + list player_languages_list; +}; +#endif diff --git a/source/WorldServer/LoginServer.cpp b/source/WorldServer/LoginServer.cpp new file mode 100644 index 0000000..3f1426c --- /dev/null +++ b/source/WorldServer/LoginServer.cpp @@ -0,0 +1,667 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +#include "../common/Log.h" +#include +using namespace std; +#include +#include +#include +using namespace std; +#include +#include "../common/version.h" +#include "../common/GlobalHeaders.h" +#include "../common/sha512.h" + +#ifdef WIN32 +#include +#include +#include +#include + +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else // Pyro: fix for linux +#include +#ifdef FREEBSD //Timothy Whitman - January 7, 2003 +#include +#endif +#include +#include +#include +#include +#include + +#include "../common/unix.h" + +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 +extern int errno; +#endif + +#include "../common/servertalk.h" +#include "LoginServer.h" +#include "../common/packet_dump.h" +#include "net.h" +#include "zoneserver.h" +#include "WorldDatabase.h" +#include "Variables.h" +#include "World.h" +#include "../common/ConfigReader.h" +#include "Rules/Rules.h" + +extern sint32 numzones; +extern sint32 numclients; +extern NetConnection net; +extern LoginServer loginserver; +extern WorldDatabase database; +extern ZoneAuth zone_auth; +extern Variables variables; +extern ZoneList zone_list; +extern ClientList client_list; +extern volatile bool RunLoops; +volatile bool LoginLoopRunning = false; +extern ConfigReader configReader; +extern RuleManager rule_manager; + +bool AttemptingConnect = false; + +LoginServer::LoginServer(const char* iAddress, int16 iPort) { + LoginServerIP = ResolveIP(iAddress); + LoginServerPort = iPort; + statusupdate_timer = new Timer(LoginServer_StatusUpdateInterval); + tcpc = new TCPConnection(false); + pTryReconnect = true; + minLockedStatus = 100; + maxPlayers = -1; + minGameFullStatus = 100; + last_checked_time = 0; + zone_updates = 0; + loginEquip_updates = 0; +} + +LoginServer::~LoginServer() { + delete statusupdate_timer; + delete tcpc; +} + +void LoginServer::SendImmediateEquipmentUpdatesForChar(int32 char_id) { + LogWrite(WORLD__DEBUG, 5, "World", "Sending login equipment updates for char_id: %u", char_id); + + int16 count = 0; + if(!loginEquip_updates) + loginEquip_updates = database.GetEquipmentUpdates(char_id); + if(loginEquip_updates && loginEquip_updates->size() > 0) + { + map send_map; + int32 size = 0; + MutexMap::iterator itr = loginEquip_updates->begin(); + while(itr.Next()) + { + send_map[itr->first] = itr->second; + size += sizeof(EquipmentUpdate_Struct); + loginEquip_updates->erase(itr->first); + count++; + } + ServerPacket* outpack = new ServerPacket(ServerOP_LoginEquipment, size + sizeof(EquipmentUpdateList_Struct)+5); + EquipmentUpdateList_Struct* updates = (EquipmentUpdateList_Struct*)outpack->pBuffer; + updates->total_updates = count; + int32 pos = sizeof(EquipmentUpdateList_Struct); + map::iterator send_itr; + for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++) + { + EquipmentUpdate_Struct* update = (EquipmentUpdate_Struct*)(outpack->pBuffer + pos); + update->id = send_itr->first; + update->world_char_id = send_itr->second.world_char_id; + update->equip_type = send_itr->second.equip_type; + update->red = send_itr->second.red; + update->green = send_itr->second.green; + update->blue = send_itr->second.blue; + update->highlight_red = send_itr->second.red; + update->highlight_green = send_itr->second.green; + update->highlight_blue = send_itr->second.blue; + update->slot = send_itr->second.slot; + pos += sizeof(EquipmentUpdate_Struct); + } + SendPacket(outpack); + outpack->Deflate(); + safe_delete(outpack); + } + if(loginEquip_updates && count) + loginEquip_updates->clear(); + if(loginEquip_updates && loginEquip_updates->size() == 0) + { + database.UpdateLoginEquipment(); + safe_delete(loginEquip_updates); + } + +} + +bool LoginServer::Process() { + if(last_checked_time > Timer::GetCurrentTime2()) + return true; + last_checked_time = Timer::GetCurrentTime2() + 50; + bool ret = true; + if (statusupdate_timer->Check()) { + this->SendStatus(); + } + + /************ Get all packets from packet manager out queue and process them ************/ + ServerPacket *pack = 0; + while((pack = tcpc->PopPacket())) + { + switch(pack->opcode) + { + case ServerOP_LSFatalError: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_LSFatalError", pack->opcode, pack->opcode); + + LogWrite(WORLD__ERROR, 0, "World", "Login Server returned a fatal error: %s\n", pack->pBuffer); + tcpc->Disconnect(); + ret = false; + net.ReadLoginINI(); + break; + } + case ServerOP_CharTimeStamp: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_CharTimeStamp", pack->opcode, pack->opcode); + + if(pack->size != sizeof(CharacterTimeStamp_Struct)) + break; + + CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) pack->pBuffer; + + // determine if the character exists and retrieve its latest timestamp from the world server + bool char_exist = false; + //int32 character_timestamp = database.GetCharacterTimeStamp(cts->char_id,cts->account_id,&char_exist); + + if(!char_exist) + { + //Character doesn't exist, get rid of it + SendDeleteCharacter ( cts ); + break; + } + + break; + } + + // Push Character Select "item appearances" to login_equipment table + case ServerOP_LoginEquipment:{ + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_LoginEquipment", pack->opcode, pack->opcode); + + LogWrite(MISC__TODO, 0, "TODO", "Implement map > method to update Login.\n%s, %s, %i", __FILE__, __FUNCTION__, __LINE__); + + if( pack->size == sizeof(EquipmentUpdateRequest_Struct) ) + { + int16 max = ((EquipmentUpdateRequest_Struct*)pack->pBuffer)->max_per_batch; + int16 count = 0; + if(!loginEquip_updates) + loginEquip_updates = database.GetEquipmentUpdates(); + if(loginEquip_updates && loginEquip_updates->size() > 0) + { + map send_map; + int32 size = 0; + MutexMap::iterator itr = loginEquip_updates->begin(); + while(itr.Next() && count < max) + { + send_map[itr->first] = itr->second; + size += sizeof(EquipmentUpdate_Struct); + loginEquip_updates->erase(itr->first); + count++; + } + ServerPacket* outpack = new ServerPacket(ServerOP_LoginEquipment, size + sizeof(EquipmentUpdateList_Struct)+5); + EquipmentUpdateList_Struct* updates = (EquipmentUpdateList_Struct*)outpack->pBuffer; + updates->total_updates = count; + int32 pos = sizeof(EquipmentUpdateList_Struct); + map::iterator send_itr; + for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++) + { + EquipmentUpdate_Struct* update = (EquipmentUpdate_Struct*)(outpack->pBuffer + pos); + update->id = send_itr->first; + update->world_char_id = send_itr->second.world_char_id; + update->equip_type = send_itr->second.equip_type; + update->red = send_itr->second.red; + update->green = send_itr->second.green; + update->blue = send_itr->second.blue; + update->highlight_red = send_itr->second.red; + update->highlight_green = send_itr->second.green; + update->highlight_blue = send_itr->second.blue; + update->slot = send_itr->second.slot; + pos += sizeof(EquipmentUpdate_Struct); + } + SendPacket(outpack); + outpack->Deflate(); + safe_delete(outpack); + } + if(loginEquip_updates && count < max) + loginEquip_updates->clear(); + if(loginEquip_updates && loginEquip_updates->size() == 0) + { + database.UpdateLoginEquipment(); + safe_delete(loginEquip_updates); + } + } + break; + } + + case ServerOP_ZoneUpdates:{ + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_ZoneUpdates", pack->opcode, pack->opcode); + + if(pack->size == sizeof(ZoneUpdateRequest_Struct)){ + int16 max = ((ZoneUpdateRequest_Struct*)pack->pBuffer)->max_per_batch; + int16 count = 0; + if(!zone_updates) + zone_updates = database.GetZoneUpdates(); + if(zone_updates && zone_updates->size() > 0){ + map send_map; + int32 size = 0; + MutexMap::iterator itr = zone_updates->begin(); + while(itr.Next() && count < max){ + send_map[itr->first] = itr->second; + size += sizeof(ZoneUpdate_Struct) + itr->second.name.length() + itr->second.description.length(); + zone_updates->erase(itr->first); + count++; + } + ServerPacket* outpack = new ServerPacket(ServerOP_ZoneUpdates, size + sizeof(ZoneUpdateList_Struct)+5); + ZoneUpdateList_Struct* updates = (ZoneUpdateList_Struct*)outpack->pBuffer; + updates->total_updates = count; + int32 pos = sizeof(ZoneUpdateList_Struct); + map::iterator send_itr; + for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++){ + ZoneUpdate_Struct* update = (ZoneUpdate_Struct*)(outpack->pBuffer + pos); + update->zone_id = send_itr->first; + update->zone_name_length = send_itr->second.name.length(); + update->zone_desc_length = send_itr->second.description.length(); + strcpy(update->data, send_itr->second.name.c_str()); + strcpy(update->data + send_itr->second.name.length(), send_itr->second.description.c_str()); + pos += sizeof(ZoneUpdate_Struct) + send_itr->second.name.length() + send_itr->second.description.length(); + } + SendPacket(outpack); + outpack->Deflate(); + safe_delete(outpack); + } + if(zone_updates && count < max) + zone_updates->clear(); + if(zone_updates && zone_updates->size() == 0){ + database.UpdateLoginZones(); + safe_delete(zone_updates); + } + } + break; + } + case ServerOP_CharacterCreate:{ + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_CharacterCreate", pack->opcode, pack->opcode); + + int16 version = 1; + if(pack->pBuffer[0] > 0) + memcpy(&version, pack->pBuffer, sizeof(int16)); + //DumpPacket(pack->pBuffer,pack->size); + PacketStruct* packet = configReader.getStruct("CreateCharacter", version); + int8 resp = 0; + int32 acct_id = 0; + int32 char_id = 0; + if(packet && packet->LoadPacketData(pack->pBuffer+sizeof(int16),pack->size - sizeof(int16), version <= 561 ? false : true)){ + EQ2_16BitString name = packet->getType_EQ2_16BitString_ByName("name"); + resp = database.CheckNameFilter(name.data.c_str()); + acct_id = packet->getType_int32_ByName("account_id"); + LogWrite(WORLD__DEBUG, 0, "World", "Response: %i", (int)resp); + + sint16 lowestStatus = database.GetLowestCharacterAdminStatus(acct_id); + if(lowestStatus == -2) + resp = UNKNOWNERROR_REPLY2; + else if(resp == CREATESUCCESS_REPLY) + char_id = database.SaveCharacter(packet, acct_id); + } + else{ + LogWrite(WORLD__ERROR, 0, "World", "Invalid creation request!"); + resp = UNKNOWNERROR_REPLY; + } + // send name filter response data back to the login server + SendFilterNameResponse ( resp , acct_id , char_id ); + safe_delete(packet); + break; + } + case ServerOP_BasicCharUpdate: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_BasicCharUpdate", pack->opcode, pack->opcode); + + if(pack->size != sizeof(CharDataUpdate_Struct)) + break; + + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*) pack->pBuffer; + + switch(cdu->update_field) + { + case DELETE_UPDATE_FLAG: + { + LogWrite(WORLD__DEBUG, 0, "World", "Delete character request: %i %i",cdu->account_id,cdu->char_id ); + database.DeleteCharacter(cdu->account_id,cdu->char_id); + break; + } + } + break; + } + case ServerOP_UsertoWorldReq:{ + LogWrite(OPCODE__DEBUG, 0, "Opcode", "Opcode 0x%X (%i): ServerOP_UsertoWorldReq", pack->opcode, pack->opcode); + + UsertoWorldRequest_Struct* utwr = (UsertoWorldRequest_Struct*) pack->pBuffer; + /*int32 id = database.GetAccountIDFromLSID(utwr->lsaccountid); + sint16 status = database.CheckStatus(id); + */ + + int32 access_key = 0; + + // if it is a accepted login, we add the zone auth request + access_key = DetermineCharacterLoginRequest ( utwr ); + + if ( access_key != 0 ) + { + zone_auth.PurgeInactiveAuth(); + char* characterName = database.GetCharacterName( utwr->char_id ); + if(characterName != 0){ + ZoneAuthRequest* zar = new ZoneAuthRequest(utwr->lsaccountid,characterName,access_key); + zar->setFirstLogin ( true ); + zone_auth.AddAuth(zar); + safe_delete_array(characterName); + } + } + break; + } + case ServerOP_ResetDatabase:{ + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_ResetDatabase", pack->opcode, pack->opcode); + + database.ResetDatabase(); + break; + } + + default: + { + LogWrite(WORLD__ERROR, 0, "World", "Unhandled opcode: %i", pack->opcode); + DumpPacket(pack); + } + } + safe_delete(pack); + + // break out if ret is now false + if (!ret) + break; + } + return ret; +} + +// this should always be called in a new thread +#ifdef WIN32 +void AutoInitLoginServer(void *tmp) { +#else +void *AutoInitLoginServer(void *tmp) { +#endif + if (loginserver.GetState() == TCPS_Ready) { + InitLoginServer(); + } +#ifndef WIN32 + return 0; +#endif +} + +bool InitLoginServer() { + if (loginserver.GetState() != TCPS_Ready) { + LogWrite(WORLD__ERROR, 0, "World", "InitLoginServer() while already attempting connect."); + return false; + } + if (!net.LoginServerInfo) { + LogWrite(WORLD__ERROR, 0, "World", "Login server info not loaded."); + return false; + } + + AttemptingConnect = true; + int16 port; + char* address = net.GetLoginInfo(&port); + LogWrite(WORLD__INFO, 0, "World", "InitLoginServer() attempt connect to %s on port %u.", address, port); + loginserver.Connect(address, port); + return true; +} + +void LoginServer::InitLoginServerVariables() +{ + minLockedStatus = rule_manager.GetGlobalRule(R_World, ServerLockedOverrideStatus)->GetSInt16(); + maxPlayers = rule_manager.GetGlobalRule(R_World, MaxPlayers)->GetSInt16(); + minGameFullStatus = rule_manager.GetGlobalRule(R_World, MaxPlayersOverrideStatus)->GetSInt16(); +} + + + +bool LoginServer::Connect(const char* iAddress, int16 iPort) { + if(!pTryReconnect) + return false; + + char errbuf[TCPConnection_ErrorBufferSize]; + memset(errbuf, 0, TCPConnection_ErrorBufferSize); + if (iAddress == 0) { + LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: address == 0"); + return false; + } + else { + if ((LoginServerIP = ResolveIP(iAddress, errbuf)) == 0) { + LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: Resolving IP address: '%s'", errbuf); + return false; + } + } + if (iPort != 0) + LoginServerPort = iPort; + + if (LoginServerIP == 0 || LoginServerPort == 0) { + LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: Connect info incomplete, cannot connect"); + return false; + } + + if (tcpc->Connect(LoginServerIP, LoginServerPort, errbuf)) { + LogWrite(WORLD__INFO, 0, "World", "Connected to LoginServer: %s: %i", iAddress, LoginServerPort); + SendInfo(); + SendStatus(); + return true; + } + else { + LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: '%s'", errbuf); + return false; + } +} +void LoginServer::GetLatestTables(){ + ServerPacket* pack = new ServerPacket(ServerOP_GetLatestTables, sizeof(GetLatestTables_Struct)); + GetLatestTables_Struct* data = (GetLatestTables_Struct*)pack->pBuffer; + data->table_version = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION; + data->data_version = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION; + SendPacket(pack); + delete pack; +} +void LoginServer::SendInfo() { + ServerPacket* pack = new ServerPacket; + pack->opcode = ServerOP_LSInfo; + pack->size = sizeof(ServerLSInfo_Struct); + pack->pBuffer = new uchar[pack->size]; + memset(pack->pBuffer, 0, pack->size); + ServerLSInfo_Struct* lsi = (ServerLSInfo_Struct*) pack->pBuffer; + strcpy(lsi->protocolversion, EQEMU_PROTOCOL_VERSION); + strcpy(lsi->serverversion, CURRENT_VERSION); + strcpy(lsi->name, net.GetWorldName()); + strcpy(lsi->account, net.GetWorldAccount()); + lsi->dbversion = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION; +#ifdef _DEBUG + lsi->servertype = 4; +#endif + string passwdSha512 = sha512(net.GetWorldPassword()); + memcpy(lsi->password, (char*)passwdSha512.c_str(), passwdSha512.length()); + strcpy(lsi->address, net.GetWorldAddress()); + SendPacket(pack); + delete pack; +} + +void LoginServer::SendStatus() { + statusupdate_timer->Start(); + ServerPacket* pack = new ServerPacket; + pack->opcode = ServerOP_LSStatus; + pack->size = sizeof(ServerLSStatus_Struct); + pack->pBuffer = new uchar[pack->size]; + memset(pack->pBuffer, 0, pack->size); + ServerLSStatus_Struct* lss = (ServerLSStatus_Struct*) pack->pBuffer; + + if (net.world_locked) + lss->status = -2; + else if(loginserver.maxPlayers > -1 && numclients >= loginserver.maxPlayers) + lss->status = -3; + else + lss->status = 1; + + lss->num_zones = numzones; + lss->num_players = numclients; + lss->world_max_level = rule_manager.GetGlobalRule(R_Player, MaxLevel)->GetInt8(); + SendPacket(pack); + delete pack; +} + +void LoginServer::SendDeleteCharacter ( CharacterTimeStamp_Struct* cts ) { + ServerPacket* outpack = new ServerPacket(ServerOP_BasicCharUpdate, sizeof(CharDataUpdate_Struct)); + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*)outpack->pBuffer; + cdu->account_id = cts->account_id; + cdu->char_id = cts->char_id; + cdu->update_field = DELETE_UPDATE_FLAG; + cdu->update_data = 1; + SendPacket(outpack); + safe_delete(outpack); +} + +void LoginServer::SendFilterNameResponse ( int8 resp, int32 acct_id , int32 char_id ) { + ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, sizeof(WorldCharNameFilterResponse_Struct)); + WorldCharNameFilterResponse_Struct* wcfr = (WorldCharNameFilterResponse_Struct*)outpack->pBuffer; + wcfr->response = resp; + wcfr->account_id = acct_id; + wcfr->char_id = char_id; + SendPacket(outpack); + safe_delete(outpack); +} + +int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr ) { + LogWrite(LOGIN__TRACE, 9, "Login", "Enter: %s", __FUNCTION__); + ServerPacket* outpack = new ServerPacket; + outpack->opcode = ServerOP_UsertoWorldResp; + outpack->size = sizeof(UsertoWorldResponse_Struct); + outpack->pBuffer = new uchar[outpack->size]; + memset(outpack->pBuffer, 0, outpack->size); + UsertoWorldResponse_Struct* utwrs = (UsertoWorldResponse_Struct*) outpack->pBuffer; + utwrs->lsaccountid = utwr->lsaccountid; + utwrs->char_id = utwr->char_id; + utwrs->ToID = utwr->FromID; + int32 timestamp = Timer::GetUnixTimeStamp(); + utwrs->access_key = timestamp; + + // set default response to 0 + utwrs->response = 0; + + sint16 lowestStatus = database.GetLowestCharacterAdminStatus( utwr->lsaccountid ); + + sint16 status = 0; + + if(lowestStatus == -2) + status = -1; + else + status = database.GetCharacterAdminStatus ( utwr->lsaccountid , utwr->char_id ); + + if(status < 100 && zone_list.ClientConnected(utwr->lsaccountid)) + status = -9; + if(status < 0){ + LogWrite(WORLD__ERROR, 0, "World", "Login Rejected based on PLAY_ERROR (UserStatus) (MinStatus: %i), UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id ); + switch(status){ + case -10: + utwrs->response = PLAY_ERROR_CHAR_NOT_LOADED; + break; + case -9: + utwrs->response = 0;//PLAY_ERROR_ACCOUNT_IN_USE; + break; + case -8: + utwrs->response = PLAY_ERROR_LOADING_ERROR; + break; + case -1: + utwrs->response = PLAY_ERROR_ACCOUNT_BANNED; + break; + default: + utwrs->response = PLAY_ERROR_PROBLEM; + } + } + else if(net.world_locked == true){ + LogWrite(WORLD__INFO, 0, "World", "Login Lock Check (MinStatus: %i):, UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id ); + + // has high enough status, allow it + if(status >= loginserver.minLockedStatus) + utwrs->response = 1; + } + else if(loginserver.maxPlayers > -1 && ((sint16)client_list.Count()) >= loginserver.maxPlayers) + { + LogWrite(WORLD__INFO, 0, "World", "Login GameFull Check (MinStatus: %i):, UserStatus: %i, CharID: %i",loginserver.minGameFullStatus,status,utwr->char_id ); + + // has high enough status, allow it + if(status >= loginserver.minGameFullStatus) + { + utwrs->response = 1; + } + else + utwrs->response = -3; // server full response is -3 + } + else + utwrs->response = 1; + + /*sint32 x = database.CommandRequirement("$MAXCLIENTS"); + if( (sint32)numplayers >= x && x != -1 && x != 255 && status < 80) + utwrs->response = -3; + + if(status == -1) + utwrs->response = -1; + if(status == -2) + utwrs->response = -2; + */ + //printf("Response is %i for %i\n",utwrs->response,id);struct sockaddr_in sa; + int32 ipv4addr = 0; + int result = 0; + #ifdef WIN32 + struct sockaddr_in myaddr; + ZeroMemory(&myaddr, sizeof(myaddr)); + result = InetPton(AF_INET, utwr->ip_address, &(myaddr.sin_addr)); + if(result) + ipv4addr = ntohl(myaddr.sin_addr.s_addr); + + #else + result = inet_pton(AF_INET, utwr->ip_address, &ipv4addr); + if(result) + ipv4addr = ntohl(ipv4addr); + #endif + if (((result > 0 && IsPrivateAddress(ipv4addr)) || (strcmp(net.GetWorldAddress(), utwr->ip_address) == 0)) && (strlen(net.GetInternalWorldAddress()) > 0)) + strcpy(utwrs->ip_address, net.GetInternalWorldAddress()); + else + strcpy(utwrs->ip_address, net.GetWorldAddress()); + + LogWrite(CCLIENT__INFO, 0, "World", "New client login attempt from %s, providing %s as the world server address.",utwr->ip_address, utwrs->ip_address ); + + utwrs->port = net.GetWorldPort(); + utwrs->worldid = utwr->worldid; + SendPacket(outpack); + delete outpack; + + LogWrite(LOGIN__TRACE, 9, "Login", "Exit: %s with timestamp=%u", __FUNCTION__, timestamp); + // depending on the response determined above, this could return 0 (for failure) + return timestamp; +} + diff --git a/source/WorldServer/LoginServer.h b/source/WorldServer/LoginServer.h new file mode 100644 index 0000000..d0b4839 --- /dev/null +++ b/source/WorldServer/LoginServer.h @@ -0,0 +1,88 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef LOGINSERVER_H +#define LOGINSERVER_H + +#include "../common/servertalk.h" +#include "../common/linked_list.h" +#include "../common/timer.h" +#include "../common/queue.h" +#include "../common/Mutex.h" +#include "../common/TCPConnection.h" +#include +#include "MutexMap.h" + +#ifdef WIN32 + void AutoInitLoginServer(void *tmp); +#else + void *AutoInitLoginServer(void *tmp); +#endif +bool InitLoginServer(); + +class LoginServer{ +public: + LoginServer(const char* iAddress = 0, int16 iPort = 5999); + ~LoginServer(); + + bool Process(); + bool Connect(const char* iAddress = 0, int16 iPort = 0); + + bool ConnectToUpdateServer(const char* iAddress = 0, int16 iPort = 0); + + void SendInfo(); + void SendStatus(); + void GetLatestTables(); + + void SendPacket(ServerPacket* pack) { tcpc->SendPacket(pack); } + int8 GetState() { return tcpc->GetState(); } + bool Connected() { return tcpc->Connected(); } + + void SendFilterNameResponse ( int8 resp , int32 acct_id , int32 char_id ); + + void SendDeleteCharacter ( CharacterTimeStamp_Struct* cts ); + + int32 DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr ); + + void InitLoginServerVariables(); + + sint16 minLockedStatus; + sint16 maxPlayers; + sint16 minGameFullStatus; + + void SendImmediateEquipmentUpdatesForChar(int32 char_id); + + bool CanReconnect() { return pTryReconnect; } + +private: + bool try_auto_update; + bool pTryReconnect; + TCPConnection* tcpc; + int32 LoginServerIP; + int32 UpdateServerIP; + int16 LoginServerPort; + + uchar* data_waiting; + MutexMap* zone_updates; + MutexMap* loginEquip_updates; + int32 last_checked_time; + + Timer* statusupdate_timer; +}; +#endif diff --git a/source/WorldServer/LuaFunctions.cpp b/source/WorldServer/LuaFunctions.cpp new file mode 100644 index 0000000..acbb07b --- /dev/null +++ b/source/WorldServer/LuaFunctions.cpp @@ -0,0 +1,14167 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "LuaFunctions.h" +#include "Spawn.h" +#include "WorldDatabase.h" +#include "LuaInterface.h" +#include "../common/ConfigReader.h" +#include "client.h" +#include "World.h" +#include "Commands/Commands.h" +#include "races.h" +#include "classes.h" +#include "Variables.h" +#include "SpellProcess.h" +#include "Rules/Rules.h" +#include "../common/Log.h" +#include +#include "HeroicOp/HeroicOp.h" +#include "RaceTypes/RaceTypes.h" +#include "ClientPacketFunctions.h" +#include "Transmute.h" +#include "Titles.h" +#include +#include +#include + +extern MasterFactionList master_faction_list; +extern WorldDatabase database; +extern LuaInterface* lua_interface; +extern ConfigReader configReader; +extern MasterQuestList master_quest_list; +extern MasterItemList master_item_list; +extern MasterSpellList master_spell_list; +extern World world; +extern Commands commands; +extern ZoneList zone_list; +extern Races races; +extern Classes classes; +extern Variables variables; +extern MasterSkillList master_skill_list; +extern MasterHeroicOPList master_ho_list; +extern MasterRaceTypeList race_types_list; +extern MasterLanguagesList master_languages_list; +extern MasterTitlesList master_titles_list; +extern RuleManager rule_manager; + +vector ParseString(string strVal, char delim) { + stringstream ss(strVal); + vector ret; + while (ss.good()) + { + string substr; + getline(ss, substr, delim); + ret.push_back(substr); + } + return ret; +} + +vector ParseStringToInt32(string strVal, char delim) { + stringstream ss(strVal); + vector ret; + while (ss.good()) + { + string substr; + getline(ss, substr, delim); + stringstream valss(substr); + unsigned int val = 0; + valss >> val; + ret.push_back(val); + } + return ret; +} + +map ParseStringMap(string strVal, char delim) { + vector pairs = ParseString(strVal, delim); + vector::iterator itr; + map ret; + for (itr = pairs.begin(); itr != pairs.end(); itr++) { + vector keyvaluepair = ParseString(*itr, ':'); + if (keyvaluepair.size() == 2) { + stringstream valss(keyvaluepair[1]); + int32 val = 0; + valss >> val; + ret[keyvaluepair[0]] = val; + } + } + + return ret; +} + +map ParseIntMap(string strVal, char delim) { + vector pairs = ParseString(strVal, delim); + vector::iterator itr; + map ret; + for (itr = pairs.begin(); itr != pairs.end(); itr++) { + vector keyvaluepair = ParseString(*itr, ':'); + int32 key = 0; + if (keyvaluepair.size() > 0) { + stringstream keyss(keyvaluepair[0]); + keyss >> key; + } + if (keyvaluepair.size() == 1) { + ret[key] = 1; + } + else if (keyvaluepair.size() == 2) { + stringstream valss(keyvaluepair[1]); + unsigned short val = 0; + valss >> val; + ret[key] = val; + } + } + return ret; +} + +map ParseSInt32Map(string strVal, char delim) { + vector pairs = ParseString(strVal, delim); + vector::iterator itr; + map ret; + for (itr = pairs.begin(); itr != pairs.end(); itr++) { + vector keyvaluepair = ParseString(*itr, ':'); + int32 key = 0; + if (keyvaluepair.size() > 0) { + stringstream keyss(keyvaluepair[0]); + keyss >> key; + } + if (keyvaluepair.size() == 1) { + ret[key] = 1; + } + else if (keyvaluepair.size() == 2) { + stringstream valss(keyvaluepair[1]); + signed int val = 0; + valss >> val; + ret[key] = val; + } + } + return ret; +} + +int EQ2Emu_lua_PlayFlavor(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string mp3_string = lua_interface->GetStringValue(state, 2); + string text_string = lua_interface->GetStringValue(state, 3); + string emote_string = lua_interface->GetStringValue(state, 4); + int32 key1 = lua_interface->GetInt32Value(state, 5); + int32 key2 = lua_interface->GetInt32Value(state, 6); + Spawn* player = lua_interface->GetSpawn(state, 7); + int8 language = lua_interface->GetInt8Value(state, 8); + lua_interface->ResetFunctionStack(state); + if (spawn) { + const char* mp3 = 0; + const char* text = 0; + const char* emote = 0; + if (mp3_string.length() > 0) + mp3 = mp3_string.c_str(); + if (text_string.length() > 0) + text = text_string.c_str(); + if (emote_string.length() > 0) + emote = emote_string.c_str(); + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) { + if (((Player*)player)->WasSentSpawn(spawn->GetID())) + spawn->GetZone()->PlayFlavor(client, spawn, mp3, text, emote, key1, key2, language); + } + else + spawn->GetZone()->PlayFlavor(spawn, mp3, text, emote, key1, key2, language); + } + return 0; +} + +int EQ2Emu_lua_PlayFlavorID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + + int8 type = lua_interface->GetInt8Value(state, 2); + int32 id = lua_interface->GetInt32Value(state, 3); + int16 index = lua_interface->GetInt16Value(state, 4); + Spawn* player = lua_interface->GetSpawn(state, 5); + int8 language = lua_interface->GetInt8Value(state, 6); + lua_interface->ResetFunctionStack(state); + if (spawn) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) { + VoiceOverStruct non_garble, garble; + bool garble_success = false; + bool success = world.FindVoiceOver(type, id, index, &non_garble, &garble_success, &garble); + client->SendPlayFlavor(spawn, language, &non_garble, &garble, success, garble_success); + } + else + spawn->GetZone()->PlayFlavorID(spawn, type, id, index, language); + } + return 0; +} + +int EQ2Emu_lua_PlaySound(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string sound_string = lua_interface->GetStringValue(state, 2); + float x = lua_interface->GetFloatValue(state, 3); + float y = lua_interface->GetFloatValue(state, 4); + float z = lua_interface->GetFloatValue(state, 5); + Spawn* player = lua_interface->GetSpawn(state, 6); + lua_interface->ResetFunctionStack(state); + if (spawn && sound_string.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->PlaySoundFile(client, sound_string.c_str(), x, y, z); + else + spawn->GetZone()->PlaySoundFile(0, sound_string.c_str(), x, y, z); + + } + return 0; +} +int EQ2Emu_lua_SetRequiredQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int16 quest_step = lua_interface->GetInt16Value(state, 3); + bool private_spawn = (lua_interface->GetInt8Value(state, 4) == 1); + bool continued_access = (lua_interface->GetInt8Value(state, 5) == 1); + int16 flag_override = lua_interface->GetInt16Value(state, 6); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetRequiredQuest command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (quest_id > 0) { + //Add this quest to the list of required quests for this spawn + spawn->SetQuestsRequired(quest_id, quest_step); + //If private spawn value set + if (private_spawn) { + //Set the spawn to be private when not granted access through this quest + spawn->AddAllowAccessSpawn(spawn); + spawn->SetPrivateQuestSpawn(true); + } + //This value allows access after a quest step, or the whole quest has been completed + if (continued_access) + spawn->SetQuestsRequiredContinuedAccess(true); + //This value will override vis_flags in the vis packet + if (flag_override > 0) + spawn->SetQuestsRequiredOverride(flag_override); + } + return 0; +} + +int EQ2Emu_lua_SpawnSetByDistance(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float max_distance = lua_interface->GetFloatValue(state, 2); + string variable = lua_interface->GetStringValue(state, 3); + string value = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + if (max_distance > 0 && spawn && value.length() > 0 && variable.length() > 0 && spawn->GetZone()) + spawn->GetZone()->SpawnSetByDistance(spawn, max_distance, variable, value); + return 0; +} +int EQ2Emu_lua_PerformCameraShake(lua_State* state) { + + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + float intensity = lua_interface->GetFloatValue(state, 2); + int8 direction = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA PerformCameraShake command error: spawn is not valid"); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA PerformCameraShake command error: spawn is not a player"); + return 0; + } + + if (player->GetZone()) + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA PerformCameraShake command error: could not find client"); + return 0; + } + PacketStruct* packet = configReader.getStruct("WS_PerformCameraShakeMsg", client->GetVersion()); + if (packet) { + /* Client Intensity Logic (does not restrict service side, but expect .01 - 1.0 range) + v1 = *(float *)(a1 + 4); + if ( v1 > 0.0 ) + v2 = fminf(v1, 1.0); + else + v2 = 0.1; + */ + packet->setDataByName("intensity", intensity); + if ( client->GetVersion() > 561 ) + packet->setDataByName("direction", direction); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_KillSpawn(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* dead = lua_interface->GetSpawn(state); + Spawn* killer = lua_interface->GetSpawn(state, 2); + bool send_packet = (lua_interface->GetInt8Value(state, 3) == 1); + lua_interface->ResetFunctionStack(state); + if (dead && dead->Alive() && dead->GetZone()) + dead->GetZone()->KillSpawn(false, dead, killer, send_packet); + return 0; +} + +int EQ2Emu_lua_KillSpawnByDistance(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float max_distance = lua_interface->GetFloatValue(state, 2); + bool include_players = lua_interface->GetInt8Value(state, 3); + bool send_packet = (lua_interface->GetInt8Value(state, 4) == 1); + lua_interface->ResetFunctionStack(state); + if (max_distance > 0 && spawn && spawn->GetZone()) + spawn->GetZone()->KillSpawnByDistance(spawn, max_distance, include_players, send_packet); + return 0; +} + +int EQ2Emu_lua_Despawn(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 delay = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->GetZone()) + spawn->GetZone()->Despawn(spawn, delay); + return 0; +} + +int EQ2Emu_lua_ChangeHandIcon(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 displayHandIcon = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->info_changed = true; + spawn->SetShowHandIcon(displayHandIcon); + } + return 0; +} + +//this function is used to force an update packet to be sent. +//Useful if certain calculated things change after the player is sent the spawn packet, like quest flags or player has access to an object now +int EQ2Emu_lua_SetVisualFlag(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->vis_changed = true; + spawn->GetZone()->AddChangedSpawn(spawn); + } + return 0; +} + +//this function is used to force an update packet to be sent. +//Useful if certain calculated things change after the player is sent the spawn packet, like quest flags or player has access to an object now +int EQ2Emu_lua_SetInfoFlag(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->info_changed = true; + spawn->GetZone()->AddChangedSpawn(spawn); + } + return 0; +} + +int EQ2Emu_lua_SendStateCommand(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 new_state = lua_interface->GetInt32Value(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + + lua_interface->ResetFunctionStack(state); + + if (spawn) { + if(player) + { + if(player->IsPlayer()) + { + Client* client = ((Player*)player)->GetClient(); + if(client) + { + ClientPacketFunctions::SendStateCommand(client, client->GetPlayer()->GetIDWithPlayerSpawn(spawn), new_state); + lua_interface->SetBooleanValue(state, true); + return 1; + } + else + LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in SendStateCommand,attempted to pass player value in argument 3, but argument does not have active client.", spawn->GetName()); + } + else + LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in SendStateCommand,attempted to pass player value in argument 3, but argument is NOT a player.", spawn->GetName()); + } + else + { + spawn->GetZone()->QueueStateCommandToClients(spawn->GetID(), new_state); + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_SpawnSet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string variable = lua_interface->GetStringValue(state, 2); + string value = lua_interface->GetStringValue(state, 3); + bool no_update = lua_interface->GetBooleanValue(state, 4); // send update is true by default in SetSpawnCommand, so allow user to specify 'true' to disable send update. + bool temporary_flag = true; + + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + int8 index = 0; + + if(num_args >= 5) + { + temporary_flag = lua_interface->GetBooleanValue(state, 5); // this used to be false, but no one bothered to set it temporary, we don't need to update the DB + + index = lua_interface->GetInt8Value(state, 6); + } + + lua_interface->ResetFunctionStack(state); + + int32 type = commands.GetSpawnSetType(variable); + if (type != 0xFFFFFFFF && value.length() > 0 && spawn) + commands.SetSpawnCommand(0, spawn, type, value.c_str(), !no_update, temporary_flag, nullptr, index); + return 0; +} + +int EQ2Emu_lua_GetSpawn(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn_id > 0) { + Spawn* closest_spawn = spawn->GetZone()->GetClosestSpawn(spawn, spawn_id); + if (closest_spawn) { + lua_interface->SetSpawnValue(state, closest_spawn); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetSpawnFromList(lua_State* state) { + if (!lua_interface) + return 0; + + vector spawns; + int32 position = 0; + + if(lua_istable(state, 1)) { + size_t len = lua_rawlen(state, 1); + for(int i=1;i <= len; i++) + { + // get the entry to stack + lua_rawgeti(state, 1, i); + int Top = lua_gettop(state); + + if(lua_islightuserdata(state,Top)) { + LUAUserData* data = (LUAUserData*)lua_touserdata(state, Top); + if(data->IsSpawn()) { + spawns.push_back(data->spawn); + } + } + // remove entry from stack + lua_pop(state,1); + } + } + + position = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + + Spawn* spawn = 0; + if(position < spawns.size()) { + spawn = spawns.at(position); + } + + if(spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpawnListSize(lua_State* state) { + if (!lua_interface) + return 0; + + vector spawns; + + if(lua_istable(state, 1)) { + size_t len = lua_rawlen(state, 1); + for(int i=1;i <= len; i++) + { + // get the entry to stack + lua_rawgeti(state, 1, i); + int Top = lua_gettop(state); + + if(lua_islightuserdata(state,Top)) { + LUAUserData* data = (LUAUserData*)lua_touserdata(state, Top); + if(data->IsSpawn()) { + spawns.push_back(data->spawn); + } + } + // remove entry from stack + lua_pop(state,1); + } + } + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetInt32Value(state, spawns.size()); + return 1; +} + +int EQ2Emu_lua_GetSpawnListBySpawnID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + vector spawns = spawn->GetZone()->GetSpawnsByID(spawn_id); + if (spawns.size() > 0) { + lua_createtable(state, spawns.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < spawns.size(); i++) { + lua_interface->SetSpawnValue(state, spawns.at(i)); + lua_rawseti(state, newTable, i + 1); + } + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetSpawnListByRailID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint64 rail_id = lua_interface->GetSInt64Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + vector spawns = spawn->GetZone()->GetSpawnsByRailID(rail_id); + if (spawns.size() > 0) { + lua_createtable(state, spawns.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < spawns.size(); i++) { + lua_interface->SetSpawnValue(state, spawns.at(i)); + lua_rawseti(state, newTable, i + 1); + } + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetPassengerSpawnList(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + vector spawns = spawn->GetPassengersOnRail(); + if (spawns.size() > 0) { + lua_createtable(state, spawns.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < spawns.size(); i++) { + lua_interface->SetSpawnValue(state, spawns.at(i)); + lua_rawseti(state, newTable, i + 1); + } + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetVariableValue(lua_State* state) { + if (!lua_interface) + return 0; + string variable_name = lua_interface->GetStringValue(state); + lua_interface->ResetFunctionStack(state); + Variable* var = variables.FindVariable(variable_name); + if (var) { + lua_interface->SetStringValue(state, var->GetValue()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetCoinMessage(lua_State* state) { + if (!lua_interface) + return 0; + int32 total_coins = lua_interface->GetInt32Value(state); + lua_interface->ResetFunctionStack(state); + if (total_coins == 0) { + lua_interface->SetStringValue(state, "0 copper"); + return 1; + } + char tmp[64] = { 0 }; + string message = ""; + int32 val = 0; + if (total_coins >= 1000000) { + val = total_coins / 1000000; + total_coins -= 1000000 * val; + sprintf(tmp, " %u Platinum", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 10000) { + val = total_coins / 10000; + total_coins -= 10000 * val; + sprintf(tmp, " %u Gold", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 100) { + val = total_coins / 100; + total_coins -= 100 * val; + sprintf(tmp, " %u Silver", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins > 0) { + sprintf(tmp, " %u Copper", (int32)total_coins); + message.append(tmp); + } + lua_interface->SetStringValue(state, message.c_str()); + return 1; +} + +int EQ2Emu_lua_GetSpawnByGroupID(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 group_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (zone) { + Spawn* spawn = zone->GetSpawnGroup(group_id); + if (spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetSpawnByLocationID(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 location_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (zone) { + Spawn* spawn = zone->GetSpawnByLocationID(location_id); + if (spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetSpawnID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetDatabaseID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpawnGroupID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetSpawnGroupID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetSpawnGroupID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 new_group_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetSpawnGroupID(new_group_id); + lua_interface->SetBooleanValue(state, true); + return 1; + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_AddSpawnToGroup(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 new_group_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if(spawn->GetSpawnGroupID() == new_group_id) { + lua_interface->SetBooleanValue(state, false); + return 1; + } + spawn->GetZone()->AddSpawnToGroup(spawn, new_group_id); + lua_interface->SetBooleanValue(state, true); + return 1; + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_GetSpawnLocationID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetSpawnLocationID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpawnLocationPlacementID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetSpawnLocationPlacementID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetFactionAmount(lua_State* state) { + Player* player = (Player*)lua_interface->GetSpawn(state); + int32 faction_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && faction_id > 0) { + lua_interface->SetSInt32Value(state, player->GetFactions()->GetFactionValue(faction_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetFactionID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 value = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetFactionID(value); + } + return 0; +} + +int EQ2Emu_lua_GetFactionID(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetFactionID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetGender(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetGender()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetTarget(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && ((Entity*)spawn)->GetTarget()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetTarget()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_PlayVoice(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string mp3_string = lua_interface->GetStringValue(state, 2); + int32 key1 = lua_interface->GetInt32Value(state, 3); + int32 key2 = lua_interface->GetInt32Value(state, 4); + Spawn* player = lua_interface->GetSpawn(state, 5); + lua_interface->ResetFunctionStack(state); + if (spawn && mp3_string.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) { + if (((Player*)player)->WasSentSpawn(spawn->GetID())) + spawn->GetZone()->PlayVoice(client, spawn, mp3_string.c_str(), key1, key2); + } + else + spawn->GetZone()->PlayVoice(spawn, mp3_string.c_str(), key1, key2); + } + return 0; +} + +int EQ2Emu_lua_GetCurrentZoneSafeLocation(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetZone()->GetSafeX()); + lua_interface->SetFloatValue(state, spawn->GetZone()->GetSafeY()); + lua_interface->SetFloatValue(state, spawn->GetZone()->GetSafeZ()); + return 3; + } + return 0; +} + +int EQ2Emu_lua_HasLootItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, spawn->HasLootItemID(item_id)); + return 1; + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddLootItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + int16 charges = lua_interface->GetInt16Value(state, 3); + if (charges == 0) + charges = 1; + ((Entity*)spawn)->AddLootItem(item_id, charges); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_RemoveLootItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + Item* item = spawn->LootItem(item_id); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddLootCoin(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + int32 val = lua_interface->GetInt32Value(state, 2); + spawn->AddLootCoins(val); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GiveLoot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* entity = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + if (entity && player && player->IsPlayer()) { + int32 coins = lua_interface->GetInt32Value(state, 3); + vector* items = 0; + int i = 0; + int32 item_id = 0; + while ((item_id = lua_interface->GetInt32Value(state, 4 + i))) { + if (items == 0) + items = new vector; + if (master_item_list.GetItem(item_id)) + items->push_back(master_item_list.GetItem(item_id)); + i++; + } + Client* client = 0; + client = ((Player*)player)->GetClient(); + if (client) + ((Player*)player)->AddPendingLootItems(entity->GetID(), items); + if(coins > 0) + entity->AddLootCoins(coins); + safe_delete(items); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_HasPendingLootItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* entity = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + int32 item_id = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (entity && entity->IsEntity() && player && player->IsPlayer() && item_id > 0) { + lua_interface->SetBooleanValue(state, ((Player*)player)->HasPendingLootItem(entity->GetID(), item_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_HasPendingLoot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* entity = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (entity && player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->HasPendingLootItems(entity->GetID())); + return 1; + } + return 0; +} + +int EQ2Emu_lua_CreateConversation(lua_State* state) { + if (!lua_interface) + return 0; + + vector* conversation = lua_interface->GetConversation(state); + lua_interface->SetLuaUserDataStale(conversation); + safe_delete(conversation); + lua_interface->ResetFunctionStack(state); + + conversation = new vector(); + lua_interface->SetConversationValue(state, conversation); + return 1; +} + +int EQ2Emu_lua_AddConversationOption(lua_State* state) { + if (!lua_interface) + return 0; + vector* conversation = lua_interface->GetConversation(state); + if (conversation) { + ConversationOption conv_option; + conv_option.option = lua_interface->GetStringValue(state, 2); + conv_option.function = lua_interface->GetStringValue(state, 3); + if (conv_option.option.length() > 0) + conversation->push_back(conv_option); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_CloseConversation(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* npc = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (npc && player && player->IsPlayer() && player->GetZone()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + int32 conversation_id = client->GetConversationID(npc, 0); + client->CloseDialog(conversation_id); + } + } + return 0; +} + +int EQ2Emu_lua_CloseItemConversation(lua_State* state) { + if (!lua_interface) + return 0; + Item* item = lua_interface->GetItem(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (item && player && player->IsPlayer() && player->GetZone()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + int32 conversation_id = client->GetConversationID(0, item); + client->CloseDialog(conversation_id); + } + } + return 0; +} + +int EQ2Emu_lua_StartDialogConversation(lua_State* state) { + if (!lua_interface) + return 0; + vector* conversation = lua_interface->GetConversation(state); + Spawn* spawn = 0; + Item* item = 0; + int8 type = lua_interface->GetInt8Value(state, 2); + if (type == 1 || type == 3) + spawn = lua_interface->GetSpawn(state, 3); + else if (type == 2 || type == 4) + item = lua_interface->GetItem(state, 3); + Spawn* player = lua_interface->GetSpawn(state, 4); + string text = lua_interface->GetStringValue(state, 5); + string mp3 = lua_interface->GetStringValue(state, 6); + int32 key1 = lua_interface->GetInt32Value(state, 7); + int32 key2 = lua_interface->GetInt32Value(state, 8); + int8 language = lua_interface->GetInt8Value(state, 9); + + int numargs = lua_interface->GetNumberOfArgs(state); + int8 can_close = 1; + if(numargs > 9) + can_close = lua_interface->GetInt32Value(state, 10); + + lua_interface->ResetFunctionStack(state); + if (conversation && text.length() > 0 && (spawn || item) && player && player->IsPlayer()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + if (spawn) { + // Need to do this so the function works the same as it did before + if (type == 1) + type++; + + if (mp3.length() > 0) + client->DisplayConversation((Entity*)spawn, type, conversation, const_cast(text.c_str()), mp3.c_str(), key1, key2, language, can_close); + else + client->DisplayConversation((Entity*)spawn, type, conversation, const_cast(text.c_str()), nullptr, 0, 0, language, can_close); + } + else { + if (mp3.length() > 0) + client->DisplayConversation(item, conversation, const_cast(text.c_str()), type, mp3.c_str(), key1, key2, language, can_close); + else + client->DisplayConversation(item, conversation, const_cast(text.c_str()), type, nullptr, 0, 0, language, can_close); + } + } + } + lua_interface->SetLuaUserDataStale(conversation); + safe_delete(conversation); + return 0; +} + +int EQ2Emu_lua_StartConversation(lua_State* state) { + if (!lua_interface) + return 0; + vector* conversation = lua_interface->GetConversation(state); + Spawn* source = lua_interface->GetSpawn(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + string text = lua_interface->GetStringValue(state, 4); + string mp3 = lua_interface->GetStringValue(state, 5); + int32 key1 = lua_interface->GetInt32Value(state, 6); + int32 key2 = lua_interface->GetInt32Value(state, 7); + int8 language = lua_interface->GetInt32Value(state, 8); + + int numargs = lua_interface->GetNumberOfArgs(state); + int8 can_close = 1; + if(numargs > 8) + can_close = lua_interface->GetInt32Value(state, 9); + + lua_interface->ResetFunctionStack(state); + if (conversation && conversation->size() > 0 && text.length() > 0 && source && player && player->IsPlayer()) { + Client* client = ((Player*)player)->GetClient(); + if (mp3.length() > 0) + client->DisplayConversation(source, 1, conversation, text.c_str(), mp3.c_str(), key1, key2, language, can_close); + else + client->DisplayConversation(source, 1, conversation, text.c_str(), nullptr, 0, 0, language, can_close); + lua_interface->SetLuaUserDataStale(conversation); + safe_delete(conversation); + } + else + LogWrite(LUA__ERROR, 0, "LUA", "Spawn %s Error in StartConversation, potentially AddConversationOption not yet called or the StartConversation arguments are incorrect, text: %s, conversationSize: %i.", source ? source->GetName() : "UNKNOWN", text.size() ? text.c_str() : "", conversation ? conversation->size() : -1); + return 0; +} + +int EQ2Emu_lua_SetPlayerProximityFunction(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float distance = lua_interface->GetFloatValue(state, 2); + string in_range_function = lua_interface->GetStringValue(state, 3); + string leaving_range_function = lua_interface->GetStringValue(state, 4); + if (spawn && !spawn->IsPlayer() && distance > 0 && in_range_function.length() > 0) + spawn->GetZone()->AddPlayerProximity(spawn, distance, in_range_function, leaving_range_function); + return 0; +} + +int EQ2Emu_lua_SetLocationProximityFunction(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + float max_variation = lua_interface->GetFloatValue(state, 5); + string in_range_function = lua_interface->GetStringValue(state, 6); + string leaving_range_function = lua_interface->GetStringValue(state, 7); + lua_interface->ResetFunctionStack(state); + if (zone && in_range_function.length() > 0) + zone->AddLocationProximity(x, y, z, max_variation, in_range_function, leaving_range_function); + return 0; +} + +int EQ2Emu_lua_SetLootCoin(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + int32 val = lua_interface->GetInt32Value(state, 2); + ((Entity*)spawn)->SetLootCoins(val); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GetLootCoin(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetLootCoins()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_MovementLoopAdd(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + float speed = lua_interface->GetFloatValue(state, 5); + int32 delay = lua_interface->GetInt32Value(state, 6); //this should be given as seconds, as it is converted to ms later + string function = lua_interface->GetStringValue(state, 7); + + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + float heading = lua_interface->GetFloatValue(state, 8); + if (spawn) { + spawn->AddMovementLocation(x, y, z, speed, delay, function.c_str(), heading, (num_args > 7) ? true : false ); + spawn->GetZone()->AddMovementNPC(spawn); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_IsPlayer(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->IsPlayer()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetCharacterID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsPlayer()) { + lua_interface->SetInt32Value(state, ((Player*)spawn)->GetCharacterID()); + return 1; + } + + lua_interface->SetInt32Value(state, 0); + return 1; +} + +int EQ2Emu_lua_FaceTarget(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + bool reset_action_state = true; + if(num_args > 2) + reset_action_state = lua_interface->GetBooleanValue(state, 3); + + if (spawn && target) { + if (spawn->IsEntity()) + // ((Entity*)spawn)->FaceTarget(target); + static_cast(spawn)->FaceTarget(target, reset_action_state); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_MoveToLocation(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + float speed = lua_interface->GetFloatValue(state, 5); + string lua_function = lua_interface->GetStringValue(state, 6); + bool more_points = lua_interface->GetBooleanValue(state, 7); + + if (spawn) { + if (speed == 0) + speed = spawn->GetSpeed(); + + spawn->AddRunningLocation(x, y, z, speed, 0.0f, true, !more_points, lua_function); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_ClearRunningLocations(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->ClearRunningLocations(); + } + return 0; +} + +int EQ2Emu_lua_Say(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + float dist = lua_interface->GetFloatValue(state, 4); + int32 language = lua_interface->GetInt32Value(state, 5); + if (spawn && message.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->HandleChatMessage(client, spawn, 0, CHANNEL_SAY, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + else + spawn->GetZone()->HandleChatMessage(spawn, 0, CHANNEL_SAY, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_Shout(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + float dist = lua_interface->GetFloatValue(state, 4); + int32 language = lua_interface->GetInt32Value(state, 5); + if (spawn && message.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->HandleChatMessage(client, spawn, 0, CHANNEL_SHOUT, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + else + spawn->GetZone()->HandleChatMessage(spawn, 0, CHANNEL_SHOUT, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_SayOOC(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + Spawn* player = lua_interface->GetSpawn(state, 3); + float dist = lua_interface->GetFloatValue(state, 4); + int32 language = lua_interface->GetInt32Value(state, 5); + if (spawn && message.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->HandleChatMessage(client, spawn, 0, CHANNEL_OUT_OF_CHARACTER, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + else + spawn->GetZone()->HandleChatMessage(spawn, 0, CHANNEL_OUT_OF_CHARACTER, message.c_str(), (dist > 0.0f) ? dist : 30.0f, 0, true, language); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_Emote(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + Spawn* spawn2 = lua_interface->GetSpawn(state, 3); + Spawn* player = lua_interface->GetSpawn(state, 4); + char* to = 0; + if (spawn2) + to = spawn2->GetName(); + if (spawn && message.length() > 0) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (client) + spawn->GetZone()->HandleChatMessage(client, spawn, to, CHANNEL_EMOTE, message.c_str(), 30); + else + spawn->GetZone()->HandleChatMessage(spawn, to, CHANNEL_EMOTE, message.c_str(), 30); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_SpellHeal(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + Spawn* caster = luaspell->caster; + string heal_type = lua_interface->GetStringValue(state);//power, heal ect + int32 min_heal = lua_interface->GetInt32Value(state, 2); + int32 max_heal = lua_interface->GetInt32Value(state, 3); + Spawn* target = lua_interface->GetSpawn(state, 4); + int8 crit_mod = lua_interface->GetInt32Value(state, 5); + bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1; + string custom_spell_name = lua_interface->GetStringValue(state, 7);//custom spell name + lua_interface->ResetFunctionStack(state); + + boost::to_lower(heal_type); + if (caster && caster->IsEntity()) { + bool success = false; + luaspell->resisted = false; + if (target) { + float distance = caster->GetDistance(target, true); + if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name)) + success = true; + } + if ((!success || luaspell->spell->GetSpellData()->group_spell) && luaspell->targets.size() > 0) { + Spawn* target = 0; + ZoneServer* zone = luaspell->caster->GetZone(); + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + if ((target = zone->GetSpawnByID(luaspell->targets[i]))) { + float distance = caster->GetDistance(target, true); + ((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + success = true; + } + if (success) { + if (caster->GetZone()) + caster->GetZone()->TriggerCharSheetTimer(); + } + } + return 0; +} + +int EQ2Emu_lua_SpellHealPct(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + Spawn* caster = luaspell->caster; + string heal_type = lua_interface->GetStringValue(state);//power, heal ect + float percentage = lua_interface->GetFloatValue(state, 2); + bool current_value = lua_interface->GetBooleanValue(state, 3); + bool caster_value = lua_interface->GetBooleanValue(state, 4); + Spawn* target = lua_interface->GetSpawn(state, 5); + int8 crit_mod = lua_interface->GetInt32Value(state, 6); + bool no_calcs = lua_interface->GetInt32Value(state, 7) == 1; + string custom_spell_name = lua_interface->GetStringValue(state, 8);//custom spell name + lua_interface->ResetFunctionStack(state); + + boost::to_lower(heal_type); + int32 min_heal = 0, max_heal = 0; + if (caster && caster->IsEntity() && target) { + if(percentage <= 0.0f) + { + LogWrite(LUA__ERROR, 0, "LUA", "Error applying SpellHealPct on '%s'. percentage %f is less than or equal to 0.",target->GetName(),percentage); + return 0; + } + + if(heal_type == "power") + { + if(current_value) + { + if(caster_value) + min_heal = max_heal = (int32)(float)caster->GetPower() * (percentage / 100.0f); + else + min_heal = max_heal = (int32)(float)target->GetPower() * (percentage / 100.0f); + } + else + { + if(caster_value) + min_heal = max_heal = (int32)(float)caster->GetTotalPower() * (percentage / 100.0f); + else + min_heal = max_heal = (int32)(float)target->GetTotalPower() * (percentage / 100.0f); + } + + } + else + { + if(current_value) + { + if(caster_value) + min_heal = max_heal = (int32)(float)caster->GetHP() * (percentage / 100.0f); + else + min_heal = max_heal = (int32)(float)target->GetHP() * (percentage / 100.0f); + } + else + { + if(caster_value) + min_heal = max_heal = (int32)(float)caster->GetTotalHP() * (percentage / 100.0f); + else + min_heal = max_heal = (int32)(float)target->GetTotalHP() * (percentage / 100.0f); + } + } + + bool success = false; + luaspell->resisted = false; + if (target) { + float distance = caster->GetDistance(target, true); + if (((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name)) + success = true; + } + if ((!success || luaspell->spell->GetSpellData()->group_spell) && luaspell->targets.size() > 0) { + Spawn* target = 0; + ZoneServer* zone = luaspell->caster->GetZone(); + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + if ((target = zone->GetSpawnByID(luaspell->targets[i]))) { + float distance = caster->GetDistance(target, true); + ((Entity*)caster)->SpellHeal(target, distance, luaspell, heal_type, min_heal, max_heal, crit_mod, no_calcs, custom_spell_name); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + success = true; + } + if (success) { + if (caster->GetZone()) + caster->GetZone()->TriggerCharSheetTimer(); + } + } + return 0; +} + +int EQ2Emu_lua_AddItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + int16 quantity = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + // default of 1 quantity to add + if (quantity == 0) + quantity = 1; + + if (spawn && spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client && item_id > 0) { + lua_interface->SetBooleanValue(state, client->AddItem(item_id, quantity)); + return 1; + } + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + + +int EQ2Emu_lua_SummonItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + bool send_messages = (lua_interface->GetInt8Value(state, 3) == 1); + string location = lua_interface->GetStringValue(state, 4); + int16 item_count = lua_interface->GetInt16Value(state,5); + + //devn00b: if we dont have a count, assume 1 item. + if(!item_count) { + item_count = 1; + } + + lua_interface->ResetFunctionStack(state); + + if (spawn && spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client && item_id > 0) { + if (strncasecmp(location.c_str(), "bank", 4) == 0) + lua_interface->SetBooleanValue(state, client->AddItemToBank(item_id, item_count)); + else + lua_interface->SetBooleanValue(state, client->AddItem(item_id, item_count)); + if (send_messages) { + Item* item = master_item_list.GetItem(item_id); + if (item) { + if(item_count > 1) { + client->Message(CHANNEL_COLOR_YELLOW, "You receive %i %s.", item_count, item->CreateItemLink(client->GetVersion()).c_str()); + string popup_text1 = "You receive "+ item_count; + string popup_text2 = " " + item->name; + string popup_text = popup_text1 + popup_text2; + client->SendPopupMessage(10, popup_text.c_str(), "ui_harvested_normal", 3, 0xFF, 0xFF, 0xFF); + // return 1; + } else { + client->Message(CHANNEL_COLOR_YELLOW, "You receive %s.", item->CreateItemLink(client->GetVersion()).c_str()); + string popup_text = "You receive " + item->name; + client->SendPopupMessage(10, popup_text.c_str(), "ui_harvested_normal", 3, 0xFF, 0xFF, 0xFF); + } + } + } + return 1; + } + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_RemoveItem(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + int16 quantity = lua_interface->GetInt16Value(state, 3); + lua_interface->ResetFunctionStack(state); + + // default of 1 to remove + if (quantity == 0) + quantity = 1; + + Client* client; + Item* item; + + if (spawn && spawn->IsPlayer() && item_id > 0) { + if ((client = ((Player*)spawn)->GetClient())) { + if ((item = client->GetPlayer()->item_list.GetItemFromID(item_id))) { + if (client->RemoveItem(item, quantity)) { + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + } + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_HasItem(lua_State* state) { + Spawn* player = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + bool include_bank = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + bool hasItem = false; + hasItem = ((Player*)player)->item_list.HasItem(item_id, include_bank); + if (!hasItem) + hasItem = ((Player*)player)->GetEquipmentList()->HasItem(item_id); + lua_interface->SetBooleanValue(state, hasItem); + return 1; + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_Spawn(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + bool restricted_npc = (lua_interface->GetInt8Value(state, 3) == 1); + float x = lua_interface->GetFloatValue(state, 4); + float y = lua_interface->GetFloatValue(state, 5); + float z = lua_interface->GetFloatValue(state, 6); + float heading = lua_interface->GetFloatValue(state, 7); + if (zone && spawn_id > 0 && (x != 0 || y != 0 || z != 0)) { + Spawn* spawn = zone->GetSpawn(spawn_id); + if (!spawn) + lua_interface->LogError("%s: LUA Spawn command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), spawn_id); + else { + spawn->SetX(x); + spawn->SetZ(z); + spawn->SetY(y,true,true); + spawn->SetLocation(zone->GetClosestLocation(spawn)); + spawn->SetHeading(heading); + if (restricted_npc) + spawn->AddAllowAccessSpawn(spawn); + + const char* spawn_script = world.GetSpawnScript(spawn_id); + bool scriptActive = false; + if (spawn_script && lua_interface->GetSpawnScript(spawn_script) != 0) { + scriptActive = true; + spawn->SetSpawnScript(string(spawn_script)); + } + + zone->CallSpawnScript(spawn, SPAWN_SCRIPT_PRESPAWN); + + zone->AddSpawn(spawn); + if (scriptActive) { + zone->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + } + lua_interface->ResetFunctionStack(state); + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + } + else { + string output = "Invalid paramaters to LUA Spawn command: \n"; + if (!zone) + output = output.append("\t").append("Missing zone reference. \n"); + if (spawn_id == 0) + output = output.append("\t").append("Missing spawn_id."); + lua_interface->LogError("%s: Error in EQ2Emu_lua_Spawn - %s", lua_interface->GetScriptName(state), output.c_str()); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GetZoneName(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetStringValue(state, zone->GetZoneName()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetZoneID(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetZoneID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetZone(lua_State* state) { + if (!lua_interface) + return 0; + int32 zone_id = lua_interface->GetInt32Value(state); + ZoneServer* zone = 0; + if (zone_id > 0) + zone = zone_list.Get(zone_id, true, false, false); + else { + string zone_name = lua_interface->GetStringValue(state); + if (zone_name.length() > 0) { + zone = zone_list.Get(zone_name.c_str(), true, false, false); + } + else { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) + zone = spawn->GetZone(); + } + } + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetZoneValue(state, zone); + return 1; + } + return 0; +} + +int EQ2Emu_lua_AddHate(lua_State* state) { + Spawn* entity = lua_interface->GetSpawn(state); + Spawn* npc = lua_interface->GetSpawn(state, 2); + sint32 amount = lua_interface->GetSInt32Value(state, 3); + bool send_packet = lua_interface->GetInt8Value(state, 4) == 1 ? true : false; + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if(luaspell && luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (entity && entity->IsEntity() && amount != 0) { + if (luaspell && luaspell->caster) { + ZoneServer* zone = luaspell->caster->GetZone(); + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + Spawn* spawn = zone->GetSpawnByID(luaspell->targets.at(i)); + if (spawn && spawn->IsNPC() && spawn->Alive() && spawn->GetZone()) { + entity->CheckEncounterState((Entity*)spawn); + ((NPC*)spawn)->AddHate((Entity*)entity, amount); + if (send_packet) + entity->GetZone()->SendThreatPacket(entity, npc, amount, luaspell->spell->GetName()); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (npc && npc->IsNPC() && npc->GetZone()) { + entity->CheckEncounterState((Entity*)npc); + ((NPC*)npc)->AddHate((Entity*)entity, amount); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} + + +int EQ2Emu_lua_Zone(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + float x = lua_interface->GetFloatValue(state, 3); + float y = lua_interface->GetFloatValue(state, 4); + float z = lua_interface->GetFloatValue(state, 5); + float heading = lua_interface->GetFloatValue(state, 6); + + if (zone && client) { + LogWrite(LUA__DEBUG, 0, "LUA", "LUA Zone Request by Player: '%s' (%u)", player->GetName(), player->GetID()); + LogWrite(LUA__DEBUG, 5, "LUA", "\tTo Zone: '%s' (%u)", zone->GetZoneName(), zone->GetZoneID()); + + if (!client->CheckZoneAccess(zone->GetZoneName())) + { + LogWrite(LUA__WARNING, 0, "LUA", "CheckZoneAccess() FAILED! LUA Zone Request Denied!"); + return 0; + } + + if (x != 0 || y != 0 || z != 0) { + LogWrite(LUA__DEBUG, 5, "LUA", "\tTo Coordinates: %2f, %2f, %2f, %2f", x, y, z, heading); + player->SetX(x); + player->SetY(y); + player->SetZ(z); + player->SetHeading(heading); + client->Zone(zone->GetZoneName(), false); + } + else + client->Zone(zone->GetZoneName()); + } + else + lua_interface->LogError("%s: Error in EQ2Emu_lua_Zone: invalid zone or spawn input.", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddSpawnAccess(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* spawn2 = lua_interface->GetSpawn(state, 2); + if (spawn && spawn2) + spawn->AddAllowAccessSpawn(spawn2); + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_CastSpell(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* target = lua_interface->GetSpawn(state); + int32 spell_id = lua_interface->GetInt32Value(state, 2); + int8 spell_tier = lua_interface->GetInt8Value(state, 3); + Spawn* caster = lua_interface->GetSpawn(state, 4); + int16 custom_cast_time = lua_interface->GetInt16Value(state, 5); + lua_interface->ResetFunctionStack(state); + + if (!target) { + lua_interface->LogError("%s: LUA CastSpell command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA CastSpell command error: target (%s) is not an entity", lua_interface->GetScriptName(state), target->GetName()); + return 0; + } + + if (spell_id <= 0) { + lua_interface->LogError("%s: LUA CastSpell command error: spell id is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (caster && !caster->IsEntity()) { + lua_interface->LogError("%s: LUA CastSpell command error: caster (%s) is not an entity", lua_interface->GetScriptName(state), caster->GetName()); + return 0; + } + + if (spell_tier == 0) + spell_tier = 1; + + if (!caster) + caster = target; + + target->GetZone()->ProcessSpell(master_spell_list.GetSpell(spell_id, spell_tier), (Entity*)caster, (Entity*)target, true, false, NULL, custom_cast_time); + return 0; +} + +int EQ2Emu_lua_SpellDamage(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* target = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + Spawn* caster = luaspell->caster; + sint32 type = lua_interface->GetSInt32Value(state, 2); + int32 min_damage = lua_interface->GetInt32Value(state, 3); + int32 max_damage = lua_interface->GetInt32Value(state, 4); + int8 crit_mod = lua_interface->GetInt32Value(state, 5); + bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1; + //lua_interface->ResetFunctionStack(state); + int32 class_id = lua_interface->GetInt32Value(state, 7); + vector faction_req; + vector race_req; + int32 class_req = 0; + int32 i = 0; + int8 f = 0; + int8 r = 0; + while ((class_id = lua_interface->GetInt32Value(state, 7 + i))) { + if (class_id < 100) { + class_req += pow(2.0, double(class_id - 1)); + } + else if (class_id > 100 && class_id < 1000) { + race_req.push_back(class_id); + r++; + } + else { + faction_req.push_back(class_id); + f++; + } + i++; + } + lua_interface->ResetFunctionStack(state); + if (caster && caster->IsEntity()) { + bool race_match = false; + bool success = false; + luaspell->resisted = false; + if (luaspell->targets.size() > 0) { + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + if ((target = zone->GetSpawnByID(luaspell->targets[i]))) { + + if (race_req.size() > 0) { + for (int8 i = 0; i < race_req.size(); i++) { + if(race_req[i] == target->GetRace() || + race_req[i] == race_types_list.GetRaceType(target->GetModelType()) || + race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + if (race_match == true) { + float distance = caster->GetDistance(target, true); + ((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs); + } + } + } + success = true; + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (target) { + + //check class and race/faction here + if (race_req.size() > 0) { + for (int8 i = 0; i < race_req.size(); i++) { + if(race_req[i] == target->GetRace() || + race_req[i] == race_types_list.GetRaceType(target->GetModelType()) || + race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + if (race_match == true) { + float distance = caster->GetDistance(target, true); + if (((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs)) + success = true; + } + } + lua_interface->SetBooleanValue(state, luaspell->has_damaged); + if (success) { + Spell* spell = luaspell->spell; + if (caster->IsPlayer() && spell && spell->GetSpellData()->target_type == 1 && spell->GetSpellData()->spell_book_type == 1) { //offense combat art + ((Player*)caster)->InCombat(true); + if (caster->GetZone()) + caster->GetZone()->TriggerCharSheetTimer(); + } + } + } + else { + lua_interface->SetBooleanValue(state, false); + } + return 1; +} + +int EQ2Emu_lua_SpellDamageExt(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* target = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + Spawn* caster = luaspell->caster; + sint32 type = lua_interface->GetSInt32Value(state, 2); + int32 min_damage = lua_interface->GetInt32Value(state, 3); + int32 max_damage = lua_interface->GetInt32Value(state, 4); + int8 crit_mod = lua_interface->GetInt32Value(state, 5); + bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1; + int32 override_packet_type = lua_interface->GetInt32Value(state, 7); + bool take_power = lua_interface->GetInt32Value(state, 8) == 1; + //lua_interface->ResetFunctionStack(state); + int32 class_id = lua_interface->GetInt32Value(state, 9); + vector faction_req; + vector race_req; + int32 class_req = 0; + int32 i = 0; + int8 f = 0; + int8 r = 0; + while ((class_id = lua_interface->GetInt32Value(state, 9 + i))) { + if (class_id < 100) { + class_req += pow(2.0, double(class_id - 1)); + } + else if (class_id > 100 && class_id < 1000) { + race_req.push_back(class_id); + r++; + } + else { + faction_req.push_back(class_id); + f++; + } + i++; + } + lua_interface->ResetFunctionStack(state); + if (caster && caster->IsEntity()) { + bool race_match = false; + bool success = false; + luaspell->resisted = false; + if (luaspell->targets.size() > 0) { + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + if ((target = zone->GetSpawnByID(luaspell->targets[i]))) { + + if (race_req.size() > 0) { + for (int8 i = 0; i < race_req.size(); i++) { + if(race_req[i] == target->GetRace() || + race_req[i] == race_types_list.GetRaceType(target->GetModelType()) || + race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + if (race_match == true) { + float distance = caster->GetDistance(target, true); + ((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power); + } + } + } + success = true; + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (target) { + + //check class and race/faction here + if (race_req.size() > 0) { + for (int8 i = 0; i < race_req.size(); i++) { + if(race_req[i] == target->GetRace() || + race_req[i] == race_types_list.GetRaceType(target->GetModelType()) || + race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) { + race_match = true; + } + } + } + else + race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true + if (race_match == true) { + float distance = caster->GetDistance(target, true); + if (((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power)) + success = true; + } + } + lua_interface->SetBooleanValue(state, luaspell->has_damaged); + if (success) { + Spell* spell = luaspell->spell; + if (caster->IsPlayer() && spell && spell->GetSpellData()->target_type == 1 && spell->GetSpellData()->spell_book_type == 1) { //offense combat art + ((Player*)caster)->InCombat(true); + if (caster->GetZone()) + caster->GetZone()->TriggerCharSheetTimer(); + } + } + } + else { + lua_interface->SetBooleanValue(state, false); + } + return 1; +} +int EQ2Emu_lua_ModifyPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value != 0) { + if (spawn->GetPower() + value > spawn->GetTotalPower()) + spawn->SetPower(spawn->GetTotalPower()); + else + spawn->SetPower(spawn->GetPower() + value); + } + return 0; +} +int EQ2Emu_lua_ModifyHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value != 0) { + if (spawn->GetHP() + value > spawn->GetTotalHP()) + spawn->SetHP(spawn->GetTotalHP()); + else + spawn->SetHP(spawn->GetHP() + value); + } + return 0; +} +int EQ2Emu_lua_ModifyMaxPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value != 0) { + spawn->SetTotalPower(value); + spawn->SetTotalPowerBaseInstance(value); + } + return 0; +} +int EQ2Emu_lua_ModifyMaxHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value != 0) { + spawn->SetTotalHP(value); + spawn->SetTotalHPBaseInstance(value); + } + return 0; +} +int EQ2Emu_lua_SetCurrentHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetHP(value); + if (value > spawn->GetTotalHPBase()) + spawn->SetTotalHP(value); + } + return 0; +} +int EQ2Emu_lua_SetMaxHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + float value = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && value > 0) + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_HEALTH, value - spawn->GetTotalHP()); + + if (spawn && spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + + return 0; +} + +int EQ2Emu_lua_SetMaxHPBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && value > 0) + ((Entity*)spawn)->SetTotalHPBase(value); + return 0; +} + +int EQ2Emu_lua_SetCurrentPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && value > 0) { + spawn->SetPower(value); + if (value > spawn->GetTotalPowerBase()) + spawn->SetTotalPower(value); + } + return 0; +} +int EQ2Emu_lua_SetMaxPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && value > 0) + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_POWER, value - spawn->GetTotalPower()); + return 0; +} +int EQ2Emu_lua_SetMaxPowerBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity() && value > 0) + ((Entity*)spawn)->SetTotalPowerBase(value); + return 0; +} +int EQ2Emu_lua_SetPosition(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + float heading = lua_interface->GetFloatValue(state, 5); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetX(x); + spawn->SetY(y); + spawn->SetZ(z); + if (heading != 0) + spawn->SetHeading(heading); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + EQ2Packet* packet = client->GetPlayer()->Move(x, y, z, client->GetVersion(), (heading == 0 ? -1.0f : (heading + 180.0f))); + client->QueuePacket(packet); + } + } + + } + return 0; +} +int EQ2Emu_lua_SetHeading(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float value = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetHeading(value); + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + EQ2Packet* packet = client->GetPlayer()->Move(spawn->GetX(), spawn->GetY(), spawn->GetZ(), client->GetVersion(), value + 180.0f); + client->QueuePacket(packet); + } + } + } + return 0; +} +int EQ2Emu_lua_SetModelType(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) + spawn->SetModelType(value); + return 0; +} +int EQ2Emu_lua_SetAdventureClass(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 value = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer()) + ((Player*)spawn)->SetPlayerAdventureClass(value); + else + spawn->SetAdventureClass(value); + } + return 0; +} +int EQ2Emu_lua_SetTradeskillClass(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 value = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetTradeskillClass(value); + if (spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_tradeskill_class1(classes.GetTSBaseClass(spawn->GetTradeskillClass())); + ((Entity*)spawn)->GetInfoStruct()->set_tradeskill_class2(classes.GetSecondaryTSBaseClass(spawn->GetTradeskillClass())); + ((Entity*)spawn)->GetInfoStruct()->set_tradeskill_class3(spawn->GetTradeskillClass()); + } + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} +int EQ2Emu_lua_SetMount(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->SetMount(value); + EQ2_Color color; + color.red = 255; + color.green = 255; + color.blue = 255; + ((Entity*)spawn)->SetMountColor(&color); + ((Entity*)spawn)->SetMountSaddleColor(&color); + } + return 0; +} +int EQ2Emu_lua_SetMountColor(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + EQ2_Color mount_color; + EQ2_Color saddle_color; + mount_color.red = lua_interface->GetInt8Value(state, 2); + mount_color.green = lua_interface->GetInt8Value(state, 3); + mount_color.blue = lua_interface->GetInt8Value(state, 4); + saddle_color.red = lua_interface->GetInt8Value(state, 5); + saddle_color.green = lua_interface->GetInt8Value(state, 6); + saddle_color.blue = lua_interface->GetInt8Value(state, 7); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->SetMountColor(&mount_color); + ((Entity*)spawn)->SetMountSaddleColor(&saddle_color); + } + return 0; +} +int EQ2Emu_lua_GetMount(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetMount()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetRace(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) + { + LogWrite(LUA__DEBUG, 0, "LUA", "%s - Race: %i", __FUNCTION__, spawn->GetRace()); + lua_interface->SetInt32Value(state, spawn->GetRace()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetRaceName(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetStringValue(state, races.GetRaceName(spawn->GetRace())); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetClass(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetAdventureClass()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetClassName(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetStringValue(state, classes.GetClassName(spawn->GetAdventureClass())); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetSpeed(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float value = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetSpeed(value); + + if(spawn->IsEntity()) + ((Entity*)spawn)->SetSpeed(value); + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + PacketStruct* packet = configReader.getStruct("WS_SetControlGhost", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + packet->setDataByName("speed", value); + packet->setDataByName("size", 0.51); + EQ2Packet* app = packet->serialize(); + client->QueuePacket(app); + safe_delete(packet); + } + } + } + } + return 0; +} + +int EQ2Emu_lua_AddSpellBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + const int16 type = lua_interface->GetInt16Value(state, 2); + const float value = lua_interface->GetFloatValue(state, 3); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if(!luaspell || luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + int64 class_req = 0; + int32 class_id = 0; + vector faction_req; + vector race_req; + int32 i = 0; + int8 f = 0; + int8 r = 0; + while ((class_id = lua_interface->GetInt32Value(state, 4 + i))) { + if (class_id < 100) { + class_req += pow(2.0, double(class_id - 1)); + } + else if (class_id > 100 && class_id < 1000) { + race_req.push_back(class_id); + r++; + } + else { + faction_req.push_back(class_id); + f++; + } + i++; + } + + if (value != 0 && type >= 0) { + if (luaspell && luaspell->spell && luaspell->caster) { + ZoneServer* zone = luaspell->caster->GetZone(); + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(luaspell->targets[i]); + if (target) { + if (target->IsPlayer()) { + ((Player*)target)->AddSpellBonus(luaspell, type, value, class_req, race_req, faction_req); + LogWrite(LUA__DEBUG, 0, "LUA", "Applying Spell Bonus to Player '%s'. Is a Group Member.", ((Player*)target)->GetName()); + if (((Player*)target)->GetGroupMemberInfo()) + ((Player*)target)->UpdateGroupMemberInfo(); + ((Player*)target)->SetCharSheetChanged(true); + } + else if (target->IsNPC()) + ((NPC*)target)->AddSpellBonus(luaspell, type, value, class_req, race_req, faction_req); + else + lua_interface->LogError("%s: Error applying spell bonus on non entity.", lua_interface->GetScriptName(state)); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SPELLBONUS)) + luaspell->effect_bitmask += EFFECT_FLAG_SPELLBONUS; + } + else if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, type, value, class_req, race_req, faction_req); + LogWrite(LUA__DEBUG, 0, "LUA", "Applying Spell Bonus to Entity '%s'. Is a Group Member.", ((Entity*)spawn)->GetName()); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + else + lua_interface->LogError("%s: Unable to apply spell bonus in AddSpellBonus.", lua_interface->GetScriptName(state)); + } + else + lua_interface->LogError("%s: Invalid parameters for AddSpellBonus.", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_AddSpawnSpellBonus(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 type = lua_interface->GetInt16Value(state, 2); + sint32 value = lua_interface->GetSInt32Value(state, 3); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (value == 0) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: value must be set", lua_interface->GetScriptName(state)); + return 0; + } + + if (!luaspell || !luaspell->spell) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + int32 class_req = 0; + vector faction_req; + vector race_req; + int32 class_id = 0; + int32 i = 0; + int8 f = 0; + int8 r = 0; + while ((class_id = lua_interface->GetInt32Value(state, 4 + i))) { + if (class_id < 100) { + class_req += pow(2.0, double(class_id - 1)); + } + else if (class_id > 100 && class_id < 1000) { + race_req.push_back(class_id); + r++; + } + else { + faction_req.push_back(class_id); + f++; + } + i++; + } + + + ((Entity*)spawn)->AddSpellBonus(luaspell, type, value, class_req, race_req, faction_req); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SPELLBONUS)) + luaspell->effect_bitmask += EFFECT_FLAG_SPELLBONUS; + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + + return 0; +} + +int EQ2Emu_lua_RemoveSpawnSpellBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!luaspell || !luaspell->spell) { + lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + ((Entity*)spawn)->RemoveSpellBonus(luaspell); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + + return 0; +} + +int EQ2Emu_lua_RemoveSpellBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (luaspell && luaspell->spell) { + ZoneServer* zone = nullptr; + if (luaspell->caster != nullptr) + zone = luaspell->caster->GetZone(); + if(!zone && spawn) { + zone = spawn->GetZone(); // workaround to try to establish a zone to find the targets and remove the spells + } + Spawn* target = 0; + if(zone) { + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target && target->IsEntity()) { + ((Entity*)target)->RemoveSpellBonus(luaspell); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else { + LogWrite(LUA__ERROR, 0, "LUA", "Error removing spell bonus buff %s called by %s, zone is not available.", luaspell->spell ? luaspell->spell->GetName() : "NotSet", spawn->GetName()); + } + } + else if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->RemoveSpellBonus(luaspell); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_AddSkillBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 skill_id = lua_interface->GetInt32Value(state, 2); + float value = lua_interface->GetFloatValue(state, 3); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (value != 0) { + int32 spell_id = 0; + if (luaspell && luaspell->spell && luaspell->caster) { + if(luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + spell_id = luaspell->spell->GetSpellID(); + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target && target->Alive()) { + if (target->IsPlayer()) { + ((Player*)target)->AddSkillBonus(spell_id, skill_id, value); + Client* client = ((Player*)target)->GetClient(); + if (client) { + EQ2Packet* packet = ((Player*)target)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SKILLBONUS)) + luaspell->effect_bitmask += EFFECT_FLAG_SKILLBONUS; + } + else if (target->IsNPC()) { + ((NPC*)target)->AddSkillBonus(spell_id, skill_id, value); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SKILLBONUS)) + luaspell->effect_bitmask += EFFECT_FLAG_SKILLBONUS; + } + else + LogWrite(LUA__ERROR, 0, "LUA", "Error applying bonus buff on '%s'. Not a NPC or player.", target->GetName()); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (spawn) { + if (spawn->IsPlayer()) { + ((Player*)spawn)->AddSkillBonus(spell_id, skill_id, value); + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + EQ2Packet* packet = ((Player*)spawn)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + else if (spawn->IsNPC()) + ((NPC*)spawn)->AddSkillBonus(spell_id, skill_id, value); + else + LogWrite(LUA__ERROR, 0, "LUA", "Error applying skill bonus on '%s'. Not a NPC or player.", spawn->GetName()); + } + } + else + lua_interface->LogError("%s: Invalid parameters for AddSkillBonus.", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_RemoveSkillBonus(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (spawn && spawn->IsPlayer()) { + int32 spell_id = 0; + if (luaspell && luaspell->spell && luaspell->caster) { + if(luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + spell_id = luaspell->spell->GetSpellID(); + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target) { + if (target->IsPlayer()) { + ((Player*)target)->RemoveSkillBonus(spell_id); + Client* client = ((Player*)target)->GetClient(); + if (client) { + EQ2Packet* packet = ((Player*)target)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + else if (target->IsNPC()) + ((NPC*)target)->RemoveSkillBonus(spell_id); + else + LogWrite(LUA__ERROR, 0, "LUA", "Error removing skill bonus on '%s'. Not a NPC or player.", spawn->GetName()); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (spawn) { + if (spawn->IsPlayer()) { + ((Player*)spawn)->RemoveSkillBonus(spell_id); + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + EQ2Packet* packet = ((Player*)spawn)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + else if (spawn->IsNPC()) + ((NPC*)spawn)->RemoveSkillBonus(spell_id); + else + LogWrite(LUA__ERROR, 0, "LUA", "Error removing skill bonus on '%s'. Not a NPC or player.", spawn->GetName()); + } + } + return 0; +} + +int EQ2Emu_lua_AddControlEffect(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt32Value(state, 2); + bool only_add_spawn = lua_interface->GetInt8Value(state, 3) == 1; + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + + if(luaspell && luaspell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!only_add_spawn && luaspell && luaspell->spell && luaspell->caster && type != 0) { + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target && target->IsEntity()) { + if (type == CONTROL_EFFECT_TYPE_MEZ) { + ((Entity*)target)->AddMezSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_MEZ)) + luaspell->effect_bitmask += EFFECT_FLAG_MEZ; + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_STIFLE) { + ((Entity*)target)->AddStifleSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_STIFLE)) + luaspell->effect_bitmask += EFFECT_FLAG_STIFLE; + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_DAZE) { + ((Entity*)target)->AddDazeSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_DAZE)) + luaspell->effect_bitmask += EFFECT_FLAG_DAZE; + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_STUN) { + if (!(luaspell->effect_bitmask & EFFECT_FLAG_STUN)) + luaspell->effect_bitmask += EFFECT_FLAG_STUN; + ((Entity*)target)->AddStunSpell(luaspell); + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_ROOT) { + if (!(luaspell->effect_bitmask & EFFECT_FLAG_ROOT)) + luaspell->effect_bitmask += EFFECT_FLAG_ROOT; + ((Entity*)target)->AddRootSpell(luaspell); + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_FEAR) { + if (!(luaspell->effect_bitmask & EFFECT_FLAG_FEAR)) + luaspell->effect_bitmask += EFFECT_FLAG_FEAR; + ((Entity*)target)->AddFearSpell(luaspell); + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_WALKUNDERWATER) { + ((Entity*)target)->AddWaterwalkSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_WATERWALK)) + luaspell->effect_bitmask += EFFECT_FLAG_WATERWALK; + } + else if (type == CONTROL_EFFECT_TYPE_JUMPUNDERWATER) { + ((Entity*)target)->AddWaterjumpSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_WATERJUMP)) + luaspell->effect_bitmask += EFFECT_FLAG_WATERJUMP; + } + else if (type == CONTROL_EFFECT_TYPE_SNARE) { + ((Entity*)target)->AddSnareSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SNARE)) + luaspell->effect_bitmask += EFFECT_FLAG_SNARE; + if (target->IsNPC()) + ((NPC*)target)->Brain()->AddHate(luaspell->caster, 5); + } + else if (type == CONTROL_EFFECT_TYPE_FLIGHT) { + ((Entity*)target)->AddFlightSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_FLIGHT)) + luaspell->effect_bitmask += EFFECT_FLAG_FLIGHT; + } + else if (type == CONTROL_EFFECT_TYPE_GLIDE) { + ((Entity*)target)->AddGlideSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_GLIDE)) + luaspell->effect_bitmask += EFFECT_FLAG_GLIDE; + } + else if (type == CONTROL_EFFECT_TYPE_SAFEFALL) { + ((Entity*)target)->AddSafefallSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SAFEFALL)) + luaspell->effect_bitmask += EFFECT_FLAG_SAFEFALL; + } + else + lua_interface->LogError("%s: Unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + } + else + lua_interface->LogError("%s: Error applying control effect on non entity '%s'.", lua_interface->GetScriptName(state), (target != nullptr) ? target->GetName() : "NO_TARGET"); + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (only_add_spawn && spawn && spawn->IsEntity()) { + if (type == CONTROL_EFFECT_TYPE_MEZ) { + ((Entity*)spawn)->AddMezSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_MEZ)) + luaspell->effect_bitmask += EFFECT_FLAG_MEZ; + } + else if (type == CONTROL_EFFECT_TYPE_STIFLE) { + ((Entity*)spawn)->AddStifleSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_STIFLE)) + luaspell->effect_bitmask += EFFECT_FLAG_STIFLE; + } + else if (type == CONTROL_EFFECT_TYPE_DAZE) { + ((Entity*)spawn)->AddDazeSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_DAZE)) + luaspell->effect_bitmask += EFFECT_FLAG_DAZE; + } + else if (type == CONTROL_EFFECT_TYPE_STUN) { + ((Entity*)spawn)->AddStunSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_STUN)) + luaspell->effect_bitmask += EFFECT_FLAG_STUN; + } + else if (type == CONTROL_EFFECT_TYPE_ROOT) { + ((Entity*)spawn)->AddRootSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_ROOT)) + luaspell->effect_bitmask += EFFECT_FLAG_ROOT; + } + else if (type == CONTROL_EFFECT_TYPE_FEAR) { + ((Entity*)spawn)->AddFearSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_FEAR)) + luaspell->effect_bitmask += EFFECT_FLAG_FEAR; + } + else if (type == CONTROL_EFFECT_TYPE_WALKUNDERWATER) { + ((Entity*)spawn)->AddWaterwalkSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_WATERWALK)) + luaspell->effect_bitmask += EFFECT_FLAG_WATERWALK; + } + else if (type == CONTROL_EFFECT_TYPE_JUMPUNDERWATER) { + ((Entity*)spawn)->AddWaterjumpSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_WATERJUMP)) + luaspell->effect_bitmask += EFFECT_FLAG_WATERJUMP; + } + else if (type == CONTROL_EFFECT_TYPE_SNARE) { + ((Entity*)spawn)->AddSnareSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SNARE)) + luaspell->effect_bitmask += EFFECT_FLAG_SNARE; + } + else if (type == CONTROL_EFFECT_TYPE_FLIGHT) { + ((Entity*)spawn)->AddFlightSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_FLIGHT)) + luaspell->effect_bitmask += EFFECT_FLAG_FLIGHT; + } + else if (type == CONTROL_EFFECT_TYPE_GLIDE) { + ((Entity*)spawn)->AddGlideSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_GLIDE)) + luaspell->effect_bitmask += EFFECT_FLAG_GLIDE; + } + else if (type == CONTROL_EFFECT_TYPE_SAFEFALL) { + ((Entity*)spawn)->AddSafefallSpell(luaspell); + if (!(luaspell->effect_bitmask & EFFECT_FLAG_SAFEFALL)) + luaspell->effect_bitmask += EFFECT_FLAG_SAFEFALL; + } + else + lua_interface->LogError("%s: Unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + } + else + lua_interface->LogError("%s: Error applying control effect on non entity '%s'.", lua_interface->GetScriptName(state), (spawn != nullptr) ? spawn->GetName() : "NO_SPAWN"); + return 0; +} + +int EQ2Emu_lua_RemoveControlEffect(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + bool only_remove_spawn = lua_interface->GetInt8Value(state, 3) == 1; + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (spawn && spawn->IsEntity()) { + if (!only_remove_spawn && luaspell && luaspell->spell && luaspell->caster) { + ZoneServer* zone = luaspell->caster->GetZone(); + Spawn* target = 0; + luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < luaspell->targets.size(); i++) { + target = zone->GetSpawnByID(luaspell->targets[i]); + if (target) { + if (type == CONTROL_EFFECT_TYPE_MEZ) + ((Entity*)target)->RemoveMezSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_STIFLE) + ((Entity*)target)->RemoveStifleSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_DAZE) + ((Entity*)target)->RemoveDazeSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_STUN) + ((Entity*)target)->RemoveStunSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_ROOT) + ((Entity*)target)->RemoveRootSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_FEAR) + ((Entity*)target)->RemoveFearSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_WALKUNDERWATER) + ((Entity*)target)->RemoveWaterwalkSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_JUMPUNDERWATER) + ((Entity*)target)->RemoveWaterjumpSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_SNARE) + ((Entity*)target)->RemoveSnareSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_FLIGHT) + ((Entity*)target)->RemoveFlightSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_GLIDE) + ((Entity*)target)->RemoveGlideSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_SAFEFALL) + ((Entity*)target)->RemoveGlideSpell(luaspell); + else + lua_interface->LogError("%s: Unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + } + } + luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else if (only_remove_spawn) { + if (type == CONTROL_EFFECT_TYPE_MEZ) + ((Entity*)spawn)->RemoveMezSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_STIFLE) + ((Entity*)spawn)->RemoveStifleSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_DAZE) + ((Entity*)spawn)->RemoveDazeSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_STUN) + ((Entity*)spawn)->RemoveStunSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_ROOT) + ((Entity*)spawn)->RemoveRootSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_FEAR) + ((Entity*)spawn)->RemoveFearSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_WALKUNDERWATER) + ((Entity*)spawn)->RemoveWaterwalkSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_JUMPUNDERWATER) + ((Entity*)spawn)->RemoveWaterjumpSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_SNARE) + ((Entity*)spawn)->RemoveSnareSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_FLIGHT) + ((Entity*)spawn)->RemoveFlightSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_GLIDE) + ((Entity*)spawn)->RemoveGlideSpell(luaspell); + else if (type == CONTROL_EFFECT_TYPE_SAFEFALL) + ((Entity*)spawn)->RemoveSafefallSpell(luaspell); + else + lua_interface->LogError("%s: Unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + } + } + return 0; +} + +int EQ2Emu_lua_HasControlEffect(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + + bool hasEffect = false; + + if (!spawn) + lua_interface->LogError("%s: LUA HasControlEffect error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsEntity()) + lua_interface->LogError("%s: LUA HasControlEffect error: spawn %s is not an entity!.", lua_interface->GetScriptName(state), spawn->GetName()); + else if (type < CONTROL_MAX_EFFECTS) + hasEffect = ((Entity*)spawn)->HasControlEffect(type); + else + lua_interface->LogError("%s: LUA HasControlEffect unhandled control effect type of %u.", lua_interface->GetScriptName(state), type); + + lua_interface->SetBooleanValue(state, hasEffect); + + return 1; +} + +int EQ2Emu_lua_GetBaseAggroRadius(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + + float distance = 0.0f; + + if (!spawn) + lua_interface->LogError("%s: LUA GetBaseAggroRadius error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA GetBaseAggroRadius error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + distance = ((NPC*)spawn)->GetBaseAggroRadius(); + + lua_interface->SetFloatValue(state, distance); + + return 1; +} + +int EQ2Emu_lua_GetAggroRadius(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + + float distance = 0.0f; + + if (!spawn) + lua_interface->LogError("%s: LUA GetAggroRadius error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA GetAggroRadius error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + distance = ((NPC*)spawn)->GetAggroRadius(); + + lua_interface->SetFloatValue(state, distance); + + return 1; +} + +int EQ2Emu_lua_SetAggroRadius(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float distance = lua_interface->GetFloatValue(state, 2); + bool override = lua_interface->GetBooleanValue(state, 3); + + bool result = false; + + lua_interface->ResetFunctionStack(state); + + if (!spawn) + lua_interface->LogError("%s: LUA SetAggroRadius error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA SetAggroRadius error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + { + ((NPC*)spawn)->SetAggroRadius(distance, override); + result = true; + } + + lua_interface->SetBooleanValue(state, result); + + return 1; +} + +int EQ2Emu_lua_SetIntBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_intel_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetAgiBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_agi_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetWisBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_wis_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetStaBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_sta_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetStrBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int16 value = lua_interface->GetInt16Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->GetInfoStruct()->set_str_base(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetDeity(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 value = lua_interface->GetInt8Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->SetDeity(value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GetDeity(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + int8 deity = ((Entity*)spawn)->GetDeity(); + lua_interface->SetInt32Value(state, deity); + return 1; + } + return 0; +} + + +int EQ2Emu_lua_SetInt(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + sint32 value = lua_interface->GetSInt32Value(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_INT, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_SetWis(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + Spawn* spawn = lua_interface->GetSpawn(state); + float value = lua_interface->GetFloatValue(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_WIS, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} +int EQ2Emu_lua_SetSta(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float value = lua_interface->GetFloatValue(state, 2); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_STA, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} +int EQ2Emu_lua_SetStr(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + float value = lua_interface->GetFloatValue(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_STR, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} +int EQ2Emu_lua_SetAgi(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + float value = lua_interface->GetFloatValue(state, 2); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->AddSpellBonus(luaspell, ITEM_STAT_AGI, value); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_GetCurrentHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetHP()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetMaxHP(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTotalHP()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetMaxHPBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTotalHPBase()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetName(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetStringValue(state, spawn->GetName()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetLevel(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetLevel()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetDifficulty(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetDifficulty()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetCurrentPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetPower()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetMaxPower(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTotalPower()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetMaxPowerBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTotalPowerBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetDistance(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* spawn2 = lua_interface->GetSpawn(state, 2); + bool include_radius = lua_interface->GetInt8Value(state, 3) == 1; + if (spawn && spawn2) { + float distance = spawn->GetDistance(spawn2, false, include_radius); + + lua_interface->SetFloatValue(state, distance); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetX(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetX()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetY(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetY()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetZ(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetZ()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetHeading(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetHeading()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetModelType(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetModelType()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpeed(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + lua_interface->SetFloatValue(state, spawn->GetSpeed()); + return 1; + } + return 0; +} +int EQ2Emu_lua_HasMoved(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->HasMoved(false)); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetInt(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetInt()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetWis(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetWis()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetSta(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetSta()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetStr(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetStr()); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetAgi(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetAgi()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetIntBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetIntBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetWisBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetWisBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetStaBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetStaBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetStrBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetStrBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetAgiBase(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetInt32Value(state, ((Entity*)spawn)->GetAgiBase()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetStepComplete(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + if (!player || !player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetStepComplete command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + int32 quest_id = lua_interface->GetInt32Value(state, 2); + if (quest_id <= 0) { + lua_interface->LogError("%s: LUA SetStepComplete command error: quest_id is not valid", lua_interface->GetScriptName(state)); + return 0; + } else if ((((Player*)player)->player_quests.count(quest_id) <= 0)) { + lua_interface->LogError("%s: LUA SetStepComplete command error: player does not have quest", lua_interface->GetScriptName(state)); + return 0; + } + int32 step = lua_interface->GetInt32Value(state, 3); + if (step > 0) { + Client* client = ((Player*)player)->GetClient(); + if (client) + client->AddPendingQuestUpdate(quest_id, step); + } else { + lua_interface->LogError("%s: LUA SetStepComplete command error: step is not valid", lua_interface->GetScriptName(state)); + } + return 0; +} + +int EQ2Emu_lua_AddStepProgress(lua_State* state) { + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int32 step = lua_interface->GetInt32Value(state, 3); + int32 progress = lua_interface->GetInt32Value(state, 4); + if (player && player->IsPlayer() && quest_id > 0 && step > 0 && progress > 0 && (((Player*)player)->player_quests.count(quest_id) > 0)) { + Client* client = ((Player*)player)->GetClient(); + if (client) + client->AddPendingQuestUpdate(quest_id, step, progress); + } + return 0; +} + +int EQ2Emu_lua_GetTaskGroupStep(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetInt32Value(state, ((Player*)player)->GetTaskGroupStep(quest_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_QuestStepIsComplete(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int32 step_id = lua_interface->GetInt32Value(state, 3); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetBooleanValue(state, ((Player*)player)->GetQuestStepComplete(quest_id, step_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetQuestStep(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetInt32Value(state, ((Player*)player)->GetQuestStep(quest_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_RegisterQuest(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string name = lua_interface->GetStringValue(state, 2); + string type = lua_interface->GetStringValue(state, 3); + string zone = lua_interface->GetStringValue(state, 4); + int16 level = lua_interface->GetInt16Value(state, 5); + string description = lua_interface->GetStringValue(state, 6); + bool load = true; + if (!quest) { + lua_interface->LogError("%s: Quest not given in RegisterQuest!", lua_interface->GetScriptName(state)); + load = false; + } + if (load && name.length() == 0) { + lua_interface->LogError("%s: Name not given in RegisterQuest!", lua_interface->GetScriptName(state)); + load = false; + } + if (load && type.length() == 0) { + lua_interface->LogError("%s: Type not given in RegisterQuest for '%s'!", lua_interface->GetScriptName(state), name.c_str()); + load = false; + } + if (load && zone.length() == 0) { + lua_interface->LogError("%s: Zone not given in RegisterQuest for '%s'!", lua_interface->GetScriptName(state), name.c_str()); + load = false; + } + if (load && description.length() == 0) { + lua_interface->LogError("%s: Description not given in RegisterQuest for '%s'!", lua_interface->GetScriptName(state), name.c_str()); + load = false; + } + if (load && level == 0) { + lua_interface->LogError("%s: Level not given in RegisterQuest for '%s'!", lua_interface->GetScriptName(state), name.c_str()); + load = false; + } + if (load) + quest->RegisterQuest(name, type, zone, level, description); + return 0; +} + +int EQ2Emu_lua_SetQuestPrereqLevel(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + if (quest) { + int8 level = lua_interface->GetInt16Value(state, 2); + quest->SetPrereqLevel(level); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqQuest(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + if (quest) { + int32 quest_id = lua_interface->GetInt32Value(state, 2); + quest->AddPrereqQuest(quest_id); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + if (quest) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + int8 quantity = lua_interface->GetInt32Value(state, 3); + if (quantity == 0) + quantity = 1; + Item* master_item = master_item_list.GetItem(item_id); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddPrereqItem(item); + } + } + return 0; +} +int EQ2Emu_lua_HasQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + if(!player || !player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasQuest command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + int32 quest_id = lua_interface->GetInt32Value(state, 2); + if (quest_id > 0) { + lua_interface->SetBooleanValue(state, (((Player*)player)->HasActiveQuest(quest_id) == TRUE)); + return 1; + } else { + lua_interface->LogError("%s: LUA HasQuest command error: quest_id is not valid", lua_interface->GetScriptName(state)); + } + return 0; +} + +int EQ2Emu_lua_QuestReturnNPC(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest && spawn_id > 0) + quest->SetQuestReturnNPC(spawn_id); + return 0; +} + +int EQ2Emu_lua_AddTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 time = lua_interface->GetInt32Value(state, 2); + string function = lua_interface->GetStringValue(state, 3); + int32 max_count = lua_interface->GetInt32Value(state, 4); + Spawn* player = lua_interface->GetSpawn(state, 5); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddTimer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (time <= 0) { + lua_interface->LogError("%s: LUA AddTimer command error: time is not set", lua_interface->GetScriptName(state)); + return 0; + } + + if (function.length() == 0) { + lua_interface->LogError("%s: LUA AddTimer command error: function is not set", lua_interface->GetScriptName(state)); + return 0; + } + + SpawnScriptTimer* timer = new SpawnScriptTimer; + if ( time < 10) + time = 10; + + timer->timer = Timer::GetCurrentTime2() + time; + timer->function = function; + timer->spawn = spawn->GetID(); + timer->player = player ? player->GetID() : 0; + if (max_count == 0) + max_count = 1; + timer->max_count = max_count; + timer->current_count = 0; + spawn->GetZone()->AddSpawnScriptTimer(timer); + + return 0; +} + +int EQ2Emu_lua_StopTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string function = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA StopTimer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if(!spawn->GetZone()) { + lua_interface->LogError("%s: LUA StopTimer command error: spawn has no zone to check spawn timers", lua_interface->GetScriptName(state)); + return 0; + } + spawn->GetZone()->StopSpawnScriptTimer(spawn, function); + + return 0; +} + +int EQ2Emu_lua_GetQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetQuestValue(state, ((Player*)player)->player_quests[quest_id]); + return 1; + } + return 0; +} + +int EQ2Emu_lua_QuestIsComplete(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && quest_id > 0 && (((Player*)player)->player_quests.count(quest_id) > 0)) { + Quest* quest = ((Player*)player)->player_quests[quest_id]; + if (quest) + lua_interface->SetBooleanValue(state, quest->GetCompleted()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_HasCompletedQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && quest_id > 0) { + lua_interface->SetBooleanValue(state, (((Player*)player)->HasQuestBeenCompleted(quest_id) != 0)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_ProvidesQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* npc = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (npc && !npc->IsPlayer() && quest_id > 0) + npc->AddProvidedQuest(quest_id); + return 0; +} + +int EQ2Emu_lua_OfferQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* npc = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + int32 quest_id = lua_interface->GetInt32Value(state, 3); + bool forced = lua_interface->GetBooleanValue(state, 4); + lua_interface->ResetFunctionStack(state); + + /* NPC is allowed to be null */ + if (player && player->IsPlayer() && quest_id > 0) { + Quest* master_quest = master_quest_list.GetQuest(quest_id, false); + if (master_quest) { + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA OfferQuest command error: client is not set", lua_interface->GetScriptName(state)); + } + Quest* quest = new Quest(master_quest); + if (!quest) { + lua_interface->LogError("%s: LUA OfferQuest command error: new Quest() failed.", lua_interface->GetScriptName(state)); + } + if (client && quest) { + if (npc) + quest->SetQuestGiver(npc->GetDatabaseID()); + else + quest->SetQuestGiver(0); + client->AddPendingQuest(quest, forced); + } + } + else { + lua_interface->LogError("%s: LUA OfferQuest command error: failed to get quest %d", lua_interface->GetScriptName(state), quest_id); + } + } + else { + lua_interface->LogError("%s: LUA OfferQuest command error: player is not set or bad quest id %p %d", lua_interface->GetScriptName(state), player, quest_id); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqClass(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int8 class_id = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddPrereqClass(class_id); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqRace(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int8 race = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddPrereqRace(race); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqModelType(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int16 model_type = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddPrereqModelType(model_type); + } + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqTradeskillLevel(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + int8 level = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA AddQuestPrereqTradeskillLevel command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetPrereqTSLevel(level); + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqTradeskillClass(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + int8 class_id = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!quest) { + lua_interface->LogError("%s: LUA AddQuestPrereqTradeskillClass command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + quest->AddPrereqTradeskillClass(class_id); + return 0; +} + +int EQ2Emu_lua_AddQuestPrereqFaction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 faction_id = lua_interface->GetInt32Value(state, 2); + sint32 min = lua_interface->GetSInt32Value(state, 3); + sint32 max = lua_interface->GetSInt32Value(state, 4); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddPrereqFaction(faction_id, min, max); + } + return 0; +} + +int EQ2Emu_lua_AddQuestSelectableRewardItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + int8 quantity = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (quantity == 0) + quantity = 1; + Item* master_item = master_item_list.GetItem(item_id); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddSelectableRewardItem(item); + } + } + return 0; +} + +int EQ2Emu_lua_HasQuestRewardItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + if (quest) { + int32 item_id = lua_interface->GetInt32Value(state, 2); + vector* items = quest->GetRewardItems(); + if (items) { + vector::iterator itr; + for (itr = items->begin(); itr != items->end(); itr++) { + if (*itr && (*itr)->details.item_id == item_id) { + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + } + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_AddQuestRewardItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + int8 quantity = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (quantity == 0) + quantity = 1; + Item* master_item = master_item_list.GetItem(item_id); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddRewardItem(item); + } + } + return 0; +} + +int EQ2Emu_lua_AddQuestRewardCoin(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 copper = lua_interface->GetInt32Value(state, 2); + int32 silver = lua_interface->GetInt32Value(state, 3); + int32 gold = lua_interface->GetInt32Value(state, 4); + int32 plat = lua_interface->GetInt32Value(state, 5); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->AddRewardCoins(copper, silver, gold, plat); + } + return 0; +} + +int EQ2Emu_lua_AddQuestRewardFaction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 faction_id = lua_interface->GetInt32Value(state, 2); + sint32 amount = lua_interface->GetSInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest && faction_id > 0 && amount != 0) + quest->AddRewardFaction(faction_id, amount); + return 0; +} + +int EQ2Emu_lua_SetQuestRewardStatus(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 status = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetRewardStatus(status); + } + return 0; +} + +int EQ2Emu_lua_SetStatusTmpReward(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 status = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetStatusTmpReward(status); + } + return 0; +} + + +int EQ2Emu_lua_SetCoinTmpReward(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int64 coins = lua_interface->GetInt64Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetCoinTmpReward(coins); + } + return 0; +} + + +int EQ2Emu_lua_SetQuestRewardComment(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string comment = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetRewardComment(comment); + } + return 0; +} + +int EQ2Emu_lua_SetQuestRewardExp(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 exp = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + quest->SetRewardXP(exp); + } + return 0; +} + +int EQ2Emu_lua_AddQuestStep(lua_State* state) { + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + int32 usableitemid = lua_interface->GetInt32Value(state, 8); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + + + int32 id = 0; + vector* ids = 0; + int i = 0; + while ((id = lua_interface->GetInt32Value(state, 9 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(id); + i++; + } + + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_NORMAL, description, ids, quantity, taskgroup, 0, 0, percentage, usableitemid); + if (quest_step && icon && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} +int EQ2Emu_lua_AddQuestStepKillLogic(lua_State* state, int8 type) +{ + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + + int32 id = 0; + vector* ids = 0; + int i = 0; + while ((id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, type, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} +int EQ2Emu_lua_AddQuestStepKill(lua_State* state) { + return EQ2Emu_lua_AddQuestStepKillLogic(state, QUEST_STEP_TYPE_KILL); +} + +int EQ2Emu_lua_AddQuestStepKillByRace(lua_State* state) { + return EQ2Emu_lua_AddQuestStepKillLogic(state, QUEST_STEP_TYPE_KILL_RACE_REQ); +} + +int EQ2Emu_lua_AddQuestStepChat(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + string str_taskgroup = lua_interface->GetStringValue(state, 5); + int16 icon = lua_interface->GetInt16Value(state, 6); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 npc_id = 0; + vector* ids = 0; + int i = 0; + while ((npc_id = lua_interface->GetInt32Value(state, 7 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(npc_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_CHAT, description, ids, quantity, taskgroup); + if (quest_step && icon > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + if(client) + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepObtainItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 item_id = 0; + vector* ids = 0; + int i = 0; + while ((item_id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(item_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_OBTAIN_ITEM, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepZoneLoc(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + float max_variation = lua_interface->GetFloatValue(state, 4); + string str_taskgroup = lua_interface->GetStringValue(state, 5); + int16 icon = lua_interface->GetInt16Value(state, 6); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + vector* locations = 0; + int8 i = 7; + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + while (true) { + Location loc; + loc.x = lua_interface->GetFloatValue(state, i); + loc.y = lua_interface->GetFloatValue(state, i + 1); + loc.z = lua_interface->GetFloatValue(state, i + 2); + loc.zone_id = lua_interface->GetInt32Value(state, i + 3); + + if (loc.x == 0 && loc.y == 0 && loc.z == 0) + break; + if (locations == 0) + locations = new vector; + locations->push_back(loc); + i += 4; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation); + safe_delete(locations); // gets duplicated into new table in QuestStep constructor + if (quest_step && icon > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepLocation(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + float max_variation = lua_interface->GetFloatValue(state, 4); + string str_taskgroup = lua_interface->GetStringValue(state, 5); + int16 icon = lua_interface->GetInt16Value(state, 6); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + vector* locations = 0; + int8 i = 7; + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + while (true) { + Location loc; + loc.x = lua_interface->GetFloatValue(state, i); + loc.y = lua_interface->GetFloatValue(state, i + 1); + loc.z = lua_interface->GetFloatValue(state, i + 2); + loc.zone_id = 0; + + if (loc.x == 0 && loc.y == 0 && loc.z == 0) + break; + if (locations == 0) + locations = new vector; + locations->push_back(loc); + i += 3; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation); + safe_delete(locations); // gets duplicated into new table in QuestStep constructor + if (quest_step && icon > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} +int EQ2Emu_lua_AddQuestUsableItem(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + float max_variation = lua_interface->GetFloatValue(state, 4); + string str_taskgroup = lua_interface->GetStringValue(state, 5); + int16 icon = lua_interface->GetInt16Value(state, 6); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + vector* locations = 0; + int i = 7; + while (true) { + Location loc; + loc.x = lua_interface->GetFloatValue(state, i); + loc.y = lua_interface->GetFloatValue(state, i + 1); + loc.z = lua_interface->GetFloatValue(state, i + 2); + if (loc.x == 0 && loc.y == 0 && loc.z == 0) + break; + if (locations == 0) + locations = new vector; + locations->push_back(loc); + i += 3; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation); + safe_delete(locations); // gets duplicated into new table in QuestStep constructor + if (quest_step && icon > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + } + lua_interface->ResetFunctionStack(state); + return 0; +} +int EQ2Emu_lua_AddQuestStepSpell(lua_State* state) { + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 spell_id = 0; + vector* ids = 0; + int i = 0; + while ((spell_id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(spell_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_SPELL, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepCraft(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 item_id = 0; + vector* ids = 0; + int i = 0; + while ((item_id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(item_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_CRAFT, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_AddQuestStepHarvest(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 quantity = lua_interface->GetInt32Value(state, 4); + float percentage = lua_interface->GetFloatValue(state, 5); + string str_taskgroup = lua_interface->GetStringValue(state, 6); + int16 icon = lua_interface->GetInt16Value(state, 7); + if (quest) { + const char* taskgroup = 0; + if (str_taskgroup.length() > 0) + taskgroup = str_taskgroup.c_str(); + int32 item_id = 0; + vector* ids = 0; + int i = 0; + while ((item_id = lua_interface->GetInt32Value(state, 8 + i))) { + if (ids == 0) + ids = new vector; + ids->push_back(item_id); + i++; + } + QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_HARVEST, description, ids, quantity, taskgroup, 0, 0, percentage, 0); + if (quest_step && icon > 0 && quantity > 0) + quest_step->SetIcon(icon); + if (quest->GetPlayer() && ((Player*)quest->GetPlayer())->GetClient()) { + Client* client = ((Player*)quest->GetPlayer())->GetClient(); + quest->GetPlayer()->GetZone()->SendQuestUpdates(client); + } + safe_delete(ids); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_SetQuestCompleteAction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string action = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (action.length() > 0) + quest->SetCompleteAction(action); + } + return 0; +} + +int EQ2Emu_lua_AddQuestStepCompleteAction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string action = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (step > 0 && action.length() > 0) + quest->AddCompleteAction(step, action); + } + return 0; +} + +int EQ2Emu_lua_AddQuestStepProgressAction(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string action = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest) { + if (step > 0 && action.length() > 0) + quest->AddProgressAction(step, action); + } + return 0; +} + +int EQ2Emu_lua_UpdateQuestDescription(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string description = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest && description.length() > 0) + quest->SetDescription(description); + return 0; +} + +int EQ2Emu_lua_SetCompletedDescription(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + string description = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest && description.length() > 0) + quest->SetCompletedDescription(description); + return 0; +} + +int EQ2Emu_lua_UpdateQuestTaskGroupDescription(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + bool display_bullets = (lua_interface->GetInt8Value(state, 4) == 1); + lua_interface->ResetFunctionStack(state); + if (quest && step > 0 && description.length() > 0) { + quest->SetTaskGroupDescription(step, description, display_bullets); + } + return 0; +} + +int EQ2Emu_lua_UpdateQuestStepDescription(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string description = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (quest && step > 0 && description.length() > 0) { + quest->SetStepDescription(step, description); + } + return 0; +} + +int EQ2Emu_lua_UpdateQuestZone(lua_State* state) { + Quest* quest = lua_interface->GetQuest(state); + string zone = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (quest && zone.length() > 0) + quest->SetZone(zone); + return 0; +} + +int EQ2Emu_lua_GiveQuestReward(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + + lua_interface->ResetFunctionStack(state); + if (quest && spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + client->AddPendingQuestReward(quest); + } + } + } + return 0; +} + +int EQ2Emu_lua_Harvest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + Spawn* node = lua_interface->GetSpawn(state, 2); + if (player && node && player->IsPlayer() && node->IsGroundSpawn()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + ((GroundSpawn*)node)->ProcessHarvest(client); + if (((GroundSpawn*)node)->GetNumberHarvests() == 0) { + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + player->GetZone()->RemoveSpawn(node, true, true, true, true, (spell != nullptr) ? false : true); + } + } + } + else if (player && player->IsPlayer()) { + Client* client = ((Player*)player)->GetClient(); + if (client) + client->Message(CHANNEL_COLOR_RED, "Invalid target for this spell."); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_Bind(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 zone_id = lua_interface->GetInt32Value(state, 2); + float x = lua_interface->GetFloatValue(state, 3); + float y = lua_interface->GetFloatValue(state, 4); + float z = lua_interface->GetFloatValue(state, 5); + float h = lua_interface->GetFloatValue(state, 6); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA Bind command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA Bind command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (zone_id == 0) { + Client* client = ((Player*)spawn)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA Bind command error: unable to get client from spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!client->Bind()) + client->SimpleMessage(CHANNEL_COLOR_RED, "Unable to set bind point."); + } + else { + Player* player = (Player*)spawn; + player->GetPlayerInfo()->SetBindZone(zone_id); + player->GetPlayerInfo()->SetBindX(x); + player->GetPlayerInfo()->SetBindY(y); + player->GetPlayerInfo()->SetBindZ(z); + player->GetPlayerInfo()->SetBindHeading(h); + } + + return 0; +} + +int EQ2Emu_lua_Gate(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + if (!client->Gate((spell != nullptr) ? true : false)) + client->SimpleMessage(CHANNEL_COLOR_RED, "Unable to gate."); + } + } + } + return 0; +} + +int EQ2Emu_lua_IsBindAllowed(lua_State* state) { + if (!lua_interface) + return 0; + bool ret = false; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + ret = client->BindAllowed(); + } + } + lua_interface->SetBooleanValue(state, ret); + return 1; +} + +int EQ2Emu_lua_IsGateAllowed(lua_State* state) { + if (!lua_interface) + return 0; + bool ret = false; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + ZoneServer* zone = lua_interface->GetZone(state); + if (client && zone){ + ret = zone->GetCanGate(); + } + } + } + lua_interface->SetBooleanValue(state, ret); + return 1; +} + +int EQ2Emu_lua_IsAlive(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->Alive()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_IsInCombat(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity()) { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->EngagedInCombat()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SendMessage(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + string color_str = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + int8 color = CHANNEL_NARRATIVE; + if (spawn && spawn->IsPlayer() && message.length() > 0) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + if (color_str.length() > 0) { + // leave for backwards compat, but all future should just use the number + if (strncasecmp(color_str.c_str(), "red", 3) == 0) + color = CHANNEL_COLOR_RED; + else if (strncasecmp(color_str.c_str(), "yellow", 6) == 0) + color = CHANNEL_COLOR_YELLOW; + else + { + // use a number to specify the channel as per Commands/Commands.h defines + color = (int8)atoul(color_str.c_str()); + } + } + client->SimpleMessage(color, message.c_str()); + } + } + return 0; +} + +int EQ2Emu_lua_SendPopUpMessage(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + string message = lua_interface->GetStringValue(state, 2); + int8 red = lua_interface->GetInt8Value(state, 3); + int8 green = lua_interface->GetInt8Value(state, 4); + int8 blue = lua_interface->GetInt8Value(state, 5); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SendPopUpMessage command error: Spawn is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA SendPopUpMessage command error: Spawn is not a player.", lua_interface->GetScriptName(state)); + return 0; + } + + int32 words = ::CountWordsInString(message.c_str()); + if (words < 5) + words = 5; + Client* client = ((Player*)spawn)->GetClient(); + if (client) + client->SendPopupMessage(10, message.c_str(), "ui_harvested_normal", words, red, green, blue); + return 0; +} + +int EQ2Emu_lua_SetServerControlFlag(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + int8 param = lua_interface->GetInt8Value(state, 2); + int8 param_value = lua_interface->GetInt8Value(state, 3); + int8 value = lua_interface->GetInt8Value(state, 4); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsPlayer() && (param >= 1 && param <= 5)) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", client->GetVersion()); + switch (param) { + case 1: { + packet->setDataByName("parameter1", param_value); + break; + } + case 2: { + packet->setDataByName("parameter2", param_value); + break; + } + case 3: { + packet->setDataByName("parameter3", param_value); + break; + } + case 4: { + packet->setDataByName("parameter4", param_value); + break; + } + case 5: { + packet->setDataByName("parameter5", param_value); + break; + } + } + packet->setDataByName("value", value); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + return 0; +} + +int EQ2Emu_lua_ToggleTracking(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsPlayer()) { + if (((Player*)spawn)->GetIsTracking()) + spawn->GetZone()->AddPlayerTracking((Player*)spawn); + else + spawn->GetZone()->RemovePlayerTracking((Player*)spawn, TRACKING_STOP); + } + return 0; +} + +int EQ2Emu_lua_AddPrimaryEntityCommand(lua_State* state) { + Spawn* player = lua_interface->GetSpawn(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + string name = lua_interface->GetStringValue(state, 3); + float distance = lua_interface->GetFloatValue(state, 4); + string command = lua_interface->GetStringValue(state, 5); + string error_text = lua_interface->GetStringValue(state, 6); + int16 cast_time = lua_interface->GetInt16Value(state, 7); + int32 spell_visual = lua_interface->GetInt32Value(state, 8); + bool denyListDefault = (lua_interface->GetInt8Value(state, 9) == 1); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (distance == 0) + distance = 10.0f; + if (command.length() == 0) + command = name; + if (command.length() < 1 && name.length() < 1) + { + // have to run this first to send a 'blank' default command, then remove all commands from the list + spawn->GetZone()->SendUpdateDefaultCommand(spawn, command.c_str(), distance); + spawn->RemovePrimaryCommands(); + } + else + { + spawn->AddPrimaryEntityCommand(name.c_str(), distance, command.c_str(), error_text.c_str(), cast_time, spell_visual, denyListDefault, (player && player->IsPlayer()) ? (Player*)player : NULL); + } + } + return 0; +} + +int EQ2Emu_lua_HasSpell(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 spellid = lua_interface->GetInt32Value(state, 2); + int16 tier = lua_interface->GetInt16Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->HasSpell(spellid, tier, true)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_AddSpellBookEntry(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 spellid = lua_interface->GetInt32Value(state, 2); + int16 tier = lua_interface->GetInt16Value(state, 3); + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + bool add_silently = lua_interface->GetBooleanValue(state, 4); + bool add_to_hotbar = true; + if (num_args > 4) { + add_to_hotbar = lua_interface->GetBooleanValue(state, 5); + } + lua_interface->ResetFunctionStack(state); + Spell* spell = master_spell_list.GetSpell(spellid, tier); + if (player && spell && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) { + if (!client->GetPlayer()->HasSpell(spellid, tier - 1, true)) + { + Spell* spell = master_spell_list.GetSpell(spellid, tier); + client->GetPlayer()->AddSpellBookEntry(spellid, 1, client->GetPlayer()->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell, add_silently, add_to_hotbar); + } + else + { + Spell* spell = master_spell_list.GetSpell(spellid, tier); + int8 old_slot = client->GetPlayer()->GetSpellSlot(spell->GetSpellID()); + client->GetPlayer()->RemoveSpellBookEntry(spell->GetSpellID()); + client->GetPlayer()->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + client->GetPlayer()->UnlockSpell(spell); + client->SendSpellUpdate(spell, add_silently, add_to_hotbar); + } + + + + //if (client ) { + // ((Player*)player)->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), ((Player*)player)->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + EQ2Packet* outapp = ((Player*)player)->GetSpellBookUpdatePacket(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + } + return 0; +} + +int EQ2Emu_lua_DeleteSpellBook(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int8 type_selection = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) { + ((Player*)player)->DeleteSpellBook(type_selection); + EQ2Packet* outapp = ((Player*)player)->GetSpellBookUpdatePacket(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + } + return 0; +} + +int EQ2Emu_lua_SendNewAdventureSpells(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) { + client->SendNewAdventureSpells(); + } + } + return 0; +} + + +int EQ2Emu_lua_SendNewTradeskillSpells(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) { + client->SendNewTradeskillSpells(); + } + } + return 0; +} + +int EQ2Emu_lua_RemoveSpellBookEntry(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 spellid = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + SpellBookEntry* sbe = ((Player*)player)->GetSpellBookSpell(spellid); + Client* client = player->GetClient(); + if (sbe && client) { + ((Player*)player)->RemoveSpellBookEntry(spellid); + EQ2Packet* outapp = ((Player*)player)->GetSpellBookUpdatePacket(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + } + return 0; +} + + +int EQ2Emu_lua_HasFreeSlot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->item_list.HasFreeSlot()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_Attack(lua_State* state) { + if (lua_interface) { + Spawn* npc = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (npc && player && npc->IsNPC() && player->IsEntity()) + ((NPC*)npc)->AddHate((Entity*)player, 100); + } + return 0; +} + +int EQ2Emu_lua_ApplySpellVisual(lua_State* state) { + if (lua_interface) { + Spawn* target = lua_interface->GetSpawn(state); + int32 spell_visual = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (target && target->GetZone()) + target->GetZone()->SendCastSpellPacket(spell_visual, target); + } + return 0; +} + +int EQ2Emu_lua_HasCollectionsToHandIn(lua_State* state) { + Spawn* player; + + if (lua_interface) { + player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->GetCollectionList()->HasCollectionsToHandIn()); + return 1; + } + } + + return 0; +} + +int EQ2Emu_lua_HandInCollections(lua_State* state) { + Spawn* player; + Client* client; + + if (lua_interface) { + player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && ((Player*)player)->IsPlayer() && ((Player*)player)->GetCollectionList()->HasCollectionsToHandIn()) + if ((client = ((Player*)player)->GetClient())) + client->HandInCollections(); + } + + return 0; +} + +int EQ2Emu_lua_UseWidget(lua_State* state) { + Spawn* widget; + + if (lua_interface) { + widget = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (widget && widget->IsWidget()) + ((Widget*)widget)->HandleUse(nullptr, ""); + } + + return 0; +} + +int EQ2Emu_lua_SetSpellList(lua_State* state) { + Spawn* spawn = 0; + int32 primary_list = 0; + int32 secondary_list = 0; + + if (lua_interface) { + spawn = lua_interface->GetSpawn(state); + primary_list = lua_interface->GetInt32Value(state, 2); + secondary_list = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA SetSpellList command error: Spawn was not a valid NPC", lua_interface->GetScriptName(state)); + return 0; + } + + NPC* npc = (NPC*)spawn; + npc->SetPrimarySpellList(primary_list); + npc->SetSecondarySpellList(secondary_list); + npc->SetSpells(world.GetNPCSpells(npc->GetPrimarySpellList(), npc->GetSecondarySpellList())); + } + return 0; +} + +int EQ2Emu_lua_GetPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsEntity() && ((Entity*)spawn)->GetPet()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetPet()); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetCharmedPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsEntity() && ((Entity*)spawn)->GetCharmedPet()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetCharmedPet()); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetDeityPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsEntity() && ((Entity*)spawn)->GetDeityPet()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetDeityPet()); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_GetCosmeticPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsEntity() && ((Entity*)spawn)->GetCosmeticPet()) { + lua_interface->SetSpawnValue(state, ((Entity*)spawn)->GetCosmeticPet()); + return 1; + } + } + return 0; +} +int EQ2Emu_lua_Charm(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* owner = lua_interface->GetSpawn(state); + Spawn* pet = lua_interface->GetSpawn(state, 2); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!luaspell) { + lua_interface->LogError("%s: LUA Charm command error: Spell is not valid, charm can only be used in spell scripts.", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + if (owner && pet && owner->IsEntity() && pet->IsNPC()) { + ((Entity*)owner)->SetCharmedPet((Entity*)pet); + pet->SetPet(true); + ((NPC*)pet)->SetPetType(PET_TYPE_CHARMED); + ((NPC*)pet)->SetOwner((Entity*)owner); + // If owner is player and player does not have a summoned pet set the players charsheet + if (owner->IsPlayer() && !((Entity*)owner)->GetPet()) { + Player* player = (Player*)owner; + player->GetInfoStruct()->set_pet_id(player->GetIDWithPlayerSpawn(pet)); + player->GetInfoStruct()->set_pet_name(std::string(pet->GetName())); + player->GetInfoStruct()->set_pet_movement(2); + player->GetInfoStruct()->set_pet_behavior(3); + player->GetInfoStruct()->set_pet_health_pct(1.0f); + player->GetInfoStruct()->set_pet_power_pct(1.0f); + // Make sure the values get sent to the client + player->SetCharSheetChanged(true); + } + // Clear the spawns script so the charmed mob doesn't try to do anything like random walks + pet->SetSpawnScript(""); + // Set faction to the same as the owner + pet->SetFactionID(owner->GetFactionID()); + + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + + // Clear hate list + ((NPC*)pet)->Brain()->ClearHate(); + + // Set the brain to a pet brain + ((NPC*)pet)->SetBrain(new CombatPetBrain((NPC*)pet)); + } + return 0; +} + +int EQ2Emu_lua_GetGroup(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetGroup command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + vector groupMembers; + if (!spawn->IsPlayer() && spawn->HasSpawnGroup()) { + groupMembers = *spawn->GetSpawnGroup(); + } + else if (spawn->IsPlayer() && ((Player*)spawn)->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)spawn)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + GroupMemberInfo* info = 0; + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + if (info->client) + groupMembers.push_back(info->client->GetPlayer()); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + return 0; + + lua_createtable(state, groupMembers.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < groupMembers.size(); i++) { + lua_interface->SetSpawnValue(state, groupMembers.at(i)); + lua_rawseti(state, newTable, i + 1); + } + return 1; + +} + +int EQ2Emu_lua_CreateOptionWindow(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + vector* option_window = new vector(); + lua_interface->SetOptionWindowValue(state, option_window); + return 1; +} + +int EQ2Emu_lua_AddOptionWindowOption(lua_State* state) { + if (!lua_interface) + return 0; + vector* option_window = lua_interface->GetOptionWindow(state); + if (option_window) { + OptionWindowOption option_window_option; + option_window_option.optionName = lua_interface->GetStringValue(state, 2); + option_window_option.optionDescription = lua_interface->GetStringValue(state, 3); + option_window_option.optionIconSheet = lua_interface->GetInt32Value(state, 4); + option_window_option.optionIconID = lua_interface->GetInt16Value(state, 5); + option_window_option.optionCommand = lua_interface->GetStringValue(state, 6); + option_window_option.optionConfirmTitle = lua_interface->GetStringValue(state, 7); + if (option_window_option.optionName.length() > 0 && option_window_option.optionDescription.length() > 0) + option_window->push_back(option_window_option); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_SendOptionWindow(lua_State* state) { + if (!lua_interface) + return 0; + + vector* option_window = lua_interface->GetOptionWindow(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + string window_title = lua_interface->GetStringValue(state, 3); + string cancel_command = lua_interface->GetStringValue(state, 4); + + lua_interface->ResetFunctionStack(state); + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SendOptionWindow command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + + if (option_window && window_title.length() > 0 && client) { + PacketStruct* packet = configReader.getStruct("WS_SelectTradeskill", client->GetVersion()); + if (!packet) + return 0; + + packet->setDataByName("title_text", window_title.c_str()); + if (cancel_command.length() > 0) + packet->setDataByName("command_text_cancel", cancel_command.c_str()); + + packet->setArrayLengthByName("num_selections", option_window->size()); + vector::iterator itr; + int8 i = 0; + for (itr = option_window->begin(); itr != option_window->end(); itr++) { + OptionWindowOption opt = *itr; + packet->setArrayDataByName("tradeskill_name", opt.optionName.c_str(), i); + packet->setArrayDataByName("tradeskill_description", opt.optionDescription.c_str(), i); + packet->setArrayDataByName("icon_sheet", opt.optionIconSheet, i); + packet->setArrayDataByName("icon_id", opt.optionIconID, i); + if (opt.optionCommand.length() > 0) + packet->setArrayDataByName("command_text", opt.optionCommand.c_str(), i); + if (opt.optionConfirmTitle.length() > 0) + packet->setArrayDataByName("confirm_window_title", opt.optionConfirmTitle.c_str(), i); + + i++; + } + client->QueuePacket(packet->serialize()); + lua_interface->SetLuaUserDataStale(option_window); + safe_delete(option_window); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_GetTradeskillClass(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTradeskillClass()); + return 1; + } + else + lua_interface->LogError("%s: LUA GetTradeskillClass command error: Spawn was not valid", lua_interface->GetScriptName(state)); + + return 0; +} + +int EQ2Emu_lua_GetTradeskillLevel(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetInt32Value(state, spawn->GetTSLevel()); + return 1; + } + else + lua_interface->LogError("%s: LUA GetTradeskillLevel command error: Spawns was not valid", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_GetTradeskillClassName(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + int8 class_id = spawn->GetTradeskillClass(); + // Need to add 42 for the offset in the array + class_id += 44; + lua_interface->SetStringValue(state, classes.GetClassNameCase(class_id).c_str()); + return 1; + } + else + lua_interface->LogError("%s: LUA GetTradeskillClassName command error: Spawn was not valid", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_SetTradeskillLevel(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 level = lua_interface->GetInt8Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (spawn) { + if (spawn->IsPlayer() && ((Player*)spawn)->GetClient()) + ((Player*)spawn)->GetClient()->ChangeTSLevel(spawn->GetTSLevel(), level); + else + spawn->SetTSLevel(level); + } + else + lua_interface->LogError("%s: LUA SetTradeskillLevel command error: Spawn was not valid", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_SetAttackable(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 attackable = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetAttackable(attackable); + spawn->vis_changed = true; //some clients store this in vis instead of info, need to make sense both are updated + } + return 0; +} + +int EQ2Emu_lua_SummonPet(lua_State* state) { + // Check to see if we have a valid lua_interface + if (!lua_interface) + return 0; + + // Get the spawn that is getting the pet + Spawn* spawn = lua_interface->GetSpawn(state); + // Get the DB ID of the pet + int32 pet_id = lua_interface->GetInt32Value(state, 2); + // The max level the pet can gain + int8 max_level = lua_interface->GetInt8Value(state, 3); + // Get the spell that this command was called from + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + // Check to make sure the spawn pointer is valid + if (!spawn) { + lua_interface->LogError("%s: LUA SummonPet command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to make sure the spawn is an entity + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SummonPet command error: Spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to make sure the spawn doesn't already have a pet of this type + if (((Entity*)spawn)->GetPet()) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You already have a pet."); + } + + lua_interface->LogError("%s: LUA SummonPet command error: spawn already has a pet of this type", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to see if the DB ID for the pet is set + if (pet_id == 0) { + lua_interface->LogError("%s: LUA SummonPet command error: pet_id can not be set to 0", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to see if the pointer to the spell is valid + if (!luaspell) { + lua_interface->LogError("%s: LUA SummonPet command error: valid spell not found, SummonPet can only be used in spell scripts", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + + // Get a pointer to a spawn with the given DB ID and check if the pointer is valid + Spawn* pet = spawn->GetZone()->GetSpawn(pet_id); + if (!pet) { + lua_interface->LogError("%s: LUA SummonPet command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + // Check to make sure the pet is an npc + if (!pet->IsNPC()) { + lua_interface->LogError("%s: LUA SummonPet command error: id (%u) did not point to a npc", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + // Spawn the pet at the same location as the owner + pet->SetX(spawn->GetX()); + pet->SetY(spawn->GetY()); + pet->SetZ(spawn->GetZ()); + pet->SetLocation(spawn->GetLocation()); + pet->SetHeading(spawn->GetHeading()); + + std::string petName = std::string(""); + if(spawn->IsEntity()) { + petName = ((Entity*)spawn)->GetInfoStruct()->get_pet_name(); + } + + if(petName.size() < 1) { + int16 rand_index = MakeRandomInt(0, spawn->GetZone()->pet_names.size() - 1); + petName = spawn->GetZone()->pet_names.at(rand_index); + LogWrite(PET__DEBUG, 0, "Pets", "Randomize Pet Name: '%s' (rand: %i)", petName.c_str(), rand_index); + } + + // If player set various values for the char sheet (pet window) + if (spawn->IsPlayer()) { + Player* player = (Player*)spawn; + + player->GetInfoStruct()->set_pet_id(player->GetIDWithPlayerSpawn(pet)); + player->GetInfoStruct()->set_pet_name(petName); + player->GetInfoStruct()->set_pet_movement(2); + player->GetInfoStruct()->set_pet_behavior(3); + player->GetInfoStruct()->set_pet_health_pct(1.0f); + player->GetInfoStruct()->set_pet_power_pct(1.0f); + // Make sure the values get sent to the client + player->SetCharSheetChanged(true); + } + + // Set the pets name + pet->SetName(petName.c_str()); + // Set the level of the pet to the owners level or max level(if set) if owners level is greater + if (max_level > 0) + pet->SetLevel(spawn->GetLevel() >= max_level ? max_level : spawn->GetLevel()); + else + pet->SetLevel(spawn->GetLevel()); + // Set the max level this pet can reach + ((NPC*)pet)->SetMaxPetLevel(max_level); + + ((NPC*)pet)->UpdateWeapons(); + + // Set the faction of the pet to the same faction as the owner + pet->SetFactionID(spawn->GetFactionID()); + // Set the spawn as a pet + pet->SetPet(true); + // Give a pointer of the owner to the pet + ((NPC*)pet)->SetOwner((Entity*)spawn); + // Give a pointer of the pet to the owner + ((Entity*)spawn)->SetCombatPet((Entity*)pet); + // Set the pet type + ((NPC*)pet)->SetPetType(PET_TYPE_COMBAT); + // Set the spell id used to create this pet + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + // Set the spell tier used to create this pet + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + // Set the pets spawn type to 6 + pet->SetSpawnType(6); + // Set the pets brain + ((NPC*)pet)->SetBrain(new CombatPetBrain((NPC*)pet)); + // Check to see if the pet has a subtitle + if (strlen(pet->GetSubTitle()) > 0) { + // Add the players name to the front of the sub title + string pet_subtitle; + pet_subtitle.append(spawn->GetName()).append("'s ").append(pet->GetSubTitle()); + LogWrite(PET__DEBUG, 0, "Pets", "Pet Subtitle: '%s'", pet_subtitle.c_str()); + // Set the pets subtitle to the new one + pet->SetSubTitle(pet_subtitle.c_str()); + } + // Add the "Pet Options" entity command to the pet + pet->AddSecondaryEntityCommand("Pet Options", 10.0f, "petoptions", "", 0, 0); + + const char* spawn_script = world.GetSpawnScript(pet_id); + bool runScript = false; + if(spawn_script && lua_interface->GetSpawnScript(spawn_script) != 0){ + runScript = true; + pet->SetSpawnScript(string(spawn_script)); + spawn->GetZone()->CallSpawnScript(pet, SPAWN_SCRIPT_PRESPAWN); + } + + spawn->GetZone()->AddSpawn(pet); + + if(runScript){ + spawn->GetZone()->CallSpawnScript(pet, SPAWN_SCRIPT_SPAWN); + } + + // Set the pet as the return value for this function + lua_interface->SetSpawnValue(state, pet); + return 1; +} + +int EQ2Emu_lua_SummonDeityPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 pet_id = lua_interface->GetInt32Value(state, 2); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: Spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)spawn)->GetDeityPet()) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You already have a deity pet."); + } + + lua_interface->LogError("%s: LUA SummonDeityPet command error: spawn already has a pet of this type", lua_interface->GetScriptName(state)); + return 0; + } + + if (pet_id == 0) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: pet_id can not be set to 0", lua_interface->GetScriptName(state)); + return 0; + } + + if (!luaspell) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: valid spell not found, SummonDeityPet can only be used in spell scripts", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + + Spawn* pet = spawn->GetZone()->GetSpawn(pet_id); + if (!pet) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + if (!pet->IsNPC()) { + lua_interface->LogError("%s: LUA SummonDeityPet command error: spawn with id of %u is not a npc", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + pet->SetX(spawn->GetX()); + pet->SetY(spawn->GetY()); + pet->SetZ(spawn->GetZ()); + pet->SetLocation(spawn->GetLocation()); + pet->SetHeading(spawn->GetHeading()); + spawn->GetZone()->AddSpawn(pet); + + string random_pet_name; + int16 rand_index = MakeRandomInt(0, spawn->GetZone()->pet_names.size() - 1); + random_pet_name = spawn->GetZone()->pet_names.at(rand_index); + LogWrite(PET__DEBUG, 0, "Pets", "Randomize Pet Name: '%s' (rand: %i)", random_pet_name.c_str(), rand_index); + + pet->SetName(random_pet_name.c_str()); + pet->SetLevel(spawn->GetLevel()); + pet->SetFactionID(spawn->GetFactionID()); + pet->SetPet(true); + ((NPC*)pet)->SetPetType(PET_TYPE_DEITY); + ((NPC*)pet)->SetOwner((Entity*)spawn); + ((Entity*)spawn)->SetDeityPet((Entity*)pet); + pet->SetSpawnType(6); + ((NPC*)pet)->SetBrain(new NonCombatPetBrain((NPC*)pet)); + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + + if (strlen(pet->GetSubTitle()) > 0) { + string pet_subtitle; + pet_subtitle.append(spawn->GetName()).append("'s ").append(pet->GetSubTitle()); + LogWrite(PET__DEBUG, 0, "Pets", "Pet Subtitle: '%s'", pet_subtitle.c_str()); + pet->SetSubTitle(pet_subtitle.c_str()); + } + + // deity and cosmetic pets are not attackable + pet->SetAttackable(false); + pet->AddSecondaryEntityCommand("Pet Options", 10.0f, "petoptions", "", 0, 0); + lua_interface->SetSpawnValue(state, pet); + return 1; +} + +int EQ2Emu_lua_SummonCosmeticPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 pet_id = lua_interface->GetInt32Value(state, 2); + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: Spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)spawn)->GetCosmeticPet()) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You already have a cosmetic pet."); + } + + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: spawn already has a pet of this type", lua_interface->GetScriptName(state)); + return 0; + } + + if (pet_id == 0) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: pet_id can not be set to 0", lua_interface->GetScriptName(state)); + return 0; + } + + if (!luaspell) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: valid spell not found, SummonCosmeticPet can only be used in spell scripts", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + + Spawn* pet = spawn->GetZone()->GetSpawn(pet_id); + if (!pet) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + if (!pet->IsNPC()) { + lua_interface->LogError("%s: LUA SummonCosmeticPet command error: spawn with id of %u is not a npc", lua_interface->GetScriptName(state), pet_id); + return 0; + } + + pet->SetX(spawn->GetX()); + pet->SetY(spawn->GetY()); + pet->SetZ(spawn->GetZ()); + pet->SetLocation(spawn->GetLocation()); + pet->SetHeading(spawn->GetHeading()); + spawn->GetZone()->AddSpawn(pet); + + string random_pet_name; + int16 rand_index = MakeRandomInt(0, spawn->GetZone()->pet_names.size() - 1); + random_pet_name = spawn->GetZone()->pet_names.at(rand_index); + LogWrite(PET__DEBUG, 0, "Pets", "Randomize Pet Name: '%s' (rand: %i)", random_pet_name.c_str(), rand_index); + + pet->SetName(random_pet_name.c_str()); + pet->SetLevel(spawn->GetLevel()); + pet->SetFactionID(spawn->GetFactionID()); + pet->SetPet(true); + ((NPC*)pet)->SetPetType(PET_TYPE_COSMETIC); + ((NPC*)pet)->SetOwner((Entity*)spawn); + ((Entity*)spawn)->SetCosmeticPet((Entity*)pet); + pet->SetSpawnType(6); + ((NPC*)pet)->SetBrain(new NonCombatPetBrain((NPC*)pet)); + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + + if (strlen(pet->GetSubTitle()) > 0) { + string pet_subtitle; + pet_subtitle.append(spawn->GetName()).append("'s ").append(pet->GetSubTitle()); + LogWrite(PET__DEBUG, 0, "Pets", "Pet Subtitle: '%s'", pet_subtitle.c_str()); + pet->SetSubTitle(pet_subtitle.c_str()); + } + + pet->SetAttackable(false); + pet->AddSecondaryEntityCommand("Pet Options", 10.0f, "petoptions", "", 0, 0); + lua_interface->SetSpawnValue(state, pet); + return 1; +} + +int EQ2Emu_lua_DismissPet(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA DismissPet command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPet()) { + lua_interface->LogError("%s: LUA DismissPet command error: spawn is not a pet", lua_interface->GetScriptName(state)); + return 0; + } + + if (!((NPC*)spawn)->IsDismissing() && ((NPC*)spawn)->GetOwner()) + ((NPC*)spawn)->GetOwner()->DismissPet((NPC*)spawn, false, true); + + return 0; +} + +int EQ2Emu_lua_SetQuestFeatherColor(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestFeatherColor command error: valid quest not found, SetQuestFeatherColor can only be called from a quest script", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + int8 feather_color = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (feather_color > 0) + quest->SetFeatherColor(feather_color); + + return 0; +} + +int EQ2Emu_lua_RemoveSpawnAccess(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* spawn2 = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA RemoveSpawnAccess command error: first spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn2) { + lua_interface->LogError("%s: LUA RemoveSpawnAccess command error: second spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->RemoveSpawnAccess(spawn2); + return 0; +} + +int EQ2Emu_lua_SpawnByLocationID(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state); + int32 location_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!zone) { + lua_interface->LogError("%s: LUA SpawnByLocationID command error: zone is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (location_id == 0) { + lua_interface->LogError("%s: LUA SpawnByLocationID command error: location id can not be 0", lua_interface->GetScriptName(state)); + return 0; + } + + SpawnLocation* location = zone->GetSpawnLocation(location_id); + if (!location) { + lua_interface->LogError("%s: LUA SpawnByLocationID command error: no location found for the given ID (%u)", lua_interface->GetScriptName(state), location_id); + return 0; + } + + Spawn* spawn = 0; + if (location->entities[0]) { + if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_NPC) + spawn = zone->AddNPCSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN) + spawn = zone->AddGroundSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT) + spawn = zone->AddObjectSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET) + spawn = zone->AddWidgetSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_SIGN) + spawn = zone->AddSignSpawn(location, location->entities[0]); + + if(spawn && spawn->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met.", location->entities[0]->spawn_id); + safe_delete(spawn); + spawn = 0; + return 0; + } + + if (spawn) { + const char* script = 0; + for (int x = 0; x < 3; x++) { + switch (x) { + case 0: + script = world.GetSpawnEntryScript(location->entities[0]->spawn_entry_id); + break; + case 1: + script = world.GetSpawnLocationScript(location->entities[0]->spawn_location_id); + break; + case 2: + script = world.GetSpawnScript(location->entities[0]->spawn_id); + break; + } + if (script && lua_interface->GetSpawnScript(script) != 0) { + spawn->SetSpawnScript(string(script)); + break; + } + } + + zone->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + else { + LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by location id to zone %s with location id %u.", zone->GetZoneName(), location_id); + safe_delete(spawn); + } + } + + return 0; +} + +int EQ2Emu_lua_CastEntityCommand(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + int32 id = lua_interface->GetInt32Value(state, 3); + string command = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!caster) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: caster is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsPlayer()) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: caster is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + EntityCommand* entity_command = caster->GetZone()->GetEntityCommand(id, command); + if (!entity_command) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: unable to get a valid EntityCommand with the given ID (%u) and name (%s)", lua_interface->GetScriptName(state), id, command.c_str()); + return 0; + } + + Client* client = ((Player*)caster)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA CastEntityCommand command error: unable to get a valid client for the given caster", lua_interface->GetScriptName(state)); + return 0; + } + + client->GetCurrentZone()->ProcessEntityCommand(entity_command, (Player*)caster, target); + + return 0; +} + +int EQ2Emu_lua_SetLuaBrain(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetLuaBrain command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA SetLuaBrain command error: spawn is not a npc", lua_interface->GetScriptName(state)); + return 0; + } + + ((NPC*)spawn)->SetBrain(new LuaBrain((NPC*)spawn)); + + return 0; +} + +int EQ2Emu_lua_SetBrainTick(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 tick = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetBrainTick command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA SetBrainTick command error: spawn is not a valid npc", lua_interface->GetScriptName(state)); + return 0; + } + + if (tick < 20) { + lua_interface->LogError("%s: LUA SetBrainTick command error: tick can not be set below 20 milliseconds", lua_interface->GetScriptName(state)); + return 0; + } + + ((NPC*)spawn)->Brain()->SetTick(tick); + + return 0; +} + +int EQ2Emu_lua_SetFollowTarget(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + int32 follow_distance = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetFollowTarget command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + // Target can be null, setting follow target to 0 clears it and will cancel follow, so no need to check it + + spawn->SetFollowTarget(target, follow_distance); + return 0; +} + +int EQ2Emu_lua_GetFollowTarget(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetFollowTarget command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* target = spawn->GetFollowTarget(); + if (target) { + lua_interface->SetSpawnValue(state, target); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_ToggleFollow(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ToggleFollow command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->following) + spawn->following = false; + else + spawn->following = true; + + return 0; +} + +int EQ2Emu_lua_IsFollowing(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsFollowing command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->following); + return 1; +} + +int EQ2Emu_lua_SetTempVariable(lua_State* state) { + // As this is unique among the rest of our lua functions as the 3rd param can be of multiple types + // I will attempt to explain how this function works for future refrence + + // Fist lets make sure lua_interface is valid, if not return out + if (!lua_interface) + return 0; + + // Next we grab the first 2 params same as we usually would + Spawn* spawn = lua_interface->GetSpawn(state); + string var = lua_interface->GetStringValue(state, 2); + + // DataType will let us know the value type so we can handle it correctly, we set these ourself so the values I used are made up + // 1 = Spawn + // 2 = Zone + // 3 = Item + // 4 = Quest + // 5 = String + // 6 = nil (null) + int8 dataType = 0; + + // Define pointers for each potential type + Spawn* spawnVal = 0; + ZoneServer* zone = 0; + Item* item = 0; + Quest* quest = 0; + string val; + + // Finally we get to grabbing the third param, we will first check to see if it is light user data + // which is custom data types, in this case it can be Spawn, Zone, Item, or Quest. Conversation and + // options window are also light user data be we do not handle those. + // We check with lua_islightuserdata(lua_State*, index) + if (lua_islightuserdata(state, 3)) { + // It is light user data so we will grab the param with lua_touserdata(lua_State*, index) + // and convert it to LUAUserData* + LUAUserData* data = (LUAUserData*)lua_touserdata(state, 3); + // Check to make sure the data we got is valid, if not give an error + if (!data || !data->IsCorrectlyInitialized()) { + lua_interface->LogError("%s: LUA SetTempVariable command error while processing %s", lua_interface->GetScriptName(state), lua_tostring(state, -1)); + } + // Check if data is a Spawn, if so set our Spawn pointer and the dataType variable + else if (data->IsSpawn()) { + spawnVal = data->spawn; + dataType = 1; + } + // Check if data is a Zone, if so set our Zone pointer and the dataType variable + else if (data->IsZone()) { + zone = data->zone; + dataType = 2; + } + // Check if data is a Item, if so set our Item pointer and the dataType variable + else if (data->IsItem()) { + item = data->item; + dataType = 3; + } + // Check if data is a Ques, if so set our Quest pointer and the dataType variable + else if (data->IsQuest()) { + quest = data->quest; + dataType = 4; + } + } + // Wasn't light user data, check if it is nil(null) + else if (lua_isnil(state, 3)) { + // It is nil (null) set the dataType variable, no need to set a pointer in this case + dataType = 6; + } + // Wasn't light user data or nil (null), must be a string + else { + // Set the string and dataType variable + val = lua_interface->GetStringValue(state, 3); + dataType = 5; + } + + lua_interface->ResetFunctionStack(state); + + // We now have all the params, lets check to make sure they are valid + if (!spawn) { + lua_interface->LogError("%s: LUA SetTempVariable command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (var.length() == 0) { + lua_interface->LogError("%s: LUA SetTempVariable command error: var must be set", lua_interface->GetScriptName(state)); + return 0; + } + + if (dataType == 0) { + lua_interface->LogError("%s: LUA SetTempVariable command error: unknown data type", lua_interface->GetScriptName(state)); + return 0; + } + + // All params are valid, lets set the spawns temp variable, this is where dataType variable comes in. + // AddTempVariable has overloads for all the types of data we support, we need to make sure the third + // param gets sent to the correct list so we check the value of dataType to know where it should go. + switch (dataType) { + case 1: + // 1 = Spawn + spawn->AddTempVariable(var, spawnVal); + break; + case 2: + // 2 = Zone + spawn->AddTempVariable(var, zone); + break; + case 3: + // 3 = Item + spawn->AddTempVariable(var, item); + break; + case 4: + // 4 = Quest + spawn->AddTempVariable(var, quest); + break; + case 5: + // 5 = String + spawn->AddTempVariable(var, val); + break; + case 6: + // 6 = nil (null) so the variable is no longer set, lets remove it from the spawn + spawn->DeleteTempVariable(var); + break; + } + + // And we are done so return out + return 0; +} + +int EQ2Emu_lua_GetTempVariable(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string var = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetTempVariable command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (var.length() == 0) { + lua_interface->LogError("%s: LUA GetTempVariable command error: var must be set", lua_interface->GetScriptName(state)); + return 0; + } + + // This will tell us the type of data this variable contains, uses the same values as the previous function + int8 type = spawn->GetTempVariableType(var); + + Spawn* spawn2 = 0; + ZoneServer* zone = 0; + Item* item = 0; + Quest* quest = 0; + + // Set the lua function return value based on the type of data the variable contains + switch (type) { + case 1: + spawn2 = spawn->GetTempVariableSpawn(var); + if (!spawn2) + return 0; + lua_interface->SetSpawnValue(state, spawn2); + break; + case 2: + zone = spawn->GetTempVariableZone(var); + if (!zone) + return 0; + lua_interface->SetZoneValue(state, zone); + break; + case 3: + item = spawn->GetTempVariableItem(var); + if (!item) + return 0; + lua_interface->SetItemValue(state, item); + break; + case 4: + quest = spawn->GetTempVariableQuest(var); + if (!quest) + return 0; + lua_interface->SetQuestValue(state, quest); + break; + case 5: + lua_interface->SetStringValue(state, spawn->GetTempVariable(var).c_str()); + break; + default: + // Not a valid type then the variable was not set so return out + return 0; + } + + // Return value was set so return out + return 1; +} + +int EQ2Emu_lua_GiveQuestItem(lua_State* state) +{ + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + string description = lua_interface->GetStringValue(state, 3); + int32 item_id = lua_interface->GetInt32Value(state, 4); + + if (!quest) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: quest is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!spawn) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: spawn is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: spawn must be a player", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (item_id == 0) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: item_id is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + Client* client = ((Player*)spawn)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: unable to get a valid client from the given player spawn", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + Item* item = master_item_list.GetItem(item_id); + if (!item) { + lua_interface->LogError("%s: LUA GiveQuestItem command error: unable to get an item from the given id (%u)", lua_interface->GetScriptName(state), item_id); + lua_interface->ResetFunctionStack(state); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + Item* firstItem = new Item(item); + quest->AddTmpRewardItem(firstItem); + + int8 num_args = (int8)lua_interface->GetNumberOfArgs(state); + bool itemsAddedSuccessfully = true; + if(num_args > 4) + { + for(int8 n=5;nGetInt32Value(state, n); + Item* tmpItem = master_item_list.GetItem(new_item); + if(tmpItem) + { + Item* newTmpItem = new Item(tmpItem); + quest->AddTmpRewardItem(newTmpItem); + } + else + itemsAddedSuccessfully = false; + } + } + client->AddPendingQuestReward(quest, true, true, description); // queue for display + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, itemsAddedSuccessfully); + return 1; +} + +int EQ2Emu_lua_SetQuestRepeatable(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + lua_interface->ResetFunctionStack(state); + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestRepeatable command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetRepeatable(true); + return 0; +} + +int EQ2Emu_lua_GetArchetypeName(lua_State* state) { + + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetArchetypeName command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + int8 base_class = classes.GetBaseClass(spawn->GetAdventureClass()); + string ret = classes.GetClassNameCase(base_class); + if (ret.length() > 0) { + lua_interface->SetStringValue(state, ret.c_str()); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_SendWaypoints(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) + client->SendWaypoints(); + } + return 0; +} + +int EQ2Emu_lua_AddWaypoint(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + string name = lua_interface->GetStringValue(state, 2); + int32 type = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (type == 0) + type = 2; + if (name.length() > 0) { + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) + client->AddWaypoint(name, type); + } + } + return 0; +} + +int EQ2Emu_lua_RemoveWaypoint(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (name.length() > 0) { + if (player && player->IsPlayer()) { + Client* client = player->GetClient(); + if (client) + client->RemoveWaypoint(name); + } + } + return 0; +} + +int EQ2Emu_lua_AddWard(lua_State* state) { + if (!lua_interface) + return 0; + + int32 damage = lua_interface->GetInt32Value(state); + bool keepWard = (lua_interface->GetInt8Value(state, 2) == 1); + int8 wardType = lua_interface->GetInt8Value(state, 3); + int8 damageTypes = lua_interface->GetInt8Value(state, 4); + int32 damageAbsorptionPercent = lua_interface->GetInt32Value(state, 5); + int32 damageAbsorptionMaxHealthPercent = lua_interface->GetInt32Value(state, 6); + int32 redirectDamagePercent = lua_interface->GetInt32Value(state, 7); + int32 maxHitCount = lua_interface->GetInt32Value(state, 8); + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if(!spell || spell->resisted) { + return 0; + } + + bool ward_was_added = false; + + ZoneServer* zone = spell->caster->GetZone(); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (!target) + continue; + if (target->IsEntity()) { + // If the ward is already active remove it + if (((Entity*)target)->GetWard(spell->spell->GetSpellID())) + ((Entity*)target)->RemoveWard(spell->spell->GetSpellID()); + + // Create new ward info + WardInfo* ward = new WardInfo; + ward->Spell = spell; + ward->BaseDamage = damage; + ward->DamageLeft = damage; + ward->AbsorbAllDamage = (damage == 0) ? true : false; + + ward->keepWard = keepWard; + ward->WardType = wardType; + if (damageAbsorptionPercent > 100) + damageAbsorptionPercent = 100; + + ward->DamageAbsorptionPercentage = damageAbsorptionPercent; + + if (damageAbsorptionMaxHealthPercent > 100) + damageAbsorptionMaxHealthPercent = 100; + + ward->DamageAbsorptionMaxHealthPercent = damageAbsorptionMaxHealthPercent; + + ward->RedirectDamagePercent = redirectDamagePercent; + + ward->LastRedirectDamage = 0; + ward->LastAbsorbedDamage = 0; + ward->HitCount = 0; + + spell->num_triggers = maxHitCount; + spell->had_triggers = true; + spell->cancel_after_all_triggers = false; + ward->MaxHitCount = maxHitCount; + ward->RoundTriggered = false; + + if (wardType == WARD_TYPE_MAGICAL) + ward->DamageType = damageTypes; + + // Add the ward to the entity + ((Entity*)target)->AddWard(spell->spell->GetSpellID(), ward); + ward_was_added = true; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + if (ward_was_added && spell->caster->IsPlayer()) { + spell->had_dmg_remaining = true; + ClientPacketFunctions::SendMaintainedExamineUpdate(((Player*)spell->caster)->GetClient(), spell->slot_pos, damage, 1); + } + + return 0; +} + +int EQ2Emu_lua_AddToWard(lua_State* state) { + if (!lua_interface) + return 0; + + int32 amount = lua_interface->GetInt32Value(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + WardInfo* ward = 0; + + if(!spell || spell->resisted) { + return 0; + } + ZoneServer* zone = spell->caster->GetZone(); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (spell->targets.size() > 0 && zone->GetSpawnByID(spell->targets.at(0))->IsEntity()) { + Entity* target = (Entity*)zone->GetSpawnByID(spell->targets.at(0)); + ward = target->GetWard(spell->spell->GetSpellID()); + if (target && ward) { + ward->DamageLeft += amount; + if (ward->DamageLeft > ward->BaseDamage) + ward->DamageLeft = ward->BaseDamage; + + for (int32 i = 0; i < spell->targets.size(); i++) { + if (Spawn* spawn = zone->GetSpawnByID(spell->targets.at(i))) + zone->SendHealPacket(ward->Spell->caster, spawn, HEAL_PACKET_TYPE_REGEN_ABSORB, amount, ward->Spell->spell->GetName()); + } + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + if (ward && spell->caster->IsPlayer()) + ClientPacketFunctions::SendMaintainedExamineUpdate(((Player*)spell->caster)->GetClient(), spell->slot_pos, ward->DamageLeft, 1); + + return 0; +} + +int EQ2Emu_lua_GetWardAmountLeft(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!spell) { + lua_interface->LogError("%s: LUA GetWardAmountLeft command error: this command can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (spell->caster && spell->caster->GetZone() && spell->targets.size() > 0 && spell->caster->GetZone()->GetSpawnByID(spell->targets.at(0))->IsEntity()) { + Entity* target = (Entity*)spell->caster->GetZone()->GetSpawnByID(spell->targets.at(0)); + WardInfo* ward = target->GetWard(spell->spell->GetSpellID()); + if (ward) { + lua_interface->SetInt32Value(state, ward->DamageLeft); + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + return 1; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + return 0; +} + +int EQ2Emu_lua_GetWardValue(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + if (!spell) { + lua_interface->LogError("%s: LUA GetWardValue command error: this command can only be used in a spell script", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + string type = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (spell->caster && spell->caster->GetZone() && spell->targets.size() > 0 && spell->caster->GetZone()->GetSpawnByID(spell->targets.at(0))->IsEntity()) { + + Entity* target = (Entity*)spell->caster->GetZone()->GetSpawnByID(spell->targets.at(0)); + WardInfo* ward = target->GetWard(spell->spell->GetSpellID()); + if (ward) { + if (boost::iequals(type, "damageleft")) + lua_interface->SetInt32Value(state, ward->DamageLeft); + else if (boost::iequals(type, "basedamage")) + lua_interface->SetInt32Value(state, ward->BaseDamage); + else if (boost::iequals(type, "keepward")) + lua_interface->SetBooleanValue(state, ward->keepWard); + else if (boost::iequals(type, "wardtype")) + lua_interface->SetInt32Value(state, ward->WardType); + else if (boost::iequals(type, "dmgabsorptionpct")) + lua_interface->SetInt32Value(state, ward->DamageAbsorptionPercentage); + else if (boost::iequals(type, "dmgabsorptionmaxhealthpct")) + lua_interface->SetInt32Value(state, ward->DamageAbsorptionMaxHealthPercent); + else if (boost::iequals(type, "redirectdamagepercent")) + lua_interface->SetInt32Value(state, ward->RedirectDamagePercent); + else if (boost::iequals(type, "lastredirectdamage")) + lua_interface->SetInt32Value(state, ward->LastRedirectDamage); + else if (boost::iequals(type, "lastabsorbeddamage")) + lua_interface->SetInt32Value(state, ward->LastAbsorbedDamage); + else if (boost::iequals(type, "hitcount")) + lua_interface->SetInt32Value(state, ward->HitCount); + else if (boost::iequals(type, "maxhitcount")) + lua_interface->SetInt32Value(state, ward->MaxHitCount); + else + lua_interface->LogError("%s: LUA GetWardValue command argument type '%s' did not match any options", lua_interface->GetScriptName(state), type); + + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + return 1; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + return 0; +} + +int EQ2Emu_lua_RemoveWard(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if(!spell) { + return 0; + } + + ZoneServer* zone = spell->caster->GetZone(); + Spawn* target = 0; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->RemoveWard(spell->spell->GetSpellID()); + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + return 0; +} + +int EQ2Emu_lua_Interrupt(lua_State* state) +{ + + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); // Second param in lua_interface->get functions defaults to 1 + Spawn* target = lua_interface->GetSpawn(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!caster) + { + lua_interface->LogError("%s: LUA Interrupt command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) + { + lua_interface->LogError("%s: LUA Interrupt command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell) { + lua_interface->LogError("%s: LUA Interrupt command error: spell is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity() && !spell) + { + lua_interface->LogError("%s: LUA Interrupt command error: Target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target && spell) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + target = caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->GetZone()->GetSpellProcess()->Interrupted((Entity*)target, caster, SPELL_ERROR_INTERRUPTED); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else + caster->GetZone()->GetSpellProcess()->Interrupted((Entity*)target, caster, SPELL_ERROR_INTERRUPTED); + + return 0; +} + +int EQ2Emu_lua_Stealth(lua_State* state) { + if (!lua_interface) + return 0; + + int8 type = lua_interface->GetInt8Value(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA Stealth command error: must be used from spell script", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spell->caster->GetZone(); + + if (spawn) { + if (spawn->IsEntity()) { + if (type == 1) { + ((Entity*)spawn)->AddStealthSpell(spell); + if (!(spell->effect_bitmask & EFFECT_FLAG_STEALTH)) + spell->effect_bitmask += EFFECT_FLAG_STEALTH; + } + else if (type == 2) { + ((Entity*)spawn)->AddInvisSpell(spell); + if (!(spell->effect_bitmask & EFFECT_FLAG_INVIS)) + spell->effect_bitmask += EFFECT_FLAG_INVIS; + } + return 0; + } + else { + lua_interface->LogError("%s: LUA Stealth command error: target override is not Entity", lua_interface->GetScriptName(state)); + return 0; + } + } + else { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + spawn = zone->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + + if (type == 1) { + ((Entity*)spawn)->AddStealthSpell(spell); + if (!(spell->effect_bitmask & EFFECT_FLAG_STEALTH)) + spell->effect_bitmask += EFFECT_FLAG_STEALTH; + } + else if (type == 2) { + ((Entity*)spawn)->AddInvisSpell(spell); + if (!(spell->effect_bitmask & EFFECT_FLAG_INVIS)) + spell->effect_bitmask += EFFECT_FLAG_INVIS; + } + else { + lua_interface->LogError("%s: LUA Stealth command error: invalid stealth type given", lua_interface->GetScriptName(state)); + break; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_IsStealthed(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsStealthed command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->IsEntity()) { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsStealthed()); + return 1; + } + else + lua_interface->LogError("%s: LUA IsStealthed command error: spawn is not entity", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_IsInvis(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsInvis command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->IsEntity()) { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsInvis()); + return 1; + } + else + lua_interface->LogError("%s: LUA IsInvis command error: spawn is not entity", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_HasItemEquipped(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 item_id = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasItemEquipped command error: spawn is not player", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Player*)player)->GetEquipmentList()->HasItem(item_id)); + return 1; +} + +int EQ2Emu_lua_GetEquippedItemBySlot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetEquippedItemBySlot command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA GetEquippedItemBySlot command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Item* item = ((Entity*)spawn)->GetEquipmentList()->GetItem(slot); + if (!item) { + lua_interface->LogError("%s: LUA GetEquippedItemBySlot command error: item was not found in slot", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetItemValue(state, item); + return 1; +} + +int EQ2Emu_lua_GetEquippedItemByID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 id = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("%s: LUA GetEquippedItemByID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetEquippedItemByID command error: spawn is not player", lua_interface->GetScriptName(state)); + return 0; + } + Item* item = ((Player*)player)->GetEquipmentList()->GetItemFromItemID(id); + if (!item) { + lua_interface->LogError("%s: LUA GetEquippedItemByID command error: equipped item with used id not found", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetItemValue(state, item); + return 1; +} + +int EQ2Emu_lua_SetEquippedItemByID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + int32 item_id = lua_interface->GetInt32Value(state, 3); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetEquippedItemByID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetEquippedItemByID command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + Item* item = master_item_list.GetItem(item_id); + if (!item) { + lua_interface->LogError("%s: LUA SetEquippedItemByID command error: equipped item with used id %u not found", item_id, lua_interface->GetScriptName(state)); + return 0; + } + + Item* copy = new Item(item); + bool result = ((Entity*)spawn)->GetEquipmentList()->AddItem(slot, copy); + + if(result) + { + ((Entity*)spawn)->SetEquipment(copy, slot); + spawn->vis_changed = true; + + if(spawn->IsPlayer()) + ((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot); + } + else + { + safe_delete(copy); + } + + + lua_interface->SetBooleanValue(state, result); + return 1; +} + +int EQ2Emu_lua_SetEquippedItem(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + Item* item = lua_interface->GetItem(state, 3); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetEquippedItem command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetEquippedItem command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + if (!item) { + lua_interface->LogError("%s: LUA SetEquippedItem command error: passed item not found", lua_interface->GetScriptName(state)); + return 0; + } + + bool result = ((Entity*)spawn)->GetEquipmentList()->AddItem(slot, item); + if(result) + { + ((Entity*)spawn)->SetEquipment(item, slot); + spawn->vis_changed = true; + + if(spawn->IsPlayer()) + ((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot); + } + lua_interface->SetBooleanValue(state, result); + return 1; +} + +int EQ2Emu_lua_UnequipSlot(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + bool no_delete_item = (lua_interface->GetBooleanValue(state, 3) == false); // if not set then we default to deleting it, otherwise if set to true we don't delete + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA UnequipSlot command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA UnequipSlot command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + if(slot >= NUM_SLOTS) { + lua_interface->LogError("%s: LUA UnequipSlot command error: wrong slot id given %u, max %u", lua_interface->GetScriptName(state), slot, NUM_SLOTS); + return 0; + } + + if(spawn->IsPlayer() && ((Player*)spawn)->GetClient()) { + Item* item = ((Player*)spawn)->GetEquipmentList()->GetItem(slot); + if(item) { + item->save_needed = true; + if(no_delete_item) { + database.DeleteItem(((Player*)spawn)->GetClient()->GetCharacterID(), item, 0); + ((Player*)spawn)->GetEquipmentList()->RemoveItem(slot, no_delete_item); + } + else{ + Client* client = ((Player*)spawn)->GetClient(); + client->UnequipItem(item->details.index); + } + } + } + else + ((Entity*)spawn)->GetEquipmentList()->RemoveItem(slot, no_delete_item); + + ((Entity*)spawn)->SetEquipment(nullptr, slot); + spawn->vis_changed = true; + + if(spawn->IsPlayer()) + ((Player*)spawn)->GetClient()->SendEquipOrInvUpdateBySlot(slot); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_SetEquipment(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int8 slot = lua_interface->GetInt8Value(state, 2); + int16 type = lua_interface->GetInt16Value(state, 3); + int8 r = lua_interface->GetInt8Value(state, 4); + int8 g = lua_interface->GetInt8Value(state, 5); + int8 b = lua_interface->GetInt8Value(state, 6); + int8 h_r = lua_interface->GetInt8Value(state, 7); + int8 h_g = lua_interface->GetInt8Value(state, 8); + int8 h_b = lua_interface->GetInt8Value(state, 9); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetEquipment command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetEquipment command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + ((Entity*)spawn)->SetEquipment(slot, type, r, g, b, h_r, h_g, h_b); + spawn->vis_changed = true; + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_GetItemByID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 id = lua_interface->GetInt32Value(state, 2); + int8 count = lua_interface->GetInt8Value(state, 3); + bool include_bank = lua_interface->GetInt8Value(state, 4); + + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("%s: LUA GetItemByID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetItemByID command error: spawn is not player", lua_interface->GetScriptName(state)); + return 0; + } + if (!count) + count = 1; + Item* item = ((Player*)player)->GetPlayerItemList()->GetItemFromID(id, count, include_bank); + if (!item) { + lua_interface->LogError("%s: LUA GetItemByID command error: item with used id not found", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetItemValue(state, item); + return 1; +} + +int EQ2Emu_lua_PlayAnimation(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 anim = lua_interface->GetInt32Value(state, 2); + Spawn* spawn2 = lua_interface->GetSpawn(state, 3); + int8 type = lua_interface->GetInt8Value(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA PlayAnimation command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn2) { + if (spawn2->IsPlayer()) { + if (type != 1 && type != 2) + spawn->GetZone()->PlayAnimation(spawn, anim, spawn2); + else + spawn->GetZone()->PlayAnimation(spawn, anim, spawn2, type); + return 0; + } + else { + lua_interface->LogError("%s: LUA PlayAnimation command error: second spawn not a player", lua_interface->GetScriptName(state)); + return 0; + } + } + else + spawn->GetZone()->PlayAnimation(spawn, anim); + return 0; +} + +int EQ2Emu_lua_IsPet(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsPet command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, spawn->IsPet()); + return 1; +} + +int EQ2Emu_lua_GetOwner(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetOwner command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetOwner command error: spawn is not a NPC", lua_interface->GetScriptName(state)); + return 0; + } + if (((NPC*)spawn)->GetOwner()) { + lua_interface->SetSpawnValue(state, ((NPC*)spawn)->GetOwner()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetTarget(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetTarget command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn) { + lua_interface->LogError("%s: LUA SetTarget command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + spawn->SetTarget(target); + return 0; +} + +int EQ2Emu_lua_SetInCombat(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool val = lua_interface->GetBooleanValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA SetInCombat command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetInCombat command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + ((Entity*)spawn)->InCombat(val); + + if (val) { + spawn->ClearRunningLocations(); + spawn->CalculateRunningLocation(true); + } + return 0; +} + +int EQ2Emu_lua_CompareSpawns(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn1 = lua_interface->GetSpawn(state); + Spawn* spawn2 = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn1) { + lua_interface->LogError("%s: LUA CompareSpawns command error: first spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn2) { + lua_interface->LogError("%s: LUA CompareSpawns command error: second spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, (spawn1 == spawn2)); + return 1; +} + +int EQ2Emu_lua_ClearRunback(lua_State* state){ + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ClearRunback command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ClearRunback command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->ClearRunback(); + return 0; +} + +int EQ2Emu_lua_Runback(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA Runback command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA Runback command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->Runback(); + return 0; +} + +int EQ2Emu_lua_GetRunbackDistance(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetRunbackDistance command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetRunbackDistance command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetFloatValue(state, ((NPC*)spawn)->GetRunbackDistance()); + return 1; +} + +int EQ2Emu_lua_IsCasting(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsCasting command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsCasting command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsCasting()); + return 1; +} + +int EQ2Emu_lua_IsMezzed(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsMezzed command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsMezzed command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsMezzed()); + return 1; +} + +int EQ2Emu_lua_IsStunned(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsStunned command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsStunned command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsStunned()); + return 1; +} + +int EQ2Emu_lua_IsMezzedOrStunned(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsMezzedOrStunned command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsMezzedOrStunned command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsMezzedOrStunned()); + return 1; +} + +int EQ2Emu_lua_ProcessSpell(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + float distance = lua_interface->GetFloatValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ProcessSpell command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!target) { + lua_interface->LogError("%s: LUA ProcessSpell command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ProcessSpell command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA ProcessSpell command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((NPC*)spawn)->Brain()->ProcessSpell(((Entity*)target), distance)); + return 1; +} + +int EQ2Emu_lua_ProcessMelee(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + float distance = lua_interface->GetFloatValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ProcessMelee command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!target) { + lua_interface->LogError("%s: LUA ProcessMelee command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ProcessMelee command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA ProcessMelee command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->Brain()->ProcessMelee(((Entity*)target), distance); + return 0; +} + +int EQ2Emu_lua_HasRecovered(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA HasRecovered command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA HasRecovered command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((NPC*)spawn)->Brain()->HasRecovered()); + return 1; +} + +int EQ2Emu_lua_GetEncounterSize(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetEncounterSize command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetEncounterSize command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetInt32Value(state, ((NPC*)spawn)->Brain()->GetEncounterSize()); + return 1; +} + +int EQ2Emu_lua_GetMostHated(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetMostHated command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetMostHated command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + + Entity* hated = ((NPC*)spawn)->Brain()->GetMostHated(); + if (hated) { + lua_interface->SetSpawnValue(state, hated); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_ClearHate(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* hated = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ClearHate command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ClearHate command error: spawn is not NPC", lua_interface->GetScriptName(state)); + return 0; + } + if (!hated) { + ((NPC*)spawn)->Brain()->ClearHate(); + return 0; + } + else + { + if (!hated->IsEntity()) { + lua_interface->LogError("%s: LUA ClearHate command error: second param is not entity", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->Brain()->ClearHate(((Entity*)hated)); + return 0; + } + return 0; +} + +int EQ2Emu_lua_ClearEncounter(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA ClearEncounter command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA ClearEncounter command error: spawn is not an NPC", lua_interface->GetScriptName(state)); + return 0; + } + ((NPC*)spawn)->Brain()->ClearEncounter(); + return 0; +} + +int EQ2Emu_lua_GetEncounter(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetEncounter command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetEncounter command error: spawn is not a NPC", lua_interface->GetScriptName(state)); + return 0; + } + + // Temp list to store hate list + vector* encounterList = ((NPC*)spawn)->Brain()->GetEncounter(); + + if (encounterList->size() == 0) { + safe_delete(encounterList); + return 0; + } + + lua_createtable(state, encounterList->size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < encounterList->size(); i++) { + Spawn* temp = spawn->GetZone()->GetSpawnByID(encounterList->at(i)); + if (temp) + lua_interface->SetSpawnValue(state, temp); + lua_rawseti(state, newTable, i + 1); + } + + safe_delete(encounterList); + return 1; +} + +int EQ2Emu_lua_GetHateList(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetHateList command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsNPC()) { + lua_interface->LogError("%s: LUA GetHateList command error: spawn is not a NPC", lua_interface->GetScriptName(state)); + return 0; + } + + // Temp list to store hate list + vector* hateList = ((NPC*)spawn)->Brain()->GetHateList(); + + if (hateList->size() == 0) { + safe_delete(hateList); + return 0; + } + + lua_createtable(state, hateList->size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < hateList->size(); i++) { + lua_interface->SetSpawnValue(state, hateList->at(i)); + lua_rawseti(state, newTable, i + 1); + } + + safe_delete(hateList); + return 1; +} + +int EQ2Emu_lua_HasGroup(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA HasGroup command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->IsPlayer()) { + if (((Player*)spawn)->GetGroupMemberInfo() && world.GetGroupManager()->GetGroupSize(((Player*)spawn)->GetGroupMemberInfo()->group_id) > 1) + lua_interface->SetBooleanValue(state, true); + else + lua_interface->SetBooleanValue(state, false); + + return 1; + } + else { + lua_interface->SetBooleanValue(state, spawn->HasSpawnGroup()); + return 1; + } +} + +int EQ2Emu_lua_SetCompleteFlag(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetCompleteFlag command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetCompletedFlag(true); + return 0; +} + +int EQ2Emu_lua_HasSpellEffect(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spellID = lua_interface->GetInt32Value(state, 2); + int8 tier = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA HasSpellEffect command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA HasSpellEffect command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (spellID == 0) { + lua_interface->LogError("%s: LUA HasSpellEffect command error: spell id is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + SpellEffects* effect = ((Entity*)spawn)->GetSpellEffect(spellID); + if (effect) { + if (tier > 0) { + // If a tier was passed chec to see if it is the same as the effect + if (tier == effect->tier) + lua_interface->SetBooleanValue(state, true); + else + lua_interface->SetBooleanValue(state, false); + + return 1; + } + else { + // Have an effect but no tier was passed so return true + lua_interface->SetBooleanValue(state, true); + } + + return 1; + } + + // no effect so return false + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_AddSpawnIDAccess(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 id = lua_interface->GetInt32Value(state, 2); + ZoneServer* zone = lua_interface->GetZone(state, 3); + lua_interface->ResetFunctionStack(state); + Spawn* spawn2 = 0; + vector list; + + if (!spawn) { + lua_interface->LogError("%s: LUA AddSpawnIDAccess command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + //If zone not provided, use spawn's zone + if (!zone) + zone = spawn->GetZone(); + + list = zone->GetSpawnsByID(id); + if (list.size() == 0) { + lua_interface->LogError("%s: LUA AddSpawnIDAccess command error: GetSpawnsByID returned no spawns", lua_interface->GetScriptName(state)); + return 0; + } + + vector::iterator itr = list.begin(); + for (int8 i = 0; i < list.size(); i++) { + spawn2 = itr[i]; + if (spawn2) + spawn2->AddAllowAccessSpawn(spawn); + } + + return 0; +} + +int EQ2Emu_lua_RemoveSpawnIDAccess(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 id = lua_interface->GetInt32Value(state, 2); + ZoneServer* zone = lua_interface->GetZone(state, 3); + lua_interface->ResetFunctionStack(state); + Spawn* spawn2 = 0; + + if (!spawn) { + lua_interface->LogError("%s: LUA RemoveSpawnIDAccess command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + //If zone not provided, use spawn's zone + if (!zone) + zone = spawn->GetZone(); + + vector list = zone->GetSpawnsByID(id); + vector::iterator itr = list.begin(); + if (list.size() == 0) { + lua_interface->LogError("%s: LUA RemoveSpawnIDAccess command error: GetSpawnsByID returned no spawns", lua_interface->GetScriptName(state)); + return 0; + } + for (int8 i = 0; i < list.size(); i++) { + spawn2 = itr[i]; + if (spawn2) + spawn2->RemoveSpawnAccess(spawn); + } + + return 0; +} + +int EQ2Emu_lua_SetQuestYellow(lua_State* state) { + if (!lua_interface) + return 0; + Quest* quest = lua_interface->GetQuest(state); + lua_interface->ResetFunctionStack(state); + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestYellow command error: quest is not valid", lua_interface->GetScriptName(state)); + return 0; + } + quest->SetYellowName(true); + return 0; +} + +int EQ2Emu_lua_CanReceiveQuest(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA CanReceieveQuest command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA CanReceieveQuest command error: spawn is not player", lua_interface->GetScriptName(state)); + return 0; + } + lua_interface->SetBooleanValue(state, ((Player*)spawn)->CanReceiveQuest(quest_id)); + return 1; +} + +int EQ2Emu_lua_SetSuccessTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetSuccessTimer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA SetSuccessTimer command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (!zone) { + lua_interface->LogError("%s: LUA SetSuccessTimer command error: unable to get a valid zone for the given spawn", lua_interface->GetScriptName(state)); + return 0; + } + + Instance_Type iType = zone->GetInstanceType(); + if (iType == SOLO_LOCKOUT_INSTANCE || + iType == GROUP_LOCKOUT_INSTANCE || + iType == RAID_LOCKOUT_INSTANCE || + iType == SOLO_PERSIST_INSTANCE || + iType == GROUP_PERSIST_INSTANCE || + iType == RAID_PERSIST_INSTANCE) { + InstanceData* data = ((Player*)spawn)->GetCharacterInstances()->FindInstanceByZoneID(spawn->GetZone()->GetZoneID()); + if (data) { + // Check to see if the timer has already been set, if it has return out. + if (Timer::GetUnixTimeStamp() <= data->last_success_timestamp + data->success_lockout_time) + return 0; + + + database.UpdateCharacterInstance(((Player*)spawn)->GetCharacterID(), string(spawn->GetZone()->GetZoneName()), spawn->GetZone()->GetInstanceID(), 1, Timer::GetUnixTimeStamp()); + data->last_success_timestamp = Timer::GetUnixTimeStamp(); + + if(spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + string time_msg = ""; + int32 time = data->success_lockout_time; + int16 hour; + int8 min; + int8 sec; + hour = time / 3600; + time = time % 3600; + min = time / 60; + time = time % 60; + sec = time; + + if (hour > 0) { + char temp[10]; + sprintf(temp, " %i", hour); + time_msg.append(temp); + time_msg.append(" hour"); + time_msg.append((hour > 1) ? "s" : ""); + } + if (min > 0) { + char temp[5]; + sprintf(temp, " %i", min); + time_msg.append(temp); + time_msg.append(" minute"); + time_msg.append((min > 1) ? "s" : ""); + } + // Only add seconds if minutes and hours are 0 + if (hour == 0 && min == 0 && sec > 0) { + char temp[5]; + sprintf(temp, " %i", sec); + time_msg.append(temp); + time_msg.append(" second"); + time_msg.append((sec > 1) ? "s" : ""); + } + + client->Message(CHANNEL_COLOR_YELLOW, "The success zone reuse timer for %s has been set. You can return in%s.", data->zone_name.c_str(), time_msg.c_str()); + } + else { + lua_interface->LogError("LUA SetSuccessTimer command error: instance id %u client missing for player %s", spawn->GetZone()->GetInstanceID(), spawn->GetName()); + } + } + } + else + lua_interface->LogError("LUA SetSuccessTimer command error: unable to get instance data for instance %u for player %s", spawn->GetZone()->GetInstanceID(), spawn->GetName()); + } + else + lua_interface->LogError("%s: LUA SetSuccessTimer command error: current zone for given spawn is not a lockout or persistent instance", lua_interface->GetScriptName(state)); + + + return 0; +} + +int EQ2Emu_lua_SetFailureTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetFailureTimer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA SetFailureTimer command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (!zone) { + lua_interface->LogError("%s: LUA SetFailureTimer command error: unable to get a valid zone for the given spawn", lua_interface->GetScriptName(state)); + return 0; + } + + Instance_Type iType = zone->GetInstanceType(); + if (iType == SOLO_LOCKOUT_INSTANCE || + iType == GROUP_LOCKOUT_INSTANCE || + iType == RAID_LOCKOUT_INSTANCE || + iType == SOLO_PERSIST_INSTANCE || + iType == GROUP_PERSIST_INSTANCE || + iType == RAID_PERSIST_INSTANCE) { + InstanceData* data = ((Player*)spawn)->GetCharacterInstances()->FindInstanceByZoneID(spawn->GetZone()->GetZoneID()); + if (data) { + // Check to see if the timer has already been set, if it has return out. + if (Timer::GetUnixTimeStamp() <= data->last_failure_timestamp + data->failure_lockout_time) + return 0; + + database.UpdateCharacterInstance(((Player*)spawn)->GetCharacterID(), string(spawn->GetZone()->GetZoneName()), spawn->GetZone()->GetInstanceID(), 2, Timer::GetUnixTimeStamp()); + data->last_failure_timestamp = Timer::GetUnixTimeStamp(); + + if(spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + string time_msg = ""; + int32 time = data->failure_lockout_time; + int16 hour; + int8 min; + int8 sec; + hour = time / 3600; + time = time % 3600; + min = time / 60; + time = time % 60; + sec = time; + + if (hour > 0) { + char temp[10]; + sprintf(temp, " %i", hour); + time_msg.append(temp); + time_msg.append(" hour"); + time_msg.append((hour > 1) ? "s" : ""); + } + if (min > 0) { + char temp[5]; + sprintf(temp, " %i", min); + time_msg.append(temp); + time_msg.append(" minute"); + time_msg.append((min > 1) ? "s" : ""); + } + // Only add seconds if minutes and hours are 0 + if (hour == 0 && min == 0 && sec > 0) { + char temp[5]; + sprintf(temp, " %i", sec); + time_msg.append(temp); + time_msg.append(" second"); + time_msg.append((sec > 1) ? "s" : ""); + } + + client->Message(CHANNEL_COLOR_YELLOW, "The failure zone reuse timer for %s has been set. You can return in%s", data->zone_name.c_str(), time_msg.c_str()); + } + } + else { + lua_interface->LogError("LUA SetFailureTimer command error: instance id %u client missing for player %s", spawn->GetZone()->GetInstanceID(), spawn->GetName()); + } + } + else + lua_interface->LogError("LUA SetFailureTimer command error: unable to get instance data for instance %u for player %s", spawn->GetZone()->GetInstanceID(), spawn->GetName()); + } + else + lua_interface->LogError("%s: LUA SetFailureTimer command error: current zone for given spawn is not a lockout or persistent instance", lua_interface->GetScriptName(state)); + + return 0; +} + +int EQ2Emu_lua_IsGroundSpawn(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsGroundSpawn command error: not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->IsGroundSpawn()); + return 1; +} + +int EQ2Emu_lua_CanHarvest(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + Spawn* ground = lua_interface->GetSpawn(state, 2); + + if (!player) { + lua_interface->LogError("%s: LUA CanHarvest command error: not a valid spawn", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA CanHarvest command error: spawn is not a player", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!ground) { + lua_interface->LogError("%s: LUA CanHarvest command error: not a valid spawn", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!ground->IsGroundSpawn()) { + lua_interface->LogError("%s: LUA CanHarvest command error: spawn is not a ground spawn", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + vector* groundspawn_entries = player->GetZone()->GetGroundSpawnEntries(((GroundSpawn*)ground)->GetGroundSpawnEntryID()); + + if (!groundspawn_entries) { + lua_interface->LogError("LUA CanHarvest command error: No groundspawn entries assigned to groundspawn id: %u", ((GroundSpawn*)ground)->GetGroundSpawnEntryID()); + lua_interface->ResetFunctionStack(state); + return 0; + } + + Skill* skill = 0; + string collection_skill = string(((GroundSpawn*)ground)->GetCollectionSkill()); + if (collection_skill == "Collecting") + skill = ((Player*)player)->GetSkillByName("Gathering"); + else + skill = ((Player*)player)->GetSkillByName(collection_skill.c_str()); + + if (!skill) { + lua_interface->LogError("LUA CanHarvest command error: Player '%s' lacks the skill: '%s'", player->GetName(), collection_skill.c_str()); + lua_interface->ResetFunctionStack(state); + return 0; + } + + + + vector::iterator itr; + GroundSpawnEntry* entry = 0; + bool can_harvest = false; + sint32 min_skill = -1; + int16 totalSkill = skill->current_val; + int32 skillID = master_item_list.GetItemStatIDByName(collection_skill); + if(skillID != 0xFFFFFFFF) + { + ((Entity*)player)->MStats.lock(); + totalSkill += ((Entity*)player)->stats[skillID]; + ((Entity*)player)->MStats.unlock(); + } + + // first, iterate through groundspawn_entries, discard tables player cannot use + for (itr = groundspawn_entries->begin(); itr != groundspawn_entries->end(); itr++) + { + entry = *itr; + + if (min_skill == -1 || entry->min_skill_level < min_skill) + min_skill = entry->min_skill_level; + // if player lacks skill, skip table + if (entry->min_skill_level > totalSkill) + continue; + // if bonus, but player lacks level, skip table + if (entry->bonus_table && (player->GetLevel() < entry->min_adventure_level)) + continue; + + can_harvest = true; + break; + } + + lua_interface->SetBooleanValue(state, can_harvest); + + // If false, send the message to the client + if (!can_harvest) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + string msg = "You do not have enough skill to "; + if (collection_skill == "Gathering" || collection_skill == "Collecting") + msg.append("gather"); + else if (collection_skill == "Mining") + msg.append("mine"); + else if (collection_skill == "Trapping") + msg.append("trap"); + else if (collection_skill == "Foresting") + msg.append("forest"); + else if (collection_skill == "Fishing") + msg.append("catch"); + + msg.append(" the %s. It requires %i %s skill, and your skill is %i."); + client->Message(CHANNEL_HARVESTING_WARNINGS, msg.c_str(), ground->GetName(), min_skill, skill->name.data.c_str(), totalSkill); + // You do not have enough skill to catch the band of fish. It requires 20 Fishing skill, and your skill is 12. + } + } + lua_interface->ResetFunctionStack(state); + return 1; +} + +int EQ2Emu_lua_HasRecipeBook(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 recipe_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + bool ret = ((Player*)player)->HasRecipeBook(recipe_id); + lua_interface->SetBooleanValue(state, ret); + return 1; +} + +int EQ2Emu_lua_SummonDumbFirePet(lua_State* state) { + // Check to see if we have a valid lua_interface + if (!lua_interface) + return 0; + + // Get the spawn that is getting the pet + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + // Get the DB ID of the pet + int32 pet_id = lua_interface->GetInt32Value(state, 3); + + float x = lua_interface->GetFloatValue(state, 4); + float y = lua_interface->GetFloatValue(state, 5); + float z = lua_interface->GetFloatValue(state, 6); + // Get the spell that this command was called from + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + // Check to make sure the spawn pointer is valid + if (!spawn) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to make sure the spawn is an entity + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: Spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to see if the DB ID for the pet is set + if (pet_id == 0) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: pet_id can not be set to 0", lua_interface->GetScriptName(state)); + return 0; + } + + // Check to see if the pointer to the spell is valid + if (!luaspell) { + lua_interface->LogError("%s: LUA SummonDumbFirePet command error: valid spell not found, SummonPet can only be used in spell scripts", lua_interface->GetScriptName(state)); + return 0; + } + + if(luaspell->resisted) { + return 0; + } + + // Get a pointer to a spawn with the given DB ID and check if the pointer is valid + Spawn* pet = spawn->GetZone()->GetSpawn(pet_id); + if (!pet) { + lua_interface->LogError("LUA SummonDumbFirePet command error: Could not find spawn with id of %u.", pet_id); + return 0; + } + + // Check to make sure the pet is an npc + if (!pet->IsNPC()) { + lua_interface->LogError("LUA SummonDumbFirePet command error: id (%u) did not point to a npc", pet_id); + return 0; + } + + if (x == 0) + x = spawn->GetX(); + + if (y == 0) + y = spawn->GetY(); + + if (z == 0) + z = spawn->GetZ(); + + // Spawn the pet at the same location as the owner + pet->SetX(x); + pet->SetY(y); + pet->SetZ(z); + pet->SetLocation(spawn->GetLocation()); + pet->SetHeading(spawn->GetHeading()); + spawn->GetZone()->AddSpawn(pet); + + const char* spawn_script = world.GetSpawnScript(pet_id); + if(spawn_script && lua_interface->GetSpawnScript(spawn_script) != 0){ + spawn->SetSpawnScript(string(spawn_script)); + spawn->GetZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + } + + std::string petName = std::string(""); + if(spawn->IsEntity()) { + petName = ((Entity*)spawn)->GetInfoStruct()->get_pet_name(); + } + + if(petName.size() < 1) { + int16 rand_index = MakeRandomInt(0, spawn->GetZone()->pet_names.size() - 1); + petName = spawn->GetZone()->pet_names.at(rand_index); + LogWrite(PET__DEBUG, 0, "Pets", "Randomize Pet Name: '%s' (rand: %i)", petName.c_str(), rand_index); + } + + // Set the pets name + pet->SetName(petName.c_str()); + + // Set the level of the pet to the owners level + pet->SetLevel(spawn->GetLevel()); + // Set the faction of the pet to the same faction as the owner + pet->SetFactionID(spawn->GetFactionID()); + // Set the spawn as a pet + pet->SetPet(true); + // Give a pointer of the owner to the pet + ((NPC*)pet)->SetOwner((Entity*)spawn); + + // Set the pet type + ((NPC*)pet)->SetPetType(PET_TYPE_DUMBFIRE); + // Set the spell id used to create this pet + ((NPC*)pet)->SetPetSpellID(luaspell->spell->GetSpellData()->id); + // Set the spell tier used to create this pet + ((NPC*)pet)->SetPetSpellTier(luaspell->spell->GetSpellData()->tier); + // Set the pets spawn type to 6 + pet->SetSpawnType(6); + // Set the pets brain + ((NPC*)pet)->SetBrain(new DumbFirePetBrain((NPC*)pet, (Entity*)target, luaspell->spell->GetSpellDuration() * 100)); + // Check to see if the pet has a subtitle + if (strlen(pet->GetSubTitle()) > 0) { + // Add the players name to the front of the sub title + string pet_subtitle; + pet_subtitle.append(spawn->GetName()).append("'s ").append(pet->GetSubTitle()); + LogWrite(PET__DEBUG, 0, "Pets", "Pet Subtitle: '%s'", pet_subtitle.c_str()); + // Set the pets subtitle to the new one + pet->SetSubTitle(pet_subtitle.c_str()); + } + + // Set the pet as the return value for this function + lua_interface->SetSpawnValue(state, pet); + return 1; +} + +int EQ2Emu_lua_SpawnMove(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + float max_distance = lua_interface->GetFloatValue(state, 3); + string type = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!spawn || (spawn && spawn->IsPlayer())) { + lua_interface->LogError("%s: LUA SpawnMove command error: first param spawn is not valid or is player", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player || !player->IsPlayer()) { + lua_interface->LogError("%s: LUA SpawnMove command error: second param is not player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("%s: LUA SpawnMove command error: could not find client", lua_interface->GetScriptName(state)); + return 0; + } + + //Set max_distance to default if not set or not proper value + if (max_distance <= 0) + max_distance = 500; + PacketStruct* packet = configReader.getStruct("WS_MoveObjectMode", client->GetVersion()); + if (packet) { + float unknown2_3 = 0; + int8 placement_mode = 0; + if (type == "wall") { + placement_mode = 2; + unknown2_3 = 150; + } + else if (type == "ceiling") + placement_mode = 1; + packet->setDataByName("placement_mode", placement_mode); + packet->setDataByName("spawn_id", ((Player*)player)->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("model_type", spawn->GetModelType()); + packet->setDataByName("unknown", 1); //size + packet->setDataByName("unknown2", 1); //size 2 + packet->setDataByName("unknown2", .5, 1); //size 3 + packet->setDataByName("unknown2", 3, 2); + packet->setDataByName("unknown2", unknown2_3, 3); + packet->setDataByName("max_distance", max_distance); + packet->setDataByName("CoEunknown", 0xFFFFFFFF); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_GetItemType(lua_State* state) { + if (!lua_interface) + return 0; + Item* item = lua_interface->GetItem(state); + lua_interface->ResetFunctionStack(state); + + if (!item) { + lua_interface->LogError("%s: LUA GetItemType command error: item pointer is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, item->generic_info.item_type); + return 1; +} + +int EQ2Emu_lua_GetItemEffectType(lua_State* state) { + if (!lua_interface) + return 0; + Item* item = lua_interface->GetItem(state); + lua_interface->ResetFunctionStack(state); + + if (!item) { + lua_interface->LogError("%s: LUA GetItemEffectType command error: item pointer is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, item->effect_type); + return 1; +} + +int EQ2Emu_lua_AddTransportSpawn(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddTransportSpawn command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn->GetZone()) + spawn->GetZone()->AddTransportSpawn(spawn); + + return 0; +} + +int EQ2Emu_lua_IsTransportSpawn(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddTransportSpawn command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->IsTransportSpawn()); + return 1; +} + +int EQ2Emu_lua_GetSkillValue(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA GetSkillValue command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, skill->current_val); + return 1; +} + +int EQ2Emu_lua_GetSkillMaxValue(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA GetSkillMaxValue command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, skill->max_val); + return 1; +} + +int EQ2Emu_lua_GetSkillName(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA GetSkillName command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetStringValue(state, skill->name.data.c_str()); + return 1; +} + +int EQ2Emu_lua_SetSkillMaxValue(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + int16 value = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA SetSkillMaxValue command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + skill->max_val = value; + if (skill->max_val < skill->current_val) + skill->current_val = skill->max_val; + + return 0; +} + +int EQ2Emu_lua_SetSkillValue(lua_State* state) { + if (!lua_interface) + return 0; + + Skill* skill = lua_interface->GetSkill(state); + int16 value = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!skill) { + lua_interface->LogError("%s: LUA SetSkillValue command error: skill is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (value > skill->max_val) + skill->current_val = skill->max_val; + else + skill->current_val = value; + + return 0; +} + +int EQ2Emu_lua_HasSkill(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 skill_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (skill_id > 0 && player && player->IsPlayer()) { + lua_interface->SetBooleanValue(state, ((Player*)player)->skill_list.HasSkill(skill_id)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_AddSkill(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player_spawn = lua_interface->GetSpawn(state); + int32 skill_id = lua_interface->GetInt32Value(state, 2); + int16 current_val = lua_interface->GetInt16Value(state, 3); + int16 max_val = lua_interface->GetInt16Value(state, 4); + bool more_to_add = lua_interface->GetBooleanValue(state, 5); + lua_interface->ResetFunctionStack(state); + if (skill_id > 0 && current_val > 0 && max_val > 0) { + if (player_spawn && player_spawn->IsPlayer()) { + Player* player = (Player*)player_spawn; + bool added = false; + if (!player->skill_list.HasSkill(skill_id)) { + player->AddSkill(skill_id, current_val, max_val, true); + added = true; + } + if (!more_to_add) { //need to send update regardless, even if THIS skill wasn't added, otherwise if you have a list and the last item wasn't added but the previous ones were, it wouldn't send the update + Client* client = player->GetClient(); + if (client) { + EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + if (added) { + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + else { + lua_interface->LogError("%s: LUA AddSkill command error: Given spawn is not a player", lua_interface->GetScriptName(state)); + } + } + else { + lua_interface->LogError("%s: LUA AddSkill command error: Required parameters not set", lua_interface->GetScriptName(state)); + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_RemoveSkill(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player_spawn = lua_interface->GetSpawn(state); + int32 skill_id = lua_interface->GetInt32Value(state, 2); + bool more_to_remove = lua_interface->GetBooleanValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (skill_id > 0) { + if (player_spawn && player_spawn->IsPlayer()) { + Player* player = (Player*)player_spawn; + if (player->skill_list.HasSkill(skill_id)) { + player->RemovePlayerSkill(skill_id, true); + if (!more_to_remove) { + Client* client = player->GetClient(); + if (client) { + EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + } + } + else { + lua_interface->LogError("%s: LUA RemoveSkill command error: Given spawn is not a player", lua_interface->GetScriptName(state)); + } + } + else { + lua_interface->LogError("%s: LUA RemoveSkill command error: skill_id not set", lua_interface->GetScriptName(state)); + } + return 0; +} + +int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player_spawn = lua_interface->GetSpawn(state); + int8 skill_type = lua_interface->GetInt8Value(state, 2); + int16 amount = lua_interface->GetInt8Value(state, 3); + bool more_to_increase = lua_interface->GetBooleanValue(state, 4); + lua_interface->ResetFunctionStack(state); + if (amount > 0 && skill_type < 100) { + if (player_spawn && player_spawn->IsPlayer()) { + Player* player = (Player*)player_spawn; + player->skill_list.IncreaseSkillCapsByType(skill_type, amount); + if (!more_to_increase) { + Client* client = player->GetClient(); + if (client) { + EQ2Packet* packet = player->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + } + else { + lua_interface->LogError("%s: LUA IncreaseSkillCapsByType command error: Given spawn is not a player", lua_interface->GetScriptName(state)); + } + } + else { + lua_interface->LogError("%s: LUA IncreaseSkillCapsByType command error: Invalid parameters", lua_interface->GetScriptName(state)); + } + return 0; +} + +int EQ2Emu_lua_GetSkill(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetSkill command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA GetSkill command error: spawn is not a valid entity", lua_interface->GetScriptName(state)); + return 0; + } + + Skill* skill = ((Entity*)spawn)->GetSkillByName(name.c_str()); + if (skill) { + lua_interface->SetSkillValue(state, skill); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_AddProc(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + float chance = lua_interface->GetFloatValue(state, 3); + Item* item = lua_interface->GetItem(state, 4); + bool use_all_spelltargets = (lua_interface->GetInt8Value(state, 5) == 1); + LuaSpell* spell = 0; + + if (!item) + spell = lua_interface->GetCurrentSpell(state); + + if (!spawn && (!spell || !use_all_spelltargets)) { + lua_interface->LogError("%s: LUA AddProc command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if ((!spell || use_all_spelltargets) && spawn && !spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddProc command error: spawn is not a valid entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!item && !spell) { + lua_interface->LogError("%s: LUA AddProc command error: can only use with an item provided or inside a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if(spell && spell->resisted) { + return 0; + } + + if (spell && spell->caster && spell->caster->GetZone() && use_all_spelltargets) { + Spawn* target; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->AddProc(type, chance, item, spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else + ((Entity*)spawn)->AddProc(type, chance, item, spell); + + return 0; +} + +int EQ2Emu_lua_AddProcExt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + int8 damage_type = lua_interface->GetInt8Value(state, 3); + float chance = lua_interface->GetFloatValue(state, 4); + int8 hp_ratio = lua_interface->GetInt8Value(state, 5); + bool below_health = lua_interface->GetBooleanValue(state, 6); + bool target_health = lua_interface->GetBooleanValue(state, 7); + Item* item = lua_interface->GetItem(state, 8); + bool use_all_spelltargets = (lua_interface->GetInt8Value(state, 9) == 1); + bool extended_version = true; + LuaSpell* spell = 0; + + if (!item) + spell = lua_interface->GetCurrentSpell(state); + + if (!spawn && (!spell || !use_all_spelltargets)) { + lua_interface->LogError("%s: LUA AddProc command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if ((!spell || use_all_spelltargets) && spawn && !spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddProc command error: spawn is not a valid entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!item && !spell) { + lua_interface->LogError("%s: LUA AddProc command error: can only use with an item provided or inside a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if(spell && spell->resisted) { + return 0; + } + + if (spell && spell->caster && spell->caster->GetZone() && use_all_spelltargets) { + Spawn* target; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->AddProc(type, chance, item, spell, damage_type, hp_ratio, below_health, target_health, extended_version); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else + ((Entity*)spawn)->AddProc(type, chance, item, spell); + + return 0; +} + +int EQ2Emu_lua_RemoveProc(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Item* item = lua_interface->GetItem(state, 2); + LuaSpell* spell = 0; + + if (!spawn) { + lua_interface->LogError("%s: LUA RemoveProc command error: spawn is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA RemoveProc command error: spawn is not a valid entity", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!item) + spell = lua_interface->GetCurrentSpell(state); + + lua_interface->ResetFunctionStack(state); + + if (!item && !spell) { + lua_interface->LogError("%s: LUA RemoveProc command error: can only use with an item provided or inside a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell && spell->caster && spell->caster->GetZone()) { + Spawn* target; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->RemoveProc(item, spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + spell->caster->RemoveProc(item, spell); + } + else + ((Entity*)spawn)->RemoveProc(item, spell); + + return 0; +} + +int EQ2Emu_lua_Knockback(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* target_spawn = lua_interface->GetSpawn(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + int32 duration = lua_interface->GetInt32Value(state, 3); + float vertical = lua_interface->GetFloatValue(state, 4); + float horizontal = lua_interface->GetFloatValue(state, 5); + bool use_heading = lua_interface->GetInt8Value(state, 6) == 1 ? true : false; + lua_interface->ResetFunctionStack(state); + + if (!target_spawn) { + lua_interface->LogError("%s: LUA Knockback command error: target_spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn) { + lua_interface->LogError("%s: LUA Knockback command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if ((vertical != 0 || horizontal != 0)) { + if(spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + PacketStruct* packet = configReader.getStruct("WS_PlayerKnockback", client->GetVersion()); + if (packet) { + packet->setDataByName("target_x", target_spawn->GetX()); + packet->setDataByName("target_y", target_spawn->GetY()); + packet->setDataByName("target_z", target_spawn->GetZ()); + packet->setDataByName("vertical_movement", vertical); + packet->setDataByName("horizontal_movement", horizontal); + if (use_heading) + packet->setDataByName("use_player_heading", 1); + + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + else { + spawn->SetKnockback(target_spawn, duration, vertical, horizontal); + } + } + + return 0; +} + +int EQ2Emu_lua_IsEpic(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsEpic command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, (spawn->GetHeroic() >= 2)); + return 1; +} + +int EQ2Emu_lua_ProcDamage(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + string name = lua_interface->GetStringValue(state, 3); + int8 dmg_type = lua_interface->GetInt8Value(state, 4); + int32 low_damage = lua_interface->GetInt32Value(state, 5); + int32 high_damage = lua_interface->GetInt32Value(state, 6); + string success_msg = lua_interface->GetStringValue(state, 7); + string effect_msg = lua_interface->GetStringValue(state, 8); + lua_interface->ResetFunctionStack(state); + + if (!caster) { + lua_interface->LogError("%s: LUA ProcDamage command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsEntity()) { + lua_interface->LogError("%s: LUA ProcDamage command error: caster is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA ProcDamage command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA ProcDamage command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (name.length() == 0) { + lua_interface->LogError("%s: LUA ProcDamage command error: name is empty", lua_interface->GetScriptName(state)); + return 0; + } + + ((Entity*)caster)->ProcAttack(target, dmg_type, low_damage, high_damage, name, success_msg, effect_msg); + return 0; +} + +int EQ2Emu_lua_GetSkillIDByName(lua_State* state) { + if (!lua_interface) + return 0; + + string name = lua_interface->GetStringValue(state); + lua_interface->ResetFunctionStack(state); + + if (name.length() == 0) { + lua_interface->LogError("%s: LUA GetSkillIDByName command error: name param was not set", lua_interface->GetScriptName(state)); + return 0; + } + + Skill* skill = master_skill_list.GetSkillByName(name.c_str()); + if (!skill) { + lua_interface->LogError("LUA GetSkillIDByName command error: skill with name of %s not found", name.c_str()); + return 0; + } + + lua_interface->SetInt32Value(state, skill->skill_id); + return 1; +} + +int EQ2Emu_lua_IsHeroic(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA IsHeroic command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->GetHeroic() == 1); + return 1; +} + +int EQ2Emu_lua_LastSpellAttackHit(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!luaspell) { + lua_interface->LogError("%s: LUA LastSpellAttackHit command error: this must be called from a spellscript", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, luaspell->last_spellattack_hit); + return 1; +} +int EQ2Emu_lua_IsBehind(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA IsBehind command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsBehind command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA IsBehind command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->BehindTarget(target)); + return 1; +} + +int EQ2Emu_lua_IsFlanking(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA IsFlanking command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA IsFlanking command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA IsFlanking command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->FlankingTarget(target)); + return 1; +} + +int EQ2Emu_lua_InFront(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA InFrontSpawn command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA InFrontSpawn command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA InFrontSpawn command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->InFrontSpawn(target, spawn->GetX(), spawn->GetZ())); + return 1; +} + +int EQ2Emu_lua_GetItemCount(lua_State* state) { + if (!lua_interface) + return 0; + + Item* item = lua_interface->GetItem(state); + lua_interface->ResetFunctionStack(state); + + if (!item) { + lua_interface->LogError("%s: LUA GetItemCount command error: item not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, item->details.count); + return 1; +} + +int EQ2Emu_lua_SetItemCount(lua_State* state) { + if (!lua_interface) + return 0; + + Item* item = lua_interface->GetItem(state); + Spawn* owner = lua_interface->GetSpawn(state, 2); + int16 new_count = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!item) { + lua_interface->LogError("%s: LUA SetItemCount command error: item not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!owner) { + lua_interface->LogError("%s: LUA SetItemCount command error: spawn not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!owner->IsPlayer()) { + lua_interface->LogError("%s: LUA SetItemCount command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (item->stack_count < new_count) { + lua_interface->LogError("%s: LUA SetItemCount command error: new item count cannot be more than max stack count", lua_interface->GetScriptName(state)); + return 0; + } + + if (new_count > 0) { + item->details.count = new_count; + item->save_needed = true; + } + else if (((Player*)owner)->GetEquipmentList()->GetItem(item->details.slot_id) == item) + ((Player*)owner)->GetEquipmentList()->RemoveItem(item->details.slot_id, true); + else if (((Player*)owner)->GetPlayerItemList()->GetItemFromUniqueID(item->details.unique_id) == item) + ((Player*)owner)->GetPlayerItemList()->RemoveItem(item, true); + else + { + lua_interface->LogError("%s: LUA SetItemCount command error: could not remove item from player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)owner)->GetClient(); + + if (!client) + return 0; + + ((Player*)owner)->SendInventoryUpdate(client->GetVersion()); + + EQ2Packet* app = ((Player*)owner)->GetEquipmentList()->serialize(client->GetVersion(), client->GetPlayer()); + if (app) + client->QueuePacket(app); + + return 0; +} + +int EQ2Emu_lua_AddSpellTimer(lua_State* state) { + if (!lua_interface) + return 0; + + int32 time = lua_interface->GetInt32Value(state); + string function = lua_interface->GetStringValue(state, 2); + Spawn* caster = lua_interface->GetSpawn(state, 3); + Spawn* target = lua_interface->GetSpawn(state, 4); + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (time == 0) { + lua_interface->LogError("%s: LUA AddSpellTimer command error: time must be set", lua_interface->GetScriptName(state)); + return 0; + } + + if (function.length() == 0) { + lua_interface->LogError("%s: LUA AddSpellTimer command error: function name must be set", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell || (!spell->caster || !spell->caster->GetZone())) { + lua_interface->LogError("%s: LUA AddSpellTimer command error: spell not found, AddSpellTimer must be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + SpellScriptTimer* timer = new SpellScriptTimer; + + /* //Google tells me memsetting a string is bad, manually setting just in case - Foof + #ifdef WIN32 + ZeroMemory(timer, sizeof(SpellScriptTimer)); + #else + bzero(timer, sizeof(SpellScriptTimer)); + #endif*/ + + timer->caster = 0; + timer->deleteWhenDone = false; + timer->target = 0; + + timer->time = Timer::GetCurrentTime2() + time; + timer->customFunction = function; + timer->spell = spell; + if (caster) + timer->caster = caster->GetID(); + if (target) + timer->target = target->GetID(); + + spell->caster->GetZone()->GetSpellProcess()->AddSpellScriptTimer(timer); + return 0; +} + +int EQ2Emu_lua_Resurrect(lua_State* state) { + if (!lua_interface) + return 0; + + float hp_perc = lua_interface->GetFloatValue(state); + float power_perc = lua_interface->GetFloatValue(state, 2); + bool send_window = lua_interface->GetInt32Value(state, 3) == 1; + Spawn* target = lua_interface->GetSpawn(state, 4); + string heal_name = lua_interface->GetStringValue(state, 5); + int8 crit_mod = lua_interface->GetInt32Value(state, 6); + bool no_calcs = lua_interface->GetInt32Value(state, 7) == 1; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!spell) { + lua_interface->LogError("%s: LUA command error: this function must be used in a spellscript", lua_interface->GetScriptName(state)); + return 0; + } + + Entity* caster = spell->caster; + if (!caster) { + lua_interface->LogError("%s: LUA command error: could not find caster", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = 0; + PendingResurrection* rez = 0; + ZoneServer* zone = spell->caster->GetZone(); + if (!target) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (spell->targets.size() > 0) { + vector spell_targets = spell->targets; + for (int8 i = 0; i < spell_targets.size(); i++) { + target = zone->GetSpawnByID(spell_targets.at(i)); + if (!target) + continue; + if (!target->IsPlayer()) + continue; + + client = ((Player*)target)->GetClient(); + + if (!client) + continue; + + rez = client->GetCurrentRez(); + if (rez->active) + continue; + + client->GetResurrectMutex()->writelock(__FUNCTION__, __LINE__); + rez->active = true; + rez->caster = caster; + rez->expire_timer = new Timer; + int32 duration = spell->spell->GetSpellDuration(); + rez->expire_timer->Start(duration * 100); + rez->hp_perc = hp_perc; + rez->mp_perc = power_perc; + rez->range = spell->spell->GetSpellData()->range; + rez->spell_name = spell->spell->GetName(); + if (heal_name.length() > 0) + rez->heal_name = heal_name; + else + rez->heal_name = rez->spell_name; + rez->no_calcs = no_calcs; + rez->crit_mod = crit_mod; + rez->spell_visual = spell->spell->GetSpellData()->spell_visual; + if (send_window) + client->SendResurrectionWindow(); + else { + target->GetZone()->ResurrectSpawn(target, client); + rez->should_delete = true; + } + client->GetResurrectMutex()->releasewritelock(__FUNCTION__, __LINE__); + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + else { + if(!target->IsPlayer()) + return 0; + + client = ((Player*)target)->GetClient(); + + if (!client) + return 0; + + rez = client->GetCurrentRez(); + if (rez->active) + return 0; + + client->GetResurrectMutex()->writelock(__FUNCTION__, __LINE__); + rez->active = true; + rez->caster = caster; + rez->expire_timer = new Timer; + int32 duration = spell->spell->GetSpellDuration(); + rez->expire_timer->Start(duration * 100); + rez->hp_perc = hp_perc; + rez->mp_perc = power_perc; + rez->range = spell->spell->GetSpellData()->range; + rez->spell_name = spell->spell->GetName(); + if (heal_name.length() > 0) + rez->heal_name = heal_name; + else + rez->heal_name = rez->spell_name; + rez->no_calcs = no_calcs; + rez->crit_mod = crit_mod; + rez->spell_visual = spell->spell->GetSpellData()->spell_visual; + if (send_window) + client->SendResurrectionWindow(); + else { + target->GetZone()->ResurrectSpawn(target, client); + rez->should_delete = true; + } + client->GetResurrectMutex()->releasewritelock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_SetVision(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 vision = lua_interface->GetInt32Value(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetVision command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetVision command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->GetInfoStruct()->set_vision(vision); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + } + else { + ((Entity*)spawn)->GetInfoStruct()->set_vision(vision); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_BlurVision(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + float intensity = lua_interface->GetFloatValue(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA BlurVision command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA BlurVision command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->GetInfoStruct()->set_drunk(intensity); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + } + else { + ((Entity*)spawn)->GetInfoStruct()->set_drunk(intensity); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_BreatheUnderwater(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + bool breatheUnderwater = lua_interface->GetBooleanValue(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA BreathUnderwater command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA BreathUnderwater command error: spawn is not en entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->GetInfoStruct()->set_breathe_underwater(breatheUnderwater); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + } + else { + ((Entity*)spawn)->GetInfoStruct()->set_breathe_underwater(breatheUnderwater); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_GetItemSkillReq(lua_State* state) { + if (!lua_interface) + return 0; + + Item* item = lua_interface->GetItem(state); + int8 type = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!item) { + lua_interface->LogError("%s: LUA GetItemSkillReq command error: item not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (type == 1) + lua_interface->SetInt32Value(state, item->generic_info.skill_req1); + else if (type == 2) + lua_interface->SetInt32Value(state, item->generic_info.skill_req2); + + return 1; +} + +int EQ2Emu_lua_SetSpeedMultiplier(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* target = lua_interface->GetSpawn(state); + float val = lua_interface->GetFloatValue(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + // Added from Gangrenous post + if (spell && spell->resisted) + return 0; + + // if its a percentage of 100 its a slow, we want to go at a fraction of the speed + if (val > 1.0f) + val = 1.0f - (val / 100.0f); + + if (spell && spell->spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int32 i = 0; i != spell->targets.size(); i++) { + Spawn* spawn = zone->GetSpawnByID(spell->targets.at(i)); + if (spawn && spawn->IsEntity()) { + ((Entity*)spawn)->SetSpeedMultiplier(val); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + } + } + else { + if (target && target->IsEntity()) { + ((Entity*)target)->SetSpeedMultiplier(val); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + + return 0; +} + +int EQ2Emu_lua_SetIllusion(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 model = lua_interface->GetInt16Value(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (spell && spell->spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int32 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target) + target->SetIllusionModel(model); + } + } + else { + if (!spawn) { + lua_interface->LogError("%s: LUA SetIllusion command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->SetIllusionModel(model); + } + + return 0; +} + +int EQ2Emu_lua_ResetIllusion(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (spell && spell->spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int32 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target) + target->SetIllusionModel(0); + } + } + else { + if (!spawn) { + lua_interface->LogError("%s: LUA ResetIllusion command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->SetIllusionModel(0); + } + + return 0; +} + +int EQ2Emu_lua_AddThreatTransfer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + float chance = lua_interface->GetFloatValue(state, 3); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!caster) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsEntity()) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: caster is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (chance <= 0) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: chance must be greater then 0%", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell) { + lua_interface->LogError("%s: LUA AddThreatTransfer command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if(spell->resisted) { + return 0; + } + + if (((Entity*)caster)->GetThreatTransfer()) { + return 0; + } + + ThreatTransfer* transfer = new ThreatTransfer; + transfer->Target = target->GetID(); + transfer->Amount = chance; + transfer->Spell = spell; + + ((Entity*)caster)->SetThreatTransfer(transfer); + return 0; +} + +int EQ2Emu_lua_RemoveThreatTransfer(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA RemoveThreatTransfer command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveThreatTransfer command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)spawn)->GetThreatTransfer() && ((Entity*)spawn)->GetThreatTransfer()->Spell == spell) { + ThreatTransfer* transfer = ((Entity*)spawn)->GetThreatTransfer(); + if(transfer && transfer->Spell != spell) { + lua_interface->LogError("%s: LUA RemoveThreatTransfer called, but there was a different spell set for the threat transfer.", lua_interface->GetScriptName(state)); + return 0; + } + ((Entity*)spawn)->SetThreatTransfer(nullptr); + } + + return 0; +} + +int EQ2Emu_lua_CureByType(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + int8 cure_count = lua_interface->GetInt8Value(state); + int8 cure_type = lua_interface->GetInt8Value(state, 2); + string cure_name = lua_interface->GetStringValue(state, 3); + int8 cure_level = lua_interface->GetInt8Value(state, 4); + Spawn* target = lua_interface->GetSpawn(state, 5); + Spawn* caster = lua_interface->GetSpawn(state, 6); // optional + lua_interface->ResetFunctionStack(state); + + if(spell && spell->resisted) { + return 0; + } + + if (target) { + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA CureByType command error: spawn override must be entity if used", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)target)->GetDetTypeCount(cure_type) > 0) { + std::string alternate_name = "item"; + if(spell) + alternate_name = std::string(spell->spell->GetName()); + + if(!caster && spell) + caster = (Spawn*)spell->caster; + + ((Entity*)target)->CureDetrimentByType(cure_count, cure_type, cure_name.length() > 0 ? cure_name : alternate_name, (Entity*)caster, cure_level); + } + } + else { + ZoneServer* zone = spell->caster->GetZone(); + vector targets = spell->targets; + vector targets_to_cure; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < targets.size(); i++) { + target = zone->GetSpawnByID(targets.at(i)); + + if (!target || !target->IsEntity()) + continue; + + if (((Entity*)target)->GetDetTypeCount(cure_type) > 0) { + targets_to_cure.push_back((Entity*)target); + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + vector::iterator itr; + for(itr = targets_to_cure.begin(); itr != targets_to_cure.end(); itr++) { + ((Entity*)*itr)->CureDetrimentByType(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level); + } + } + return 0; +} + +int EQ2Emu_lua_CureByControlEffect(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + if (!spell) { + lua_interface->LogError("%s: LUA CureByControlEffect command error: can only be used in a spell script", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if(spell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + int8 cure_count = lua_interface->GetInt8Value(state); + int8 cure_type = lua_interface->GetInt8Value(state, 2); + string cure_name = lua_interface->GetStringValue(state, 3); + int8 cure_level = lua_interface->GetInt8Value(state, 4); + Spawn* target = lua_interface->GetSpawn(state, 5); + lua_interface->ResetFunctionStack(state); + + if (target) { + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA CureByControlEffect command error: spawn override must be entity if used", lua_interface->GetScriptName(state)); + return 0; + } + + if (((Entity*)target)->GetDetCount() > 0) + ((Entity*)target)->CureDetrimentByControlEffect(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level); + } + else { + ZoneServer* zone = spell->caster->GetZone(); + vector targets = spell->targets; + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < targets.size(); i++) { + target = zone->GetSpawnByID(targets.at(i)); + + if (!target || !target->IsEntity()) + continue; + + if (((Entity*)target)->GetDetCount() > 0) + ((Entity*)target)->CureDetrimentByControlEffect(cure_count, cure_type, cure_name.length() > 0 ? cure_name : (string)spell->spell->GetName(), spell->caster, cure_level); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_CancelSpell(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA CancelSpell command error: can only be use in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell->caster) { + lua_interface->LogError("%s: LUA CancelSpell command error: unable to get the caster of the spell", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spell->caster->GetZone()) { + lua_interface->LogError("%s: LUA CancelSpell command error: unable to get the zone of the caster", lua_interface->GetScriptName(state)); + return 0; + } + + spell->caster->GetZone()->GetSpellProcess()->AddSpellCancel(spell); + + return 0; +} + +int EQ2Emu_lua_RemoveStealth(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveStealth command error: must be used from spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn && spawn->IsEntity()) + ((Entity*)spawn)->RemoveStealthSpell(spell); + else { + ZoneServer* zone = spell->caster->GetZone(); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + spawn = zone->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + + ((Entity*)spawn)->RemoveStealthSpell(spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_RemoveInvis(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveInvis command error: must be used from spell script", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn && spawn->IsEntity()) + ((Entity*)spawn)->RemoveInvisSpell(spell); + else { + ZoneServer* zone = spell->caster->GetZone(); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + spawn = zone->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + + ((Entity*)spawn)->RemoveInvisSpell(spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + return 0; +} + +int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + int8 class_id = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!caster) { + lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: caster is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsPlayer()) { + lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: caster must be a player", lua_interface->GetScriptName(state)); + return 0; + } + + Spawn* target = caster->GetTarget(); + if (!target) { + lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: target is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)caster)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: unable to get a client for the given caster", lua_interface->GetScriptName(state)); + return 0; + } + + HeroicOP* ho = master_ho_list.GetHeroicOP(class_id); + if (ho) { + ho->SetTarget(target->GetID()); + LogWrite(SPELL__ERROR, 0, "HO", "caster: %u", caster->GetID()); + LogWrite(SPELL__ERROR, 0, "HO", "target: %u", target->GetID()); + if (((Entity*)caster)->GetGroupMemberInfo()) { + if (caster->GetZone()->GetSpellProcess()->AddHO(client, ho)) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Entity*)caster)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + if ((*itr)->client) + ClientPacketFunctions::SendHeroicOPUpdate((*itr)->client, ho); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + safe_delete(ho); + } + else { + if (caster->GetZone()->GetSpellProcess()->AddHO(client, ho)) { + ClientPacketFunctions::SendHeroicOPUpdate(client, ho); + } + else + safe_delete(ho); + } + } + + return 0; +} + +int EQ2Emu_lua_SetSpellTriggerCount(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + + if (!spell) { + lua_interface->LogError("%s: LUA SetSpellTriggerCount command error: you must use this function in a spellscript!", lua_interface->GetScriptName(state)); + return 0; + } + + int16 triggerCount = lua_interface->GetInt16Value(state); + bool cancel_after_triggers = (lua_interface->GetInt8Value(state, 2) == 1); + + if (!triggerCount) { + lua_interface->LogError("%s: LUA SetSpellTriggerCount command error: used trigger value equals zero!", lua_interface->GetScriptName(state)); + return 0; + } + + spell->num_triggers = triggerCount; + spell->had_triggers = true; + spell->cancel_after_all_triggers = cancel_after_triggers; + + return 0; +} + +int EQ2Emu_lua_GetSpellTriggerCount(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA GetSpellTriggerCount command error: you must use this function in a spellscript!", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, spell->num_triggers); + + return 1; +} + +int EQ2Emu_lua_RemoveTriggerFromSpell(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveTriggerFromSpell command error: you must use this function in a spellscript!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (!spell->caster || !spell->caster->GetZone()) { + lua_interface->LogError("%s: LUA RemoveTriggerFromSpell command error: caster / caster zone must be set!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + int16 remove_count = lua_interface->GetInt16Value(state); + + lua_interface->ResetFunctionStack(state); + + if (!remove_count) + remove_count = 1; + + if (remove_count >= spell->num_triggers) { + spell->num_triggers = 0; + if (spell->cancel_after_all_triggers) + spell->caster->GetZone()->GetSpellProcess()->AddSpellCancel(spell); + } + else { + spell->num_triggers -= remove_count; + Client* client = spell->caster->IsPlayer() ? ((Player*)spell->caster)->GetClient() : nullptr; + ClientPacketFunctions::SendMaintainedExamineUpdate(client, spell->slot_pos, spell->num_triggers, 0); + } + return 0; +} + +int EQ2Emu_lua_CopySpawnAppearance(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* copy_spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA CopySpawnAppearance command error: the first spawn used was not valid!", lua_interface->GetScriptName(state)); + return 0; + } + + if (!copy_spawn) { + lua_interface->LogError("%s: LUA CopySpawnAppearance command error: the second spawn used was not valid!", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->CopySpawnAppearance(copy_spawn); + return 0; +} + +int EQ2Emu_lua_HasSpellImmunity(lua_State* state) { + Spawn* spawn = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA HasSpellImmunity command error: spawn does not exist.", lua_interface->GetScriptName(state)); + return 0; + } + else if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA HasSpellImmunity command error: spawn %s is not an entity.", lua_interface->GetScriptName(state), spawn->GetName()); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->IsImmune(type)); + return 1; +} + +int EQ2Emu_lua_AddImmunitySpell(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + int8 type = lua_interface->GetInt8Value(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA AddImmunitySpell command error: This must be used in a spellscript", lua_interface->GetScriptName(state)); + return 0; + } + + if(spell->resisted) { + return 0; + } + + if (spawn) { + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA AddImmunitySpell command error: The spawn provided is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* entity = ((Entity*)spawn); + entity->AddImmunity(spell, type); + } + else if(spell->caster && spell->caster->GetZone()) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + spawn = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + Entity* entity = ((Entity*)spawn); + entity->AddImmunity(spell, type); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} + +int EQ2Emu_lua_RemoveImmunitySpell(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + int8 type = lua_interface->GetInt8Value(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA RemoveImmunitySpell command error: This must be used in a spellscript", lua_interface->GetScriptName(state)); + return 0; + } + + if (spawn) { + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA RemoveImmunitySpell command error: The spawn provided is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* entity = ((Entity*)spawn); + entity->RemoveImmunity(spell, type); + } + else if(spell->caster && spell->caster->GetZone()) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + spawn = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + Entity* entity = ((Entity*)spawn); + entity->RemoveImmunity(spell, type); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} + +int EQ2Emu_lua_SetSpellSnareValue(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + if (!spell) { + lua_interface->LogError("%s: LUA SetSpellSnareValue command error: This can only be used in a spell script!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if(spell->resisted) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + float snare = lua_interface->GetFloatValue(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + // convert the val to the speed multipler value (100 - val) + float val = 100.0 - snare; + val /= 100.0; + + if (spawn) { + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetSpellSnareValue command error: spawn must be an entity.", lua_interface->GetScriptName(state)); + return 0; + } + + ((Entity*)spawn)->SetSnareValue(spell, val); + } + else if(spell->caster && spell->caster->GetZone()) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + spawn = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!spawn || !spawn->IsEntity()) + continue; + + ((Entity*)spawn)->SetSnareValue(spell, val); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} + +int EQ2Emu_lua_CheckRaceType(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int16 race_id = lua_interface->GetInt16Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA CheckRaceType command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (race_id == 0) { + lua_interface->LogError("%s: LUA CheckRaceType command error: race id must be set", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, (race_id == race_types_list.GetRaceType(spawn->GetModelType()) || race_id == race_types_list.GetRaceBaseType(spawn->GetModelType()))); + return 1; +} + +int EQ2Emu_lua_GetRaceType(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetRaceType command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, race_types_list.GetRaceType(spawn->GetModelType())); + return 1; +} + +int EQ2Emu_lua_GetRaceBaseType(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetRaceBaseType command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, race_types_list.GetRaceBaseType(spawn->GetModelType())); + return 1; +} + +int EQ2Emu_lua_GetSpellName(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: LUA GetSpellName command error: this function must be used from a spell script!", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetStringValue(state, spell->spell->GetName()); + return 1; +} + +int EQ2Emu_lua_GetQuestFlags(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + lua_interface->ResetFunctionStack(state); + if (!quest) { + lua_interface->LogError("%s: LUA GetQuestFlags command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, quest->GetQuestFlags()); + return 1; +} + +int EQ2Emu_lua_SetQuestFlags(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + int32 flags = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestFlags command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetQuestFlags(flags); + return 0; +} + +int EQ2Emu_lua_SetQuestTimer(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + int32 step = lua_interface->GetInt32Value(state, 3); + int32 duration = lua_interface->GetInt32Value(state, 4); + string action = lua_interface->GetStringValue(state, 5); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: player is not a valid spawn.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: player is not a valid player.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + if (duration == 0) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: duration must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + if (action.length() == 0) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: failed action must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA SetQuestTimer command error: failed to get a valid client pointer for the given player", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetTimerStep(step); + quest->AddFailedAction(step, action); + quest->SetStepTimer(duration); + client->AddQuestTimer(quest->GetQuestID()); + + return 0; +} + +int EQ2Emu_lua_SetQuestTimerComplete(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA SetQuestTimerComplete command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player) { + lua_interface->LogError("%s: LUA SetQuestTimerComplete command error: player is not a valid spawn.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetQuestTimerComplete command error: player is not a valid player.", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA SetQuestTimerComplete command error: failed to get a valid client pointer for the given player", lua_interface->GetScriptName(state)); + return 0; + } + + quest->SetTimerStep(0); + quest->SetStepTimer(0); + client->RemoveQuestTimer(quest->GetQuestID()); + + return 0; +} + +int EQ2Emu_lua_RemoveQuestStep(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + Quest* quest = lua_interface->GetQuest(state, 2); + int32 step = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: player is not a valid spawn.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: player is not a valid player.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!quest) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA RemoveQuestStep command error: unable to get a valid client pointer from the given player.", lua_interface->GetScriptName(state)); + return 0; + } + + if (quest->RemoveQuestStep(step, client)) { + client->QueuePacket(quest->QuestJournalReply(client->GetVersion(), client->GetNameCRC(), (Player*)player, 0, 0, 0, true)); + client->GetCurrentZone()->SendQuestUpdates(client); + } + else + lua_interface->LogError("LUA RemoveQuestStep command error: unable to remove the step (%u) from the quest (%s).", step, quest->GetName()); + + return 0; +} + +int EQ2Emu_lua_ResetQuestStep(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state, 1); + int32 step = lua_interface->GetInt32Value(state, 2); + string desc = lua_interface->GetStringValue(state, 3); + string task_group = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA ResetQuestStep command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA ResetQuestStep command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + QuestStep* quest_step = quest->GetQuestStep(step); + if (!quest_step) { + lua_interface->LogError("%s: LUA ResetQuestStep command error: unable to get a valid quest step.", lua_interface->GetScriptName(state)); + return 0; + } + + quest_step->SetStepProgress(0); + quest_step->SetTaskGroup(task_group); + quest_step->SetDescription(desc); + + return 0; +} + +int EQ2Emu_lua_AddQuestStepFailureAction(lua_State* state) { + if (!lua_interface) + return 0; + + Quest* quest = lua_interface->GetQuest(state); + int32 step = lua_interface->GetInt32Value(state, 2); + string action = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!quest) { + lua_interface->LogError("%s: LUA AddQuestStepFailureAction command error: quest is not valid.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA AddQuestStepFailureAction command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + if (action.length() == 0) { + lua_interface->LogError("%s: LUA AddQuestStepFailureAction command error: action must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + quest->AddFailedAction(step, action); + return 0; +} + +int EQ2Emu_lua_SetStepFailed(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int32 step = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA SetStepFailed command error: player is not a valid spawn.", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetStepFailed command error: player is not a valid player.", lua_interface->GetScriptName(state)); + return 0; + } + + if (quest_id == 0) { + lua_interface->LogError("%s: LUA SetStepFailed command error: quest_id must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + if (step == 0) { + lua_interface->LogError("%s: LUA SetStepFailed command error: step must be set.", lua_interface->GetScriptName(state)); + return 0; + } + + Quest* quest = ((Player*)player)->GetQuest(quest_id); + if (!quest) { + lua_interface->LogError("LUA SetStepFailed command error: unable to get a valid quest from the given id (%u).", quest_id); + return 0; + } + + quest->StepFailed(step); + return 0; +} + +int EQ2Emu_lua_GetQuestCompleteCount(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA GetQuestCompleteCount command error: player is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetQuestCompleteCount command error: player is not a valid player", lua_interface->GetScriptName(state)); + return 0; + } + + if (quest_id == 0) { + lua_interface->LogError("%s: LUA GetQuestCompleteCount command error: quest id is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, ((Player*)player)->GetQuestCompletedCount(quest_id)); + return 1; +} + +int EQ2Emu_lua_SetServerVariable(lua_State* state) { + if (!lua_interface) + return 0; + + string name = lua_interface->GetStringValue(state); + string value = lua_interface->GetStringValue(state, 2); + string comment = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + + if (name.length() == 0) { + lua_interface->LogError("%s: LUA SetServerVariable command error: name is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (value.length() == 0) { + lua_interface->LogError("%s: LUA SetServerVariable command error: value is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + string varname = string("lua_").append(name); + Variable* var = variables.FindVariable(varname); + if (var) + var->SetValue(value.c_str()); + else { + var = new Variable(varname.c_str(), value.c_str(), comment.c_str()); + variables.AddVariable(var); + } + + database.SaveVariable(var->GetName(), var->GetValue(), var->GetComment()); + return 0; +} + +int EQ2Emu_lua_GetServerVariable(lua_State* state) { + if (!lua_interface) + return 0; + + string name = lua_interface->GetStringValue(state); + lua_interface->ResetFunctionStack(state); + if (name.length() == 0) { + lua_interface->LogError("%s: LUA GetServerVariable command error: name is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + string varname = string("lua_").append(name); + Variable* var = variables.FindVariable(varname); + if (var) + lua_interface->SetStringValue(state, var->GetValue()); + else + lua_interface->SetStringValue(state, "NULL"); + + return 1; +} + +int EQ2Emu_lua_HasLanguage(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 language_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA HasLanguage command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasLanguage command error: player is not a valid player", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Player*)player)->HasLanguage(language_id)); + return 1; +} + +int EQ2Emu_lua_AddLanguage(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 language_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA AddLanguage command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA AddLanguage command error: player is not a valid player", lua_interface->GetScriptName(state)); + return 0; + } + + Language* language = master_languages_list.GetLanguage(language_id); + if (language) + { + ((Player*)player)->AddLanguage(language->GetID(), language->GetName(), true); + ((Player*)player)->GetClient()->SendLanguagesUpdate(language->GetID(), 0); + } + + return 0; +} + +int EQ2Emu_lua_IsNight(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (!zone) { + lua_interface->LogError("%s: LUA IsNight command error: zone is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, zone->IsDusk()); + return 1; +} + +int EQ2Emu_lua_AddMultiFloorLift(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA AddMultiFloorLift command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsWidget()) { + lua_interface->LogError("%s: LUA AddMultiFloorLift command error: spawn is not a widget", lua_interface->GetScriptName(state)); + return 0; + } + + ((Widget*)spawn)->SetMultiFloorLift(true); + + if (spawn->GetZone()) + spawn->GetZone()->AddTransportSpawn(spawn); + + return 0; +} + +int EQ2Emu_lua_StartAutoMount(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 path = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA StartAutoMount command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA StartAutoMount command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (path == 0) { + lua_interface->LogError("%s: LUA StartAutoMount command error: path must be greater then zero", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA StartAutoMount command error: unable to get a client from the given player", lua_interface->GetScriptName(state)); + return 0; + } + + client->SendFlightAutoMount(path); + + return 0; +} + +int EQ2Emu_lua_EndAutoMount(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + if (!player) { + lua_interface->LogError("%s: LUA EndAutoMount command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA EndAutoMount command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA EndAutoMount command error: unable to get a client from the given player", lua_interface->GetScriptName(state)); + return 0; + } + client->EndAutoMount(); + + return 0; +} + +int EQ2Emu_lua_IsOnAutoMount(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("%s: LUA IsOnAutoMount command error: spawn in not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA IsOnAutoMount command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA IsOnAutoMount command error: unable to get a client from the given player", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, client->GetOnAutoMount()); + return 1; +} + +int EQ2Emu_lua_SetPlayerHistory(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 event_id = lua_interface->GetInt32Value(state, 2); + int32 value = lua_interface->GetInt32Value(state, 3); + int32 value2 = lua_interface->GetInt32Value(state, 4); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA SetPlayerHistory command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetPlayerHistory command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ((Player*)player)->UpdateLUAHistory(event_id, value, value2); + return 0; +} + +int EQ2Emu_lua_GetPlayerHistory(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 event_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA GetPlayerHistory command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetPlayerHistory command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + LUAHistory* hd = ((Player*)player)->GetLUAHistory(event_id); + if (!hd) + return 0; + + lua_interface->SetInt32Value(state, hd->Value); + lua_interface->SetInt32Value(state, hd->Value2); + return 2; +} + +int EQ2Emu_lua_SetGridID(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 grid = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetGridID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (grid == 0) { + lua_interface->LogError("%s: LUA SetGridID command error: grid is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + spawn->SetLocation(grid); + return 0; +} + +int EQ2Emu_lua_SetRequiredHistory(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 event_id = lua_interface->GetInt32Value(state, 2); + int32 value1 = lua_interface->GetInt32Value(state, 3); + int32 value2 = lua_interface->GetInt32Value(state, 4); + bool private_spawn = (lua_interface->GetInt8Value(state, 5) == 1); + int16 flag_override = lua_interface->GetInt16Value(state, 6); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetRequiredHistory command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + //Add this quest to the list of required quests for this spawn + spawn->SetRequiredHistory(event_id, value1, value2); + //If private spawn value set + if (private_spawn) { + //Set the spawn to be private when not granted access via history + spawn->AddAllowAccessSpawn(spawn); + spawn->SetPrivateQuestSpawn(true); + } + //This value will override vis_flags in the vis packet + if (flag_override > 0) + spawn->SetQuestsRequiredOverride(flag_override); + return 0; +} + +int EQ2Emu_lua_GetQuestStepProgress(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 quest_id = lua_interface->GetInt32Value(state, 2); + int32 step_id = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA GetQuestStepProgress command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA GetQuestStepProgress command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, ((Player*)player)->GetStepProgress(quest_id, step_id)); + + return 1; +} + +int EQ2Emu_lua_SetPlayerLevel(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int8 level = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA SetPlayerLevel command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA SetPlayerLevel command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (level == 0) { + lua_interface->LogError("%s: LUA SetPlayerLevel command error: new level can't be 0", lua_interface->GetScriptName(state)); + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + lua_interface->LogError("%s: LUA SetPlayerLevel command error: unable to get a client from the given spawn", lua_interface->GetScriptName(state)); + return 0; + } + + client->ChangeLevel(client->GetPlayer()->GetLevel(), level); + return 0; +} + +int EQ2Emu_lua_AddCoin(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 amount = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA AddCoin command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA AddCoin command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (amount == 0) { + lua_interface->LogError("%s: LUA AddCoin command error: amount must be greater then 0", lua_interface->GetScriptName(state)); + return 0; + } + + ((Player*)player)->AddCoins(amount); + return 0; +} + +int EQ2Emu_lua_RemoveCoin(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 amount = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA RemoveCoin command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA RemoveCoin command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + if (amount == 0) { + lua_interface->LogError("%s: LUA RemoveCoin command error: amount must be greater then 0", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, ((Player*)player)->RemoveCoins(amount)); + return 1; +} + +int EQ2Emu_lua_GetPlayersInZone(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (!zone) { + lua_interface->LogError("%s: LUA GetPlayersInZone command error: zone is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + vector players = zone->GetPlayers(); + if (players.size() == 0) + return 0; + + lua_createtable(state, players.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < players.size(); i++) { + lua_interface->SetSpawnValue(state, players.at(i)); + lua_rawseti(state, newTable, i + 1); + } + + return 1; +} + +int EQ2Emu_lua_SpawnGroupByID(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state, 1); + if (!zone) { + lua_interface->LogError("%s: LUA GetPlayersInZone command error: zone is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + int32 group_id = lua_interface->GetInt32Value(state, 2); + int16 custom_level = lua_interface->GetInt32Value(state, 3); + + lua_interface->ResetFunctionStack(state); + + //Map of + map* locs = zone->GetSpawnLocationsByGroup(group_id); + map::iterator itr; + + vector group; + + Spawn* leader = 0; + for (itr = locs->begin(); itr != locs->end(); itr++) { + SpawnLocation* location = zone->GetSpawnLocation(itr->second); + if (!location) { + lua_interface->LogError("LUA SpawnByLocationID command error: no location found for the given ID (%u)", itr->second); + return 0; + } + + Spawn* spawn = 0; + if (location->entities[0]) { + if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_NPC) + spawn = zone->AddNPCSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN) + spawn = zone->AddGroundSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT) + spawn = zone->AddObjectSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET) + spawn = zone->AddWidgetSpawn(location, location->entities[0]); + else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_SIGN) + spawn = zone->AddSignSpawn(location, location->entities[0]); + + if(spawn && spawn->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met (LUA SpawnGroupByID).", location->entities[0]->spawn_id); + safe_delete(spawn); + continue; + } + if (spawn) { + if(!leader) + leader = spawn; + if(leader) + leader->AddSpawnToGroup(spawn); + + spawn->SetSpawnGroupID(group_id); + + if(custom_level > 0 && custom_level != 0xFFFF) { + spawn->SetLevel(custom_level); + } + + const char* script = 0; + for (int x = 0; x < 3; x++) { + switch (x) { + case 0: + script = world.GetSpawnEntryScript(location->entities[0]->spawn_entry_id); + break; + case 1: + script = world.GetSpawnLocationScript(location->entities[0]->spawn_location_id); + break; + case 2: + script = world.GetSpawnScript(location->entities[0]->spawn_id); + break; + } + if (script && lua_interface->GetSpawnScript(script) != 0) { + spawn->SetSpawnScript(string(script)); + break; + } + } + zone->CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + lua_interface->SetSpawnValue(state, spawn); + group.push_back(spawn); + } + else { + LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by group id to zone %s with location id %u.", zone->GetZoneName(), group_id); + safe_delete(spawn); + } + } + } + + if (!group.empty()) { + lua_createtable(state, group.size(), 0); + int newTable = lua_gettop(state); + for (int32 i = 0; i < group.size(); i++) { + lua_interface->SetSpawnValue(state, group[i]); + lua_rawseti(state, newTable, i + 1); + } + } + else + lua_pushnil(state); + + return 1; +} + +int EQ2Emu_lua_SetSpawnAnimation(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state, 1); + int32 anim_id = lua_interface->GetInt32Value(state, 2); + int16 leeway = lua_interface->GetInt16Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetSpawnAnimation command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (anim_id == 0) { + lua_interface->LogError("%s: LUA SetSpawnAnimation command error: anim_id is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (leeway == 0) + leeway = 5000; + + spawn->SetSpawnAnim(anim_id); + spawn->SetSpawnAnimLeeway(leeway); + + return 0; +} + +int EQ2Emu_lua_GetClientVersion(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!player || !player->IsPlayer()) { + return 0; + } + + Client* client = ((Player*)player)->GetClient(); + if (!client) { + return 0; + } + + + lua_interface->SetInt32Value(state, client->GetVersion()); + return 1; +} + +int EQ2Emu_lua_GetItemID(lua_State* state) { + if (!lua_interface) + return 0; + + Item* item = lua_interface->GetItem(state); + lua_interface->ResetFunctionStack(state); + if (!item) { + lua_interface->LogError("%s: LUA GetItemID command error: item is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, item->details.item_id); + return 1; +} + +int EQ2Emu_lua_IsEntity(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA IsEntity command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetBooleanValue(state, spawn->IsEntity()); + return 1; +} + +int EQ2Emu_lua_GetOrigX(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetOrigX command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetFloatValue(state, spawn->GetSpawnOrigX()); + return 1; +} + +int EQ2Emu_lua_GetOrigY(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetOrigY command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetFloatValue(state, spawn->GetSpawnOrigY()); + return 1; +} + +int EQ2Emu_lua_GetOrigZ(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetOrigZ command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetFloatValue(state, spawn->GetSpawnOrigZ()); + return 1; +} + +int EQ2Emu_lua_GetPCTOfHP(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + float pct = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetPCTOfHP command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (pct == 0) { + lua_interface->LogError("%s: LUA GetPCTOfHP command error: pct is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + int32 amount = std::round(spawn->GetTotalHP() * (pct / 100)); + lua_interface->SetInt32Value(state, amount); + return 1; +} + +int EQ2Emu_lua_GetPCTOfPower(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + float pct = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetPCTOfPower command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (pct == 0) { + lua_interface->LogError("%s: LUA GetPCTOfPower command error: pct is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + int32 amount = std::round(spawn->GetTotalPower() * (pct / 100)); + lua_interface->SetInt32Value(state, amount); + return 1; +} + +int EQ2Emu_lua_GetBoundZoneID(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: LUA GetBoundZoneID command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: LUA GetBoundZoneID command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetInt32Value(state, ((Player*)spawn)->GetPlayerInfo()->GetBindZoneID()); + return 1; +} + +int EQ2Emu_lua_Evac(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* target = lua_interface->GetSpawn(state); + + if (target) { + float x = target->GetZone()->GetSafeX(); + float y = target->GetZone()->GetSafeY(); + float z = target->GetZone()->GetSafeZ(); + float h = target->GetZone()->GetSafeHeading(); + + target->SetX(x); + target->SetY(y); + target->SetZ(z); + target->SetHeading(h); + + target->SetSpawnOrigX(x); + target->SetSpawnOrigY(y); + target->SetSpawnOrigZ(z); + target->SetSpawnOrigHeading(h); + + if (target->IsPlayer()) { + Client* client = ((Player*)target)->GetClient(); + if (client) { + client->GetCurrentZone()->ClearHate(client->GetPlayer()); + + int numargs = lua_interface->GetNumberOfArgs(state); + if(numargs == 4) { + x = lua_interface->GetFloatValue(state,1); + y = lua_interface->GetFloatValue(state,2); + z = lua_interface->GetFloatValue(state,3); + h = lua_interface->GetFloatValue(state,4); + } + + client->SetReloadingZone(true); + target->SetX(x); + target->SetY(y); + target->SetZ(z); + target->SetHeading(h); + + target->SetSpawnOrigX(x); + target->SetSpawnOrigY(y); + target->SetSpawnOrigZ(z); + target->SetSpawnOrigHeading(h); + + target->SetAppearancePosition(x,y,z); + + client->SetZoningCoords(x,y,z,h); + + PacketStruct* packet = configReader.getStruct("WS_TeleportWithinZone", client->GetVersion()); + if (packet) + { + packet->setDataByName("x", x); + packet->setDataByName("y", y); + packet->setDataByName("z", z); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + + client->GetCurrentZone()->RemoveSpawn(target, false, false, true, true); + + client->GetPlayer()->SetSpawnSentState(target, SpawnState::SPAWN_STATE_SENT); + } + } + lua_interface->ResetFunctionStack(state); + } + else { + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + + if(!spell || !spell->caster || !spell->caster->GetZone()) { + lua_interface->ResetFunctionStack(state); + return 0; + } + + ZoneServer* zone = spell->caster->GetZone(); + + float x = spell->caster->GetZone()->GetSafeX(); + float y = spell->caster->GetZone()->GetSafeY(); + float z = spell->caster->GetZone()->GetSafeZ(); + float h = spell->caster->GetZone()->GetSafeHeading(); + + int numargs = lua_interface->GetNumberOfArgs(state); + + if(numargs == 4) { + x = lua_interface->GetFloatValue(state,1); + y = lua_interface->GetFloatValue(state,2); + z = lua_interface->GetFloatValue(state,3); + h = lua_interface->GetFloatValue(state,4); + } + + lua_interface->ResetFunctionStack(state); + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + Spawn* target2 = zone->GetSpawnByID(spell->targets.at(i)); + if (!target2) + continue; + + if (target2->IsPlayer()) { + Client* client = ((Player*)target2)->GetClient(); + if (client) { + client->GetCurrentZone()->ClearHate(client->GetPlayer()); + + client->SetReloadingZone(true); + target2->SetX(x); + target2->SetY(y); + target2->SetZ(z); + target2->SetHeading(h); + + target2->SetSpawnOrigX(x); + target2->SetSpawnOrigY(y); + target2->SetSpawnOrigZ(z); + target2->SetSpawnOrigHeading(h); + + target2->SetAppearancePosition(x,y,z); + + client->SetZoningCoords(x,y,z,h); + + PacketStruct* packet = configReader.getStruct("WS_TeleportWithinZone", client->GetVersion()); + if (packet) + { + packet->setDataByName("x", x); + packet->setDataByName("y", y); + packet->setDataByName("z", z); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + + client->GetCurrentZone()->RemoveSpawn(target2, false, false, true, true); + + client->GetPlayer()->SetSpawnSentState(target2, SpawnState::SPAWN_STATE_SENT); + } + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + + return 0; +} + +int EQ2Emu_lua_GetSpellTier(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!luaspell || !luaspell->spell) { + lua_interface->LogError("%s: LUA GetSpellTier command error: must be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + int8 tier = luaspell->spell->GetSpellTier(); + lua_interface->SetInt32Value(state, tier); + return 1; +} + +int EQ2Emu_lua_GetSpellID(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* luaspell = lua_interface->GetCurrentSpell(state); + lua_interface->ResetFunctionStack(state); + if (!luaspell || !luaspell->spell) { + lua_interface->LogError("%s: LUA GetSpellID command error: must be used in a spell script", lua_interface->GetScriptName(state)); + return 0; + } + + int32 spell_id = luaspell->spell->GetSpellID(); + lua_interface->SetInt32Value(state, spell_id); + return 1; +} + +int EQ2Emu_lua_StartTransmute(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state, 1); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: Lua StartTransmute command error: no spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: Lua StartTransmute command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (!zone) { + return 0; + } + + Client* client = ((Player*)spawn)->GetClient(); + if (!client) { + return 0; + } + + Transmute::CreateItemRequest(client, static_cast(spawn)); + return 0; +} + +int EQ2Emu_lua_CompleteTransmute(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state, 1); + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("%s: Lua CompleteTransmute command error: no spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsPlayer()) { + lua_interface->LogError("%s: Lua CompleteTransmute command error: spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (!zone) { + return 0; + } + + Client* client = ((Player*)spawn)->GetClient(); + if (!client) { + return 0; + } + + Transmute::CompleteTransmutation(client, static_cast(spawn)); + return 0; +} + +int EQ2Emu_lua_ProcHate(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* caster = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + int32 threat_amt = lua_interface->GetInt32Value(state, 3); + string spell_name = lua_interface->GetStringValue(state, 4); + lua_interface->ResetFunctionStack(state); + if (!caster) { + lua_interface->LogError("%s: LUA ProcHate command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!caster->IsEntity()) { + lua_interface->LogError("%s: LUA ProcHate command error: caster is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target) { + lua_interface->LogError("%s: LUA ProcHate command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA ProcHate command error: target is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + static_cast(target)->AddHate(static_cast(caster), threat_amt); + caster->GetZone()->SendThreatPacket(static_cast(caster), target, threat_amt, spell_name.c_str()); + return 0; +} + +int EQ2Emu_lua_GiveExp(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 amount = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (player && player->IsPlayer() && amount > 0) { + ((Player*)player)->AddXP(amount); + } + return 0; +} + +int EQ2Emu_lua_DisplayText(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int8 type = lua_interface->GetInt8Value(state, 2); + string text = lua_interface->GetStringValue(state, 3); + lua_interface->ResetFunctionStack(state); + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (!client || text.length() == 0) { + lua_interface->LogError("%s: LUA DisplayText required parameters not given", lua_interface->GetScriptName(state)); + return 0; + } + client->SimpleMessage(type, text.c_str()); + return 0; +} + +int EQ2Emu_lua_ShowLootWindow(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + if (!client || !spawn) { + lua_interface->LogError("%s: LUA ShowLootWindow required parameters not given", lua_interface->GetScriptName(state)); + return 0; + } + vector* items = ((Player*)player)->GetPendingLootItems(spawn->GetID()); + if (!items) { + lua_interface->LogError("%s: LUA ShowLootWindow has no items", lua_interface->GetScriptName(state)); + return 0; + } + client->SendLootResponsePacket(spawn->GetLootCoins(), items, spawn, true); + return 0; +} + +int EQ2Emu_lua_GetRandomSpawnByID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawnref = lua_interface->GetSpawn(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn_id > 0 && spawnref) { + vector spawns = spawnref->GetZone()->GetSpawnsByID(spawn_id); + if (spawns.size() == 0) { + lua_interface->LogError("%s: LUA EQ2Emu_lua_GetRandomSpawnByID command error: GetSpawnsByID returned no spawns", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* spawn = 0; + int16 index = MakeRandomInt(0, spawns.size()); + if (index >= spawns.size() || index < 0) + index = 0; + spawn = spawns[index]; + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + else { + lua_interface->LogError("%s: LUA GetRandomSpawnByID required parameters not given", lua_interface->GetScriptName(state)); + } + + return 0; +} + +int EQ2Emu_lua_AddPrimaryEntityCommandAllSpawns(lua_State* state) { + Spawn* player = lua_interface->GetSpawn(state); + int32 spawn_id = lua_interface->GetInt32Value(state, 2); + string name = lua_interface->GetStringValue(state, 3); + float distance = lua_interface->GetFloatValue(state, 4); + string command = lua_interface->GetStringValue(state, 5); + string error_text = lua_interface->GetStringValue(state, 6); + int16 cast_time = lua_interface->GetInt16Value(state, 7); + int32 spell_visual = lua_interface->GetInt32Value(state, 8); + lua_interface->ResetFunctionStack(state); + if (spawn_id && player && player->IsPlayer() && name.length() > 0) { + if (distance == 0) + distance = 10.0f; + if (command.length() == 0) + command = name; + vector spawns = player->GetZone()->GetSpawnsByID(spawn_id); + if (spawns.size() == 0) { + lua_interface->LogError("%s: LUA AddPrimaryEntityCommandAllSpawns command error: GetSpawnsByID returned no spawns", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* spawn = 0; + for (vector::iterator itr = spawns.begin(); itr != spawns.end(); itr++) { + spawn = *itr; + if (spawn) { + spawn->AddPrimaryEntityCommand(name.c_str(), distance, command.c_str(), error_text.c_str(), cast_time, spell_visual); + player->GetZone()->SendUpdateDefaultCommand(spawn, command.c_str(), distance); + } + } + + } + return 0; +} + +int EQ2Emu_lua_InstructionWindowGoal(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + int8 goal_num = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && player->GetZone()) + client = ((Player*)player)->GetClient(); + else{ + lua_interface->LogError("LUA InstructionWindowGoal command error: player is not valid"); + return 0; + } + if (client) { + PacketStruct* packet = configReader.getStruct("WS_InstructionWindow", client->GetVersion()); + if (packet) { + packet->setDataByName("goal_num", goal_num); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + return 0; +} + +int EQ2Emu_lua_InstructionWindowClose(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && player->GetZone()) + client = ((Player*)player)->GetClient(); + else { + lua_interface->LogError("LUA InstructionWindowClose command error: player is not valid"); + return 0; + } + if (client && client->GetVersion() >= 374) { + client->QueuePacket(new EQ2Packet(OP_EqInstructionWindowCloseCmd, 0, 0)); + } + return 0; +} + +int EQ2Emu_lua_InstructionWindow(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + float duration = lua_interface->GetFloatValue(state, 2); + string text = lua_interface->GetStringValue(state, 3); + string voice = lua_interface->GetStringValue(state, 4); + int32 voice_key1 = lua_interface->GetInt32Value(state, 5); + int32 voice_key2 = lua_interface->GetInt32Value(state, 6); + string signal = lua_interface->GetStringValue(state, 7); + string goal1 = lua_interface->GetStringValue(state, 8); + string task1 = lua_interface->GetStringValue(state, 9); + string goal2 = lua_interface->GetStringValue(state, 10); + string task2 = lua_interface->GetStringValue(state, 11); + string goal3 = lua_interface->GetStringValue(state, 12); + string task3 = lua_interface->GetStringValue(state, 13); + string goal4 = lua_interface->GetStringValue(state, 14); + string task4 = lua_interface->GetStringValue(state, 15); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("LUA InstructionWindow command error: spawn is not valid"); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("LUA InstructionWindow command error: spawn is not a player"); + return 0; + } + else + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA InstructionWindow command error: could not find client"); + return 0; + } + if (text.length() == 0) { + lua_interface->LogError("LUA InstructionWindow required parameters not given"); + return 0; + } + if (duration >= 0 && duration < 2) + duration = 2; + PacketStruct* packet = configReader.getStruct("WS_InstructionWindow", client->GetVersion()); + if (packet) { + packet->setDataByName("open_seconds_max", duration); + packet->setDataByName("text", text.c_str()); + packet->setDataByName("voice", voice.c_str()); + int8 num_goals = 1; + if (task2.length() > 0) + num_goals++; + if (task3.length() > 0) + num_goals++; + if (task4.length() > 0) + num_goals++; + packet->setArrayLengthByName("num_goals", num_goals); + for (int8 i = 0; i < num_goals; i++) { + packet->setSubArrayLengthByName("num_tasks", 1, i); + } + if (goal1.length() > 0) + packet->setArrayDataByName("goal_text", goal1.c_str()); + if (goal2.length() > 0) + packet->setArrayDataByName("goal_text", goal2.c_str(), 1); + if (goal3.length() > 0) + packet->setArrayDataByName("goal_text", goal3.c_str(), 2); + if (goal4.length() > 0) + packet->setArrayDataByName("goal_text", goal4.c_str(), 3); + packet->setSubArrayDataByName("task_text", task1.c_str()); + if (task2.length() > 0) + packet->setSubArrayDataByName("task_text", task2.c_str(), 1); + if (task3.length() > 0) + packet->setSubArrayDataByName("task_text", task3.c_str(), 2); + if (task4.length() > 0) + packet->setSubArrayDataByName("task_text", task4.c_str(), 3); + packet->setDataByName("complete_sound", "click"); + packet->setDataByName("signal", signal.c_str()); + packet->setDataByName("voice_key1", voice_key1); + packet->setDataByName("voice_key2", voice_key2); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_ShowWindow(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + string window = lua_interface->GetStringValue(state, 2); + int8 show = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA ShowWindow command error: spawn is not valid"); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("LUA ShowWindow command error: spawn is not a player"); + return 0; + } + else + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA ShowWindow command error: could not find client"); + return 0; + } + if (window.length() == 0) { + lua_interface->LogError("LUA ShowWindow required parameters not given"); + return 0; + } + PacketStruct* packet = configReader.getStruct("WS_ShowWindow", client->GetVersion()); + if (packet) { + packet->setDataByName("window", window.c_str()); + packet->setDataByName("show", show); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_EnableGameEvent(lua_State* state) { + //See GameEvents.txt for options that can be used for this function + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + string event_name = lua_interface->GetStringValue(state, 2); + int8 enabled = lua_interface->GetInt8Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (!player || !player->IsPlayer()) { + lua_interface->LogError("LUA EnableGameEvent error: player is not valid"); + return 0; + } + + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA EnableGameEvent error: could not find client"); + return 0; + } + PacketStruct* packet = configReader.getStruct("WS_EnableGameEvent", client->GetVersion()); + if (packet) { + packet->setDataByName("event_name", event_name.c_str()); + packet->setDataByName("enabled", enabled); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_GetTutorialStep(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + lua_interface->SetInt32Value(state, ((Player*)player)->GetTutorialStep()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetTutorialStep(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int8 step = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer() && step > 0) { + ((Player*)player)->SetTutorialStep(step); + } + return 0; +} + +int EQ2Emu_lua_FlashWindow(lua_State* state) { + if (!lua_interface) + return 0; + Client* client = 0; + Spawn* player = lua_interface->GetSpawn(state); + string window = lua_interface->GetStringValue(state, 2); + float flash_seconds = lua_interface->GetFloatValue(state, 3); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA FlashWindow command error: spawn is not valid"); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("LUA FlashWindow command error: spawn is not a player"); + return 0; + } + else + client = ((Player*)player)->GetClient(); + + if (!client) { + lua_interface->LogError("LUA FlashWindow command error: could not find client"); + return 0; + } + if (window.length() == 0) { + lua_interface->LogError("LUA FlashWindow required parameters not given"); + return 0; + } + PacketStruct* packet = configReader.getStruct("WS_FlashWindow", client->GetVersion()); + if (packet) { + packet->setDataByName("window", window.c_str()); + packet->setDataByName("flash_seconds", flash_seconds); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + return 0; +} + +int EQ2Emu_lua_CheckLOS(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && target) + return spawn->CheckLoS(target); + + return 0; +} +int EQ2Emu_lua_CheckLOSByCoordinates(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + float x = lua_interface->GetFloatValue(state, 2); + float y = lua_interface->GetFloatValue(state, 3); + float z = lua_interface->GetFloatValue(state, 4); + lua_interface->ResetFunctionStack(state); + if (spawn) + return spawn->CheckLoS(glm::vec3(spawn->GetX(), spawn->GetZ(), spawn->GetY() + 1.0f), glm::vec3(x, z, y+1.0f)); + + return 0; +} + +int EQ2Emu_lua_SetZoneExpansionFlag(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + int32 xpackFlag = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (zone) + zone->SetExpansionFlag(xpackFlag); + return 0; +} + +int EQ2Emu_lua_GetZoneExpansionFlag(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetExpansionFlag()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetZoneHolidayFlag(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + int32 holidayFlag = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (zone) + zone->SetHolidayFlag(holidayFlag); + return 0; +} + +int EQ2Emu_lua_GetZoneHolidayFlag(lua_State* state) { + if (!lua_interface) + return 0; + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetHolidayFlag()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetCanBind(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool canbind = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA SetCanBind command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) + zone->SetCanBind(canbind); + return 0; +} + +int EQ2Emu_lua_GetCanBind(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA GetCanBind command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetCanBind()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetCanGate(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + bool cangate = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA SetCanGate command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) + zone->SetCanGate(cangate); + return 0; +} + +int EQ2Emu_lua_GetCanGate(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA GetCanGate command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetCanGate()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetCanEvac(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + bool canevac = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA SetCanEvac command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) + zone->SetCanEvac(canevac); + return 0; +} + +int EQ2Emu_lua_GetCanEvac(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if(!spawn) { + lua_interface->LogError("%s: LUA GetCanEvac command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + ZoneServer* zone = spawn->GetZone(); + if (zone) { + lua_interface->SetInt32Value(state, zone->GetCanEvac()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_AddSpawnProximity(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spawn_value = lua_interface->GetInt32Value(state, 2); + int8 spawn_type = lua_interface->GetInt8Value(state, 3); + float distance = lua_interface->GetFloatValue(state, 4); + string in_range_function = lua_interface->GetStringValue(state, 5); + string leaving_range_function = lua_interface->GetStringValue(state, 6); + lua_interface->ResetFunctionStack(state); + if (spawn && distance > 0 && in_range_function.length() > 0) + spawn->AddLUASpawnProximity(spawn_value, (Spawn::SpawnProximityType)spawn_type, distance, in_range_function, leaving_range_function); + return 0; +} + +int EQ2Emu_lua_CanSeeInvis(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* target = lua_interface->GetSpawn(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn && target) + { + if (spawn->IsPlayer() && target->IsEntity()) + { + lua_interface->SetBooleanValue(state, ((Player*)spawn)->CanSeeInvis((Entity*)target)); + return 1; + } + else if (spawn->IsEntity() && target->IsEntity()) + { + lua_interface->SetBooleanValue(state, ((Entity*)spawn)->CanSeeInvis((Entity*)target)); + return 1; + } + } + + return 0; +} + +int EQ2Emu_lua_SetSeeInvis(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool val = (lua_interface->GetInt8Value(state, 2) == 1); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity()) + { + ((Entity*)spawn)->SetSeeInvisSpell(val); + if (spawn->IsPlayer()) + { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + ((Player*)spawn)->GetZone()->SendAllSpawnsForSeeInvisChange(client); + } + } + + return 0; +} + +int EQ2Emu_lua_SetSeeHide(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool val = (lua_interface->GetInt8Value(state, 2) == 1); + lua_interface->ResetFunctionStack(state); + if (spawn && spawn->IsEntity()) + { + ((Entity*)spawn)->SetSeeHideSpell(val); + if (spawn->IsPlayer()) + { + Client* client = ((Player*)spawn)->GetClient(); + if (client) + ((Player*)spawn)->GetZone()->SendAllSpawnsForVisChange(client); + } + } + + return 0; +} + + +int EQ2Emu_lua_SetAccessToEntityCommand(lua_State* state) +{ + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + Spawn* spawn = lua_interface->GetSpawn(state, 2); + string command = lua_interface->GetStringValue(state, 3); + bool val = (lua_interface->GetInt8Value(state, 4) == 1); + + lua_interface->ResetFunctionStack(state); + if (spawn && player && player->IsPlayer()) + { + EntityCommand* cmd = spawn->FindEntityCommand(string(command), true); + bool res = false; + if (cmd) + res = spawn->SetPermissionToEntityCommand(cmd, (Player*)player, val); + + lua_interface->SetBooleanValue(state, res); + return 1; + } + + return 0; +} + + +int EQ2Emu_lua_SetAccessToEntityCommandByCharID(lua_State* state) +{ + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 charID = lua_interface->GetInt32Value(state, 2); + string command = lua_interface->GetStringValue(state, 3); + bool val = (lua_interface->GetInt8Value(state, 4) == 1); + + lua_interface->ResetFunctionStack(state); + if (spawn && charID) + { + EntityCommand* cmd = spawn->FindEntityCommand(string(command), true); + bool res = false; + if (cmd) + res = spawn->SetPermissionToEntityCommandByCharID(cmd, charID, val); + + lua_interface->SetBooleanValue(state, res); + return 1; + } + + return 0; +} + +int EQ2Emu_lua_RemovePrimaryEntityCommand(lua_State* state) +{ + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string command = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if (spawn && command.length() > 0) + spawn->RemovePrimaryEntityCommand(command.c_str()); + + return 0; +} + + +int EQ2Emu_lua_SendUpdateDefaultCommand(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + float distance = lua_interface->GetFloatValue(state, 2); + string command = lua_interface->GetStringValue(state, 3); + Spawn* player = lua_interface->GetSpawn(state, 4); + + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->GetZone()->SendUpdateDefaultCommand(spawn, command.c_str(), distance, player); + } + return 0; +} + +int EQ2Emu_lua_SendTransporters(lua_State* state) { + if (!lua_interface) + return 0; + + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + Spawn* spawn = lua_interface->GetSpawn(state); + Spawn* player = lua_interface->GetSpawn(state, 2); + int32 transport_id = lua_interface->GetInt32Value(state, 3); + + lua_interface->ResetFunctionStack(state); + if (spawn && player && transport_id && player->IsPlayer()) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + + if (!client) + return 0; + + vector destinations; + player->GetZone()->GetTransporters(&destinations, client, transport_id); + + if (destinations.size()) + { + client->SetTemporaryTransportID(transport_id); + client->ProcessTeleport(spawn, &destinations, transport_id, (spell != nullptr) ? true : false); + } + else + client->Message(CHANNEL_COLOR_RED, "There are no transporters available (ID: %u)", transport_id); + } + return 0; +} + +int EQ2Emu_lua_SetTemporaryTransportID(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + int32 transport_id = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + + if (!client) + return 0; + + client->SetTemporaryTransportID(transport_id); + } + return 0; +} + +int EQ2Emu_lua_GetTemporaryTransportID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + + lua_interface->ResetFunctionStack(state); + if (player && player->IsPlayer()) { + Client* client = 0; + if (player && player->IsPlayer()) + client = ((Player*)player)->GetClient(); + + if (!client) + return 0; + + lua_interface->SetInt32Value(state, client->GetTemporaryTransportID()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetAlignment(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + sint32 alignment = lua_interface->GetSInt32Value(state, 2); + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA SetAlignment command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA SetAlignment command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + if (alignment < SCHAR_MIN || alignment > SCHAR_MAX) + { + lua_interface->LogError("%s: LUA SetAlignment command error: alignment value beyond supported min: %i and max: %i", lua_interface->GetScriptName(state), SCHAR_MIN, SCHAR_MAX); + return 0; + } + + lua_interface->ResetFunctionStack(state); + + if (spell && spell->targets.size() > 0) { + ZoneServer* zone = spell->caster->GetZone(); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = zone->GetSpawnByID(spell->targets.at(i)); + if (target && target->IsEntity()) { + ((Entity*)target)->GetInfoStruct()->set_alignment((sint8)alignment); + if (target->IsPlayer()) + ((Player*)target)->SetCharSheetChanged(true); + } + } + } + else { + ((Entity*)spawn)->GetInfoStruct()->set_alignment((sint8)alignment); + if (spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_GetAlignment(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + + lua_interface->ResetFunctionStack(state); + + if (!spawn) { + lua_interface->LogError("%s: LUA GetAlignment command error: spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (!spawn->IsEntity()) { + lua_interface->LogError("%s: LUA GetAlignment command error: spawn is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + + lua_interface->SetSInt32Value(state, ((Entity*)spawn)->GetAlignment()); + return 1; +} + + +int EQ2Emu_lua_GetSpell(lua_State* state) { + if (!lua_interface) + return 0; + int32 spell_id = lua_interface->GetInt32Value(state); + int8 spell_tier = lua_interface->GetInt8Value(state, 2); + string custom_lua_script = lua_interface->GetStringValue(state, 3); + if (spell_id > 0) { + + if (spell_tier == 0) + spell_tier = 1; + + Spell* spell = master_spell_list.GetSpell(spell_id, spell_tier); + + if(!spell) { + lua_interface->LogError("%s: GetSpell: Failed, spell id %u spell tier %u does not exist.", lua_interface->GetScriptName(state), spell_id, spell_tier); + lua_interface->ResetFunctionStack(state); + return 0; + } + + LuaSpell* lua_spell = 0; + if(custom_lua_script.size() > 0) + { + // attempt to load the custom script since it isn't already loaded + // we will re-obtain the lua_spell further below + if((lua_spell = lua_interface->GetSpell(custom_lua_script.c_str())) == nullptr) + { + LogWrite(LUA__WARNING, 0, "LUA", "GetSpell(%u, %u, '%s'), custom lua script not loaded, attempting to load.", spell_id, spell_tier, custom_lua_script.c_str()); + lua_interface->LoadLuaSpell(custom_lua_script); + } + } + else + custom_lua_script = spell->GetSpellData()->lua_script; + + + if (!lua_spell && lua_interface) + lua_spell = lua_interface->GetSpell(custom_lua_script.c_str()); + + lua_interface->ResetFunctionStack(state); + + if (!lua_spell) + { + LogWrite(LUA__ERROR, 0, "LUA", "GetSpell(%u, %u, '%s') spell could not be loaded.", spell_id, spell_tier, custom_lua_script.c_str()); + return 0; + } + + lua_spell->spell = new Spell(spell); + + lua_interface->AddCustomSpell(lua_spell); + + lua_interface->SetSpellValue(state, lua_spell); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpellData(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + string field = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in GetSpellData!", lua_interface->GetScriptName(state)); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in GetSpellData!", lua_interface->GetScriptName(state)); + return 0; + } + + boost::to_lower(field); + + + return spell->spell->GetSpellData(state, field); +} + + +int EQ2Emu_lua_SetSpellData(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + string field = lua_interface->GetStringValue(state, 2); + int8 fieldArg = 3; // field value after the initial set + + if (!spell) { + lua_interface->LogError("%s: Spell not given in SetSpellData!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in SetSpellData!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + boost::to_lower(field); + + bool valSet = false; + + spell->spell->SetSpellData(state, field, fieldArg); + lua_interface->ResetFunctionStack(state); + + return valSet; +} + +int EQ2Emu_lua_SetSpellDataIndex(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + int8 idx = lua_interface->GetInt32Value(state, 2); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in SetSpellDataIndex!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in SetSpellDataIndex!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (spell->spell->lua_data.size() <= idx) + { + lua_interface->LogError("%s: lua_data size %i <= %i (idx passed) SetSpellDataIndex!", lua_interface->GetScriptName(state), spell->spell->lua_data.size(), idx); + lua_interface->ResetFunctionStack(state); + return 0; + } + + bool setVal = true; + + LUAData* data = spell->spell->lua_data[idx]; + + switch (data->type) + { + case 0: + { + sint32 value = lua_interface->GetSInt32Value(state, 3); + sint32 value2 = lua_interface->GetSInt32Value(state, 4); + data->int_value = value; + data->int_value2 = value2; + break; + } + case 1: + { + float value = lua_interface->GetFloatValue(state, 3); + float value2 = lua_interface->GetFloatValue(state, 4); + data->float_value = value; + data->float_value2 = value2; + break; + } + case 2: + { + bool value = lua_interface->GetBooleanValue(state, 3); + data->bool_value = value; + break; + } + case 3: + { + string value = lua_interface->GetStringValue(state, 3); + string value2 = lua_interface->GetStringValue(state, 4); + data->string_value = value; + data->string_value2 = value2; + break; + } + default: + setVal = false; + } + + lua_interface->ResetFunctionStack(state); + + return setVal; +} + + +int EQ2Emu_lua_GetSpellDataIndex(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + int8 idx = lua_interface->GetInt32Value(state, 2); + bool secondfield = lua_interface->GetBooleanValue(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in GetSpellDataIndex!", lua_interface->GetScriptName(state)); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in GetSpellDataIndex!", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell->spell->lua_data.size() <= idx) + { + lua_interface->LogError("%s: lua_data size %i <= %i (idx passed) GetSpellDataIndex!", lua_interface->GetScriptName(state), spell->spell->lua_data.size(), idx); + return 0; + } + + bool setVal = true; + + LUAData* data = spell->spell->lua_data[idx]; + + switch (data->type) + { + case 0: + { + if(!secondfield) + lua_interface->SetSInt32Value(state, data->int_value); + else + lua_interface->SetSInt32Value(state, data->int_value2); + break; + } + case 1: + { + if (!secondfield) + lua_interface->SetFloatValue(state, data->float_value); + else + lua_interface->SetFloatValue(state, data->float_value2); + break; + } + case 2: + { + lua_interface->SetBooleanValue(state, data->bool_value); + break; + } + case 3: + { + if (!secondfield) + lua_interface->SetStringValue(state, data->string_value.c_str()); + else + lua_interface->SetStringValue(state, data->string_value2.c_str()); + break; + } + default: + setVal = false; + } + + return setVal; +} + + +int EQ2Emu_lua_SetSpellDisplayEffect(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + int8 idx = lua_interface->GetInt32Value(state, 2); + string field = lua_interface->GetStringValue(state, 3); + + boost::to_lower(field); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in SetSpellDisplayEffect!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in SetSpellDisplayEffect!", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + if (spell->spell->effects.size() <= idx) + { + lua_interface->LogError("%s: lua_data size %i <= %i (idx passed) SetSpellDisplayEffect!", lua_interface->GetScriptName(state), spell->spell->lua_data.size(), idx); + lua_interface->ResetFunctionStack(state); + return 0; + } + + // do we need to lock? eh probably not this should only be used before use of the custom spell + SpellDisplayEffect* effect = spell->spell->effects[idx]; + + if (field == "description") + effect->description = string(lua_interface->GetStringValue(state, 4)); + else if (field == "bullet") + effect->subbullet = lua_interface->GetInt8Value(state, 4); + else if (field == "percentage") + effect->percentage = lua_interface->GetInt8Value(state, 4); + else { // no match + lua_interface->ResetFunctionStack(state); + return 0; + } + + lua_interface->ResetFunctionStack(state); + + return 1; +} + +int EQ2Emu_lua_GetSpellDisplayEffect(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + int8 idx = lua_interface->GetInt32Value(state, 2); + string field = lua_interface->GetStringValue(state, 3); + + lua_interface->ResetFunctionStack(state); + + boost::to_lower(field); + + if (!spell) { + lua_interface->LogError("%s: Spell not given in GetSpellDisplayEffect!", lua_interface->GetScriptName(state)); + return 0; + } + if (!spell->spell || !spell->spell->GetSpellData()) { + lua_interface->LogError("%s: Inner Spell or SpellData not given in GetSpellDisplayEffect!", lua_interface->GetScriptName(state)); + return 0; + } + + if (spell->spell->effects.size() <= idx) + { + lua_interface->LogError("%s: lua_data size %i <= %i (idx passed) GetSpellDisplayEffect!", lua_interface->GetScriptName(state), spell->spell->lua_data.size(), idx); + return 0; + } + + // do we need to lock? eh probably not this should only be used before use of the custom spell + SpellDisplayEffect* effect = spell->spell->effects[idx]; + + if (field == "description") + lua_interface->SetStringValue(state, effect->description.c_str()); + else if (field == "bullet") + lua_interface->SetInt32Value(state, effect->subbullet); + else if (field == "percentage") + lua_interface->SetInt32Value(state, effect->percentage); + else // no match + return 0; + + + return 1; +} +int EQ2Emu_lua_CastCustomSpell(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetSpell(state); + Spawn* caster = lua_interface->GetSpawn(state, 2); + Spawn* target = lua_interface->GetSpawn(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!target) { + lua_interface->LogError("%s: LUA CastCustomSpell command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + return 0; + } + + if (!target->IsEntity()) { + lua_interface->LogError("%s: LUA CastCustomSpell command error: target (%s) is not an entity", lua_interface->GetScriptName(state), target->GetName()); + return 0; + } + + if (!spell) { + lua_interface->LogError("%s: LUA CastCustomSpell command error: spell is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + if (caster && !caster->IsEntity()) { + lua_interface->LogError("%s: LUA CastSpell command error: caster (%s) is not an entity", lua_interface->GetScriptName(state), caster->GetName()); + return 0; + } + + target->GetZone()->ProcessSpell(NULL, (Entity*)caster, (Entity*)target, true, false, spell, 0); + return 0; +} + +int EQ2Emu_lua_InWater(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->InWater()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_InLava(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->InLava()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_DamageSpawn(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* attacker = lua_interface->GetSpawn(state); + Spawn* victim = lua_interface->GetSpawn(state, 2); + int8 type = lua_interface->GetInt8Value(state, 3); + int8 dmg_type = lua_interface->GetInt8Value(state, 4); + int32 low_damage = lua_interface->GetInt32Value(state, 5); + int32 high_damage = lua_interface->GetInt32Value(state, 6); + string spell_name = lua_interface->GetStringValue(state, 7); + int8 crit_mod = lua_interface->GetInt8Value(state, 8); + bool is_tick = (lua_interface->GetInt8Value(state, 9) == 1); + bool no_calcs = (lua_interface->GetInt8Value(state, 10) == 1); + bool ignore_attacker = (lua_interface->GetInt8Value(state, 11) == 1); + bool take_power = (lua_interface->GetInt8Value(state, 12) == 1); + + lua_interface->ResetFunctionStack(state); + if (!attacker) { + lua_interface->LogError("%s: LUA ProcDamage command error: caster is not a valid spawn", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + + } + + if (!attacker->IsEntity()) { + lua_interface->LogError("%s: LUA ProcDamage command error: caster is not an entity", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + + } + + if (!victim) { + lua_interface->LogError("%s: LUA ProcDamage command error: target is not a valid spawn", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + + } + + if (!victim->IsEntity()) { + lua_interface->LogError("%s: LUA ProcDamage command error: target is not an entity", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + bool has_damaged = ((Entity*)attacker)->DamageSpawn((Entity*)victim, type, dmg_type, low_damage, high_damage, spell_name.c_str(), crit_mod, is_tick, no_calcs, ignore_attacker, take_power); + lua_interface->SetBooleanValue(state, has_damaged); + return 1; +} + +int EQ2Emu_lua_IsInvulnerable(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->GetInvulnerable()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetInvulnerable(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool invul = lua_interface->GetBooleanValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + spawn->SetInvulnerable(invul); + } + return 0; +} + +int EQ2Emu_lua_GetRuleFlagBool(lua_State* state) { + if (!lua_interface) + return 0; + string category = lua_interface->GetStringValue(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + Rule *ret = 0; + if ((ret = rule_manager.GetGlobalRule(category.c_str(), name.c_str()))) { + + lua_interface->SetBooleanValue(state, ret->GetBool()); + return 1; + } + + lua_interface->LogError("%s: LUA GetRuleFlagBool Unknown rule with category '%s' and type '%s'", lua_interface->GetScriptName(state), category.c_str(), name.c_str()); + return 0; +} + +int EQ2Emu_lua_GetRuleFlagInt32(lua_State* state) { + if (!lua_interface) + return 0; + string category = lua_interface->GetStringValue(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + Rule *ret = 0; + if ((ret = rule_manager.GetGlobalRule(category.c_str(), name.c_str()))) { + + lua_interface->SetInt32Value(state, ret->GetInt32()); + return 1; + } + + lua_interface->LogError("%s: LUA GetRuleFlagInt32 Unknown rule with category '%s' and type '%s'", lua_interface->GetScriptName(state), category.c_str(), name.c_str()); + return 0; +} + +int EQ2Emu_lua_GetRuleFlagFloat(lua_State* state) { + if (!lua_interface) + return 0; + string category = lua_interface->GetStringValue(state); + string name = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + Rule *ret = 0; + if ((ret = rule_manager.GetGlobalRule(category.c_str(), name.c_str()))) { + + lua_interface->SetFloatValue(state, ret->GetFloat()); + return 1; + } + + lua_interface->LogError("%s: LUA GetRuleFlagFloat Unknown rule with category '%s' and type '%s'", lua_interface->GetScriptName(state), category.c_str(), name.c_str()); + return 0; +} + + +int EQ2Emu_lua_GetAAInfo(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + string type = lua_interface->GetStringValue(state, 2); + lua_interface->ResetFunctionStack(state); + if (spawn) { + int res = 1; + + boost::to_lower(type); + if(type == "assigned_aa") + lua_interface->SetSInt32Value(state, spawn->GetAssignedAA()); + else if ( type == "unassigned_aa") + lua_interface->SetSInt32Value(state, spawn->GetUnassignedAA()); + else if ( type == "assigned_tradeskill_aa") + lua_interface->SetSInt32Value(state, spawn->GetTradeskillAA()); + else if ( type == "unassigned_tradeskill_aa") + lua_interface->SetSInt32Value(state, spawn->GetUnassignedTradeskillAA()); + else if ( type == "assigned_prestige_aa") + lua_interface->SetSInt32Value(state, spawn->GetPrestigeAA()); + else if ( type == "unassigned_prestige_aa") + lua_interface->SetSInt32Value(state, spawn->GetUnassignedPretigeAA()); + else if ( type == "assigned_tradeskill_prestige_aa") + lua_interface->SetSInt32Value(state, spawn->GetTradeskillPrestigeAA()); + else if ( type == "unassigned_tradeskill_prestige_aa") + lua_interface->SetSInt32Value(state, spawn->GetUnassignedTradeskillPrestigeAA()); + else + res = 0; + + return res; + } + + lua_interface->LogError("%s: LUA GetAAInfo spawn does not exist", lua_interface->GetScriptName(state)); + return 0; +} + +int EQ2Emu_lua_SetAAInfo(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string type = lua_interface->GetStringValue(state, 2); + sint32 value = lua_interface->GetSInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + if (spawn) { + boost::to_lower(type); + if(type == "assigned_aa") + spawn->SetAssignedAA((sint16)value); + else if ( type == "unassigned_aa") + spawn->SetUnassignedAA((sint16)value); + else if ( type == "assigned_tradeskill_aa") + spawn->SetTradeskillAA((sint16)value); + else if ( type == "unassigned_tradeskill_aa") + spawn->SetUnassignedTradeskillAA((sint16)value); + else if ( type == "assigned_prestige_aa") + spawn->SetPrestigeAA((sint16)value); + else if ( type == "unassigned_prestige_aa") + spawn->SetUnassignedPrestigeAA((sint16)value); + else if ( type == "assigned_tradeskill_prestige_aa") + spawn->SetTradeskillPrestigeAA((sint16)value); + else if ( type == "unassigned_tradeskill_prestige_aa") + spawn->SetUnassignedTradeskillPrestigeAA((sint16)value); + + if(spawn->IsPlayer()) + ((Player*)spawn)->SetCharSheetChanged(true); + } + return 0; +} + +int EQ2Emu_lua_AddMasterTitle(lua_State* state) { + if (!lua_interface) + return 0; + + string titleName = lua_interface->GetStringValue(state); + int8 isPrefix = lua_interface->GetInt8Value(state, 2); + + lua_interface->ResetFunctionStack(state); + sint32 index = database.AddMasterTitle(titleName.c_str(), isPrefix); + lua_interface->SetSInt32Value(state, index); + + return 1; +} + +int EQ2Emu_lua_AddCharacterTitle(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string titleName = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA AddCharacterTitle command error: player is not valid", lua_interface->GetScriptName(state)); + lua_interface->SetSInt32Value(state, -1); + return 1; + } + + Player* player = (Player*)spawn; + // check if player already has the title, don't need to add twice + Title* playerHasTitle = player->GetPlayerTitles()->GetTitleByName(titleName.c_str()); + + if ( playerHasTitle) + { + lua_interface->SetSInt32Value(state, playerHasTitle->GetID()); + return 1; + } + + Title* title = master_titles_list.GetTitleByName(titleName.c_str()); + + if(!title) + { + lua_interface->LogError("%s: LUA AddCharacterTitle command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str()); + lua_interface->SetSInt32Value(state, -1); + return 1; + } + + + sint32 returnIdx = database.AddCharacterTitle(title->GetID(), player->GetCharacterID(), player); + + if(returnIdx < 0) + { + lua_interface->LogError("%s: LUA AddCharacterTitle command error: got invalid index (-1) returned for database.AddCharacterTitle '%s'", lua_interface->GetScriptName(state), titleName.c_str()); + } + + lua_interface->SetSInt32Value(state, returnIdx); + + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_SetCharacterTitleSuffix(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string titleName = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Player* player = (Player*)spawn; + + Title* title = player->GetPlayerTitles()->GetTitleByName(titleName.c_str()); + + if(!title) + { + lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str()); + return 0; + } + + if(title->GetPrefix()) + { + lua_interface->LogError("%s: LUA SetCharacterTitleSuffix command error: title with name '%s' is not valid as a suffix, only prefix", lua_interface->GetScriptName(state), titleName.c_str()); + return 0; + } + + database.SaveCharSuffixIndex(title->GetID(), player->GetCharacterID()); + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_SetCharacterTitlePrefix(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string titleName = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Player* player = (Player*)spawn; + + Title* title = player->GetPlayerTitles()->GetTitleByName(titleName.c_str()); + + if(!title) + { + lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: title is not valid with name '%s'", lua_interface->GetScriptName(state), titleName.c_str()); + return 0; + } + + if(!title->GetPrefix()) + { + lua_interface->LogError("%s: LUA SetCharacterTitlePrefix command error: title with name '%s' is not valid as a prefix, only suffix", lua_interface->GetScriptName(state), titleName.c_str()); + return 0; + } + + database.SaveCharPrefixIndex(title->GetID(), player->GetCharacterID()); + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_ResetCharacterTitleSuffix(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA ResetCharacterTitleSuffix command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Player* player = (Player*)spawn; + + + database.SaveCharSuffixIndex(-1, player->GetCharacterID()); + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_ResetCharacterTitlePrefix(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + + lua_interface->ResetFunctionStack(state); + if(!spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA ResetCharacterTitlePrefix command error: player is not valid", lua_interface->GetScriptName(state)); + return 0; + } + + Player* player = (Player*)spawn; + + + database.SaveCharPrefixIndex(-1, player->GetCharacterID()); + player->GetClient()->SendTitleUpdate(); + + return 1; +} + +int EQ2Emu_lua_GetInfoStructString(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA GetInfoStructString command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + lua_interface->SetStringValue(state, ent->GetInfoStructString(field).c_str()); + + return 1; +} + +int EQ2Emu_lua_GetInfoStructUInt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA GetInfoStructUInt command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + lua_interface->SetInt64Value(state, ent->GetInfoStructUInt(field)); + + return 1; +} + +int EQ2Emu_lua_GetInfoStructSInt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA GetInfoStructSInt command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + lua_interface->SetSInt64Value(state, ent->GetInfoStructSInt(field)); + + return 1; +} + +int EQ2Emu_lua_GetInfoStructFloat(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA GetInfoStructFloat command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + lua_interface->SetFloatValue(state, ent->GetInfoStructFloat(field)); + + return 1; +} + +int EQ2Emu_lua_SetInfoStructString(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + string value = lua_interface->GetStringValue(state, 3); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA SetInfoStructString command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + bool set_ = ent->SetInfoStructString(field, value); + lua_interface->SetBooleanValue(state, set_); + return 1; +} + +int EQ2Emu_lua_SetInfoStructUInt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + int64 value = lua_interface->GetInt64Value(state, 3); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA SetInfoStructUInt command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + bool set_ = ent->SetInfoStructUInt(field, value); + lua_interface->SetBooleanValue(state, set_); + return 1; +} + +int EQ2Emu_lua_SetInfoStructSInt(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + sint64 value = lua_interface->GetSInt64Value(state, 3); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA SetInfoStructSInt command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + bool set_ = ent->SetInfoStructSInt(field, value); + lua_interface->SetBooleanValue(state, set_); + return 1; +} + +int EQ2Emu_lua_SetInfoStructFloat(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + string field = lua_interface->GetStringValue(state, 2); + float value = lua_interface->GetFloatValue(state, 3); + + lua_interface->ResetFunctionStack(state); + if(!spawn || !spawn->IsEntity()) + { + lua_interface->LogError("%s: LUA SetInfoStructFloat command error: spawn is not valid, either does not exist or is not an entity", lua_interface->GetScriptName(state)); + return 0; + } + Entity* ent = (Entity*)spawn; + bool set_ = ent->SetInfoStructFloat(field, value); + lua_interface->SetBooleanValue(state, set_); + return 1; +} + +int EQ2Emu_lua_SetCharSheetChanged(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + bool value = lua_interface->GetBooleanValue(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!spawn || !spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA SetCharSheetChanged command error: spawn is not valid, either does not exist or is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ((Player*)spawn)->SetCharSheetChanged(value); + + return 0; +} + +int EQ2Emu_lua_AddPlayerMail(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + std::string fromName = lua_interface->GetStringValue(state, 2); + std::string subjectName = lua_interface->GetStringValue(state, 3); + std::string mailBody = lua_interface->GetStringValue(state, 4); + int8 mailType = lua_interface->GetInt8Value(state, 5); + + int32 copper = lua_interface->GetInt32Value(state, 6); + int32 silver = lua_interface->GetInt32Value(state, 7); + int32 gold = lua_interface->GetInt32Value(state, 8); + int32 platinum = lua_interface->GetInt32Value(state, 9); + + int32 item_id = lua_interface->GetInt32Value(state, 10); + + int16 stack_size = lua_interface->GetInt32Value(state, 11); + + int32 expire_time = lua_interface->GetInt32Value(state, 12); + + int32 sent_time = lua_interface->GetInt32Value(state, 13); + + lua_interface->ResetFunctionStack(state); + + if(!spawn || !spawn->IsPlayer()) + { + lua_interface->LogError("%s: LUA AddPlayerMail command error: spawn is not valid, either does not exist or is not a player", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + int32 time_sent = sent_time > 0 ? sent_time : Timer::GetUnixTimeStamp(); + + ((Player*)spawn)->GetClient()->CreateAndUpdateMail(fromName, subjectName, mailBody, mailType, copper, silver, gold, platinum, item_id, stack_size, time_sent, expire_time); + lua_interface->SetBooleanValue(state, true); + return 1; +} + + +int EQ2Emu_lua_AddPlayerMailByCharID(lua_State* state) { + if (!lua_interface) + return 0; + int32 char_id = lua_interface->GetInt32Value(state); + std::string fromName = lua_interface->GetStringValue(state, 2); + std::string subjectName = lua_interface->GetStringValue(state, 3); + std::string mailBody = lua_interface->GetStringValue(state, 4); + int8 mailType = lua_interface->GetInt8Value(state, 5); + + int32 copper = lua_interface->GetInt32Value(state, 6); + int32 silver = lua_interface->GetInt32Value(state, 7); + int32 gold = lua_interface->GetInt32Value(state, 8); + int32 platinum = lua_interface->GetInt32Value(state, 9); + + int32 item_id = lua_interface->GetInt32Value(state, 10); + + int16 stack_size = lua_interface->GetInt32Value(state, 11); + + int32 expire_time = lua_interface->GetInt32Value(state, 12); + + int32 sent_time = lua_interface->GetInt32Value(state, 13); + + lua_interface->ResetFunctionStack(state); + + int32 time_sent = sent_time > 0 ? sent_time : Timer::GetUnixTimeStamp(); + + Client::CreateMail(char_id, fromName, subjectName, mailBody, mailType, copper, silver, gold, platinum, item_id, stack_size, time_sent, expire_time); + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_OpenDoor(lua_State* state) { + Spawn* widget; + + if (lua_interface) { + widget = lua_interface->GetSpawn(state); + bool disable_open_sound = lua_interface->GetBooleanValue(state, 2); + + lua_interface->ResetFunctionStack(state); + + if (widget && widget->IsWidget()) + { + ((Widget*)widget)->OpenDoor(); + + if(!disable_open_sound && ((Widget*)widget)->IsOpen() && ((Widget*)widget)->GetOpenSound()) + widget->GetZone()->PlaySoundFile(0, ((Widget*)widget)->GetOpenSound(), ((Widget*)widget)->GetX(), ((Widget*)widget)->GetY(), ((Widget*)widget)->GetZ()); + } + else + lua_interface->LogError("%s: LUA OpenDoor command error: spawn is not valid, either does not exist or is not a widget", lua_interface->GetScriptName(state)); + } + + return 0; +} + +int EQ2Emu_lua_CloseDoor(lua_State* state) { + Spawn* widget; + + if (lua_interface) { + widget = lua_interface->GetSpawn(state); + bool disable_close_sound = lua_interface->GetBooleanValue(state, 2); + + lua_interface->ResetFunctionStack(state); + + if (widget && widget->IsWidget()) + { + ((Widget*)widget)->CloseDoor(); + + if(!disable_close_sound && !((Widget*)widget)->IsOpen() && ((Widget*)widget)->GetCloseSound()) + widget->GetZone()->PlaySoundFile(0, ((Widget*)widget)->GetCloseSound(), ((Widget*)widget)->GetX(), ((Widget*)widget)->GetY(), ((Widget*)widget)->GetZ()); + } + else + lua_interface->LogError("%s: LUA CloseDoor command error: spawn is not valid, either does not exist or is not a widget", lua_interface->GetScriptName(state)); + } + + return 0; +} + +int EQ2Emu_lua_IsOpen(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* widget = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (widget && widget->IsWidget()) + { + lua_interface->SetBooleanValue(state, ((Widget*)widget)->IsOpen()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_MakeRandomInt(lua_State* state) { + if (!lua_interface) + return 0; + + sint32 min = lua_interface->GetSInt32Value(state); + sint32 max = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + sint32 result = MakeRandomInt(min, max); + lua_interface->SetSInt32Value(state, result); + return 1; +} + +int EQ2Emu_lua_MakeRandomFloat(lua_State* state) { + if (!lua_interface) + return 0; + + float min = lua_interface->GetFloatValue(state); + float max = lua_interface->GetFloatValue(state, 2); + lua_interface->ResetFunctionStack(state); + + float result = MakeRandomFloat(min, max); + lua_interface->SetFloatValue(state, result); + return 1; +} + +int EQ2Emu_lua_AddIconValue(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 value = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + + if(!spawn) + { + lua_interface->LogError("%s: LUA AddIconValue command error: spawn is not valid, does not exist", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + spawn->AddIconValue(value); + lua_interface->SetBooleanValue(state, true); + + return 1; +} + +int EQ2Emu_lua_RemoveIconValue(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 value = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + + if(!spawn) + { + lua_interface->LogError("%s: LUA RemoveIconValue command error: spawn is not valid, does not exist", lua_interface->GetScriptName(state)); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + spawn->RemoveIconValue(value); + lua_interface->SetBooleanValue(state, true); + + return 1; +} + +int EQ2Emu_lua_GetShardID(lua_State* state) { + Spawn* npc = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (npc && npc->IsNPC()) { + NPC* shard = (NPC*)npc; + int32 shardid = shard->GetShardID(); + lua_interface->SetInt32Value(state, shardid); + return 1; + } + lua_interface->SetInt32Value(state, 0); + return 1; +} + +int EQ2Emu_lua_GetShardCharID(lua_State* state) { + Spawn* npc = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (npc && npc->IsNPC()) { + NPC* shard = (NPC*)npc; + int32 charid = shard->GetShardCharID(); + lua_interface->SetInt32Value(state, charid); + return 1; + } + lua_interface->SetInt32Value(state, 0); + return 1; +} + +int EQ2Emu_lua_GetShardCreatedTimestamp(lua_State* state) { + Spawn* npc = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (npc && npc->IsNPC()) { + NPC* shard = (NPC*)npc; + int64 timestamp = shard->GetShardCreatedTimestamp(); + lua_interface->SetSInt64Value(state, timestamp); + return 1; + } + lua_interface->SetSInt64Value(state, 0); + return 1; +} + +int EQ2Emu_lua_DeleteDBShardID(lua_State* state) { + if (!lua_interface) + return 0; + int32 shardid = lua_interface->GetInt32Value(state); + + lua_interface->ResetFunctionStack(state); + + if(shardid < 1) + lua_interface->SetBooleanValue(state, false); + else + lua_interface->SetBooleanValue(state, database.DeleteSpiritShard(shardid)); + return 1; +} + +int EQ2Emu_lua_PauseMovement(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 delay_in_ms = lua_interface->GetInt32Value(state, 2); + if (spawn) { + spawn->PauseMovement(delay_in_ms); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_StopMovement(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + if (spawn) { + spawn->ResetMovement(); + } + lua_interface->ResetFunctionStack(state); + return 0; +} + +int EQ2Emu_lua_GetArrowColor(lua_State* state) { + Player* player = (Player*)lua_interface->GetSpawn(state); + int8 level = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (player && player->IsPlayer() && level > 0) { + lua_interface->SetInt32Value(state, player->GetArrowColor(level)); + return 1; + } + return 0; +} +int EQ2Emu_lua_GetTSArrowColor(lua_State* state) { + Player* player = (Player*)lua_interface->GetSpawn(state); + int8 level = lua_interface->GetInt8Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (player && player->IsPlayer() && level > 0) { + lua_interface->SetInt32Value(state, player->GetTSArrowColor(level)); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetSpawnByRailID(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + sint64 rail_id = lua_interface->GetSInt64Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (zone) { + Spawn* spawn = zone->GetTransportByRailID(rail_id); + if (spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + } + return 0; +} + +int EQ2Emu_lua_SetRailID(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + sint64 rail_id = lua_interface->GetSInt64Value(state, 2); + + lua_interface->ResetFunctionStack(state); + + bool res = false; + if(spawn && spawn->IsTransportSpawn()) + { + //printf("Set rail id %i for %s\n",rail_id,spawn->GetName()); + spawn->SetRailID(rail_id); + res = true; + } + else if (!spawn) { + lua_interface->LogError("%s: LUA SetRailID command error: spawn is not valid, does not exist", lua_interface->GetScriptName(state)); + } + else if(!spawn->IsTransportSpawn()) { + lua_interface->LogError("%s: LUA SetRailID command error: spawn %s is not a transport spawn, call AddTransportSpawn(NPC) first", lua_interface->GetScriptName(state), spawn->GetName()); + } + lua_interface->SetBooleanValue(state, res); + return 1; +} + +int EQ2Emu_lua_IsZoneLoading(lua_State* state) { + if (!lua_interface) + return 0; + + ZoneServer* zone = lua_interface->GetZone(state); + lua_interface->ResetFunctionStack(state); + + if (zone) { + lua_interface->SetBooleanValue(state, zone->IsLoading()); + return 1; + } + return 0; +} +int EQ2Emu_lua_IsRunning(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + lua_interface->SetBooleanValue(state, spawn->IsRunning()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_GetZoneLockoutTimer(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* player = lua_interface->GetSpawn(state); + int32 zoneID = lua_interface->GetInt32Value(state, 2); + bool displayClient = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if (!player || !player->IsPlayer() || !player->GetClient()) { + lua_interface->LogError("%s: LUA GetZoneLockoutTimer command error: player is not valid, does not exist", lua_interface->GetScriptName(state)); + } + else if(!zoneID) { + lua_interface->LogError("%s: LUA GetZoneLockoutTimer command error: zoneID is not set."); + } + else + { + lua_interface->SetStringValue(state, player->GetClient()->IdentifyInstanceLockout(zoneID, displayClient).c_str()); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetWorldTime(lua_State* state) { + if (!lua_interface) + return 0; + + int16 newYear = lua_interface->GetInt16Value(state, 1); + sint32 newMonth = lua_interface->GetInt16Value(state, 2); + int16 newHour = lua_interface->GetInt16Value(state, 3); + int16 newMinute = lua_interface->GetInt16Value(state, 4); + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.writelock(__FUNCTION__, __LINE__); + world.GetWorldTimeStruct()->year = newYear; + world.GetWorldTimeStruct()->month = newMonth; + world.GetWorldTimeStruct()->hour = newHour; + world.GetWorldTimeStruct()->minute = newMinute; + world.MWorldTime.releasewritelock(__FUNCTION__, __LINE__); + + return 0; +} + +int EQ2Emu_lua_GetWorldTimeYear(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + lua_interface->SetInt32Value(state, world.GetWorldTimeStruct()->year); + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} + +int EQ2Emu_lua_GetWorldTimeMonth(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + lua_interface->SetSInt32Value(state, world.GetWorldTimeStruct()->month); + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} + +int EQ2Emu_lua_GetWorldTimeHour(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + lua_interface->SetSInt32Value(state, world.GetWorldTimeStruct()->hour); + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} + +int EQ2Emu_lua_GetWorldTimeMinute(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + lua_interface->SetSInt32Value(state, world.GetWorldTimeStruct()->minute); + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + return 1; +} + +int EQ2Emu_lua_SendTimeUpdate(lua_State* state) { + if (!lua_interface) + return 0; + + lua_interface->ResetFunctionStack(state); + + world.SendTimeUpdate(); + + return 0; +} + +int EQ2Emu_lua_ChangeFaction(lua_State* state) { + bool update_result = false; + Faction* faction = 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int32 faction_id = lua_interface->GetInt32Value(state, 2); + sint32 increase = lua_interface->GetInt32Value(state, 3); + lua_interface->ResetFunctionStack(state); + + if(!spawn || !spawn->IsPlayer()) { + lua_interface->LogError("LUA ChangeFaction command error: player is not valid"); + return 0; + } + + Player* player = (Player*)spawn; + Client* client = player->GetClient(); + if (faction_id > 0) { + + bool hasfaction = database.VerifyFactionID(player->GetCharacterID(),faction_id); + if(hasfaction == 0) { + //they do not have the faction. Lets get the default value and feed it in. + sint32 defaultfaction = master_faction_list.GetDefaultFactionValue(faction_id); + //add the default faction for the player. + player->SetFactionValue(faction_id, defaultfaction); + } + + if(increase >= 0) { + update_result = player->GetFactions()->IncreaseFaction(faction_id,increase); + faction = master_faction_list.GetFaction(faction_id); + + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got better.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any better.", faction->name.c_str()); + + lua_interface->SetBooleanValue(state, true); + return 1; + + } + + if(increase < 0){ + //change the negative to a positive, since thats how decreasefaction() likes it. + increase *= -1; + + update_result = player->GetFactions()->DecreaseFaction(faction_id,increase); + faction = master_faction_list.GetFaction(faction_id); + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got worse.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any worse.", faction->name.c_str()); + + lua_interface->SetBooleanValue(state, true); + return 1; + } + } + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_HasCoin(lua_State* state) { + bool hascoin = 0; + Player* player = (Player*)lua_interface->GetSpawn(state); + int32 coins = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + +if (player && player->IsPlayer()) { + hascoin = player->HasCoins(coins); + if(hascoin == 0) { + lua_interface->SetBooleanValue(state, false); + return 1; + } + if(hascoin == 1) { + lua_interface->SetBooleanValue(state, true); + return 1; + } + +} + return 0; +} + +int EQ2Emu_lua_GetLootTier(lua_State* state) { + int32 loot_tier = 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + loot_tier = spawn->GetLootTier(); + lua_interface->SetInt32Value(state, loot_tier); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetLootTier(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 loot_tier = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + spawn->SetLootTier(loot_tier); + lua_interface->SetBooleanValue(state, true); + return 1; + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + +int EQ2Emu_lua_GetLootDropType(lua_State* state) { + int32 loot_drop_type = 0; + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + loot_drop_type = spawn->GetLootDropType(); + lua_interface->SetInt32Value(state, loot_drop_type); + return 1; + } + return 0; +} + +int EQ2Emu_lua_SetLootDropType(lua_State* state) { + if (!lua_interface) + return 0; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 loot_drop_type = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (spawn) { + spawn->SetLootDropType(loot_drop_type); + lua_interface->SetBooleanValue(state, true); + return 1; + } + + lua_interface->SetBooleanValue(state, false); + return 1; +} + + +int EQ2Emu_lua_DamageEquippedItems(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* spawn = lua_interface->GetSpawn(state); + int8 damage_amount = lua_interface->GetInt32Value(state, 2); + if(damage_amount > 100) { + damage_amount = 100; + } + + if (!spawn) { + lua_interface->LogError("%s: LUA DamageEquippedItems command error: spawn is not valid", lua_interface->GetScriptName(state)); + lua_interface->ResetFunctionStack(state); + return 0; + } + + lua_interface->ResetFunctionStack(state); + if (spawn->IsPlayer()) { + if (((Player*)spawn)->GetClient() && ((Player*)spawn)->DamageEquippedItems(damage_amount, ((Player*)spawn)->GetClient())) + lua_interface->SetBooleanValue(state, true); + else + lua_interface->SetBooleanValue(state, false); + } + else { + lua_interface->SetBooleanValue(state, false); + } + + return 1; +} + +int EQ2Emu_lua_CreateWidgetRegion(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 version = lua_interface->GetInt32Value(state, 2); + + RegionMap* region_map = world.GetRegionMap(std::string(zone->GetZoneFile()), version); + if(region_map == nullptr) { + lua_interface->LogError("%s: LUA CreateWidgetRegion command error: region map is not valid for version %u", lua_interface->GetScriptName(state), version); + lua_interface->ResetFunctionStack(state); + return 0; + } + string region_name = lua_interface->GetStringValue(state, 3); + string env_name = lua_interface->GetStringValue(state, 4); + int32 grid_id = lua_interface->GetInt32Value(state, 5); + int32 widget_id = lua_interface->GetInt32Value(state, 6); + float dist = lua_interface->GetFloatValue(state, 7); + region_map->InsertRegionNode(zone, version, region_name, env_name, grid_id, widget_id, dist); + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_RemoveRegion(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 version = lua_interface->GetInt32Value(state, 2); + + RegionMap* region_map = world.GetRegionMap(std::string(zone->GetZoneFile()), version); + if(region_map == nullptr) { + lua_interface->LogError("%s: LUA RemoveRegion command error: region map is not valid for version %u", lua_interface->GetScriptName(state), version); + lua_interface->ResetFunctionStack(state); + return 0; + } + string region_name = lua_interface->GetStringValue(state, 3); + region_map->RemoveRegionNode(region_name); + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + + +int EQ2Emu_lua_SetPlayerPOVGhost(lua_State* state) { + Client* client = nullptr; + Spawn* player = lua_interface->GetSpawn(state); + if (!player) { + lua_interface->LogError("LUA SetPlayerPOVGhost command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + lua_interface->ResetFunctionStack(state); + return 1; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA SetPlayerPOVGhost command error: spawn is not a player"); + lua_interface->SetBooleanValue(state, false); + lua_interface->ResetFunctionStack(state); + return 1; + } + + client = player->GetClient(); + + if (!client) { + lua_interface->LogError("LUA SetPlayerPOVGhost command error: could not find client"); + lua_interface->SetBooleanValue(state, false); + lua_interface->ResetFunctionStack(state); + return 1; + } + + Spawn* spawn = lua_interface->GetSpawn(state, 2); + + bool success_sight = client->SetPlayerPOVGhost(spawn); + + lua_interface->ResetFunctionStack(state); + + lua_interface->SetBooleanValue(state, success_sight); + return 1; +} + + +int EQ2Emu_lua_SetCastOnAggroComplete(lua_State* state) { + if (!lua_interface) + return 0; + bool result = false; + + Spawn* spawn = lua_interface->GetSpawn(state); + bool cast_completed = (lua_interface->GetInt8Value(state, 2) == 1); + lua_interface->ResetFunctionStack(state); + + if (!spawn) + lua_interface->LogError("%s: LUA SetCastOnAggroComplete error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA SetCastOnAggroComplete error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + { + ((NPC*)spawn)->cast_on_aggro_completed = cast_completed; + result = true; + } + + lua_interface->SetBooleanValue(state, result); + + return 1; +} + +int EQ2Emu_lua_IsCastOnAggroComplete(lua_State* state) { + if (!lua_interface) + return 0; + bool result = false; + + Spawn* spawn = lua_interface->GetSpawn(state); + lua_interface->ResetFunctionStack(state); + + if (!spawn) + lua_interface->LogError("%s: LUA IsCastOnAggroComplete error: Could not find spawn.", lua_interface->GetScriptName(state)); + else if (!spawn->IsNPC()) + lua_interface->LogError("%s: LUA IsCastOnAggroComplete error: spawn %s is not an NPC!.", lua_interface->GetScriptName(state), spawn->GetName()); + else + { + if(((NPC*)spawn)->cast_on_aggro_completed) + result = true; + } + + lua_interface->SetBooleanValue(state, result); + + return 1; +} + +int EQ2Emu_lua_AddRecipeBookToPlayer(lua_State* state) { + Client* client = nullptr; + Spawn* player = lua_interface->GetSpawn(state); + int32 recipe_book_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA AddRecipeBookToPlayer command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA AddRecipeBookToPlayer command error: spawn is not a player"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + client = player->GetClient(); + + if (!client) { + lua_interface->LogError("LUA AddRecipeBookToPlayer command error: could not find client"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + bool result = client->AddRecipeBookToPlayer(recipe_book_id); + + lua_interface->SetBooleanValue(state, result); + return 1; +} + +int EQ2Emu_lua_RemoveRecipeFromPlayer(lua_State* state) { + Client* client = nullptr; + Spawn* player = lua_interface->GetSpawn(state); + int32 recipe_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA RemoveRecipeFromPlayer command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA RemoveRecipeFromPlayer command error: spawn is not a player"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + client = player->GetClient(); + + if (!client) { + lua_interface->LogError("LUA RemoveRecipeFromPlayer command error: could not find client"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + bool result = client->RemoveRecipeFromPlayer(recipe_id); + + lua_interface->SetBooleanValue(state, result); + return 1; +} + +int EQ2Emu_lua_ReplaceWidgetFromClient(lua_State* state) { + Client* client = nullptr; + Spawn* player = lua_interface->GetSpawn(state); + int32 widget_id = lua_interface->GetInt32Value(state, 2); + bool delete_widget = (lua_interface->GetInt8Value(state, 3) == 1); + + // rest are all optional fields + float x = lua_interface->GetFloatValue(state, 4); + float y = lua_interface->GetFloatValue(state, 5); + float z = lua_interface->GetFloatValue(state, 6); + int32 grid_id = lua_interface->GetInt32Value(state, 7); + + lua_interface->ResetFunctionStack(state); + if (!player) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if (!player->IsPlayer()) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: spawn is not a player"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if(!player->GetZone()) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: player is not in a zone"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + client = player->GetClient(); + + if (!client) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: could not find client"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + if(!client->IsReadyForUpdates()) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command failed: client has not signaled sys_client_avatar_ready"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + client->SendReplaceWidget(widget_id, delete_widget, x, y, z, grid_id); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_RemoveWidgetFromSpawnMap(lua_State* state) { + Client* client = nullptr; + Spawn* spawn = lua_interface->GetSpawn(state); + int32 widget_id = lua_interface->GetInt32Value(state, 2); + + lua_interface->ResetFunctionStack(state); + if (!spawn) { + lua_interface->LogError("LUA RemoveWidgetFromSpawnMap command error: spawn is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if(!spawn->GetZone()) { + lua_interface->LogError("LUA ReplaceWidgetFromClient command error: player is not in a zone"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + spawn->AddIgnoredWidget(widget_id); + + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state) { + ZoneServer* zone = lua_interface->GetZone(state); + int32 widget_id = lua_interface->GetInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if(!zone) { + lua_interface->LogError("LUA RemoveWidgetFromZoneMap command error: zone is not valid"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + if(!zone->IsLoading()) { + lua_interface->LogError("LUA RemoveWidgetFromZoneMap command error: can only be called during zone loading, in preinit_zone_script ZoneScript function"); + lua_interface->SetBooleanValue(state, false); + return 1; + } + + zone->AddIgnoredWidget(widget_id); + lua_interface->SetBooleanValue(state, true); + return 1; +} + +int EQ2Emu_lua_SendHearCast(lua_State* state) { + if (!lua_interface) + return 0; + LuaSpell* spell = lua_interface->GetCurrentSpell(state); + Spawn* spawn = lua_interface->GetSpawn(state); + int32 spell_visual_id = lua_interface->GetInt32Value(state, 2); + int16 cast_time = lua_interface->GetInt16Value(state, 3); + Spawn* caster = lua_interface->GetSpawn(state, 4); + Spawn* target = lua_interface->GetSpawn(state, 5); + lua_interface->ResetFunctionStack(state); + if(spell && spawn && spawn->IsEntity()) { + ZoneServer* zone = spawn->GetZone(); + if(zone) { + zone->SendCastSpellPacket(spell, caster && caster->IsEntity() ? (Entity*)caster : (Entity*)spawn, spell_visual_id, cast_time > 0 ? cast_time : 0xFFFF); + } + } + else if (spawn) { + if (spawn->IsPlayer()) { + Client* client = ((Player*)spawn)->GetClient(); + if (client) { + client->SendHearCast(caster ? caster : client->GetPlayer(), target ? target : client->GetPlayer(), spell_visual_id, cast_time); + } + } + } + return 0; +} + +int EQ2Emu_lua_GetCharacterFlag(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + sint32 flag_id = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + bool ret = ((Player*)player)->get_character_flag(flag_id); + lua_interface->SetBooleanValue(state, ret); + return 1; +} + +int EQ2Emu_lua_ToggleCharacterFlag(lua_State* state) { + if (!lua_interface) + return 0; + + Spawn* player = lua_interface->GetSpawn(state); + sint32 flag_id = lua_interface->GetSInt32Value(state, 2); + lua_interface->ResetFunctionStack(state); + + if (!player) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not valid", lua_interface->GetScriptName(state)); + return 0; + } + if (!player->IsPlayer()) { + lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not a player", lua_interface->GetScriptName(state)); + return 0; + } + + ((Player*)player)->toggle_character_flag(flag_id); + return 0; +} + +int EQ2Emu_lua_GetSpellInitialTarget(lua_State* state) { + LuaSpell* spell = lua_interface->GetSpell(state); + + if(!spell) { + spell = lua_interface->GetCurrentSpell(state); + } + + lua_interface->ResetFunctionStack(state); + if (spell) { + if(!spell->caster) { + lua_interface->LogError("%s: LUA GetSpellTarget command error, caster does not exist.", lua_interface->GetScriptName(state)); + return 0; + } + if(!spell->caster->GetZone()) { + lua_interface->LogError("%s: LUA GetSpellTarget command error, zone does not exist.", lua_interface->GetScriptName(state)); + return 0; + } + Spawn* spawn = spell->caster->GetZone()->GetSpawnByID(spell->initial_target); + if (spawn) { + lua_interface->SetSpawnValue(state, spawn); + return 1; + } + else { + lua_interface->LogError("%s: LUA GetSpellTarget command error, could not find initial target %u to map to spawn.", lua_interface->GetScriptName(state), spell->initial_target); + } + } + return 0; +} \ No newline at end of file diff --git a/source/WorldServer/LuaFunctions.h b/source/WorldServer/LuaFunctions.h new file mode 100644 index 0000000..b538954 --- /dev/null +++ b/source/WorldServer/LuaFunctions.h @@ -0,0 +1,660 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef LUA_FUNCTIONS_H +#define LUA_FUNCTIONS_H + +#include "../LUA/lua.hpp" +#include +#include +#include +using namespace std; + +vector ParseString(string strVal, char delim=','); +vector ParseStringToInt32(string strVal, char delim=','); +map ParseStringMap(string strVal, char delim=','); +map ParseIntMap(string strVal, char delim = ','); +map ParseSInt32Map(string strVal, char delim = ','); + + +//Sets +int EQ2Emu_lua_SetCurrentHP(lua_State* state); +int EQ2Emu_lua_SetMaxHP(lua_State* state); +int EQ2Emu_lua_SetMaxHPBase(lua_State* state); +int EQ2Emu_lua_SetCurrentPower(lua_State* state); +int EQ2Emu_lua_SetMaxPower(lua_State* state); +int EQ2Emu_lua_SetMaxPowerBase(lua_State* state); +int EQ2Emu_lua_ModifyMaxHP(lua_State* state); +int EQ2Emu_lua_ModifyMaxPower(lua_State* state); +int EQ2Emu_lua_SetHeading(lua_State* state); +int EQ2Emu_lua_SetModelType(lua_State* state); +int EQ2Emu_lua_SetAdventureClass(lua_State* state); +int EQ2Emu_lua_SetTradeskillClass(lua_State* state); +int EQ2Emu_lua_SetMount(lua_State* state); +int EQ2Emu_lua_SetMountColor(lua_State* state); +int EQ2Emu_lua_SetSpeed(lua_State* state); +int EQ2Emu_lua_SetPosition(lua_State* state); +int EQ2Emu_lua_AddSpellBonus(lua_State* state); +int EQ2Emu_lua_RemoveSpellBonus(lua_State* state); +int EQ2Emu_lua_AddSkillBonus(lua_State* state); +int EQ2Emu_lua_RemoveSkillBonus(lua_State* state); +int EQ2Emu_lua_AddControlEffect(lua_State* state); +int EQ2Emu_lua_RemoveControlEffect(lua_State* state); +int EQ2Emu_lua_HasControlEffect(lua_State* state); + +int EQ2Emu_lua_GetBaseAggroRadius(lua_State* state); +int EQ2Emu_lua_GetAggroRadius(lua_State* state); +int EQ2Emu_lua_SetAggroRadius(lua_State* state); + +int EQ2Emu_lua_SetDeity(lua_State* state); +int EQ2Emu_lua_GetDeity(lua_State* state); + +int EQ2Emu_lua_SetInt(lua_State* state); +int EQ2Emu_lua_SetWis(lua_State* state); +int EQ2Emu_lua_SetSta(lua_State* state); +int EQ2Emu_lua_SetStr(lua_State* state); +int EQ2Emu_lua_SetAgi(lua_State* state); +int EQ2Emu_lua_SetIntBase(lua_State* state); +int EQ2Emu_lua_SetWisBase(lua_State* state); +int EQ2Emu_lua_SetStaBase(lua_State* state); +int EQ2Emu_lua_SetStrBase(lua_State* state); +int EQ2Emu_lua_SetAgiBase(lua_State* state); +int EQ2Emu_lua_SetLootCoin(lua_State* state); +int EQ2Emu_lua_HasCoin(lua_State* state); +int EQ2Emu_lua_SetQuestYellow(lua_State* state); + +//Gets +int EQ2Emu_lua_GetLevel(lua_State* state); +int EQ2Emu_lua_GetDifficulty(lua_State* state); +int EQ2Emu_lua_GetCurrentHP(lua_State* state); +int EQ2Emu_lua_GetMaxHP(lua_State* state); +int EQ2Emu_lua_GetMaxHPBase(lua_State* state); +int EQ2Emu_lua_GetCurrentPower(lua_State* state); +int EQ2Emu_lua_GetName(lua_State* state); +int EQ2Emu_lua_GetMaxPower(lua_State* state); +int EQ2Emu_lua_GetMaxPowerBase(lua_State* state); +int EQ2Emu_lua_GetDistance(lua_State* state); +int EQ2Emu_lua_GetX(lua_State* state); +int EQ2Emu_lua_GetY(lua_State* state); +int EQ2Emu_lua_GetZ(lua_State* state); +int EQ2Emu_lua_GetHeading(lua_State* state); +int EQ2Emu_lua_GetModelType(lua_State* state); +int EQ2Emu_lua_GetRace(lua_State* state); +int EQ2Emu_lua_GetRaceName(lua_State* state); +int EQ2Emu_lua_GetMount(lua_State* state); +int EQ2Emu_lua_GetClass(lua_State* state); +int EQ2Emu_lua_GetClassName(lua_State* state); +int EQ2Emu_lua_GetArchetypeName(lua_State* state); +int EQ2Emu_lua_GetSpeed(lua_State* state); +int EQ2Emu_lua_HasMoved(lua_State* state); +int EQ2Emu_lua_GetInt(lua_State* state); +int EQ2Emu_lua_GetWis(lua_State* state); +int EQ2Emu_lua_GetSta(lua_State* state); +int EQ2Emu_lua_GetStr(lua_State* state); +int EQ2Emu_lua_GetAgi(lua_State* state); +int EQ2Emu_lua_GetIntBase(lua_State* state); +int EQ2Emu_lua_GetWisBase(lua_State* state); +int EQ2Emu_lua_GetStaBase(lua_State* state); +int EQ2Emu_lua_GetStrBase(lua_State* state); +int EQ2Emu_lua_GetAgiBase(lua_State* state); +int EQ2Emu_lua_GetLootCoin(lua_State* state); +int EQ2Emu_lua_GetSpawn(lua_State* state); +int EQ2Emu_lua_GetSpawnFromList(lua_State* state); +int EQ2Emu_lua_GetSpawnListSize(lua_State* state); +int EQ2Emu_lua_CreateSpawnList(lua_State* state); +int EQ2Emu_lua_AddSpawnToSpawnList(lua_State* state); +int EQ2Emu_lua_RemoveSpawnFromSpawnList(lua_State* state); +int EQ2Emu_lua_GetSpawnListBySpawnID(lua_State* state); +int EQ2Emu_lua_GetSpawnListByRailID(lua_State* state); +int EQ2Emu_lua_GetPassengerSpawnList(lua_State* state); +int EQ2Emu_lua_GetVariableValue(lua_State* state); +int EQ2Emu_lua_GetCoinMessage(lua_State* state); +int EQ2Emu_lua_GetSpawnByGroupID(lua_State* state); +int EQ2Emu_lua_GetSpawnByLocationID(lua_State* state); +int EQ2Emu_lua_GetSpawnID(lua_State* state); +int EQ2Emu_lua_GetSpawnGroupID(lua_State* state); +int EQ2Emu_lua_SetSpawnGroupID(lua_State* state); +int EQ2Emu_lua_AddSpawnToGroup(lua_State* state); +int EQ2Emu_lua_GetSpawnLocationID(lua_State* state); +int EQ2Emu_lua_GetSpawnLocationPlacementID(lua_State* state); +int EQ2Emu_lua_GetFactionAmount(lua_State* state); +int EQ2Emu_lua_SetFactionID(lua_State* state); +int EQ2Emu_lua_GetFactionID(lua_State* state); +int EQ2Emu_lua_ChangeFaction(lua_State* state); +int EQ2Emu_lua_GetGender(lua_State* state); +int EQ2Emu_lua_GetTarget(lua_State* state); +int EQ2Emu_lua_HasFreeSlot(lua_State* state); +int EQ2Emu_lua_HasItemEquipped(lua_State* state); +int EQ2Emu_lua_GetEquippedItemBySlot(lua_State* state); +int EQ2Emu_lua_GetEquippedItemByID(lua_State* state); +int EQ2Emu_lua_SetEquippedItemByID(lua_State* state); +int EQ2Emu_lua_SetEquippedItem(lua_State* state); +int EQ2Emu_lua_UnequipSlot(lua_State* state); +int EQ2Emu_lua_SetEquipment(lua_State* state); +int EQ2Emu_lua_GetItemByID(lua_State* state); +int EQ2Emu_lua_GetItemType(lua_State* state); +int EQ2Emu_lua_GetItemEffectType(lua_State* state); +int EQ2Emu_lua_GetSpellName(lua_State* state); + +//Misc +int EQ2Emu_lua_SetAttackable(lua_State* state); +int EQ2Emu_lua_SendStateCommand(lua_State* state); +int EQ2Emu_lua_SpawnSet(lua_State* state); +int EQ2Emu_lua_KillSpawn(lua_State* state); +int EQ2Emu_lua_KillSpawnByDistance(lua_State* state); +int EQ2Emu_lua_SpawnSetByDistance(lua_State* state); +int EQ2Emu_lua_SetRequiredQuest(lua_State* state); +int EQ2Emu_lua_SetRequiredHistory(lua_State* state); +int EQ2Emu_lua_Despawn(lua_State* state); +int EQ2Emu_lua_ChangeHandIcon(lua_State* state); +int EQ2Emu_lua_SetVisualFlag(lua_State* state); +int EQ2Emu_lua_SetInfoFlag(lua_State* state); +int EQ2Emu_lua_AddHate(lua_State* state); +int EQ2Emu_lua_GetZone(lua_State* state); +int EQ2Emu_lua_GetZoneName(lua_State* state); +int EQ2Emu_lua_GetZoneID(lua_State* state); +int EQ2Emu_lua_Zone(lua_State* state); +int EQ2Emu_lua_ModifyPower(lua_State* state); +int EQ2Emu_lua_ModifyHP(lua_State* state); +int EQ2Emu_lua_ModifyTotalPower(lua_State* state); +int EQ2Emu_lua_ModifyTotalHP(lua_State* state); +int EQ2Emu_lua_SpellHeal(lua_State* state); +int EQ2Emu_lua_SpellHealPct(lua_State* state); + +int EQ2Emu_lua_AddItem(lua_State* state); +int EQ2Emu_lua_SummonItem(lua_State* state); +int EQ2Emu_lua_RemoveItem(lua_State* state); +int EQ2Emu_lua_HasItem(lua_State* state); +int EQ2Emu_lua_Spawn(lua_State* state); +int EQ2Emu_lua_AddSpawnAccess(lua_State* state); +int EQ2Emu_lua_CastSpell(lua_State* state); +int EQ2Emu_lua_SpellDamage(lua_State* state); +int EQ2Emu_lua_SpellDamageExt(lua_State* state); +int EQ2Emu_lua_FaceTarget(lua_State* state); +int EQ2Emu_lua_MoveToLocation(lua_State* state); +int EQ2Emu_lua_ClearRunningLocations(lua_State* state); +int EQ2Emu_lua_Say(lua_State* state); +int EQ2Emu_lua_Shout(lua_State* state); +int EQ2Emu_lua_SayOOC(lua_State* state); +int EQ2Emu_lua_Emote(lua_State* state); +int EQ2Emu_lua_IsPlayer(lua_State* state); +int EQ2Emu_lua_GetCharacterID(lua_State* state); +int EQ2Emu_lua_MovementLoopAdd(lua_State* state); +int EQ2Emu_lua_GetCurrentZoneSafeLocation(lua_State* state); +int EQ2Emu_lua_PlayFlavor(lua_State* state); +int EQ2Emu_lua_PlayFlavorID(lua_State* state); +int EQ2Emu_lua_PlaySound(lua_State* state); +int EQ2Emu_lua_PlayVoice(lua_State* state); +int EQ2Emu_lua_PlayAnimation(lua_State* state); +int EQ2Emu_lua_AddLootItem(lua_State* state); +int EQ2Emu_lua_HasLootItem(lua_State* state); +int EQ2Emu_lua_RemoveLootItem(lua_State* state); +int EQ2Emu_lua_AddLootCoin(lua_State* state); +int EQ2Emu_lua_GiveLoot(lua_State* state); +int EQ2Emu_lua_HasPendingLoot(lua_State* state); +int EQ2Emu_lua_HasPendingLootItem(lua_State* state); +int EQ2Emu_lua_CreateConversation(lua_State* state); +int EQ2Emu_lua_AddConversationOption(lua_State* state); +int EQ2Emu_lua_StartConversation(lua_State* state); +int EQ2Emu_lua_StartDialogConversation(lua_State* state); +//int EQ2Emu_lua_StartItemConversation(lua_State* state); +int EQ2Emu_lua_CloseConversation(lua_State* state); +int EQ2Emu_lua_CloseItemConversation(lua_State* state); +int EQ2Emu_lua_SetPlayerProximityFunction(lua_State* state); +int EQ2Emu_lua_SetLocationProximityFunction(lua_State* state); +int EQ2Emu_lua_IsBindAllowed(lua_State* state); +int EQ2Emu_lua_IsGateAllowed(lua_State* state); +int EQ2Emu_lua_Bind(lua_State* state); +int EQ2Emu_lua_Gate(lua_State* state); +int EQ2Emu_lua_IsAlive(lua_State* state); +int EQ2Emu_lua_IsInCombat(lua_State* state); +int EQ2Emu_lua_SendMessage(lua_State* state); +int EQ2Emu_lua_SendPopUpMessage(lua_State* state); +int EQ2Emu_lua_SetServerControlFlag(lua_State* state); +int EQ2Emu_lua_ToggleTracking(lua_State* state); +int EQ2Emu_lua_AddPrimaryEntityCommand(lua_State* state); +int EQ2Emu_lua_AddSpellBookEntry(lua_State* state); +int EQ2Emu_lua_DeleteSpellBook(lua_State* state); +int EQ2Emu_lua_RemoveSpellBookEntry(lua_State* state); +int EQ2Emu_lua_SendNewAdventureSpells(lua_State* state); +int EQ2Emu_lua_SendNewTradeskillSpells(lua_State* state); +int EQ2Emu_lua_HasSpell(lua_State* state); +int EQ2Emu_lua_Attack(lua_State* state); +int EQ2Emu_lua_ApplySpellVisual(lua_State* state); +int EQ2Emu_lua_Interrupt(lua_State* state); +int EQ2Emu_lua_Stealth(lua_State* state); +int EQ2Emu_lua_IsStealthed(lua_State* state); +int EQ2Emu_lua_IsInvis(lua_State* state); +int EQ2Emu_lua_AddSpawnIDAccess(lua_State* state); +int EQ2Emu_lua_RemoveSpawnIDAccess(lua_State* state); +int EQ2Emu_lua_HasRecipeBook(lua_State* state); +int EQ2Emu_lua_SpawnMove(lua_State* state); +int EQ2Emu_lua_AddTransportSpawn(lua_State* state); +int EQ2Emu_lua_IsTransportSpawn(lua_State* state); +int EQ2Emu_lua_PerformCameraShake(lua_State* state); + +//Quest Stuff +int EQ2Emu_lua_SetStepComplete(lua_State* state); +int EQ2Emu_lua_AddStepProgress(lua_State* state); +int EQ2Emu_lua_GetTaskGroupStep(lua_State* state); +int EQ2Emu_lua_QuestStepIsComplete(lua_State* state); +int EQ2Emu_lua_GetQuestStep(lua_State* state); +int EQ2Emu_lua_RegisterQuest(lua_State* state); +int EQ2Emu_lua_OfferQuest(lua_State* state); +int EQ2Emu_lua_SetQuestPrereqLevel(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqQuest(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqItem(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqFaction(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqClass(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqRace(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqModelType(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqTradeskillLevel(lua_State* state); +int EQ2Emu_lua_AddQuestPrereqTradeskillClass(lua_State* state); +int EQ2Emu_lua_HasQuestRewardItem(lua_State* state); +int EQ2Emu_lua_AddQuestRewardItem(lua_State* state); +int EQ2Emu_lua_AddQuestSelectableRewardItem(lua_State* state); +int EQ2Emu_lua_AddQuestRewardCoin(lua_State* state); +int EQ2Emu_lua_AddQuestRewardFaction(lua_State* state); +int EQ2Emu_lua_SetQuestRewardStatus(lua_State* state); +int EQ2Emu_lua_SetStatusTmpReward(lua_State* state); +int EQ2Emu_lua_SetCoinTmpReward(lua_State* state); +int EQ2Emu_lua_SetQuestRewardComment(lua_State* state); +int EQ2Emu_lua_SetQuestRewardExp(lua_State* state); +int EQ2Emu_lua_AddQuestStep(lua_State* state); +int EQ2Emu_lua_AddQuestStepKillLogic(lua_State* state); +int EQ2Emu_lua_AddQuestStepKill(lua_State* state); +int EQ2Emu_lua_AddQuestStepKillByRace(lua_State* state); +int EQ2Emu_lua_AddQuestStepChat(lua_State* state); +int EQ2Emu_lua_AddQuestStepObtainItem(lua_State* state); +int EQ2Emu_lua_AddQuestStepZoneLoc(lua_State* state); +int EQ2Emu_lua_AddQuestStepLocation(lua_State* state); +int EQ2Emu_lua_AddQuestStepLoc(lua_State* state); +int EQ2Emu_lua_AddQuestStepSpell(lua_State* state); +int EQ2Emu_lua_AddQuestStepCraft(lua_State* state); +int EQ2Emu_lua_AddQuestStepHarvest(lua_State* state); +int EQ2Emu_lua_AddQuestStepCompleteAction(lua_State* state); +int EQ2Emu_lua_AddQuestStepProgressAction(lua_State* state); +int EQ2Emu_lua_SetQuestCompleteAction(lua_State* state); +int EQ2Emu_lua_GiveQuestReward(lua_State* state); +int EQ2Emu_lua_UpdateQuestTaskGroupDescription(lua_State* state); +int EQ2Emu_lua_UpdateQuestStepDescription(lua_State* state); +int EQ2Emu_lua_UpdateQuestDescription(lua_State* state); +int EQ2Emu_lua_UpdateQuestZone(lua_State* state); +int EQ2Emu_lua_SetCompletedDescription(lua_State* state); +int EQ2Emu_lua_ProvidesQuest(lua_State* state); +int EQ2Emu_lua_HasQuest(lua_State* state); +int EQ2Emu_lua_HasCompletedQuest(lua_State* state); +int EQ2Emu_lua_QuestIsComplete(lua_State* state); +int EQ2Emu_lua_QuestReturnNPC(lua_State* state); +int EQ2Emu_lua_GetQuest(lua_State* state); +int EQ2Emu_lua_AddTimer(lua_State* state); +int EQ2Emu_lua_StopTimer(lua_State* state); +int EQ2Emu_lua_Harvest(lua_State* state); +int EQ2Emu_lua_SetCompleteFlag(lua_State* state); +int EQ2Emu_lua_CanReceiveQuest(lua_State* state); + +int EQ2Emu_lua_HasCollectionsToHandIn(lua_State *state); +int EQ2Emu_lua_HandInCollections(lua_State *state); + +int EQ2Emu_lua_UseWidget(lua_State* state); +int EQ2Emu_lua_SummonPet(lua_State* state); +int EQ2Emu_lua_Charm(lua_State* state); + +int EQ2Emu_lua_SetSpellList(lua_State* state); +int EQ2Emu_lua_GetPet(lua_State* state); +int EQ2Emu_lua_GetGroup(lua_State* state); + +int EQ2Emu_lua_CreateOptionWindow(lua_State* state); +int EQ2Emu_lua_AddOptionWindowOption(lua_State* state); +int EQ2Emu_lua_SendOptionWindow(lua_State* state); + +int EQ2Emu_lua_GetTradeskillClass(lua_State* state); +int EQ2Emu_lua_GetTradeskillLevel(lua_State* state); +int EQ2Emu_lua_GetTradeskillClassName(lua_State* state); +int EQ2Emu_lua_SetTradeskillLevel(lua_State* state); + +int EQ2Emu_lua_SummonDeityPet(lua_State* state); +int EQ2Emu_lua_SummonCosmeticPet(lua_State* state); +int EQ2Emu_lua_DismissPet(lua_State* state); +int EQ2Emu_lua_GetCharmedPet(lua_State* state); +int EQ2Emu_lua_GetDeityPet(lua_State* state); +int EQ2Emu_lua_GetCosmeticPet(lua_State* state); + +int EQ2Emu_lua_SetQuestFeatherColor(lua_State* state); +int EQ2Emu_lua_RemoveSpawnAccess(lua_State* state); +int EQ2Emu_lua_SpawnByLocationID(lua_State* state); +int EQ2Emu_lua_SpawnGroupByID(lua_State* state); +int EQ2Emu_lua_CastEntityCommand(lua_State* state); +int EQ2Emu_lua_SetLuaBrain(lua_State* state); +int EQ2Emu_lua_SetBrainTick(lua_State* state); +int EQ2Emu_lua_SetFollowTarget(lua_State* state); +int EQ2Emu_lua_GetFollowTarget(lua_State* state); +int EQ2Emu_lua_ToggleFollow(lua_State* state); +int EQ2Emu_lua_IsFollowing(lua_State* state); +int EQ2Emu_lua_SetTempVariable(lua_State* state); +int EQ2Emu_lua_GetTempVariable(lua_State* state); +int EQ2Emu_lua_GiveQuestItem(lua_State*state); +int EQ2Emu_lua_SetQuestRepeatable(lua_State* state); + + +int EQ2Emu_lua_AddWaypoint(lua_State* state); +int EQ2Emu_lua_RemoveWaypoint(lua_State* state); +int EQ2Emu_lua_SendWaypoints(lua_State* state); + +int EQ2Emu_lua_AddWard(lua_State* state); +int EQ2Emu_lua_AddToWard(lua_State* state); +int EQ2Emu_lua_RemoveWard(lua_State* state); +int EQ2Emu_lua_GetWardAmountLeft(lua_State* state); +int EQ2Emu_lua_GetWardValue(lua_State* state); + +//Combat AI related +int EQ2Emu_lua_SetTarget(lua_State* state); +int EQ2Emu_lua_IsPet(lua_State* state); +int EQ2Emu_lua_GetOwner(lua_State* state); +int EQ2Emu_lua_SetInCombat(lua_State* state); +int EQ2Emu_lua_CompareSpawns(lua_State* state); +int EQ2Emu_lua_ClearRunback(lua_State* state); +int EQ2Emu_lua_Runback(lua_State* state); +int EQ2Emu_lua_GetRunbackDistance(lua_State* state); +int EQ2Emu_lua_IsCasting(lua_State* state); +int EQ2Emu_lua_IsMezzed(lua_State* state); +int EQ2Emu_lua_IsStunned(lua_State* state); +int EQ2Emu_lua_IsMezzedOrStunned(lua_State* state); +int EQ2Emu_lua_ClearEncounter(lua_State* state); +int EQ2Emu_lua_ClearHate(lua_State* state); +int EQ2Emu_lua_GetMostHated(lua_State* state); +int EQ2Emu_lua_GetEncounterSize(lua_State* state); +int EQ2Emu_lua_HasRecovered(lua_State* state); +int EQ2Emu_lua_ProcessMelee(lua_State* state); +int EQ2Emu_lua_ProcessSpell(lua_State* state); +int EQ2Emu_lua_GetEncounter(lua_State* state); +int EQ2Emu_lua_GetHateList(lua_State* state); +int EQ2Emu_lua_HasGroup(lua_State* state); +int EQ2Emu_lua_HasSpellEffect(lua_State* state); + +int EQ2Emu_lua_SetSuccessTimer(lua_State* state); +int EQ2Emu_lua_SetFailureTimer(lua_State* state); +int EQ2Emu_lua_IsGroundSpawn(lua_State* state); +int EQ2Emu_lua_CanHarvest(lua_State* state); + +int EQ2Emu_lua_SummonDumbFirePet(lua_State* state); + +int EQ2Emu_lua_GetSkillValue(lua_State* state); +int EQ2Emu_lua_GetSkillMaxValue(lua_State* state); +int EQ2Emu_lua_GetSkillName(lua_State* state); +int EQ2Emu_lua_SetSkillMaxValue(lua_State* state); +int EQ2Emu_lua_SetSkillValue(lua_State* state); +int EQ2Emu_lua_GetSkill(lua_State* state); +int EQ2Emu_lua_GetSkillIDByName(lua_State* state); +int EQ2Emu_lua_HasSkill(lua_State* state); +int EQ2Emu_lua_AddSkill(lua_State* state); +int EQ2Emu_lua_RemoveSkill(lua_State* state); +int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state); + +int EQ2Emu_lua_AddProc(lua_State* state); +int EQ2Emu_lua_AddProcExt(lua_State* state); +int EQ2Emu_lua_RemoveProc(lua_State* state); +int EQ2Emu_lua_Knockback(lua_State* state); + +int EQ2Emu_lua_IsEpic(lua_State* state); +int EQ2Emu_lua_IsHeroic(lua_State* state); +int EQ2Emu_lua_ProcDamage(lua_State* state); +int EQ2Emu_lua_LastSpellAttackHit(lua_State* state); + +int EQ2Emu_lua_IsBehind(lua_State* state); +int EQ2Emu_lua_IsFlanking(lua_State* state); +int EQ2Emu_lua_InFront(lua_State* state); +int EQ2Emu_lua_AddSpellTimer(lua_State* state); +int EQ2Emu_lua_SetItemCount(lua_State* state); +int EQ2Emu_lua_GetItemCount(lua_State* state); +int EQ2Emu_lua_Resurrect(lua_State* state); +int EQ2Emu_lua_BreatheUnderwater(lua_State* state); +int EQ2Emu_lua_BlurVision(lua_State* state); +int EQ2Emu_lua_SetVision(lua_State* state); +int EQ2Emu_lua_GetItemSkillReq(lua_State* state); +int EQ2Emu_lua_SetSpeedMultiplier(lua_State* state); +int EQ2Emu_lua_SetIllusion(lua_State* state); +int EQ2Emu_lua_ResetIllusion(lua_State* state); +int EQ2Emu_lua_AddThreatTransfer(lua_State* state); +int EQ2Emu_lua_RemoveThreatTransfer(lua_State* state); +int EQ2Emu_lua_CureByType(lua_State* state); +int EQ2Emu_lua_CureByControlEffect(lua_State* state); +int EQ2Emu_lua_AddSpawnSpellBonus(lua_State* state); +int EQ2Emu_lua_RemoveSpawnSpellBonus(lua_State* state); +int EQ2Emu_lua_CancelSpell(lua_State* state); +int EQ2Emu_lua_RemoveStealth(lua_State* state); +int EQ2Emu_lua_RemoveInvis(lua_State* state); +int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state); +int EQ2Emu_lua_CopySpawnAppearance(lua_State* state); +int EQ2Emu_lua_RemoveTriggerFromSpell(lua_State* state); +int EQ2Emu_lua_GetSpellTriggerCount(lua_State* state); +int EQ2Emu_lua_SetSpellTriggerCount(lua_State* state); +int EQ2Emu_lua_HasSpellImmunity(lua_State* state); +int EQ2Emu_lua_AddImmunitySpell(lua_State* state); +int EQ2Emu_lua_RemoveImmunitySpell(lua_State* state); +int EQ2Emu_lua_SetSpellSnareValue(lua_State* state); +int EQ2Emu_lua_CheckRaceType(lua_State* state); +int EQ2Emu_lua_GetRaceType(lua_State* state); +int EQ2Emu_lua_GetRaceBaseType(lua_State* state); +int EQ2Emu_lua_GetQuestFlags(lua_State* state); +int EQ2Emu_lua_SetQuestFlags(lua_State* state); +int EQ2Emu_lua_SetQuestTimer(lua_State* state); +int EQ2Emu_lua_RemoveQuestStep(lua_State* state); +int EQ2Emu_lua_ResetQuestStep(lua_State* state); +int EQ2Emu_lua_SetQuestTimerComplete(lua_State* state); +int EQ2Emu_lua_AddQuestStepFailureAction(lua_State* state); +int EQ2Emu_lua_SetStepFailed(lua_State* state); +int EQ2Emu_lua_GetQuestCompleteCount(lua_State* state); +int EQ2Emu_lua_SetServerVariable(lua_State* state); +int EQ2Emu_lua_GetServerVariable(lua_State* state); +int EQ2Emu_lua_HasLanguage(lua_State* state); +int EQ2Emu_lua_AddLanguage(lua_State* state); +int EQ2Emu_lua_IsNight(lua_State* state); +int EQ2Emu_lua_AddMultiFloorLift(lua_State* state); +int EQ2Emu_lua_StartAutoMount(lua_State* state); +int EQ2Emu_lua_EndAutoMount(lua_State* state); +int EQ2Emu_lua_IsOnAutoMount(lua_State* state); +int EQ2Emu_lua_SetPlayerHistory(lua_State* state); +int EQ2Emu_lua_GetPlayerHistory(lua_State* state); +int EQ2Emu_lua_SetGridID(lua_State* state); +int EQ2Emu_lua_GetQuestStepProgress(lua_State* state); +int EQ2Emu_lua_SetPlayerLevel(lua_State* state); +int EQ2Emu_lua_AddCoin(lua_State* state); +int EQ2Emu_lua_RemoveCoin(lua_State* state); +int EQ2Emu_lua_GetPlayersInZone(lua_State* state); +int EQ2Emu_lua_SetSpawnAnimation(lua_State* state); +int EQ2Emu_lua_GetClientVersion(lua_State* state); +int EQ2Emu_lua_GetItemID(lua_State* state); +int EQ2Emu_lua_IsEntity(lua_State* state); +int EQ2Emu_lua_GetOrigX(lua_State* state); +int EQ2Emu_lua_GetOrigY(lua_State* state); +int EQ2Emu_lua_GetOrigZ(lua_State* state); +int EQ2Emu_lua_GetPCTOfHP(lua_State* state); +int EQ2Emu_lua_GetPCTOfPower(lua_State* state); +int EQ2Emu_lua_GetBoundZoneID(lua_State* state); +int EQ2Emu_lua_Evac(lua_State* state); +int EQ2Emu_lua_GetSpellTier(lua_State* state); +int EQ2Emu_lua_GetSpellID(lua_State* state); +int EQ2Emu_lua_StartTransmute(lua_State* state); +int EQ2Emu_lua_CompleteTransmute(lua_State* state); +int EQ2Emu_lua_ProcHate(lua_State* state); + +int EQ2Emu_lua_GiveExp(lua_State* state); +int EQ2Emu_lua_DisplayText(lua_State* state); +int EQ2Emu_lua_ShowLootWindow(lua_State* state); +int EQ2Emu_lua_GetRandomSpawnByID(lua_State* state); +int EQ2Emu_lua_AddPrimaryEntityCommandAllSpawns(lua_State* state); +int EQ2Emu_lua_InstructionWindow(lua_State* state); +int EQ2Emu_lua_InstructionWindowClose(lua_State* state); +int EQ2Emu_lua_InstructionWindowGoal(lua_State* state); +int EQ2Emu_lua_ShowWindow(lua_State* state); +int EQ2Emu_lua_FlashWindow(lua_State* state); +int EQ2Emu_lua_EnableGameEvent(lua_State* state); +int EQ2Emu_lua_GetTutorialStep(lua_State* state); +int EQ2Emu_lua_SetTutorialStep(lua_State* state); + +int EQ2Emu_lua_CheckLOS(lua_State* state); +int EQ2Emu_lua_CheckLOSByCoordinates(lua_State* state); + +int EQ2Emu_lua_SetZoneExpansionFlag(lua_State* state); +int EQ2Emu_lua_GetZoneExpansionFlag(lua_State* state); +int EQ2Emu_lua_SetZoneHolidayFlag(lua_State* state); +int EQ2Emu_lua_GetZoneHolidayFlag(lua_State* state); + +int EQ2Emu_lua_SetCanBind(lua_State* state); +int EQ2Emu_lua_GetCanBind(lua_State* state); + +int EQ2Emu_lua_GetCanGate(lua_State* state); +int EQ2Emu_lua_SetCanGate(lua_State* state); + +int EQ2Emu_lua_GetCanEvac(lua_State* state); +int EQ2Emu_lua_SetCanEvac(lua_State* state); + +int EQ2Emu_lua_AddSpawnProximity(lua_State* state); + +int EQ2Emu_lua_CanSeeInvis(lua_State* state); +int EQ2Emu_lua_SetSeeInvis(lua_State* state); +int EQ2Emu_lua_SetSeeHide(lua_State* state); + +int EQ2Emu_lua_SetAccessToEntityCommand(lua_State* state); +int EQ2Emu_lua_SetAccessToEntityCommandByCharID(lua_State* state); +int EQ2Emu_lua_RemovePrimaryEntityCommand(lua_State* state); +int EQ2Emu_lua_SendUpdateDefaultCommand(lua_State* state); + + +int EQ2Emu_lua_SendTransporters(lua_State* state); +int EQ2Emu_lua_SetTemporaryTransportID(lua_State* state); +int EQ2Emu_lua_GetTemporaryTransportID(lua_State* state); + +int EQ2Emu_lua_GetAlignment(lua_State* state); +int EQ2Emu_lua_SetAlignment(lua_State* state); + +int EQ2Emu_lua_GetSpell(lua_State* state); +int EQ2Emu_lua_GetSpellData(lua_State* state); +int EQ2Emu_lua_SetSpellData(lua_State* state); +int EQ2Emu_lua_CastCustomSpell(lua_State* state); + +int EQ2Emu_lua_SetSpellDataIndex(lua_State* state); +int EQ2Emu_lua_GetSpellDataIndex(lua_State* state); + +int EQ2Emu_lua_SetSpellDisplayEffect(lua_State* state); +int EQ2Emu_lua_GetSpellDisplayEffect(lua_State* state); + +int EQ2Emu_lua_InWater(lua_State* state); +int EQ2Emu_lua_InLava(lua_State* state); + +int EQ2Emu_lua_DamageSpawn(lua_State* state); + +int EQ2Emu_lua_IsInvulnerable(lua_State* state); +int EQ2Emu_lua_SetInvulnerable(lua_State* state); + +int EQ2Emu_lua_GetRuleFlagBool(lua_State* state); +int EQ2Emu_lua_GetRuleFlagInt32(lua_State* state); +int EQ2Emu_lua_GetRuleFlagFloat(lua_State* state); + +int EQ2Emu_lua_GetAAInfo(lua_State* state); +int EQ2Emu_lua_SetAAInfo(lua_State* state); + +int EQ2Emu_lua_AddMasterTitle(lua_State* state); +int EQ2Emu_lua_AddCharacterTitle(lua_State* state); +int EQ2Emu_lua_SetCharacterTitleSuffix(lua_State* state); +int EQ2Emu_lua_SetCharacterTitlePrefix(lua_State* state); +int EQ2Emu_lua_ResetCharacterTitleSuffix(lua_State* state); +int EQ2Emu_lua_ResetCharacterTitlePrefix(lua_State* state); + +int EQ2Emu_lua_GetInfoStructString(lua_State* state); +int EQ2Emu_lua_GetInfoStructUInt(lua_State* state); +int EQ2Emu_lua_GetInfoStructSInt(lua_State* state); +int EQ2Emu_lua_GetInfoStructFloat(lua_State* state); + +int EQ2Emu_lua_SetInfoStructString(lua_State* state); +int EQ2Emu_lua_SetInfoStructUInt(lua_State* state); +int EQ2Emu_lua_SetInfoStructSInt(lua_State* state); +int EQ2Emu_lua_SetInfoStructFloat(lua_State* state); + +int EQ2Emu_lua_SetCharSheetChanged(lua_State* state); + +int EQ2Emu_lua_AddPlayerMail(lua_State* state); +int EQ2Emu_lua_AddPlayerMailByCharID(lua_State* state); + +int EQ2Emu_lua_OpenDoor(lua_State* state); +int EQ2Emu_lua_CloseDoor(lua_State* state); +int EQ2Emu_lua_IsOpen(lua_State* state); + +int EQ2Emu_lua_MakeRandomInt(lua_State* state); +int EQ2Emu_lua_MakeRandomFloat(lua_State* state); + +int EQ2Emu_lua_AddIconValue(lua_State* state); +int EQ2Emu_lua_RemoveIconValue(lua_State* state); + +int EQ2Emu_lua_GetShardID(lua_State* state); +int EQ2Emu_lua_GetShardCharID(lua_State* state); +int EQ2Emu_lua_GetShardCreatedTimestamp(lua_State* state); +int EQ2Emu_lua_DeleteDBShardID(lua_State* state); + +int EQ2Emu_lua_PauseMovement(lua_State* state); +int EQ2Emu_lua_StopMovement(lua_State* state); + +int EQ2Emu_lua_GetArrowColor(lua_State* state); +int EQ2Emu_lua_GetTSArrowColor(lua_State* state); + +int EQ2Emu_lua_GetSpawnByRailID(lua_State* state); +int EQ2Emu_lua_SetRailID(lua_State* state); +int EQ2Emu_lua_IsZoneLoading(lua_State* state); +int EQ2Emu_lua_IsRunning(lua_State* state); + +int EQ2Emu_lua_GetZoneLockoutTimer(lua_State* state); + +int EQ2Emu_lua_SetWorldTime(lua_State* state); +int EQ2Emu_lua_GetWorldTimeYear(lua_State* state); +int EQ2Emu_lua_GetWorldTimeMonth(lua_State* state); +int EQ2Emu_lua_GetWorldTimeHour(lua_State* state); +int EQ2Emu_lua_GetWorldTimeMinute(lua_State* state); +int EQ2Emu_lua_SendTimeUpdate(lua_State* state); + + +int EQ2Emu_lua_SetLootTier(lua_State* state); +int EQ2Emu_lua_GetLootTier(lua_State* state); + +int EQ2Emu_lua_SetLootDropType(lua_State* state); +int EQ2Emu_lua_GetLootDropType(lua_State* state); + +int EQ2Emu_lua_DamageEquippedItems(lua_State* state); + +int EQ2Emu_lua_CreateWidgetRegion(lua_State* state); +int EQ2Emu_lua_RemoveRegion(lua_State* state); + +int EQ2Emu_lua_SetPlayerPOVGhost(lua_State* state); +int EQ2Emu_lua_SetCastOnAggroComplete(lua_State* state); +int EQ2Emu_lua_IsCastOnAggroComplete(lua_State* state); + +int EQ2Emu_lua_AddRecipeBookToPlayer(lua_State* state); +int EQ2Emu_lua_RemoveRecipeFromPlayer(lua_State* state); + +int EQ2Emu_lua_ReplaceWidgetFromClient(lua_State* state); +int EQ2Emu_lua_RemoveWidgetFromSpawnMap(lua_State* state); +int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state); + +int EQ2Emu_lua_SendHearCast(lua_State* state); + +int EQ2Emu_lua_GetCharacterFlag(lua_State* state); +int EQ2Emu_lua_ToggleCharacterFlag(lua_State* state); + +int EQ2Emu_lua_GetSpellInitialTarget(lua_State* state); +#endif \ No newline at end of file diff --git a/source/WorldServer/LuaInterface.cpp b/source/WorldServer/LuaInterface.cpp new file mode 100644 index 0000000..490665b --- /dev/null +++ b/source/WorldServer/LuaInterface.cpp @@ -0,0 +1,2774 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "LuaInterface.h" +#include "LuaFunctions.h" +#include "WorldDatabase.h" +#include "SpellProcess.h" +#include "../common/Log.h" +#include "World.h" + +#ifndef WIN32 + #include + #include + #include + #include + #include +#else + #include +#endif + +extern WorldDatabase database; +extern ZoneList zone_list; + + +LuaInterface::LuaInterface() { + shutting_down = false; + lua_system_reloading = false; + MDebugClients.SetName("LuaInterface::MDebugClients"); + MSpells.SetName("LuaInterface::MSpells"); + MSpawnScripts.SetName("LuaInterface::MSpawnScripts"); + MZoneScripts.SetName("LuaInterface::MZoneScripts"); + MQuests.SetName("LuaInterface::MQuests"); + MLUAMain.SetName("LuaInterface::MLUAMain"); + MItemScripts.SetName("LuaInterface::MItemScripts"); + MSpellDelete.SetName("LuaInterface::MSpellDelete"); + MCustomSpell.SetName("LuaInterface::MCustomSpell"); + MRegionScripts.SetName("LuaInterface::MRegionScripts"); + user_data_timer = new Timer(20000); + user_data_timer->Start(); + spell_delete_timer = new Timer(5000); + spell_delete_timer->Start(); +} +#ifdef WIN32 +vector* LuaInterface::GetDirectoryListing(const char* directory) { + vector* ret = new vector; + WIN32_FIND_DATA fdata; + HANDLE dhandle; + char buf[MAX_PATH]; + snprintf(buf, sizeof(buf), "%s\\*", directory); + if((dhandle = FindFirstFile(buf, &fdata)) == INVALID_HANDLE_VALUE) { + safe_delete(ret); + return 0; + } + + ret->push_back(string(fdata.cFileName)); + + while(1) { + if(FindNextFile(dhandle, &fdata)) { + ret->push_back(string(fdata.cFileName)); + } + else{ + if(GetLastError() == ERROR_NO_MORE_FILES) { + break; + } else { + safe_delete(ret); + FindClose(dhandle); + return 0; + } + } + } + + if(FindClose(dhandle) == 0) { + safe_delete(ret); + return 0; + } + return ret; +} +#else +vector* LuaInterface::GetDirectoryListing(const char* directory) { + vector* ret = new vector; + DIR *dp; + struct dirent *ep; + dp = opendir (directory); + if (dp != NULL){ + while ((ep = readdir (dp))) + ret->push_back(string(ep->d_name)); + (void) closedir (dp); + } + else { + safe_delete(ret); + return 0; + } + + return ret; +} +#endif + +LuaInterface::~LuaInterface() { + shutting_down = true; + MLUAMain.lock(); + DestroySpells(); + DestroySpawnScripts(); + DestroyQuests(); + DestroyItemScripts(); + DestroyZoneScripts(); + DestroyRegionScripts(); + DeleteUserDataPtrs(true); + DeletePendingSpells(true); + safe_delete(user_data_timer); + safe_delete(spell_delete_timer); + MLUAMain.unlock(); +} + +int LuaInterface::GetNumberOfArgs(lua_State* state) { + return lua_gettop(state); +} + +void LuaInterface::Process() { + if(shutting_down) + return; + MLUAMain.lock(); + if(user_data_timer && user_data_timer->Check()) + DeleteUserDataPtrs(false); + if(spell_delete_timer && spell_delete_timer->Check()) + DeletePendingSpells(false); + MLUAMain.unlock(); +} + +void LuaInterface::DestroySpells() { + map::iterator itr; + MSpells.lock(); + for(itr = spells.begin(); itr != spells.end(); itr++){ + MSpellDelete.lock(); + RemoveCurrentSpell(itr->second->state, false); + MSpellDelete.unlock(); + lua_close(itr->second->state); + safe_delete(itr->second); + } + spells.clear(); + MSpells.unlock(); +} + +void LuaInterface::DestroyQuests(bool reload) { + map::iterator itr; + MQuests.lock(); + for(itr = quest_states.begin(); itr != quest_states.end(); itr++){ + safe_delete(quests[itr->first]); + lua_close(itr->second); + } + quests.clear(); + quest_states.clear(); + map::iterator mutex_itr; + for(mutex_itr = quests_mutex.begin(); mutex_itr != quests_mutex.end(); mutex_itr++){ + safe_delete(mutex_itr->second); + } + quests_mutex.clear(); + if(reload) + database.LoadQuests(); + MQuests.unlock(); +} + +void LuaInterface::DestroyItemScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MItemScripts.writelock(__FUNCTION__, __LINE__); + for(itr = item_scripts.begin(); itr != item_scripts.end(); itr++){ + mutex = GetItemScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + item_scripts.clear(); + item_inverse_scripts.clear(); + item_scripts_mutex.clear(); + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::DestroySpawnScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + for(itr = spawn_scripts.begin(); itr != spawn_scripts.end(); itr++){ + mutex = GetSpawnScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + spawn_scripts.clear(); + spawn_inverse_scripts.clear(); + spawn_scripts_mutex.clear(); + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::DestroyZoneScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MZoneScripts.writelock(__FUNCTION__, __LINE__); + for (itr = zone_scripts.begin(); itr != zone_scripts.end(); itr++){ + mutex = GetZoneScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + zone_scripts.clear(); + zone_inverse_scripts.clear(); + zone_scripts_mutex.clear(); + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::DestroyRegionScripts() { + map >::iterator itr; + map::iterator state_itr; + Mutex* mutex = 0; + MRegionScripts.writelock(__FUNCTION__, __LINE__); + for (itr = region_scripts.begin(); itr != region_scripts.end(); itr++){ + mutex = GetRegionScriptMutex(itr->first.c_str()); + mutex->writelock(__FUNCTION__, __LINE__); + for(state_itr = itr->second.begin(); state_itr != itr->second.end(); state_itr++) + lua_close(state_itr->first); + mutex->releasewritelock(__FUNCTION__, __LINE__); + safe_delete(mutex); + } + region_scripts.clear(); + region_inverse_scripts.clear(); + region_scripts_mutex.clear(); + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::ReloadSpells() { + DestroySpells(); + database.LoadSpellScriptData(); +} + +bool LuaInterface::LoadLuaSpell(const char* name) { + LuaSpell* spell = 0; + string lua_script = string(name); + if (lua_script.find(".lua") == string::npos) + lua_script.append(".lua"); + lua_State* state = LoadLuaFile(lua_script.c_str()); + if(state){ + spell = new LuaSpell; + spell->file_name = lua_script; + spell->state = state; + spell->spell = 0; + spell->caster = 0; + spell->initial_target = 0; + spell->resisted = false; + spell->has_damaged = false; + spell->is_damage_spell = false; + spell->interrupted = false; + spell->last_spellattack_hit = false; + spell->crit = false; + spell->MSpellTargets.SetName("LuaSpell.MSpellTargets"); + spell->cancel_after_all_triggers = false; + spell->num_triggers = 0; + spell->num_calls = 0; + spell->is_recast_timer = false; + spell->had_triggers = false; + spell->had_dmg_remaining = false; + spell->slot_pos = 0; + spell->damage_remaining = 0; + spell->effect_bitmask = 0; + spell->restored = false; + spell->initial_caster_char_id = 0; + spell->initial_target_char_id = 0; + + MSpells.lock(); + if (spells.count(lua_script) > 0) { + + SetLuaUserDataStale(spells[lua_script]); + MSpellDelete.lock(); + RemoveCurrentSpell(spells[lua_script]->state, false); + MSpellDelete.unlock(); + lua_close(spells[lua_script]->state); + safe_delete(spells[lua_script]); + } + spells[lua_script] = spell; + MSpells.unlock(); + + return true; + } + return false; +} + +bool LuaInterface::LoadLuaSpell(string name) { + return LoadLuaSpell(name.c_str()); +} + +bool LuaInterface::LoadItemScript(string name) { + return LoadItemScript(name.c_str()); +} + +bool LuaInterface::LoadItemScript(const char* name) { + bool ret = false; + if(name){ + lua_State* state = LoadLuaFile(name); + if(state){ + MItemScripts.writelock(__FUNCTION__, __LINE__); + item_scripts[name][state] = false; + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + +bool LuaInterface::LoadSpawnScript(const char* name) { + bool ret = false; + if(name){ + lua_State* state = LoadLuaFile(name); + if(state){ + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + spawn_scripts[name][state] = false; + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + +bool LuaInterface::LoadZoneScript(const char* name) { + bool ret = false; + if (name) { + lua_State* state = LoadLuaFile(name); + if (state) { + MZoneScripts.writelock(__FUNCTION__, __LINE__); + zone_scripts[name][state] = false; + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + +bool LuaInterface::LoadRegionScript(const char* name) { + bool ret = false; + if (name) { + lua_State* state = LoadLuaFile(name); + if (state) { + MRegionScripts.writelock(__FUNCTION__, __LINE__); + region_scripts[name][state] = false; + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); + ret = true; + } + } + return ret; +} + +void LuaInterface::ProcessErrorMessage(const char* message) { + MDebugClients.lock(); + vector delete_clients; + map::iterator itr; + for(itr = debug_clients.begin(); itr != debug_clients.end(); itr++){ + if((Timer::GetCurrentTime2() - itr->second) > 60000) + delete_clients.push_back(itr->first); + else + itr->first->Message(CHANNEL_COLOR_RED, "LUA Error: %s", message); + } + for(int32 i=0;iGetQuestID()) == 0){ + ret = new Mutex(); + quests_mutex[quest->GetQuestID()] = ret; + ret->SetName(string("Quest::").append(quest->GetName())); + } + else + ret = quests_mutex[quest->GetQuestID()]; + MQuests.unlock(); + return ret; +} + +bool LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id, int32* returnValue) { + if(shutting_down) + return false; + lua_State* state = 0; + if(quest){ + LogWrite(LUA__DEBUG, 0, "LUA", "Quest: %s, function: %s", quest->GetName(), function); + Mutex* mutex = GetQuestMutex(quest); + mutex->readlock(__FUNCTION__, __LINE__); + if(quest_states.count(quest->GetQuestID()) > 0) + state = quest_states[quest->GetQuestID()]; + bool success = false; // if no state then we return false + if(state){ + int8 arg_count = 3; + lua_getglobal(state, function); + + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + return false; + } + + SetQuestValue(state, quest); + Spawn* spawn = player->GetZone()->GetSpawnByDatabaseID(quest->GetQuestGiver()); + SetSpawnValue(state, spawn); + SetSpawnValue(state, player); + if(step_id != 0xFFFFFFFF){ + SetInt32Value(state, step_id); + arg_count++; + } + + success = CallScriptInt32(state, arg_count, returnValue); + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + LogWrite(LUA__DEBUG, 0, "LUA", "Done!"); + return success; + } + return false; +} + +Quest* LuaInterface::LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name) { + if(shutting_down) + return 0; + lua_State* state = LoadLuaFile(script_name); + Quest* quest = 0; + if(state){ + quest = new Quest(id); + if (name) + quest->SetName(string(name)); + if (type) + quest->SetType(string(type)); + if (zone) + quest->SetZone(string(zone)); + quest->SetLevel(level); + if (description) + quest->SetDescription(string(description)); + lua_getglobal(state, "Init"); + SetQuestValue(state, quest); + if(lua_pcall(state, 1, 0, 0) != 0){ + LogError("Error processing Quest \"%s\" (%u): %s", name ? name : "unknown", id, lua_tostring(state, -1)); + lua_pop(state, 1); + SetLuaUserDataStale(quest); + safe_delete(quest); + return 0; + } + if(!quest->GetName()){ + SetLuaUserDataStale(quest); + safe_delete(quest); + return 0; + } + quest_states[id] = state; + quests[id] = quest; + } + return quest; +} + +const char* LuaInterface::GetScriptName(lua_State* state) +{ + map::iterator itr; + MItemScripts.writelock(__FUNCTION__, __LINE__); + itr = item_inverse_scripts.find(state); + if (itr != item_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + itr = spawn_inverse_scripts.find(state); + if (itr != spawn_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); + + MZoneScripts.writelock(__FUNCTION__, __LINE__); + itr = zone_inverse_scripts.find(state); + if (itr != zone_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); + + MRegionScripts.writelock(__FUNCTION__, __LINE__); + itr = region_inverse_scripts.find(state); + if (itr != region_inverse_scripts.end()) + { + const char* scriptName = itr->second.c_str(); + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); + return scriptName; + } + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); + + MSpells.lock(); + LuaSpell* spell = GetCurrentSpell(state, false); + if (spell) + { + const char* fileName = (spell->file_name.length() > 0) ? spell->file_name.c_str() : ""; + MSpells.unlock(); + return fileName; + } + MSpells.unlock(); + + return ""; +} + +bool LuaInterface::LoadSpawnScript(string name) { + return LoadSpawnScript(name.c_str()); +} + +bool LuaInterface::LoadZoneScript(string name) { + return LoadZoneScript(name.c_str()); +} + +bool LuaInterface::LoadRegionScript(string name) { + return LoadRegionScript(name.c_str()); +} + +std::string LuaInterface::AddSpawnPointers(LuaSpell* spell, bool first_cast, bool precast, const char* function, SpellScriptTimer* timer, bool passLuaSpell, Spawn* altTarget) { + std::string functionCalled = string(""); + if (function) + { + functionCalled = string(function); + lua_getglobal(spell->state, function); + } + else if (precast) + { + functionCalled = "precast"; + lua_getglobal(spell->state, "precast"); + } + else if(first_cast) + { + functionCalled = "cast"; + lua_getglobal(spell->state, "cast"); + } + else + { + functionCalled = "tick"; + lua_getglobal(spell->state, "tick"); + } + + LogWrite(SPELL__DEBUG, 0, "Spell", "LuaInterface::AddSpawnPointers spell %s (%u) function %s, caster %s.", spell->spell ? spell->spell->GetName() : "UnknownUnset", spell->spell ? spell->spell->GetSpellID() : 0, functionCalled.c_str(), spell->caster ? spell->caster->GetName() : "Unknown"); + + if (!lua_isfunction(spell->state, lua_gettop(spell->state))){ + lua_pop(spell->state, 1); + return string(""); + } + else { + lua_getglobal(spell->state, functionCalled.c_str()); + } + + if(passLuaSpell) + SetSpellValue(spell->state, spell); + + Spawn* temp_spawn = 0; + if (timer && timer->caster && spell->caster && spell->caster->GetZone()) + temp_spawn = spell->caster->GetZone()->GetSpawnByID(timer->caster); + + if (temp_spawn) + SetSpawnValue(spell->state, temp_spawn); + else if (spell->caster) + SetSpawnValue(spell->state, spell->caster); + + temp_spawn = 0; + + if (timer && timer->target && spell->caster && spell->caster->GetZone()) + temp_spawn = spell->caster->GetZone()->GetSpawnByID(timer->target); + + if (temp_spawn) + SetSpawnValue(spell->state, temp_spawn); + else { + if(altTarget) + { + SetSpawnValue(spell->state, altTarget); + } + else if(spell->caster && spell->caster->GetZone() != nullptr && spell->initial_target) + { + // easier to debug target id as ptr + Spawn* new_target = spell->caster->GetZone()->GetSpawnByID(spell->initial_target); + SetSpawnValue(spell->state, new_target); + } + else if(spell->caster && spell->caster->GetTarget()) + SetSpawnValue(spell->state, spell->caster->GetTarget()); + else + SetSpawnValue(spell->state, 0); + } + + return functionCalled; +} + +LuaSpell* LuaInterface::GetCurrentSpell(lua_State* state, bool needsLock) { + LuaSpell* spell = 0; + + if(needsLock) + MSpells.lock(); + + if(current_spells.count(state) > 0) + spell = current_spells[state]; + + if(needsLock) + MSpells.unlock(); + + return spell; +} + +void LuaInterface::RemoveCurrentSpell(lua_State* state, bool needsLock) { + if(needsLock) { + MSpells.lock(); + MSpellDelete.lock(); + } + map::iterator itr = current_spells.find(state); + if(itr != current_spells.end()) + current_spells.erase(itr); + if(needsLock) { + MSpellDelete.unlock(); + MSpells.unlock(); + } +} + +bool LuaInterface::CallSpellProcess(LuaSpell* spell, int8 num_parameters, std::string customFunction) { + if(shutting_down || !spell || !spell->caster) + return false; + + MSpells.lock(); + current_spells[spell->state] = spell; + MSpells.unlock(); + + LogWrite(SPELL__DEBUG, 0, "Spell", "LuaInterface::CallSpellProcess spell %s (%u) function %s, caster %s.", spell->spell ? spell->spell->GetName() : "UnknownUnset", spell->spell ? spell->spell->GetSpellID() : 0, customFunction.c_str(), spell->caster->GetName()); + + if(lua_pcall(spell->state, num_parameters, 0, 0) != 0){ + LogError("Error running function '%s' in %s: %s", customFunction.c_str(), spell->spell->GetName(), lua_tostring(spell->state, -1)); + lua_pop(spell->state, 1); + RemoveSpell(spell, false); // may be in a lock + return false; + } + return true; +} + +void LuaInterface::RemoveSpawnScript(const char* name) { + lua_State* state = 0; + Mutex* mutex = GetSpawnScriptMutex(name); + while((state = GetSpawnScript(name, false))){ + mutex->writelock(__FUNCTION__, __LINE__); + lua_close(state); + spawn_scripts[name].erase(state); + mutex->releasewritelock(__FUNCTION__, __LINE__); + } + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + spawn_scripts.erase(name); + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters, std::string* returnValue) { + if(shutting_down) + return false; + if(!state || lua_pcall(state, num_parameters, 1, 0) != 0){ + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + std::string result = std::string(""); + + if(lua_isstring(state, -1)){ + size_t size = 0; + const char* str = lua_tolstring(state, -1, &size); + if(str) + result = string(str); + } + + if(returnValue) + *returnValue = std::string(result); + + return true; +} + +bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue) { + if(shutting_down) + return false; + if(!state || lua_pcall(state, num_parameters, 1, 0) != 0){ + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + sint64 result = 0; + + if (lua_isnumber(state, -1)) + { + result = (sint64)lua_tonumber(state, -1); + lua_pop(state, 1); + } + + if(returnValue) + *returnValue = result; + + return true; +} + +bool LuaInterface::CallSpawnScript(lua_State* state, int8 num_parameters) { + if(shutting_down || lua_system_reloading) + return false; + if(!state || lua_pcall(state, num_parameters, 0, 0) != 0){ + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + return true; +} + +bool LuaInterface::CallScriptInt32(lua_State* state, int8 num_parameters, int32* returnValue) { + if(shutting_down) + return false; + if (!state || lua_pcall(state, num_parameters, 1, 0) != 0) { + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + int32 result = 0; + + if (lua_isnumber(state, -1)) + { + result = (int32)lua_tonumber(state, -1); + lua_pop(state, 1); + } + + if(returnValue) + *returnValue = result; + + return true; +} + +bool LuaInterface::CallScriptSInt32(lua_State* state, int8 num_parameters, sint32* returnValue) { + if(shutting_down) + return false; + if (!state || lua_pcall(state, num_parameters, 1, 0) != 0) { + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + sint32 result = 0; + + if (lua_isnumber(state, -1)) + { + result = (sint32)lua_tointeger(state, -1); + lua_pop(state, 1); + } + + if(returnValue) + *returnValue = result; + + return true; +} + +bool LuaInterface::CallRegionScript(lua_State* state, int8 num_parameters, int32* returnValue) { + if(shutting_down) + return false; + if (!state || lua_pcall(state, num_parameters, 1, 0) != 0) { + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + int32 result = 0; + + if (lua_isnumber(state, -1)) + { + result = (int32)lua_tonumber(state, -1); + lua_pop(state, 1); + } + + if(returnValue) + *returnValue = result; + + return true; +} + +lua_State* LuaInterface::LoadLuaFile(const char* name) { + if(shutting_down) + return 0; + lua_State* state = luaL_newstate(); + luaL_openlibs(state); + if(luaL_dofile(state, name) == 0){ + RegisterFunctions(state); + return state; + } + else{ + LogError("Error loading %s (file name: '%s')", lua_tostring(state, -1), name); + lua_pop(state, 1); + lua_close(state); + } + return 0; +} + +void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool can_delete, string reason, bool removing_all_spells) { + if(call_remove_function){ + lua_getglobal(spell->state, "remove"); + LUASpawnWrapper* spawn_wrapper = new LUASpawnWrapper(); + spawn_wrapper->spawn = spell->caster; + AddUserDataPtr(spawn_wrapper, spawn_wrapper->spawn); + lua_pushlightuserdata(spell->state, spawn_wrapper); + if(spell->caster && (spell->initial_target || spell->caster->GetTarget())){ + spawn_wrapper = new LUASpawnWrapper(); + if(!spell->initial_target) + spawn_wrapper->spawn = spell->caster->GetTarget(); + else if(spell->caster->GetZone()) { + spawn_wrapper->spawn = spell->caster->GetZone()->GetSpawnByID(spell->initial_target); + } + else { + spawn_wrapper->spawn = nullptr; // we need it set to something or else the ptr could be loose + } + AddUserDataPtr(spawn_wrapper, spawn_wrapper->spawn); + lua_pushlightuserdata(spell->state, spawn_wrapper); + } + else + lua_pushlightuserdata(spell->state, 0); + + if (spell->caster && !spell->caster->Alive()) + reason = "dead"; + + lua_pushstring(spell->state, (char*)reason.c_str()); + + MSpells.lock(); + current_spells[spell->state] = spell; + MSpells.unlock(); + lua_pcall(spell->state, 3, 0, 0); + ResetFunctionStack(spell->state); + } + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if(spell->caster) { + for (int8 i = 0; i < spell->targets.size(); i++) { + if(!spell->caster->GetZone()) + continue; + + Spawn* target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + + ((Entity*)target)->RemoveProc(0, spell); + ((Entity*)target)->RemoveSpellEffect(spell); + ((Entity*)target)->RemoveSpellBonus(spell); + } + } + + multimap::iterator entries; + for(entries = spell->char_id_targets.begin(); entries != spell->char_id_targets.end(); entries++) + { + Client* tmpClient = zone_list.GetClientByCharID(entries->first); + if(tmpClient && tmpClient->GetPlayer()) + { + tmpClient->GetPlayer()->RemoveProc(0, spell); + tmpClient->GetPlayer()->RemoveSpellEffect(spell); + tmpClient->GetPlayer()->RemoveSpellBonus(spell); + } + } + spell->char_id_targets.clear(); // TODO: reach out to those clients in different + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + if(removing_all_spells) { + if(spell->caster && spell->caster->GetZone()) { + spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell); + spell->caster->GetZone()->GetSpellProcess()->DeleteSpell(spell); + } + } + else { + AddPendingSpellDelete(spell); + if (spell->caster) + { + if(spell->caster->GetZone()) { + spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell); + } + spell->caster->RemoveProc(0, spell); + spell->caster->RemoveMaintainedSpell(spell); + + int8 spell_type = spell->spell->GetSpellData()->spell_type; + if(spell->caster->IsPlayer() && !removing_all_spells) + { + Player* player = (Player*)spell->caster; + switch(spell_type) + { + case SPELL_TYPE_FOOD: + if(player->get_character_flag(CF_FOOD_AUTO_CONSUME) && player->GetClient()) + { + Item* item = nullptr; + if(player->GetActiveFoodUniqueID()) { + item = player->item_list.GetItemFromUniqueID(player->GetActiveFoodUniqueID()); + } + if(item == nullptr) { + item = player->GetEquipmentList()->GetItem(EQ2_FOOD_SLOT); + } + if(item && player->GetClient()->CheckConsumptionAllowed(EQ2_FOOD_SLOT, false)) + player->GetClient()->ConsumeFoodDrink(item, EQ2_FOOD_SLOT); + } + break; + case SPELL_TYPE_DRINK: + if(player->get_character_flag(CF_DRINK_AUTO_CONSUME) && player->GetClient()) + { + Item* item = nullptr; + if(player->GetActiveDrinkUniqueID()) { + item = player->item_list.GetItemFromUniqueID(player->GetActiveDrinkUniqueID()); + } + if(item == nullptr) { + item = player->GetEquipmentList()->GetItem(EQ2_FOOD_SLOT); + } + + item = player->GetEquipmentList()->GetItem(EQ2_DRINK_SLOT); + if(item && player->GetClient()->CheckConsumptionAllowed(EQ2_DRINK_SLOT, false)) + player->GetClient()->ConsumeFoodDrink(item, EQ2_DRINK_SLOT); + } + break; + } + } + } + } +} + +void LuaInterface::RegisterFunctions(lua_State* state) { + lua_register(state, "SetHP", EQ2Emu_lua_SetCurrentHP); + lua_register(state, "SetMaxHP", EQ2Emu_lua_SetMaxHP); + lua_register(state, "SetMaxHPBase", EQ2Emu_lua_SetMaxHPBase); + lua_register(state, "SetPower", EQ2Emu_lua_SetCurrentPower); + lua_register(state, "SetMaxPower", EQ2Emu_lua_SetMaxPower); + lua_register(state, "SetMaxPowerBase", EQ2Emu_lua_SetMaxPowerBase); + lua_register(state, "ModifyMaxHP", EQ2Emu_lua_ModifyMaxHP); + lua_register(state, "ModifyMaxPower", EQ2Emu_lua_ModifyMaxPower); + lua_register(state, "SetPosition", EQ2Emu_lua_SetPosition); + lua_register(state, "SetHeading", EQ2Emu_lua_SetHeading); + lua_register(state, "SetModelType", EQ2Emu_lua_SetModelType); + lua_register(state, "SetAdventureClass", EQ2Emu_lua_SetAdventureClass); + lua_register(state, "SetTradeskillClass", EQ2Emu_lua_SetTradeskillClass); + lua_register(state, "SetMount", EQ2Emu_lua_SetMount); + lua_register(state, "SetMountColor", EQ2Emu_lua_SetMountColor); + lua_register(state, "GetMount", EQ2Emu_lua_GetMount); + lua_register(state, "GetRace", EQ2Emu_lua_GetRace); + lua_register(state, "GetRaceName", EQ2Emu_lua_GetRaceName); + lua_register(state, "GetClass", EQ2Emu_lua_GetClass); + lua_register(state, "GetClassName", EQ2Emu_lua_GetClassName); + lua_register(state, "GetArchetypeName", EQ2Emu_lua_GetArchetypeName); + lua_register(state, "SetSpeed", EQ2Emu_lua_SetSpeed); + lua_register(state, "ModifyPower", EQ2Emu_lua_ModifyPower); + lua_register(state, "ModifyHP", EQ2Emu_lua_ModifyHP); + + lua_register(state, "GetDistance", EQ2Emu_lua_GetDistance); + lua_register(state, "GetHeading", EQ2Emu_lua_GetHeading); + lua_register(state, "GetLevel", EQ2Emu_lua_GetLevel); + lua_register(state, "GetDifficulty", EQ2Emu_lua_GetDifficulty); + lua_register(state, "GetHP", EQ2Emu_lua_GetCurrentHP); + lua_register(state, "GetMaxHP", EQ2Emu_lua_GetMaxHP); + lua_register(state, "GetMaxHPBase", EQ2Emu_lua_GetMaxHPBase); + lua_register(state, "GetMaxPower", EQ2Emu_lua_GetMaxPower); + lua_register(state, "GetMaxPowerBase", EQ2Emu_lua_GetMaxPowerBase); + lua_register(state, "GetName", EQ2Emu_lua_GetName); + lua_register(state, "GetPower", EQ2Emu_lua_GetCurrentPower); + lua_register(state, "GetX", EQ2Emu_lua_GetX); + lua_register(state, "GetY", EQ2Emu_lua_GetY); + lua_register(state, "GetZ", EQ2Emu_lua_GetZ); + lua_register(state, "GetSpawnID", EQ2Emu_lua_GetSpawnID); + lua_register(state, "GetSpawnGroupID", EQ2Emu_lua_GetSpawnGroupID); + lua_register(state, "SetSpawnGroupID", EQ2Emu_lua_SetSpawnGroupID); + lua_register(state, "AddSpawnToGroup", EQ2Emu_lua_AddSpawnToGroup); + lua_register(state, "GetSpawnLocationID", EQ2Emu_lua_GetSpawnLocationID); + lua_register(state, "GetSpawnLocationPlacementID", EQ2Emu_lua_GetSpawnLocationPlacementID); + lua_register(state, "GetSpawnListBySpawnID", EQ2Emu_lua_GetSpawnListBySpawnID); + lua_register(state, "GetSpawnListByRailID", EQ2Emu_lua_GetSpawnListByRailID); + lua_register(state, "GetPassengerSpawnList", EQ2Emu_lua_GetPassengerSpawnList); + lua_register(state, "GetSpawnFromList", EQ2Emu_lua_GetSpawnFromList); + lua_register(state, "GetSpawnListSize", EQ2Emu_lua_GetSpawnListSize); + lua_register(state, "SetFactionID", EQ2Emu_lua_SetFactionID); + lua_register(state, "GetFactionID", EQ2Emu_lua_GetFactionID); + lua_register(state, "GetFactionAmount", EQ2Emu_lua_GetFactionAmount); + lua_register(state, "ChangeFaction", EQ2Emu_lua_ChangeFaction); + lua_register(state, "GetGender", EQ2Emu_lua_GetGender); + lua_register(state, "GetTarget", EQ2Emu_lua_GetTarget); + lua_register(state, "HasFreeSlot", EQ2Emu_lua_HasFreeSlot); + lua_register(state, "HasItemEquipped", EQ2Emu_lua_HasItemEquipped); + lua_register(state, "GetEquippedItemByID", EQ2Emu_lua_GetEquippedItemByID); + lua_register(state, "SetEquippedItemByID", EQ2Emu_lua_SetEquippedItemByID); + lua_register(state, "SetEquippedItem", EQ2Emu_lua_SetEquippedItem); + lua_register(state, "UnequipSlot", EQ2Emu_lua_UnequipSlot); + lua_register(state, "SetEquipment", EQ2Emu_lua_SetEquipment); + lua_register(state, "GetEquippedItemBySlot", EQ2Emu_lua_GetEquippedItemBySlot); + lua_register(state, "GetItemByID", EQ2Emu_lua_GetItemByID); + lua_register(state, "GetItemType", EQ2Emu_lua_GetItemType); + lua_register(state, "GetItemEffectType", EQ2Emu_lua_GetItemEffectType); + lua_register(state, "GetSpellName", EQ2Emu_lua_GetSpellName); + lua_register(state, "PerformCameraShake", EQ2Emu_lua_PerformCameraShake); + lua_register(state, "GetModelType", EQ2Emu_lua_GetModelType); + lua_register(state, "GetSpeed", EQ2Emu_lua_GetSpeed); + lua_register(state, "HasMoved", EQ2Emu_lua_HasMoved); + lua_register(state, "SpellDamage", EQ2Emu_lua_SpellDamage); + lua_register(state, "SpellDamageExt", EQ2Emu_lua_SpellDamageExt); + lua_register(state, "CastSpell", EQ2Emu_lua_CastSpell); + lua_register(state, "SpellHeal", EQ2Emu_lua_SpellHeal); + lua_register(state, "SpellHealPct", EQ2Emu_lua_SpellHealPct); + lua_register(state, "AddItem", EQ2Emu_lua_AddItem); + lua_register(state, "SummonItem", EQ2Emu_lua_SummonItem); + lua_register(state, "RemoveItem", EQ2Emu_lua_RemoveItem); + lua_register(state, "HasItem", EQ2Emu_lua_HasItem); + lua_register(state, "SpawnMob", EQ2Emu_lua_Spawn); + lua_register(state, "SummonPet", EQ2Emu_lua_SummonPet); + lua_register(state, "AddSpawnAccess", EQ2Emu_lua_AddSpawnAccess); + lua_register(state, "GetZone", EQ2Emu_lua_GetZone); + lua_register(state, "GetZoneName", EQ2Emu_lua_GetZoneName); + lua_register(state, "GetZoneID", EQ2Emu_lua_GetZoneID); + lua_register(state, "Zone", EQ2Emu_lua_Zone); + lua_register(state, "AddHate", EQ2Emu_lua_AddHate); + lua_register(state, "IsAlive", EQ2Emu_lua_IsAlive); + lua_register(state, "IsInCombat", EQ2Emu_lua_IsInCombat); + lua_register(state, "Attack", EQ2Emu_lua_Attack); + lua_register(state, "ApplySpellVisual", EQ2Emu_lua_ApplySpellVisual); + + + lua_register(state, "IsPlayer", EQ2Emu_lua_IsPlayer); + lua_register(state, "GetCharacterID", EQ2Emu_lua_GetCharacterID); + lua_register(state, "FaceTarget", EQ2Emu_lua_FaceTarget); + lua_register(state, "MoveToLocation", EQ2Emu_lua_MoveToLocation); + lua_register(state, "ClearRunningLocations", EQ2Emu_lua_ClearRunningLocations); + lua_register(state, "Shout", EQ2Emu_lua_Shout); + lua_register(state, "Say", EQ2Emu_lua_Say); + lua_register(state, "SayOOC", EQ2Emu_lua_SayOOC); + lua_register(state, "Emote", EQ2Emu_lua_Emote); + lua_register(state, "MovementLoopAddLocation", EQ2Emu_lua_MovementLoopAdd); // do not remove this function, it is already heavily used by the content team + lua_register(state, "MovementLoopAdd", EQ2Emu_lua_MovementLoopAdd); +// lua_register(state, "GetCurrentZoneSafeLocation", EQ2Emu_lua_GetCurrentZoneSafeLocation); // This is already added below. + lua_register(state, "AddTimer", EQ2Emu_lua_AddTimer); + lua_register(state, "StopTimer", EQ2Emu_lua_StopTimer); + lua_register(state, "Harvest", EQ2Emu_lua_Harvest); + lua_register(state, "SetAttackable", EQ2Emu_lua_SetAttackable); + + + lua_register(state, "AddSpellBonus", EQ2Emu_lua_AddSpellBonus); + lua_register(state, "RemoveSpellBonus", EQ2Emu_lua_RemoveSpellBonus); + lua_register(state, "AddSkillBonus", EQ2Emu_lua_AddSkillBonus); + lua_register(state, "RemoveSkillBonus", EQ2Emu_lua_RemoveSkillBonus); + lua_register(state, "AddControlEffect", EQ2Emu_lua_AddControlEffect); + lua_register(state, "RemoveControlEffect", EQ2Emu_lua_RemoveControlEffect); + lua_register(state, "HasControlEffect", EQ2Emu_lua_HasControlEffect); + + lua_register(state, "GetBaseAggroRadius", EQ2Emu_lua_GetBaseAggroRadius); + lua_register(state, "GetAggroRadius", EQ2Emu_lua_GetAggroRadius); + lua_register(state, "SetAggroRadius", EQ2Emu_lua_SetAggroRadius); + + lua_register(state, "GetCurrentZoneSafeLocation", EQ2Emu_lua_GetCurrentZoneSafeLocation); + + + lua_register(state, "SetDeity", EQ2Emu_lua_SetDeity); + lua_register(state, "GetDeity", EQ2Emu_lua_GetDeity); + + + lua_register(state, "GetInt", EQ2Emu_lua_GetInt); + lua_register(state, "GetWis", EQ2Emu_lua_GetWis); + lua_register(state, "GetSta", EQ2Emu_lua_GetSta); + lua_register(state, "GetStr", EQ2Emu_lua_GetStr); + lua_register(state, "GetAgi", EQ2Emu_lua_GetAgi); + lua_register(state, "SetInt", EQ2Emu_lua_SetInt); + lua_register(state, "SetWis", EQ2Emu_lua_SetWis); + lua_register(state, "SetSta", EQ2Emu_lua_SetSta); + lua_register(state, "SetStr", EQ2Emu_lua_SetStr); + lua_register(state, "SetAgi", EQ2Emu_lua_SetAgi); + lua_register(state, "GetIntBase", EQ2Emu_lua_GetIntBase); + lua_register(state, "GetWisBase", EQ2Emu_lua_GetWisBase); + lua_register(state, "GetStaBase", EQ2Emu_lua_GetStaBase); + lua_register(state, "GetStrBase", EQ2Emu_lua_GetStrBase); + lua_register(state, "GetAgiBase", EQ2Emu_lua_GetAgiBase); + lua_register(state, "SetIntBase", EQ2Emu_lua_SetIntBase); + lua_register(state, "SetWisBase", EQ2Emu_lua_SetWisBase); + lua_register(state, "SetStaBase", EQ2Emu_lua_SetStaBase); + lua_register(state, "SetStrBase", EQ2Emu_lua_SetStrBase); + lua_register(state, "SetAgiBase", EQ2Emu_lua_SetAgiBase); + lua_register(state, "GetSpawn", EQ2Emu_lua_GetSpawn); + lua_register(state, "GetVariableValue", EQ2Emu_lua_GetVariableValue); + lua_register(state, "GetCoinMessage", EQ2Emu_lua_GetCoinMessage); + lua_register(state, "GetSpawnByGroupID", EQ2Emu_lua_GetSpawnByGroupID); + lua_register(state, "GetSpawnByLocationID", EQ2Emu_lua_GetSpawnByLocationID); + lua_register(state, "PlayFlavor", EQ2Emu_lua_PlayFlavor); + lua_register(state, "PlayFlavorID", EQ2Emu_lua_PlayFlavorID); + lua_register(state, "PlaySound", EQ2Emu_lua_PlaySound); + lua_register(state, "PlayVoice", EQ2Emu_lua_PlayVoice); + lua_register(state, "PlayAnimation", EQ2Emu_lua_PlayAnimation); + lua_register(state, "AddLootItem", EQ2Emu_lua_AddLootItem); + lua_register(state, "HasLootItem", EQ2Emu_lua_HasLootItem); + lua_register(state, "RemoveLootItem", EQ2Emu_lua_RemoveLootItem); + lua_register(state, "AddLootCoin", EQ2Emu_lua_AddLootCoin); + lua_register(state, "GiveLoot", EQ2Emu_lua_GiveLoot); + lua_register(state, "HasPendingLootItem", EQ2Emu_lua_HasPendingLootItem); + lua_register(state, "HasPendingLoot", EQ2Emu_lua_HasPendingLoot); + lua_register(state, "SetLootCoin", EQ2Emu_lua_SetLootCoin); + lua_register(state, "HasCoin", EQ2Emu_lua_HasCoin); + lua_register(state, "GetLootCoin", EQ2Emu_lua_GetLootCoin); + lua_register(state, "SetPlayerProximityFunction", EQ2Emu_lua_SetPlayerProximityFunction); + lua_register(state, "SetLocationProximityFunction", EQ2Emu_lua_SetLocationProximityFunction); + lua_register(state, "CreateConversation", EQ2Emu_lua_CreateConversation); + lua_register(state, "AddConversationOption", EQ2Emu_lua_AddConversationOption); + lua_register(state, "StartConversation", EQ2Emu_lua_StartConversation); + lua_register(state, "CloseConversation", EQ2Emu_lua_CloseConversation); + lua_register(state, "CloseItemConversation", EQ2Emu_lua_CloseItemConversation); + //lua_register(state, "StartItemConversation", EQ2Emu_lua_StartItemConversation); + lua_register(state, "StartDialogConversation", EQ2Emu_lua_StartDialogConversation); + lua_register(state, "SendStateCommand", EQ2Emu_lua_SendStateCommand); + lua_register(state, "SpawnSet", EQ2Emu_lua_SpawnSet); + lua_register(state, "SpawnSetByDistance", EQ2Emu_lua_SpawnSetByDistance); + lua_register(state, "SpawnMove", EQ2Emu_lua_SpawnMove); + lua_register(state, "KillSpawn", EQ2Emu_lua_KillSpawn); + lua_register(state, "KillSpawnByDistance", EQ2Emu_lua_KillSpawnByDistance); + lua_register(state, "Despawn", EQ2Emu_lua_Despawn); + lua_register(state, "ChangeHandIcon", EQ2Emu_lua_ChangeHandIcon); + lua_register(state, "SetVisualFlag", EQ2Emu_lua_SetVisualFlag); + lua_register(state, "SetInfoFlag", EQ2Emu_lua_SetInfoFlag); + lua_register(state, "IsBindAllowed", EQ2Emu_lua_IsBindAllowed); + lua_register(state, "IsGateAllowed", EQ2Emu_lua_IsGateAllowed); + lua_register(state, "Bind", EQ2Emu_lua_Bind); + lua_register(state, "Gate", EQ2Emu_lua_Gate); + lua_register(state, "SendMessage", EQ2Emu_lua_SendMessage); + lua_register(state, "SendPopUpMessage", EQ2Emu_lua_SendPopUpMessage); + lua_register(state, "SetServerControlFlag", EQ2Emu_lua_SetServerControlFlag); + lua_register(state, "ToggleTracking", EQ2Emu_lua_ToggleTracking); + lua_register(state, "AddPrimaryEntityCommand", EQ2Emu_lua_AddPrimaryEntityCommand); + lua_register(state, "AddSpellBookEntry", EQ2Emu_lua_AddSpellBookEntry); + lua_register(state, "DeleteSpellBook", EQ2Emu_lua_DeleteSpellBook); + lua_register(state, "RemoveSpellBookEntry", EQ2Emu_lua_RemoveSpellBookEntry); + lua_register(state, "SendNewAdventureSpells", EQ2Emu_lua_SendNewAdventureSpells); + lua_register(state, "SendNewTradeskillSpells", EQ2Emu_lua_SendNewTradeskillSpells); + lua_register(state, "HasSpell", EQ2Emu_lua_HasSpell); + lua_register(state, "Interrupt", EQ2Emu_lua_Interrupt); + lua_register(state, "Stealth", EQ2Emu_lua_Stealth); + lua_register(state, "IsInvis", EQ2Emu_lua_IsInvis); + lua_register(state, "IsStealthed", EQ2Emu_lua_IsStealthed); + lua_register(state, "AddSpawnIDAccess", EQ2Emu_lua_AddSpawnIDAccess); + lua_register(state, "RemoveSpawnIDAccess", EQ2Emu_lua_RemoveSpawnIDAccess); + lua_register(state, "HasRecipeBook", EQ2Emu_lua_HasRecipeBook); + + lua_register(state, "SetRequiredQuest", EQ2Emu_lua_SetRequiredQuest); + lua_register(state, "SetRequiredHistory", EQ2Emu_lua_SetRequiredHistory); + lua_register(state, "SetStepComplete", EQ2Emu_lua_SetStepComplete); + lua_register(state, "AddStepProgress", EQ2Emu_lua_AddStepProgress); + lua_register(state, "UpdateQuestTaskGroupDescription", EQ2Emu_lua_UpdateQuestTaskGroupDescription); + lua_register(state, "GetTaskGroupStep", EQ2Emu_lua_GetTaskGroupStep); + lua_register(state, "GetQuestStep", EQ2Emu_lua_GetQuestStep); + lua_register(state, "QuestStepIsComplete", EQ2Emu_lua_QuestStepIsComplete); + lua_register(state, "RegisterQuest", EQ2Emu_lua_RegisterQuest); + lua_register(state, "SetQuestPrereqLevel", EQ2Emu_lua_SetQuestPrereqLevel); + lua_register(state, "AddQuestPrereqQuest", EQ2Emu_lua_AddQuestPrereqQuest); + lua_register(state, "AddQuestPrereqItem", EQ2Emu_lua_AddQuestPrereqItem); + lua_register(state, "AddQuestPrereqFaction", EQ2Emu_lua_AddQuestPrereqFaction); + lua_register(state, "AddQuestPrereqRace", EQ2Emu_lua_AddQuestPrereqRace); + lua_register(state, "AddQuestPrereqModelType", EQ2Emu_lua_AddQuestPrereqModelType); + lua_register(state, "AddQuestPrereqClass", EQ2Emu_lua_AddQuestPrereqClass); + lua_register(state, "AddQuestPrereqTradeskillLevel", EQ2Emu_lua_AddQuestPrereqTradeskillLevel); + lua_register(state, "AddQuestPrereqTradeskillClass", EQ2Emu_lua_AddQuestPrereqTradeskillClass); + lua_register(state, "AddQuestSelectableRewardItem", EQ2Emu_lua_AddQuestSelectableRewardItem); + lua_register(state, "HasQuestRewardItem", EQ2Emu_lua_HasQuestRewardItem); + lua_register(state, "AddQuestRewardItem", EQ2Emu_lua_AddQuestRewardItem); + lua_register(state, "AddQuestRewardCoin", EQ2Emu_lua_AddQuestRewardCoin); + lua_register(state, "AddQuestRewardFaction", EQ2Emu_lua_AddQuestRewardFaction); + lua_register(state, "SetQuestRewardStatus", EQ2Emu_lua_SetQuestRewardStatus); + lua_register(state, "SetStatusTmpReward", EQ2Emu_lua_SetStatusTmpReward); + lua_register(state, "SetCoinTmpReward", EQ2Emu_lua_SetCoinTmpReward); + lua_register(state, "SetQuestRewardComment", EQ2Emu_lua_SetQuestRewardComment); + lua_register(state, "SetQuestRewardExp", EQ2Emu_lua_SetQuestRewardExp); + lua_register(state, "AddQuestStepKill", EQ2Emu_lua_AddQuestStepKill); + lua_register(state, "AddQuestStepKillByRace", EQ2Emu_lua_AddQuestStepKillByRace); + lua_register(state, "AddQuestStep", EQ2Emu_lua_AddQuestStep); + lua_register(state, "AddQuestStepChat", EQ2Emu_lua_AddQuestStepChat); + lua_register(state, "AddQuestStepObtainItem", EQ2Emu_lua_AddQuestStepObtainItem); + lua_register(state, "AddQuestStepZoneLoc", EQ2Emu_lua_AddQuestStepZoneLoc); + lua_register(state, "AddQuestStepLocation", EQ2Emu_lua_AddQuestStepLocation); + lua_register(state, "AddQuestStepSpell", EQ2Emu_lua_AddQuestStepSpell); + lua_register(state, "AddQuestStepCraft", EQ2Emu_lua_AddQuestStepCraft); + lua_register(state, "AddQuestStepHarvest", EQ2Emu_lua_AddQuestStepHarvest); + lua_register(state, "AddQuestStepCompleteAction", EQ2Emu_lua_AddQuestStepCompleteAction); + lua_register(state, "AddQuestStepProgressAction", EQ2Emu_lua_AddQuestStepProgressAction); + lua_register(state, "SetQuestCompleteAction", EQ2Emu_lua_SetQuestCompleteAction); + lua_register(state, "GiveQuestReward", EQ2Emu_lua_GiveQuestReward); + lua_register(state, "UpdateQuestStepDescription", EQ2Emu_lua_UpdateQuestStepDescription); + lua_register(state, "UpdateQuestDescription", EQ2Emu_lua_UpdateQuestDescription); + lua_register(state, "UpdateQuestZone", EQ2Emu_lua_UpdateQuestZone); + lua_register(state, "SetCompletedDescription", EQ2Emu_lua_SetCompletedDescription); + lua_register(state, "OfferQuest", EQ2Emu_lua_OfferQuest); + lua_register(state, "ProvidesQuest", EQ2Emu_lua_ProvidesQuest); + lua_register(state, "HasQuest", EQ2Emu_lua_HasQuest); + lua_register(state, "HasCompletedQuest", EQ2Emu_lua_HasCompletedQuest); + lua_register(state, "QuestIsComplete", EQ2Emu_lua_QuestIsComplete); + lua_register(state, "QuestReturnNPC", EQ2Emu_lua_QuestReturnNPC); + lua_register(state, "GetQuest", EQ2Emu_lua_GetQuest); + lua_register(state, "HasCollectionsToHandIn", EQ2Emu_lua_HasCollectionsToHandIn); + lua_register(state, "HandInCollections", EQ2Emu_lua_HandInCollections); + lua_register(state, "UseWidget", EQ2Emu_lua_UseWidget); + lua_register(state, "SetSpellList", EQ2Emu_lua_SetSpellList); + lua_register(state, "GetPet", EQ2Emu_lua_GetPet); + lua_register(state, "Charm", EQ2Emu_lua_Charm); + lua_register(state, "GetGroup", EQ2Emu_lua_GetGroup); + lua_register(state, "SetCompleteFlag", EQ2Emu_lua_SetCompleteFlag); + lua_register(state, "SetQuestYellow", EQ2Emu_lua_SetQuestYellow); + lua_register(state, "CanReceiveQuest", EQ2Emu_lua_CanReceiveQuest); + lua_register(state, "AddTransportSpawn", EQ2Emu_lua_AddTransportSpawn); + lua_register(state, "IsTransportSpawn", EQ2Emu_lua_IsTransportSpawn); + + // Option window + lua_register(state, "CreateOptionWindow", EQ2Emu_lua_CreateOptionWindow); + lua_register(state, "AddOptionWindowOption", EQ2Emu_lua_AddOptionWindowOption); + lua_register(state, "SendOptionWindow", EQ2Emu_lua_SendOptionWindow); + + lua_register(state, "GetTradeskillClass", EQ2Emu_lua_GetTradeskillClass); + lua_register(state, "GetTradeskillLevel", EQ2Emu_lua_GetTradeskillLevel); + lua_register(state, "GetTradeskillClassName", EQ2Emu_lua_GetTradeskillClassName); + lua_register(state, "SetTradeskillLevel", EQ2Emu_lua_SetTradeskillLevel); + + lua_register(state, "SummonDeityPet", EQ2Emu_lua_SummonDeityPet); + lua_register(state, "SummonCosmeticPet", EQ2Emu_lua_SummonCosmeticPet); + lua_register(state, "DismissPet", EQ2Emu_lua_DismissPet); + + lua_register(state, "GetCharmedPet", EQ2Emu_lua_GetCharmedPet); + lua_register(state, "GetDeityPet", EQ2Emu_lua_GetDeityPet); + lua_register(state, "GetCosmeticPet", EQ2Emu_lua_GetCosmeticPet); + + lua_register(state, "SetQuestFeatherColor", EQ2Emu_lua_SetQuestFeatherColor); + lua_register(state, "RemoveSpawnAccess", EQ2Emu_lua_RemoveSpawnAccess); + lua_register(state, "SpawnByLocationID", EQ2Emu_lua_SpawnByLocationID); + lua_register(state, "CastEntityCommand", EQ2Emu_lua_CastEntityCommand); + lua_register(state, "SetLuaBrain", EQ2Emu_lua_SetLuaBrain); + lua_register(state, "SetBrainTick", EQ2Emu_lua_SetBrainTick); + lua_register(state, "SetFollowTarget", EQ2Emu_lua_SetFollowTarget); + lua_register(state, "GetFollowTarget", EQ2Emu_lua_GetFollowTarget); + lua_register(state, "ToggleFollow", EQ2Emu_lua_ToggleFollow); + lua_register(state, "IsFollowing", EQ2Emu_lua_IsFollowing); + lua_register(state, "SetTempVariable", EQ2Emu_lua_SetTempVariable); + lua_register(state, "GetTempVariable", EQ2Emu_lua_GetTempVariable); + lua_register(state, "GiveQuestItem", EQ2Emu_lua_GiveQuestItem); + lua_register(state, "SetQuestRepeatable", EQ2Emu_lua_SetQuestRepeatable); + + lua_register(state, "AddWaypoint", EQ2Emu_lua_AddWaypoint); + lua_register(state, "RemoveWaypoint", EQ2Emu_lua_RemoveWaypoint); + lua_register(state, "SendWaypoints", EQ2Emu_lua_SendWaypoints); + + lua_register(state, "AddWard", EQ2Emu_lua_AddWard); + lua_register(state, "AddToWard", EQ2Emu_lua_AddToWard); + lua_register(state, "RemoveWard", EQ2Emu_lua_RemoveWard); + lua_register(state, "GetWardAmountLeft", EQ2Emu_lua_GetWardAmountLeft); + lua_register(state, "GetWardValue", EQ2Emu_lua_GetWardValue); + + lua_register(state, "SetTarget", EQ2Emu_lua_SetTarget); + lua_register(state, "IsPet", EQ2Emu_lua_IsPet); + lua_register(state, "GetOwner", EQ2Emu_lua_GetOwner); + lua_register(state, "SetInCombat", EQ2Emu_lua_SetInCombat); + lua_register(state, "CompareSpawns", EQ2Emu_lua_CompareSpawns); + lua_register(state, "ClearRunback", EQ2Emu_lua_ClearRunback); + lua_register(state, "Runback", EQ2Emu_lua_Runback); + lua_register(state, "GetRunbackDistance", EQ2Emu_lua_GetRunbackDistance); + lua_register(state, "IsCasting", EQ2Emu_lua_IsCasting); + lua_register(state, "IsMezzed", EQ2Emu_lua_IsMezzed); + lua_register(state, "IsStunned", EQ2Emu_lua_IsStunned); + lua_register(state, "IsMezzedOrStunned", EQ2Emu_lua_IsMezzedOrStunned); + lua_register(state, "ProcessSpell", EQ2Emu_lua_ProcessSpell); + lua_register(state, "ProcessMelee", EQ2Emu_lua_ProcessMelee); + lua_register(state, "HasRecovered", EQ2Emu_lua_HasRecovered); + lua_register(state, "GetEncounterSize", EQ2Emu_lua_GetEncounterSize); + lua_register(state, "GetMostHated", EQ2Emu_lua_GetMostHated); + lua_register(state, "ClearHate", EQ2Emu_lua_ClearHate); + lua_register(state, "ClearEncounter", EQ2Emu_lua_ClearEncounter); + lua_register(state, "GetEncounter", EQ2Emu_lua_GetEncounter); + lua_register(state, "GetHateList", EQ2Emu_lua_GetHateList); + lua_register(state, "HasGroup", EQ2Emu_lua_HasGroup); + lua_register(state, "HasSpellEffect", EQ2Emu_lua_HasSpellEffect); + + lua_register(state, "SetSuccessTimer", EQ2Emu_lua_SetSuccessTimer); + lua_register(state, "SetFailureTimer", EQ2Emu_lua_SetFailureTimer); + lua_register(state, "IsGroundSpawn", EQ2Emu_lua_IsGroundSpawn); + lua_register(state, "CanHarvest", EQ2Emu_lua_CanHarvest); + lua_register(state, "SummonDumbFirePet", EQ2Emu_lua_SummonDumbFirePet); + + lua_register(state, "GetSkillValue", EQ2Emu_lua_GetSkillValue); + lua_register(state, "GetSkillMaxValue", EQ2Emu_lua_GetSkillMaxValue); + lua_register(state, "GetSkillName", EQ2Emu_lua_GetSkillName); + lua_register(state, "SetSkillMaxValue", EQ2Emu_lua_SetSkillMaxValue); + lua_register(state, "SetSkillValue", EQ2Emu_lua_SetSkillValue); + lua_register(state, "GetSkill", EQ2Emu_lua_GetSkill); + lua_register(state, "GetSkillIDByName", EQ2Emu_lua_GetSkillIDByName); + lua_register(state, "HasSkill", EQ2Emu_lua_HasSkill); + lua_register(state, "AddSkill", EQ2Emu_lua_AddSkill); + lua_register(state, "IncreaseSkillCapsByType", EQ2Emu_lua_IncreaseSkillCapsByType); + lua_register(state, "RemoveSkill", EQ2Emu_lua_RemoveSkill); + lua_register(state, "AddProc", EQ2Emu_lua_AddProc); + lua_register(state, "AddProcExt", EQ2Emu_lua_AddProcExt); + lua_register(state, "RemoveProc", EQ2Emu_lua_RemoveProc); + lua_register(state, "Knockback", EQ2Emu_lua_Knockback); + + lua_register(state, "IsEpic", EQ2Emu_lua_IsEpic); + lua_register(state, "IsHeroic", EQ2Emu_lua_IsHeroic); + lua_register(state, "ProcDamage", EQ2Emu_lua_ProcDamage); + lua_register(state, "LastSpellAttackHit", EQ2Emu_lua_LastSpellAttackHit); + lua_register(state, "IsBehind", EQ2Emu_lua_IsBehind); + lua_register(state, "IsFlanking", EQ2Emu_lua_IsFlanking); + lua_register(state, "InFront", EQ2Emu_lua_InFront); + lua_register(state, "AddSpellTimer", EQ2Emu_lua_AddSpellTimer); + lua_register(state, "GetItemCount", EQ2Emu_lua_GetItemCount); + lua_register(state, "SetItemCount", EQ2Emu_lua_SetItemCount); + lua_register(state, "Resurrect", EQ2Emu_lua_Resurrect); + lua_register(state, "BreatheUnderwater", EQ2Emu_lua_BreatheUnderwater); + lua_register(state, "BlurVision", EQ2Emu_lua_BlurVision); + lua_register(state, "SetVision", EQ2Emu_lua_SetVision); + lua_register(state, "GetItemSkillReq", EQ2Emu_lua_GetItemSkillReq); + lua_register(state, "SetSpeedMultiplier", EQ2Emu_lua_SetSpeedMultiplier); + lua_register(state, "SetIllusion", EQ2Emu_lua_SetIllusion); + lua_register(state, "ResetIllusion", EQ2Emu_lua_ResetIllusion); + lua_register(state, "AddThreatTransfer", EQ2Emu_lua_AddThreatTransfer); + lua_register(state, "RemoveThreatTransfer", EQ2Emu_lua_RemoveThreatTransfer); + lua_register(state, "CureByType", EQ2Emu_lua_CureByType); + lua_register(state, "CureByControlEffect", EQ2Emu_lua_CureByControlEffect); + lua_register(state, "AddSpawnSpellBonus", EQ2Emu_lua_AddSpawnSpellBonus); + lua_register(state, "RemoveSpawnSpellBonus", EQ2Emu_lua_RemoveSpawnSpellBonus); + lua_register(state, "CancelSpell", EQ2Emu_lua_CancelSpell); + lua_register(state, "RemoveStealth", EQ2Emu_lua_RemoveStealth); + lua_register(state, "RemoveInvis", EQ2Emu_lua_RemoveInvis); + lua_register(state, "StartHeroicOpportunity", EQ2Emu_lua_StartHeroicOpportunity); + lua_register(state, "CopySpawnAppearance", EQ2Emu_lua_CopySpawnAppearance); + lua_register(state, "SetSpellTriggerCount", EQ2Emu_lua_SetSpellTriggerCount); + lua_register(state, "GetSpellTriggerCount", EQ2Emu_lua_GetSpellTriggerCount); + lua_register(state, "RemoveTriggerFromSpell", EQ2Emu_lua_RemoveTriggerFromSpell); + lua_register(state, "HasSpellImmunity", EQ2Emu_lua_HasSpellImmunity); + lua_register(state, "AddImmunitySpell", EQ2Emu_lua_AddImmunitySpell); + lua_register(state, "RemoveImmunitySpell", EQ2Emu_lua_RemoveImmunitySpell); + lua_register(state, "SetSpellSnareValue", EQ2Emu_lua_SetSpellSnareValue); + lua_register(state, "CheckRaceType", EQ2Emu_lua_CheckRaceType); + lua_register(state, "GetRaceType", EQ2Emu_lua_GetRaceType); + lua_register(state, "GetRaceBaseType", EQ2Emu_lua_GetRaceBaseType); + lua_register(state, "GetQuestFlags", EQ2Emu_lua_GetQuestFlags); + lua_register(state, "SetQuestFlags", EQ2Emu_lua_SetQuestFlags); + lua_register(state, "SetQuestTimer", EQ2Emu_lua_SetQuestTimer); + lua_register(state, "RemoveQuestStep", EQ2Emu_lua_RemoveQuestStep); + lua_register(state, "ResetQuestStep", EQ2Emu_lua_ResetQuestStep); + lua_register(state, "SetQuestTimerComplete", EQ2Emu_lua_SetQuestTimerComplete); + lua_register(state, "AddQuestStepFailureAction", EQ2Emu_lua_AddQuestStepFailureAction); + lua_register(state, "SetStepFailed", EQ2Emu_lua_SetStepFailed); + lua_register(state, "GetQuestCompleteCount", EQ2Emu_lua_GetQuestCompleteCount); + lua_register(state, "SetServerVariable", EQ2Emu_lua_SetServerVariable); + lua_register(state, "GetServerVariable", EQ2Emu_lua_GetServerVariable); + lua_register(state, "HasLanguage", EQ2Emu_lua_HasLanguage); + lua_register(state, "AddLanguage", EQ2Emu_lua_AddLanguage); + lua_register(state, "IsNight", EQ2Emu_lua_IsNight); + lua_register(state, "AddMultiFloorLift", EQ2Emu_lua_AddMultiFloorLift); + lua_register(state, "StartAutoMount", EQ2Emu_lua_StartAutoMount); + lua_register(state, "EndAutoMount", EQ2Emu_lua_EndAutoMount); + lua_register(state, "IsOnAutoMount", EQ2Emu_lua_IsOnAutoMount); + lua_register(state, "SetPlayerHistory", EQ2Emu_lua_SetPlayerHistory); + lua_register(state, "GetPlayerHistory", EQ2Emu_lua_GetPlayerHistory); + lua_register(state, "SetGridID", EQ2Emu_lua_SetGridID); + lua_register(state, "GetQuestStepProgress", EQ2Emu_lua_GetQuestStepProgress); + lua_register(state, "SetPlayerLevel", EQ2Emu_lua_SetPlayerLevel); + lua_register(state, "AddCoin", EQ2Emu_lua_AddCoin); + lua_register(state, "RemoveCoin", EQ2Emu_lua_RemoveCoin); + lua_register(state, "GetPlayersInZone", EQ2Emu_lua_GetPlayersInZone); + lua_register(state, "SpawnGroupByID", EQ2Emu_lua_SpawnGroupByID); + lua_register(state, "SetSpawnAnimation", EQ2Emu_lua_SetSpawnAnimation); + lua_register(state, "GetClientVersion", EQ2Emu_lua_GetClientVersion); + lua_register(state, "GetItemID", EQ2Emu_lua_GetItemID); + lua_register(state, "IsEntity", EQ2Emu_lua_IsEntity); + lua_register(state, "GetOrigX", EQ2Emu_lua_GetOrigX); + lua_register(state, "GetOrigY", EQ2Emu_lua_GetOrigY); + lua_register(state, "GetOrigZ", EQ2Emu_lua_GetOrigZ); + lua_register(state, "GetPCTOfHP", EQ2Emu_lua_GetPCTOfHP); + lua_register(state, "GetPCTOfPower", EQ2Emu_lua_GetPCTOfPower); + lua_register(state, "GetBoundZoneID", EQ2Emu_lua_GetBoundZoneID); + lua_register(state, "Evac", EQ2Emu_lua_Evac); + lua_register(state, "GetSpellTier", EQ2Emu_lua_GetSpellTier); + lua_register(state, "GetSpellID", EQ2Emu_lua_GetSpellID); + lua_register(state, "StartTransmute", EQ2Emu_lua_StartTransmute); + lua_register(state, "CompleteTransmute", EQ2Emu_lua_CompleteTransmute); + lua_register(state, "ProcHate", EQ2Emu_lua_ProcHate); + + lua_register(state, "GetRandomSpawnByID", EQ2Emu_lua_GetRandomSpawnByID); + lua_register(state, "ShowLootWindow", EQ2Emu_lua_ShowLootWindow); + lua_register(state, "AddPrimaryEntityCommandAllSpawns", EQ2Emu_lua_AddPrimaryEntityCommandAllSpawns); + lua_register(state, "InstructionWindow", EQ2Emu_lua_InstructionWindow); + lua_register(state, "InstructionWindowClose", EQ2Emu_lua_InstructionWindowClose); + lua_register(state, "InstructionWindowGoal", EQ2Emu_lua_InstructionWindowGoal); + lua_register(state, "ShowWindow", EQ2Emu_lua_ShowWindow); + lua_register(state, "FlashWindow", EQ2Emu_lua_FlashWindow); + lua_register(state, "EnableGameEvent", EQ2Emu_lua_EnableGameEvent); + lua_register(state, "GetTutorialStep", EQ2Emu_lua_GetTutorialStep); + lua_register(state, "SetTutorialStep", EQ2Emu_lua_SetTutorialStep); + lua_register(state, "DisplayText", EQ2Emu_lua_DisplayText); + lua_register(state, "GiveExp", EQ2Emu_lua_GiveExp); + + lua_register(state, "CheckLOS", EQ2Emu_lua_CheckLOS); + lua_register(state, "CheckLOSByCoordinates", EQ2Emu_lua_CheckLOSByCoordinates); + + lua_register(state, "SetZoneExpansionFlag", EQ2Emu_lua_SetZoneExpansionFlag); + lua_register(state, "GetZoneExpansionFlag", EQ2Emu_lua_GetZoneExpansionFlag); + lua_register(state, "SetZoneHolidayFlag", EQ2Emu_lua_SetZoneHolidayFlag); + lua_register(state, "GetZoneHolidayFlag", EQ2Emu_lua_GetZoneHolidayFlag); + + lua_register(state, "SetCanBind", EQ2Emu_lua_SetCanBind); + lua_register(state, "GetCanBind", EQ2Emu_lua_GetCanBind); + + lua_register(state, "GetCanGate", EQ2Emu_lua_GetCanGate); + lua_register(state, "SetCanGate", EQ2Emu_lua_SetCanGate); + + lua_register(state, "SetCanEvac", EQ2Emu_lua_SetCanEvac); + lua_register(state, "GetCanEvac", EQ2Emu_lua_GetCanEvac); + + lua_register(state, "AddSpawnProximity", EQ2Emu_lua_AddSpawnProximity); + + lua_register(state, "CanSeeInvis", EQ2Emu_lua_CanSeeInvis); + lua_register(state, "SetSeeInvis", EQ2Emu_lua_SetSeeInvis); + lua_register(state, "SetSeeHide", EQ2Emu_lua_SetSeeHide); + + lua_register(state, "SetAccessToEntityCommand", EQ2Emu_lua_SetAccessToEntityCommand); + lua_register(state, "SetAccessToEntityCommandByCharID", EQ2Emu_lua_SetAccessToEntityCommandByCharID); + lua_register(state, "RemovePrimaryEntityCommand", EQ2Emu_lua_RemovePrimaryEntityCommand); + lua_register(state, "SendUpdateDefaultCommand", EQ2Emu_lua_SendUpdateDefaultCommand); + + lua_register(state, "SendTransporters", EQ2Emu_lua_SendTransporters); + lua_register(state, "SetTemporaryTransportID", EQ2Emu_lua_SetTemporaryTransportID); + lua_register(state, "GetTemporaryTransportID", EQ2Emu_lua_GetTemporaryTransportID); + + lua_register(state, "SetAlignment", EQ2Emu_lua_SetAlignment); + lua_register(state, "GetAlignment", EQ2Emu_lua_GetAlignment); + + lua_register(state, "GetSpell", EQ2Emu_lua_GetSpell); + lua_register(state, "GetSpellData", EQ2Emu_lua_GetSpellData); + lua_register(state, "SetSpellData", EQ2Emu_lua_SetSpellData); + lua_register(state, "CastCustomSpell", EQ2Emu_lua_CastCustomSpell); + + lua_register(state, "SetSpellDataIndex", EQ2Emu_lua_SetSpellDataIndex); + lua_register(state, "GetSpellDataIndex", EQ2Emu_lua_GetSpellDataIndex); + + lua_register(state, "SetSpellDisplayEffect", EQ2Emu_lua_SetSpellDisplayEffect); + lua_register(state, "GetSpellDisplayEffect", EQ2Emu_lua_GetSpellDisplayEffect); + + lua_register(state, "InWater", EQ2Emu_lua_InWater); + lua_register(state, "InLava", EQ2Emu_lua_InLava); + + lua_register(state, "DamageSpawn", EQ2Emu_lua_DamageSpawn); + lua_register(state, "IsInvulnerable", EQ2Emu_lua_IsInvulnerable); + lua_register(state, "SetInvulnerable", EQ2Emu_lua_SetInvulnerable); + + lua_register(state, "GetRuleFlagBool", EQ2Emu_lua_GetRuleFlagBool); + lua_register(state, "GetRuleFlagInt32", EQ2Emu_lua_GetRuleFlagInt32); + lua_register(state, "GetRuleFlagFloat", EQ2Emu_lua_GetRuleFlagFloat); + + lua_register(state, "GetAAInfo", EQ2Emu_lua_GetAAInfo); + lua_register(state, "SetAAInfo", EQ2Emu_lua_SetAAInfo); + + lua_register(state, "AddMasterTitle", EQ2Emu_lua_AddMasterTitle); + lua_register(state, "AddCharacterTitle", EQ2Emu_lua_AddCharacterTitle); + lua_register(state, "SetCharacterTitleSuffix", EQ2Emu_lua_SetCharacterTitleSuffix); + lua_register(state, "SetCharacterTitlePrefix", EQ2Emu_lua_SetCharacterTitlePrefix); + lua_register(state, "ResetCharacterTitleSuffix", EQ2Emu_lua_ResetCharacterTitleSuffix); + lua_register(state, "ResetCharacterTitlePrefix", EQ2Emu_lua_ResetCharacterTitlePrefix); + + lua_register(state, "GetInfoStructString", EQ2Emu_lua_GetInfoStructString); + lua_register(state, "GetInfoStructUInt", EQ2Emu_lua_GetInfoStructUInt); + lua_register(state, "GetInfoStructSInt", EQ2Emu_lua_GetInfoStructSInt); + lua_register(state, "GetInfoStructFloat", EQ2Emu_lua_GetInfoStructFloat); + + lua_register(state, "SetInfoStructString", EQ2Emu_lua_SetInfoStructString); + lua_register(state, "SetInfoStructUInt", EQ2Emu_lua_SetInfoStructUInt); + lua_register(state, "SetInfoStructSInt", EQ2Emu_lua_SetInfoStructSInt); + lua_register(state, "SetInfoStructFloat", EQ2Emu_lua_SetInfoStructFloat); + + lua_register(state, "SetCharSheetChanged", EQ2Emu_lua_SetCharSheetChanged); + + lua_register(state, "AddPlayerMail", EQ2Emu_lua_AddPlayerMail); + lua_register(state, "AddPlayerMailByCharID", EQ2Emu_lua_AddPlayerMailByCharID); + + lua_register(state, "OpenDoor", EQ2Emu_lua_OpenDoor); + lua_register(state, "CloseDoor", EQ2Emu_lua_CloseDoor); + lua_register(state, "IsOpen", EQ2Emu_lua_IsOpen); + + lua_register(state, "MakeRandomInt", EQ2Emu_lua_MakeRandomInt); + lua_register(state, "MakeRandomFloat", EQ2Emu_lua_MakeRandomFloat); + + lua_register(state, "AddIconValue", EQ2Emu_lua_AddIconValue); + lua_register(state, "RemoveIconValue", EQ2Emu_lua_RemoveIconValue); + + lua_register(state, "GetShardID", EQ2Emu_lua_GetShardID); + lua_register(state, "GetShardCharID", EQ2Emu_lua_GetShardCharID); + lua_register(state, "GetShardCreatedTimestamp", EQ2Emu_lua_GetShardCreatedTimestamp); + lua_register(state, "DeleteDBShardID", EQ2Emu_lua_DeleteDBShardID); + + lua_register(state, "PauseMovement", EQ2Emu_lua_PauseMovement); + lua_register(state, "StopMovement", EQ2Emu_lua_StopMovement); + + lua_register(state, "GetArrowColor", EQ2Emu_lua_GetArrowColor); + lua_register(state, "GetTSArrowColor", EQ2Emu_lua_GetTSArrowColor); + + lua_register(state, "GetSpawnByRailID", EQ2Emu_lua_GetSpawnByRailID); + lua_register(state, "SetRailID", EQ2Emu_lua_SetRailID); + lua_register(state, "IsZoneLoading", EQ2Emu_lua_IsZoneLoading); + lua_register(state, "IsRunning", EQ2Emu_lua_IsRunning); + + lua_register(state, "GetZoneLockoutTimer", EQ2Emu_lua_GetZoneLockoutTimer); + + lua_register(state, "SetWorldTime", EQ2Emu_lua_SetWorldTime); + lua_register(state, "GetWorldTimeYear", EQ2Emu_lua_GetWorldTimeYear); + lua_register(state, "GetWorldTimeMonth", EQ2Emu_lua_GetWorldTimeMonth); + lua_register(state, "GetWorldTimeHour", EQ2Emu_lua_GetWorldTimeHour); + lua_register(state, "GetWorldTimeMinute", EQ2Emu_lua_GetWorldTimeMinute); + lua_register(state, "SendTimeUpdate", EQ2Emu_lua_SendTimeUpdate); + + lua_register(state, "GetLootTier", EQ2Emu_lua_GetLootTier); + lua_register(state, "SetLootTier", EQ2Emu_lua_SetLootTier); + lua_register(state, "GetLootDropType", EQ2Emu_lua_GetLootDropType); + lua_register(state, "SetLootDropType", EQ2Emu_lua_SetLootDropType); + + lua_register(state, "DamageEquippedItems", EQ2Emu_lua_DamageEquippedItems); + + lua_register(state, "CreateWidgetRegion", EQ2Emu_lua_CreateWidgetRegion); + lua_register(state, "RemoveRegion", EQ2Emu_lua_RemoveRegion); + + lua_register(state, "SetPlayerPOVGhost", EQ2Emu_lua_SetPlayerPOVGhost); + + lua_register(state, "SetCastOnAggroComplete", EQ2Emu_lua_SetCastOnAggroComplete); + lua_register(state, "IsCastOnAggroComplete", EQ2Emu_lua_IsCastOnAggroComplete); + + lua_register(state, "AddRecipeBookToPlayer", EQ2Emu_lua_AddRecipeBookToPlayer); + lua_register(state, "RemoveRecipeFromPlayer", EQ2Emu_lua_RemoveRecipeFromPlayer); + + lua_register(state, "ReplaceWidgetFromClient", EQ2Emu_lua_ReplaceWidgetFromClient); + lua_register(state, "RemoveWidgetFromSpawnMap", EQ2Emu_lua_RemoveWidgetFromSpawnMap); + lua_register(state, "RemoveWidgetFromZoneMap", EQ2Emu_lua_RemoveWidgetFromZoneMap); + + lua_register(state, "SendHearCast", EQ2Emu_lua_SendHearCast); + + lua_register(state, "GetCharacterFlag", EQ2Emu_lua_GetCharacterFlag); + lua_register(state, "ToggleCharacterFlag", EQ2Emu_lua_ToggleCharacterFlag); + + lua_register(state, "GetSpellInitialTarget", EQ2Emu_lua_GetSpellInitialTarget); +} + +void LuaInterface::LogError(const char* error, ...) { + va_list argptr; + char buffer[4096]; + + va_start(argptr, error); + vsnprintf(buffer, sizeof(buffer), error, argptr); + va_end(argptr); + SimpleLogError(buffer); +} + +void LuaInterface::SimpleLogError(const char* error) { + ProcessErrorMessage(error); + LogWrite(LUA__ERROR, 0, "LUA", "%s", error); +} + +void LuaInterface::ResetFunctionStack(lua_State* state) { + lua_settop(state, 0); +} + +void LuaInterface::AddUserDataPtr(LUAUserData* data, void* data_ptr) { + std::unique_lock lock(MLUAUserData); + if(data_ptr) { + user_data_ptr[data_ptr] = data; + } + user_data[data] = Timer::GetCurrentTime2() + 300000; //allow a function to use this pointer for 5 minutes +} + +void LuaInterface::DeletePendingSpells(bool all) { + MSpells.lock(); + MSpellDelete.lock(); + if (spells_pending_delete.size() > 0) { + int32 time = Timer::GetCurrentTime2(); + map::iterator itr; + vector tmp_deletes; + vector::iterator del_itr; + for (itr = spells_pending_delete.begin(); itr != spells_pending_delete.end(); itr++) { + if (all || time >= itr->second) + tmp_deletes.push_back(itr->first); + } + LuaSpell* spell = 0; + for (del_itr = tmp_deletes.begin(); del_itr != tmp_deletes.end(); del_itr++) { + spell = *del_itr; + + if(!all) { + if (spell->caster && spell->caster->GetZone()) { + spell->caster->GetZone()->GetSpellProcess()->DeleteActiveSpell(spell); + } + else if(spell->targets.size() > 0 && spell->caster && spell->caster->GetZone()) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + Spawn* target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i)); + if (!target || !target->IsEntity()) + continue; + target->GetZone()->GetSpellProcess()->DeleteActiveSpell(spell); + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + } + + spells_pending_delete.erase(spell); + + if (spell->spell->IsCopiedSpell()) + { + RemoveCustomSpell(spell->spell->GetSpellID()); + safe_delete(spell->spell); + } + + SetLuaUserDataStale(spell); + RemoveCurrentSpell(spell->state, false); + safe_delete(spell); + } + } + MSpellDelete.unlock(); + MSpells.unlock(); +} + +void LuaInterface::DeletePendingSpell(LuaSpell* spell) { + MSpellDelete.lock(); + if (spells_pending_delete.size() > 0) { + map::iterator itr = spells_pending_delete.find(spell); + if (itr != spells_pending_delete.end()) + spells_pending_delete.erase(itr); + } + MSpellDelete.unlock(); +} + +void LuaInterface::DeleteUserDataPtrs(bool all) { + std::unique_lock lock(MLUAUserData); + if(user_data.size() > 0){ + map::iterator itr; + int32 time = Timer::GetCurrentTime2(); + vector tmp_deletes; + vector::iterator del_itr; + for(itr = user_data.begin(); itr != user_data.end(); itr++){ + if(all || time >= itr->second) + tmp_deletes.push_back(itr->first); + } + LUAUserData* data = 0; + for(del_itr = tmp_deletes.begin(); del_itr != tmp_deletes.end(); del_itr++){ + data = *del_itr; + + void* target = 0; + if(data->IsConversationOption()) { + target = data->conversation_options; + } + else if(data->IsOptionWindow()) { + target = data->option_window_option; + } + else if(data->IsSpawn()) { + target = data->spawn; + } + else if(data->IsQuest()) { + target = data->quest; + } + else if(data->IsZone()) { + target = data->zone; + } + else if(data->IsItem()) { + target = data->item; + } + else if(data->IsSkill()) { + target = data->skill; + } + else if(data->IsSpell()) { + target = data->spell; + } + if(target) { + std::map::iterator itr = user_data_ptr.find(target); + if(itr != user_data_ptr.end()) { + user_data_ptr.erase(itr); + } + } + user_data.erase(data); + safe_delete(data); + } + } +} + +Spawn* LuaInterface::GetSpawn(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + Spawn* ret = 0; + if (lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetSpawn error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsSpawn()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetSpawn in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->spawn; + } + return ret; +} + +vector* LuaInterface::GetConversation(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + vector* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetConversation error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsConversationOption()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetConversation in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->conversation_options; + } + return ret; +} + +vector* LuaInterface::GetOptionWindow(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + vector* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetOptionWindow error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsOptionWindow()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetOptionWindow in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->option_window_option; + } + return ret; +} + +Quest* LuaInterface::GetQuest(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + Quest* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetQuest error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsQuest()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetQuest in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->quest; + } + return ret; +} + +Item* LuaInterface::GetItem(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + Item* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetItem error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsItem()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetItem in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->item; + } + return ret; +} + +Skill* LuaInterface::GetSkill(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + Skill* ret = 0; + if (lua_islightuserdata(state, arg_num)) { + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if (!data || !data->IsCorrectlyInitialized()) { + LogError("%s: GetSkill error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if (!data->IsSkill()) { + lua_Debug ar; + lua_getstack(state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetSkill in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->skill; + } + return ret; +} + +LuaSpell* LuaInterface::GetSpell(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + LuaSpell* ret = 0; + if (lua_islightuserdata(state, arg_num)) { + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if (!data || !data->IsCorrectlyInitialized()) { + LogError("%s: GetSpell error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if (!data->IsSpell()) { + lua_Debug ar; + lua_getstack(state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetSpell in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->spell; + } + return ret; +} + +ZoneServer* LuaInterface::GetZone(lua_State* state, int8 arg_num) { + std::shared_lock lock(MLUAUserData); + ZoneServer* ret = 0; + if(lua_islightuserdata(state, arg_num)){ + LUAUserData* data = (LUAUserData*)lua_touserdata(state, arg_num); + if(!data || !data->IsCorrectlyInitialized()){ + LogError("%s: GetZone error while processing %s", GetScriptName(state), lua_tostring(state, -1)); + } + else if(!data->IsZone()){ + lua_Debug ar; + lua_getstack (state, 1, &ar); + lua_getinfo(state, "Sln", &ar); + LogError("%s: Invalid data type used for GetZone in %s (line %d)", GetScriptName(state), ar.source, ar.currentline); + } + else + ret = data->zone; + } + return ret; +} + +sint64 LuaInterface::GetSInt64Value(lua_State* state, int8 arg_num) { + sint64 val = 0; + if(lua_isnumber(state, arg_num)){ + val = (sint64)lua_tointeger(state, arg_num); + } + return val; +} + +int64 LuaInterface::GetInt64Value(lua_State* state, int8 arg_num) { + int64 val = 0; + if(lua_isnumber(state, arg_num)){ + val = (int64)lua_tonumber(state, arg_num); + } + return val; +} + +sint32 LuaInterface::GetSInt32Value(lua_State* state, int8 arg_num) { + sint32 val = 0; + if(lua_isnumber(state, arg_num)){ + val = lua_tointeger(state, arg_num); + } + return val; +} + +int32 LuaInterface::GetInt32Value(lua_State* state, int8 arg_num) { + int32 val = 0; + if(lua_isnumber(state, arg_num)){ + val = (int32)lua_tonumber(state, arg_num); + } + return val; +} + +int16 LuaInterface::GetInt16Value(lua_State* state, int8 arg_num) { + int16 val = 0; + if(lua_isnumber(state, arg_num)){ + val = lua_tointeger(state, arg_num); + } + return val; +} + +int8 LuaInterface::GetInt8Value(lua_State* state, int8 arg_num) { + int8 val = 0; + if(lua_isnumber(state, arg_num)){ + val = lua_tointeger(state, arg_num); + } + return val; +} + +float LuaInterface::GetFloatValue(lua_State* state, int8 arg_num) { + float val = 0; + if(lua_isnumber(state, arg_num)) + val = (float)lua_tonumber(state, arg_num); + return val; +} + +string LuaInterface::GetStringValue(lua_State* state, int8 arg_num) { + string val; + if(lua_isstring(state, arg_num)){ + size_t size = 0; + const char* str = lua_tolstring(state, arg_num, &size); + if(str) + val = string(str); + } + return val; +} + +bool LuaInterface::GetBooleanValue(lua_State* state, int8 arg_num) { + return lua_toboolean(state, arg_num) == 1; +} + +void LuaInterface::SetStringValue(lua_State* state, const char* value) { + lua_pushstring(state, value); +} + +void LuaInterface::SetBooleanValue(lua_State* state, bool value) { + lua_pushboolean(state, value); +} + +void LuaInterface::SetFloatValue(lua_State* state, float value) { + lua_pushnumber(state, value); +} + +void LuaInterface::SetInt32Value(lua_State* state, int32 value) { + lua_pushinteger(state, value); +} + +void LuaInterface::SetSInt32Value(lua_State* state, sint32 value) { + lua_pushinteger(state, value); +} + +void LuaInterface::SetInt64Value(lua_State* state, int64 value) { + lua_pushinteger(state, value); +} + +void LuaInterface::SetSInt64Value(lua_State* state, sint64 value) { + lua_pushinteger(state, value); +} + +void LuaInterface::SetSpawnValue(lua_State* state, Spawn* spawn) { + LUASpawnWrapper* spawn_wrapper = new LUASpawnWrapper(); + spawn_wrapper->spawn = spawn; + AddUserDataPtr(spawn_wrapper, spawn); + lua_pushlightuserdata(state, spawn_wrapper); +} + +void LuaInterface::SetConversationValue(lua_State* state, vector* conversation) { + LUAConversationOptionWrapper* conv_wrapper = new LUAConversationOptionWrapper(); + conv_wrapper->conversation_options = conversation; + AddUserDataPtr(conv_wrapper, conversation); + lua_pushlightuserdata(state, conv_wrapper); +} + +void LuaInterface::SetOptionWindowValue(lua_State* state, vector* optionWindow) { + LUAOptionWindowWrapper* option_wrapper = new LUAOptionWindowWrapper(); + option_wrapper->option_window_option = optionWindow; + AddUserDataPtr(option_wrapper, optionWindow); + lua_pushlightuserdata(state, option_wrapper); +} + +void LuaInterface::SetItemValue(lua_State* state, Item* item) { + LUAItemWrapper* item_wrapper = new LUAItemWrapper(); + item_wrapper->item = item; + AddUserDataPtr(item_wrapper, item); + lua_pushlightuserdata(state, item_wrapper); +} + +void LuaInterface::SetSkillValue(lua_State* state, Skill* skill) { + LUASkillWrapper* skill_wrapper = new LUASkillWrapper(); + skill_wrapper->skill = skill; + AddUserDataPtr(skill_wrapper, skill); + lua_pushlightuserdata(state, skill_wrapper); +} + +void LuaInterface::SetQuestValue(lua_State* state, Quest* quest) { + LUAQuestWrapper* quest_wrapper = new LUAQuestWrapper(); + quest_wrapper->quest = quest; + AddUserDataPtr(quest_wrapper, quest); + lua_pushlightuserdata(state, quest_wrapper); +} + +void LuaInterface::SetZoneValue(lua_State* state, ZoneServer* zone) { + LUAZoneWrapper* zone_wrapper = new LUAZoneWrapper(); + zone_wrapper->zone = zone; + AddUserDataPtr(zone_wrapper, zone); + lua_pushlightuserdata(state, zone_wrapper); +} + +void LuaInterface::SetSpellValue(lua_State* state, LuaSpell* spell) { + LUASpellWrapper* spell_wrapper = new LUASpellWrapper(); + spell_wrapper->spell = spell; + AddUserDataPtr(spell_wrapper, spell); + lua_pushlightuserdata(state, spell_wrapper); +} + +LuaSpell* LuaInterface::GetSpell(const char* name) { + string lua_script = string(name); + if (lua_script.find(".lua") == string::npos) + lua_script.append(".lua"); + if(spells.count(lua_script) > 0) + { + LogWrite(LUA__DEBUG, 0, "LUA", "Found LUA Spell Script: '%s'", lua_script.c_str()); + LuaSpell* spell = spells[lua_script]; + LuaSpell* new_spell = new LuaSpell; + new_spell->state = spell->state; + new_spell->file_name = string(spell->file_name); + new_spell->timer = spell->timer; + new_spell->timer.Disable(); + new_spell->resisted = false; + new_spell->is_damage_spell = false; + new_spell->has_damaged = false; + new_spell->interrupted = false; + new_spell->crit = false; + new_spell->last_spellattack_hit = false; + new_spell->MSpellTargets.SetName("LuaSpell.MSpellTargets"); + new_spell->cancel_after_all_triggers = false; + new_spell->num_triggers = 0; + new_spell->num_calls = 0; + new_spell->is_recast_timer = false; + new_spell->had_triggers = false; + new_spell->had_dmg_remaining = false; + new_spell->slot_pos = 0; + new_spell->damage_remaining = 0; + new_spell->effect_bitmask = 0; + new_spell->caster = 0; + new_spell->initial_target = 0; + new_spell->spell = 0; + new_spell->restored = false; + new_spell->initial_caster_char_id = 0; + new_spell->initial_target_char_id = 0; + return new_spell; + } + else{ + LogWrite(LUA__ERROR, 0, "LUA", "Error LUA Spell Script: '%s'", name); + return 0; + } +} + +Mutex* LuaInterface::GetItemScriptMutex(const char* name) { + Mutex* mutex = 0; + if(item_scripts_mutex.count(name) > 0) + mutex = item_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + item_scripts_mutex[name] = mutex; + } + return mutex; +} + +Mutex* LuaInterface::GetSpawnScriptMutex(const char* name) { + Mutex* mutex = 0; + if(spawn_scripts_mutex.count(string(name)) > 0) + mutex = spawn_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + spawn_scripts_mutex[name] = mutex; + } + return mutex; +} + +Mutex* LuaInterface::GetZoneScriptMutex(const char* name) { + Mutex* mutex = 0; + if(zone_scripts_mutex.count(name) > 0) + mutex = zone_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + zone_scripts_mutex[name] = mutex; + } + return mutex; +} + +Mutex* LuaInterface::GetRegionScriptMutex(const char* name) { + Mutex* mutex = 0; + if(region_scripts_mutex.count(name) > 0) + mutex = region_scripts_mutex[name]; + if(!mutex){ + mutex = new Mutex(); + region_scripts_mutex[name] = mutex; + } + return mutex; +} + +void LuaInterface::UseItemScript(const char* name, lua_State* state, bool val) { + MItemScripts.writelock(__FUNCTION__, __LINE__); + item_scripts[name][state] = val; + item_inverse_scripts[state] = name; + MItemScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::UseSpawnScript(const char* name, lua_State* state, bool val) { + MSpawnScripts.writelock(__FUNCTION__, __LINE__); + spawn_scripts[name][state] = val; + spawn_inverse_scripts[state] = name; + MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::UseZoneScript(const char* name, lua_State* state, bool val) { + + MZoneScripts.writelock(__FUNCTION__, __LINE__); + zone_scripts[name][state] = val; + zone_inverse_scripts[state] = name; + MZoneScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +void LuaInterface::UseRegionScript(const char* name, lua_State* state, bool val) { + + MRegionScripts.writelock(__FUNCTION__, __LINE__); + region_scripts[name][state] = val; + region_inverse_scripts[state] = name; + MRegionScripts.releasewritelock(__FUNCTION__, __LINE__); +} + +lua_State* LuaInterface::GetItemScript(const char* name, bool create_new, bool use) { + map >::iterator itr; + map::iterator item_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = item_scripts.find(name); + if(itr != item_scripts.end()) { + mutex = GetItemScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(item_script_itr = itr->second.begin(); item_script_itr != itr->second.end(); item_script_itr++){ + if(!item_script_itr->second){ //not in use + ret = item_script_itr->first; + + if (use) + { + item_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadItemScript(name)) + ret = GetItemScript(name, create_new, use); + else{ + LogError("Error LUA Item Script '%s'", name); + return 0; + } + } + return ret; +} + +lua_State* LuaInterface::GetSpawnScript(const char* name, bool create_new, bool use) { + if (lua_system_reloading) + return 0; + map >::iterator itr; + map::iterator spawn_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = spawn_scripts.find(name); + if(itr != spawn_scripts.end()) { + mutex = GetSpawnScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(spawn_script_itr = itr->second.begin(); spawn_script_itr != itr->second.end(); spawn_script_itr++){ + if(!spawn_script_itr->second){ //not in use + ret = spawn_script_itr->first; + + if (use) + { + spawn_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadSpawnScript(name)) + ret = GetSpawnScript(name, create_new, use); + else{ + LogError("Error LUA Spawn Script '%s'", name); + return 0; + } + } + return ret; +} + +lua_State* LuaInterface::GetZoneScript(const char* name, bool create_new, bool use) { + map >::iterator itr; + map::iterator zone_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = zone_scripts.find(name); + if(itr != zone_scripts.end()) { + mutex = GetZoneScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(zone_script_itr = itr->second.begin(); zone_script_itr != itr->second.end(); zone_script_itr++){ + if(!zone_script_itr->second){ //not in use + ret = zone_script_itr->first; + + if (use) + { + zone_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadZoneScript(name)) + ret = GetZoneScript(name); + else{ + LogError("Error LUA Zone Script '%s'", name); + return 0; + } + } + return ret; +} + +lua_State* LuaInterface::GetRegionScript(const char* name, bool create_new, bool use) { + map >::iterator itr; + map::iterator region_script_itr; + lua_State* ret = 0; + Mutex* mutex = 0; + + itr = region_scripts.find(name); + if(itr != region_scripts.end()) { + mutex = GetRegionScriptMutex(name); + mutex->readlock(__FUNCTION__, __LINE__); + for(region_script_itr = itr->second.begin(); region_script_itr != itr->second.end(); region_script_itr++){ + if(!region_script_itr->second){ //not in use + ret = region_script_itr->first; + + if (use) + { + region_script_itr->second = true; + break; // don't keep iterating, we already have our result + } + } + } + mutex->releasereadlock(__FUNCTION__, __LINE__); + } + if(!ret && create_new){ + if(name && LoadRegionScript(name)) + ret = GetRegionScript(name); + else{ + LogError("Error LUA Zone Script '%s'", name); + return 0; + } + } + return ret; +} + +bool LuaInterface::RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn, Spawn* target, sint64* returnValue) { + if(!item) + return false; + lua_State* state = GetItemScript(script_name.c_str(), true, true); + if(state){ + Mutex* mutex = GetItemScriptMutex(script_name.c_str()); + if(mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else{ + LogError("Error getting lock for '%s'", script_name.c_str()); + UseItemScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + SetItemValue(state, item); + int8 num_parms = 1; + if(spawn){ + SetSpawnValue(state, spawn); + num_parms++; + } + if(target){ + SetSpawnValue(state, target); + num_parms++; + } + if(!CallItemScript(state, num_parms, returnValue)){ + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + +bool LuaInterface::RunItemScriptWithReturnString(string script_name, const char* function_name, Item* item, Spawn* spawn, std::string* returnValue) { + if(!item) + return false; + lua_State* state = GetItemScript(script_name.c_str(), true, true); + if(state){ + Mutex* mutex = GetItemScriptMutex(script_name.c_str()); + if(mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else{ + LogError("Error getting lock for '%s'", script_name.c_str()); + UseItemScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + SetItemValue(state, item); + int8 num_parms = 1; + if(spawn){ + SetSpawnValue(state, spawn); + num_parms++; + } + if(!CallItemScript(state, num_parms, returnValue)){ + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + + +bool LuaInterface::RunSpawnScript(string script_name, const char* function_name, Spawn* npc, Spawn* spawn, const char* message, bool is_door_open, sint32 input_value, sint32* return_value) { + if(!npc || lua_system_reloading) + return false; + + bool isUseDoorFunction = false; + if(!strcmp(function_name,"usedoor")) + isUseDoorFunction = true; + + lua_State* state = GetSpawnScript(script_name.c_str(), true, true); + if(state){ + Mutex* mutex = GetSpawnScriptMutex(script_name.c_str()); + if(mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else{ + LogError("Error getting lock for '%s'", script_name.c_str()); + UseSpawnScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseSpawnScript(script_name.c_str(), state, false); + return false; + } + SetSpawnValue(state, npc); + int8 num_parms = 1; + // we always send spawn, even if null (nil) when its 'usedoor' function + if(spawn || isUseDoorFunction){ + SetSpawnValue(state, spawn); + num_parms++; + } + + // usedoor function always passes just npc, spawn and is_door_open + if(isUseDoorFunction){ + SetBooleanValue(state, is_door_open); + num_parms++; + } + else { + if(message){ + SetStringValue(state, message); + num_parms++; + } + + if(!strcmp(function_name,"healthchanged")) + { + // passes as damage dealt + SetSInt32Value(state, input_value); + num_parms++; + } + } + if(!CallScriptSInt32(state, num_parms, return_value)){ + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseSpawnScript(script_name.c_str(), state, false); + return false; + } + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseSpawnScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + +bool LuaInterface::RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, const char* str_arg1, Spawn* spawn_arg1, int32 int32_arg2, const char* str_arg2, Spawn* spawn_arg2) { + if (!zone) + return false; + lua_State* state = GetZoneScript(script_name.c_str(), true, true); + if (state) { + Mutex* mutex = GetZoneScriptMutex(script_name.c_str()); + if (mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else { + LogError("Error getting lock for '%s'", script_name.c_str()); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))) { + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + SetZoneValue(state, zone); + int8 num_params = 1; + if (spawn) { + SetSpawnValue(state, spawn); + num_params++; + } + if (int32_arg1 > 0) { + SetInt32Value(state, int32_arg1); + num_params++; + } + if (str_arg1) { + SetStringValue(state, str_arg1); + num_params++; + } + if (spawn_arg1) { + SetSpawnValue(state, spawn_arg1); + num_params++; + } + if (int32_arg2 > 0) { + SetInt32Value(state, int32_arg2); + num_params++; + } + if (str_arg2) { + SetStringValue(state, str_arg2); + num_params++; + } + if (spawn_arg2) { + SetSpawnValue(state, spawn_arg2); + num_params++; + } + if (!CallScriptInt32(state, num_params)) { + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseZoneScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + +bool LuaInterface::RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue) { + if (!zone) + return false; + lua_State* state = GetZoneScript(script_name.c_str(), true, true); + if (state) { + Mutex* mutex = GetZoneScriptMutex(script_name.c_str()); + if (mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else { + LogError("Error getting lock for '%s'", script_name.c_str()); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))) { + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + SetZoneValue(state, zone); + int8 num_params = 1; + SetSpawnValue(state, spawn); + num_params++; + SetInt32Value(state, int32_arg1); + num_params++; + SetInt32Value(state, int32_arg2); + num_params++; + SetInt32Value(state, int32_arg3); + num_params++; + if (!CallScriptInt32(state, num_params, returnValue)) { + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseZoneScript(script_name.c_str(), state, false); + return false; + } + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseZoneScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + + +bool LuaInterface::RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, sint32 int32_arg1, int32* returnValue) { + if (!zone) + return false; + lua_State* state = GetRegionScript(script_name.c_str(), true, true); + if (state) { + Mutex* mutex = GetRegionScriptMutex(script_name.c_str()); + if (mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else { + LogError("Error getting lock for '%s'", script_name.c_str()); + UseRegionScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))) { + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseRegionScript(script_name.c_str(), state, false); + return false; + } + SetZoneValue(state, zone); + int8 num_params = 1; + if (spawn) { + SetSpawnValue(state, spawn); + num_params++; + } + if (int32_arg1 > 0) { + SetSInt32Value(state, int32_arg1); + num_params++; + } + if (!CallRegionScript(state, num_params, returnValue)) { + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseRegionScript(script_name.c_str(), state, false); + return false; + } + if (mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseRegionScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + +void LuaInterface::AddPendingSpellDelete(LuaSpell* spell) { + MSpellDelete.lock(); + if ( spells_pending_delete.count(spell) == 0 ) + spells_pending_delete[spell] = Timer::GetCurrentTime2() + 10000; + MSpellDelete.unlock(); +} + +void LuaInterface::AddCustomSpell(LuaSpell* spell) +{ + MCustomSpell.writelock(); + custom_spells[spell->spell->GetSpellID()] = spell; + MCustomSpell.releasewritelock(); +} + +void LuaInterface::RemoveCustomSpell(int32 id) +{ + MCustomSpell.writelock(); + map::iterator itr = custom_spells.find(id); + if (itr != custom_spells.end()) + { + custom_spells.erase(itr); + custom_free_spell_ids.push_front(id); + } + MCustomSpell.releasewritelock(); +} + +// prior to calling FindCustomSpell you should call FindCustomSpellLock and after FindCustomSpellUnlock +LuaSpell* LuaInterface::FindCustomSpell(int32 id) +{ + LuaSpell* spell = 0; + map::iterator itr = custom_spells.find(id); + if (itr != custom_spells.end()) + spell = itr->second; + return spell; +} + +int32 LuaInterface::GetFreeCustomSpellID() +{ + int32 id = 0; + MCustomSpell.writelock(); + if (!custom_free_spell_ids.empty()) + { + id = custom_free_spell_ids.front(); + custom_free_spell_ids.pop_front(); + } + MCustomSpell.releasewritelock(); + return id; +} + + +void LuaInterface::SetLuaUserDataStale(void* ptr) { + std::unique_lock lock(MLUAUserData); + std::map::iterator itr = user_data_ptr.find(ptr); + if(itr != user_data_ptr.end()) { + itr->second->correctly_initialized = false; + } +} + +LUAUserData::LUAUserData(){ + correctly_initialized = false; + quest = 0; + conversation_options = 0; + spawn = 0; + zone = 0; + skill = 0; + option_window_option = 0; + item = 0; +} + +bool LUAUserData::IsCorrectlyInitialized(){ + return correctly_initialized; +} + +bool LUAUserData::IsConversationOption(){ + return false; +} + +bool LUAUserData::IsOptionWindow() { + return false; +} + +bool LUAUserData::IsSpawn(){ + return false; +} + +bool LUAUserData::IsQuest(){ + return false; +} + +bool LUAUserData::IsZone(){ + return false; +} + +bool LUAUserData::IsItem(){ + return false; +} + +bool LUAUserData::IsSkill() { + return false; +} + +bool LUAUserData::IsSpell() { + return false; +} + +LUAConversationOptionWrapper::LUAConversationOptionWrapper(){ + correctly_initialized = true; +} + +bool LUAConversationOptionWrapper::IsConversationOption(){ + return true; +} + +LUAOptionWindowWrapper::LUAOptionWindowWrapper() { + correctly_initialized = true; +} + +bool LUAOptionWindowWrapper::IsOptionWindow() { + return true; +} + +LUASpawnWrapper::LUASpawnWrapper(){ + correctly_initialized = true; +} + +bool LUASpawnWrapper::IsSpawn(){ + return true; +} + +LUAZoneWrapper::LUAZoneWrapper(){ + correctly_initialized = true; +} + +bool LUAZoneWrapper::IsZone(){ + return true; +} + +LUAQuestWrapper::LUAQuestWrapper(){ + correctly_initialized = true; +} + +bool LUAQuestWrapper::IsQuest(){ + return true; +} + +LUAItemWrapper::LUAItemWrapper(){ + correctly_initialized = true; +} + +bool LUAItemWrapper::IsItem(){ + return true; +} + +LUASkillWrapper::LUASkillWrapper() { + correctly_initialized = true; +} + +bool LUASkillWrapper::IsSkill() { + return true; +} + +LUASpellWrapper::LUASpellWrapper() { + correctly_initialized = true; +} + +bool LUASpellWrapper::IsSpell() { + return true; +} \ No newline at end of file diff --git a/source/WorldServer/LuaInterface.h b/source/WorldServer/LuaInterface.h new file mode 100644 index 0000000..f0e9c98 --- /dev/null +++ b/source/WorldServer/LuaInterface.h @@ -0,0 +1,357 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef LUA_INTERFACE_H +#define LUA_INTERFACE_H + +#include +#include + +#include "Spawn.h" +#include "Spells.h" +#include "../common/Mutex.h" +#include "Quests.h" +#include "zoneserver.h" +#include "client.h" + +#include "../LUA/lua.hpp" + +using namespace std; + +struct ConversationOption{ + string option; + string function; +}; + +struct OptionWindowOption { + string optionName; + string optionDescription; + string optionCommand; + int32 optionIconSheet; + int16 optionIconID; + string optionConfirmTitle; +}; + +//Bitmask Values +#define EFFECT_FLAG_STUN 1 +#define EFFECT_FLAG_ROOT 2 +#define EFFECT_FLAG_MEZ 4 +#define EFFECT_FLAG_STIFLE 8 +#define EFFECT_FLAG_DAZE 16 +#define EFFECT_FLAG_FEAR 32 +#define EFFECT_FLAG_SPELLBONUS 64 +#define EFFECT_FLAG_SKILLBONUS 128 +#define EFFECT_FLAG_STEALTH 256 +#define EFFECT_FLAG_INVIS 512 +#define EFFECT_FLAG_SNARE 1024 +#define EFFECT_FLAG_WATERWALK 2048 +#define EFFECT_FLAG_WATERJUMP 4096 +#define EFFECT_FLAG_FLIGHT 8192 +#define EFFECT_FLAG_GLIDE 16384 +#define EFFECT_FLAG_AOE_IMMUNE 32768 +#define EFFECT_FLAG_STUN_IMMUNE 65536 +#define EFFECT_FLAG_MEZ_IMMUNE 131072 +#define EFFECT_FLAG_DAZE_IMMUNE 262144 +#define EFFECT_FLAG_ROOT_IMMUNE 524288 +#define EFFECT_FLAG_STIFLE_IMMUNE 1048576 +#define EFFECT_FLAG_FEAR_IMMUNE 2097152 +#define EFFECT_FLAG_SAFEFALL 4194304 + +struct LuaSpell{ + Entity* caster; + int32 initial_caster_char_id; + int32 initial_target; + int32 initial_target_char_id; + vector targets; + vector removed_targets; // previously cancelled, expired, used, so on + multimap char_id_targets; + Spell* spell; + lua_State* state; + string file_name; + Timer timer; + bool is_recast_timer; + int16 num_calls; + int16 num_triggers; + int8 slot_pos; + int32 damage_remaining; + bool resisted; + bool has_damaged; + bool is_damage_spell; + bool interrupted; + bool crit; + bool last_spellattack_hit; + bool cancel_after_all_triggers; + bool had_triggers; + bool had_dmg_remaining; + Mutex MSpellTargets; + int32 effect_bitmask; + bool restored; // restored spell cross zone + +}; + +class LUAUserData{ +public: + LUAUserData(); + virtual ~LUAUserData(){}; + virtual bool IsCorrectlyInitialized(); + virtual bool IsConversationOption(); + virtual bool IsOptionWindow(); + virtual bool IsSpawn(); + virtual bool IsQuest(); + virtual bool IsZone(); + virtual bool IsItem(); + virtual bool IsSkill(); + virtual bool IsSpell(); + bool correctly_initialized; + Item* item; + ZoneServer* zone; + Spawn* spawn; + vector* conversation_options; + vector* option_window_option; + vector* spawn_list; + Quest* quest; + Skill* skill; + LuaSpell* spell; +}; + +class LUAConversationOptionWrapper : public LUAUserData{ +public: + LUAConversationOptionWrapper(); + bool IsConversationOption(); +}; + +class LUAOptionWindowWrapper : public LUAUserData { +public: + LUAOptionWindowWrapper(); + bool IsOptionWindow(); +}; + +class LUASpawnWrapper : public LUAUserData{ +public: + LUASpawnWrapper(); + bool IsSpawn(); +}; + +class LUAZoneWrapper : public LUAUserData{ +public: + LUAZoneWrapper(); + bool IsZone(); +}; + +class LUAQuestWrapper : public LUAUserData{ +public: + LUAQuestWrapper(); + bool IsQuest(); +}; + +class LUAItemWrapper : public LUAUserData{ +public: + LUAItemWrapper(); + bool IsItem(); +}; + +class LUASkillWrapper: public LUAUserData { +public: + LUASkillWrapper(); + bool IsSkill(); +}; + +class LUASpellWrapper : public LUAUserData { +public: + LUASpellWrapper(); + bool IsSpell(); +}; + +class LuaInterface { +public: + LuaInterface(); + ~LuaInterface(); + int GetNumberOfArgs(lua_State* state); + bool LoadLuaSpell(const char* name); + bool LoadLuaSpell(string name); + bool LoadItemScript(string name); + bool LoadItemScript(const char* name); + bool LoadSpawnScript(string name); + bool LoadSpawnScript(const char* name); + bool LoadZoneScript(string name); + bool LoadZoneScript(const char* name); + bool LoadRegionScript(string name); + bool LoadRegionScript(const char* name); + void RemoveSpell(LuaSpell* spell, bool call_remove_function = true, bool can_delete = true, string reason = "", bool removing_all_spells = false); + Spawn* GetSpawn(lua_State* state, int8 arg_num = 1); + Item* GetItem(lua_State* state, int8 arg_num = 1); + Quest* GetQuest(lua_State* state, int8 arg_num = 1); + ZoneServer* GetZone(lua_State* state, int8 arg_num = 1); + Skill* GetSkill(lua_State* state, int8 arg_num = 1); + LuaSpell* GetSpell(lua_State* state, int8 arg_num = 1); + vector* GetConversation(lua_State* state, int8 arg_num = 1); + + vector* GetOptionWindow(lua_State* state, int8 arg_num = 1); + int8 GetInt8Value(lua_State* state, int8 arg_num = 1); + int16 GetInt16Value(lua_State* state, int8 arg_num = 1); + int32 GetInt32Value(lua_State* state, int8 arg_num = 1); + sint32 GetSInt32Value(lua_State* state, int8 arg_num = 1); + int64 GetInt64Value(lua_State* state, int8 arg_num = 1); + sint64 GetSInt64Value(lua_State* state, int8 arg_num = 1); + float GetFloatValue(lua_State* state, int8 arg_num = 1); + string GetStringValue(lua_State* state, int8 arg_num = 1); + bool GetBooleanValue(lua_State*state, int8 arg_num = 1); + + void Process(); + + void SetInt32Value(lua_State* state, int32 value); + void SetSInt32Value(lua_State* state, sint32 value); + void SetInt64Value(lua_State* state, int64 value); + void SetSInt64Value(lua_State* state, sint64 value); + void SetFloatValue(lua_State* state, float value); + void SetBooleanValue(lua_State* state, bool value); + void SetStringValue(lua_State* state, const char* value); + void SetSpawnValue(lua_State* state, Spawn* spawn); + void SetSkillValue(lua_State* state, Skill* skill); + void SetItemValue(lua_State* state, Item* item); + void SetQuestValue(lua_State* state, Quest* quest); + void SetZoneValue(lua_State* state, ZoneServer* zone); + void SetSpellValue(lua_State* state, LuaSpell* spell); + void SetConversationValue(lua_State* state, vector* conversation); + void SetOptionWindowValue(lua_State* state, vector* optionWindow); + + std::string AddSpawnPointers(LuaSpell* spell, bool first_cast, bool precast = false, const char* function = 0, SpellScriptTimer* timer = 0, bool passLuaSpell=false, Spawn* altTarget = 0); + LuaSpell* GetCurrentSpell(lua_State* state, bool needsLock = true); + void RemoveCurrentSpell(lua_State* state, bool needsLock = true); + bool CallSpellProcess(LuaSpell* spell, int8 num_parameters, std::string functionCalled); + LuaSpell* GetSpell(const char* name); + void UseItemScript(const char* name, lua_State* state, bool val); + void UseSpawnScript(const char* name, lua_State* state, bool val); + void UseZoneScript(const char* name, lua_State* state, bool val); + void UseRegionScript(const char* name, lua_State* state, bool val); + lua_State* GetItemScript(const char* name, bool create_new = true, bool use = false); + lua_State* GetSpawnScript(const char* name, bool create_new = true, bool use = false); + lua_State* GetZoneScript(const char* name, bool create_new = true, bool use = false); + lua_State* GetRegionScript(const char* name, bool create_new = true, bool use = false); + Quest* LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name); + + const char* GetScriptName(lua_State* state); + + void RemoveSpawnScript(const char* name); + bool RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, Spawn* target = 0, sint64* returnValue = 0); + bool RunItemScriptWithReturnString(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, std::string* returnValue = 0); + bool CallItemScript(lua_State* state, int8 num_parameters, std::string* returnValue = 0); + bool CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue = 0); + bool RunSpawnScript(string script_name, const char* function_name, Spawn* npc, Spawn* spawn = 0, const char* message = 0, bool is_door_open = false, sint32 input_value = 0, sint32* return_value = 0); + bool CallSpawnScript(lua_State* state, int8 num_parameters); + bool RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, int32 int32_arg1 = 0, const char* str_arg1 = 0, Spawn* spawn_arg1 = 0, int32 int32_arg2 = 0, const char* str_arg2 = 0, Spawn* spawn_arg2 = 0); + bool RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue = 0); + bool CallScriptInt32(lua_State* state, int8 num_parameters, int32* returnValue = 0); + bool CallScriptSInt32(lua_State* state, int8 num_parameters, sint32* returnValue = 0); + bool RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, sint32 int32_arg1 = 0, int32* returnValue = 0); + bool CallRegionScript(lua_State* state, int8 num_parameters, int32* returnValue); + void ResetFunctionStack(lua_State* state); + void DestroySpells(); + void DestroySpawnScripts(); + void DestroyItemScripts(); + void ReloadSpells(); + void DestroyQuests(bool reload = false); + void DestroyZoneScripts(); + void DestroyRegionScripts(); + void SimpleLogError(const char* error); + void LogError(const char* error, ...); + + + bool CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id = 0xFFFFFFFF, int32* returnValue = 0); + void RemoveDebugClients(Client* client); + void UpdateDebugClients(Client* client); + void ProcessErrorMessage(const char* message); + map GetDebugClients(){ return debug_clients; } + void AddUserDataPtr(LUAUserData* data, void* data_ptr = 0); + void DeleteUserDataPtrs(bool all); + void DeletePendingSpells(bool all); + void DeletePendingSpell(LuaSpell* spell); + Mutex* GetSpawnScriptMutex(const char* name); + Mutex* GetItemScriptMutex(const char* name); + Mutex* GetZoneScriptMutex(const char* name); + Mutex* GetRegionScriptMutex(const char* name); + Mutex* GetQuestMutex(Quest* quest); + + void SetLuaSystemReloading(bool val) { lua_system_reloading = val; } + bool IsLuaSystemReloading() { return lua_system_reloading; } + + void AddPendingSpellDelete(LuaSpell* spell); + + void AddCustomSpell(LuaSpell* spell); + void RemoveCustomSpell(int32 id); + + void FindCustomSpellLock() { MCustomSpell.readlock(); } + void FindCustomSpellUnlock() { MCustomSpell.releasereadlock(); } + LuaSpell* FindCustomSpell(int32 id); + + int32 GetFreeCustomSpellID(); + + void SetLuaUserDataStale(void* ptr); + +private: + bool shutting_down; + bool lua_system_reloading; + map spells_pending_delete; + Timer* user_data_timer; + Timer* spell_delete_timer; + map user_data; + map user_data_ptr; + map debug_clients; + map current_spells; + vector* GetDirectoryListing(const char* directory); + lua_State* LoadLuaFile(const char* name); + void RegisterFunctions(lua_State* state); + map spells; + map inverse_spells; + + map quests; + map quest_states; + map > item_scripts; + map > spawn_scripts; + map > zone_scripts; + map > region_scripts; + + map custom_spells; + std::deque custom_free_spell_ids; + + map item_inverse_scripts; + map spawn_inverse_scripts; + map zone_inverse_scripts; + map region_inverse_scripts; + + map item_scripts_mutex; + map spawn_scripts_mutex; + map zone_scripts_mutex; + map quests_mutex; + map region_scripts_mutex; + + Mutex MDebugClients; + Mutex MSpells; + Mutex MSpawnScripts; + Mutex MItemScripts; + Mutex MZoneScripts; + Mutex MQuests; + Mutex MLUAMain; + Mutex MSpellDelete; + Mutex MCustomSpell; + Mutex MRegionScripts; + + mutable std::shared_mutex MLUAUserData; +}; +#endif diff --git a/source/WorldServer/MutexHelper.h b/source/WorldServer/MutexHelper.h new file mode 100644 index 0000000..58c3aa7 --- /dev/null +++ b/source/WorldServer/MutexHelper.h @@ -0,0 +1,202 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef MUTEXHELPER_H +#define MUTEXHELPER_H + +#include "../common/timer.h" +#include "../common/Mutex.h" +#include +#include + +template +class IsPointer { +public: + static bool ValidPointer(T key){ + return false; + } + + static void Delete(T key){ + } +}; + +class Locker{ +public: + Locker(){ + #ifdef WIN32 + InitializeCriticalSection(&CSMutex); + #else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); + pthread_mutex_init(&CSMutex, &attr); + pthread_mutexattr_destroy(&attr); + #endif + } + Locker(const Locker& locker){ + #ifdef WIN32 + InitializeCriticalSection(&CSMutex); + #else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP); + pthread_mutex_init(&CSMutex, &attr); + pthread_mutexattr_destroy(&attr); + #endif + } + ~Locker(){ + #ifdef WIN32 + DeleteCriticalSection(&CSMutex); + #else + // pthread_mutex_destroy(&CSMutex); + #endif + } + void lock(){ + #ifdef WIN32 + EnterCriticalSection(&CSMutex); + #else + pthread_mutex_lock(&CSMutex); + #endif + } + void unlock(){ + #ifdef WIN32 + LeaveCriticalSection(&CSMutex); + #else + pthread_mutex_unlock(&CSMutex); + #endif + } + +private: + #ifdef WIN32 + CRITICAL_SECTION CSMutex; + #else + pthread_mutex_t CSMutex; + #endif +}; + +template +class IsPointer { +public: + static bool ValidPointer(T* key){ + return true; + } + static void Delete(T* key){ + if(key){ + delete key; + key = 0; + } + } +}; + +template +class DeleteData{ +public: + + void SetData(int type, KeyT key, ValueT value, unsigned int time){ + this->type = type; + this->key = key; + this->value = value; + this->time = time; + } + + void DeleteKey(){ + IsPointer::Delete(key); + } + + void DeleteValue(){ + IsPointer::Delete(value); + } + + unsigned int GetTime(){ + return time; + } + + int GetType(){ + return type; + } +private: + int type; + KeyT key; + ValueT value; + unsigned int time; +}; + +template +class HandleDeletes { +public: + HandleDeletes(){ + access_count = 0; + next_delete_attempt = 0; + changing = false; + has_pending_deletes = false; + } + ~HandleDeletes(){ + CheckDeletes(true); + } + void AddPendingDelete(T value, unsigned int time){ + if(IsPointer::ValidPointer(value)){ + while(changing){ + Sleep(1); + } + ++access_count; + pending_deletes[value] = time; + has_pending_deletes = true; + --access_count; + } + } + + void CheckDeletes(bool force = false){ + while(changing){ + Sleep(1); + } + if(has_pending_deletes && (force || (Timer::GetCurrentTime2() > next_delete_attempt && access_count == 0))){ + changing = true; + while(access_count > 0){ + Sleep(1); + } + ++access_count; + next_delete_attempt = Timer::GetCurrentTime2(); + std::list deletes; + typename std::map::iterator pending_delete_itr; + for(pending_delete_itr = pending_deletes.begin(); pending_delete_itr != pending_deletes.end(); pending_delete_itr++){ + if(force || next_delete_attempt >= pending_delete_itr->second) + deletes.push_back(pending_delete_itr->first); + } + if(deletes.size() > 0){ + typename std::list::iterator delete_itr; + for(delete_itr = deletes.begin(); delete_itr != deletes.end(); delete_itr++){ + IsPointer::Delete(*delete_itr); + pending_deletes.erase(*delete_itr); + } + has_pending_deletes = (pending_deletes.size() > 0); + } + next_delete_attempt += 1000; + --access_count; + changing = false; + } + } + +private: + volatile bool changing; + volatile int access_count; + volatile unsigned int next_delete_attempt; + volatile bool has_pending_deletes; + std::map pending_deletes; +}; +#endif diff --git a/source/WorldServer/MutexList.h b/source/WorldServer/MutexList.h new file mode 100644 index 0000000..818c050 --- /dev/null +++ b/source/WorldServer/MutexList.h @@ -0,0 +1,277 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef MUTEXLIST_H +#define MUTEXLIST_H +#include +#include "MutexHelper.h" + +#define MUTEXLIST_PENDING_ADD 1 +#define MUTEXLIST_PENDING_REMOVE 2 +#define MUTEXLIST_PENDING_DELETE 3 + +template +class MutexList{ +public: + MutexList(){ + pending_changing = false; + has_pending_data = false; + pending_clear = false; + changing = false; + access_count = 0; + access_pending = 0; + } + MutexList(const MutexList& list){ + pending_changing = false; + has_pending_data = false; + pending_clear = false; + changing = false; + access_count = 0; + access_pending = 0; + /*if(list.has_pending_data) + pending_data = list.pending_data; + current_data = list.current_data; */ + } + ~MutexList(){ + while(!update(true)){ + Sleep(1); + } + } + class iterator { + private: + typename std::list::iterator itr; // Current element + MutexList* list; + bool first_itr; + public: + iterator(){ + } + iterator(MutexList* list){ + if(list){ + this->list = list; + list->update(); + this->list->AddAccess(); + first_itr = true; + itr = list->current_data.begin(); + if(itr != list->current_data.end()) + value = *itr; + } + else + this->list = 0; + } + ~iterator(){ + if(list) + list->RemoveAccess(); + } + + bool HasNext(){ + return itr != list->current_data.end(); + } + + bool Next(){ + if(list->pending_clear) + return false; + if(first_itr) + first_itr = false; + else + itr++; + if(itr != list->current_data.end()){ + value = *itr; + if(list->PendingContains(value)) //pending delete + return Next(); + return true; + } + return false; + } + iterator* operator->() { + return this; + } + T value; +}; + void SetChanging(){ + ChangingLock.lock(); + changing = true; + ChangingLock.unlock(); + } + + void SetNotChanging(){ + ChangingLock.lock(); + changing = false; + ChangingLock.unlock(); + } + + void AddAccess(){ + AccessLock.lock(); + ++access_count; + AccessLock.unlock(); + } + + void RemoveAccess(){ + AccessLock.lock(); + --access_count; + AccessLock.unlock(); + } + + unsigned int size(bool include_pending = false){ + if(include_pending){ + update(); + return current_data.size() + pending_data.size(); + } + return current_data.size(); + } + iterator begin(){ + return iterator(this); + } + void clear(bool erase_all = false){ + pending_clear = true; + if(erase_all){ + AddAccess(); + PendingLock.lock(); + typename std::list::iterator itr; + for(itr = current_data.begin(); itr != current_data.end(); itr++){ + RemoveData(*itr); + } + PendingLock.unlock(); + RemoveAccess(); + } + update(); + } + + bool PendingContains(T key){ + if(!has_pending_data) + return false; + bool ret = false; + PendingLock.lock(); + ret = (pending_data.count(key) > 0 && pending_data[key] == false); + PendingLock.unlock(); + return ret; + } + + unsigned int count(T key){ + unsigned int ret = 0; + while(changing){ + Sleep(1); + } + AddAccess(); + bool retry = false; + if(!changing){ + typename std::list::iterator iter; + for(iter = current_data.begin(); iter != current_data.end(); iter++){ + if(*iter == key) + ret++; + } + } + else + retry = true; + RemoveAccess(); + if(retry) + return count(key); //only occurs whenever we change to changing state at the same time as a reading state + return ret; + } + + void RemoveData(T key, int32 erase_time = 0){ + handle_deletes.AddPendingDelete(key, Timer::GetCurrentTime2() + erase_time); + } + + void Remove(T key, bool erase = false, int32 erase_time = 0){ + while(changing){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + pending_data[key] = false; + PendingLock.unlock(); + if(erase) + RemoveData(key, erase_time); + has_pending_data = true; + RemoveAccess(); + update(); + } + + void Add(T key){ + if(count(key) > 0) + return; + while(changing){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + pending_data[key] = true; + PendingLock.unlock(); + has_pending_data = true; + RemoveAccess(); + update(); + } +private: + bool update(bool force = false){ + //if(access_count > 5) + // cout << "Possible error.\n"; + while(changing){ + Sleep(1); + } + if(pending_clear && access_count == 0){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + current_data.clear(); + has_pending_data = (pending_data.size() > 0); + PendingLock.unlock(); + pending_clear = false; + RemoveAccess(); + SetNotChanging(); + } + if(!pending_clear && has_pending_data && access_count == 0){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + typename std::map::iterator pending_itr; + for(pending_itr = pending_data.begin(); pending_itr != pending_data.end(); pending_itr++){ + if(pending_itr->second) + current_data.push_back(pending_itr->first); + else + current_data.remove(pending_itr->first); + } + pending_data.clear(); + PendingLock.unlock(); + has_pending_data = false; + RemoveAccess(); + SetNotChanging(); + } + handle_deletes.CheckDeletes(force); + return !pending_clear && !has_pending_data; + } + Locker PendingLock; + Locker AccessLock; + Locker ChangingLock; + volatile int access_count; + std::list current_data; + std::map pending_data; + HandleDeletes handle_deletes; + volatile int access_pending; + volatile bool pending_changing; + volatile bool changing; + volatile bool has_pending_data; + volatile bool pending_clear; +}; +#endif diff --git a/source/WorldServer/MutexMap.h b/source/WorldServer/MutexMap.h new file mode 100644 index 0000000..afa33b0 --- /dev/null +++ b/source/WorldServer/MutexMap.h @@ -0,0 +1,304 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef MUTEXMAP_H +#define MUTEXMAP_H +#include +#include "MutexHelper.h" + +#define MUTEXMAP_DELETE_TYPE_KEY 1 +#define MUTEXMAP_DELETE_TYPE_VALUE 2 + +template +class MutexMap{ +public: + MutexMap(){ + has_pending_data = false; + pending_clear = false; + changing = false; + access_count = 0; + has_pending_deletes = false; + next_delete_attempt = 0; + delete_all = false; + } + ~MutexMap(){ + update(true); + PendingLock.lock(); + pending_add.clear(); + pending_remove.clear(); + PendingLock.unlock(); + } + class iterator { + private: + typename std::map::iterator itr; // Current element + MutexMap* map; + bool first_itr; + public: + iterator(){ + } + iterator(MutexMap* map){ + this->map = map; + map->update(); + map->SetChanging(); + this->map->AddAccess(); + map->SetNotChanging(); + first_itr = true; + itr = map->current_data.begin(); + if(itr != map->current_data.end()){ + first = itr->first; + second = itr->second; + } + } + ~iterator(){ + map->RemoveAccess(); + } + + bool HasNext(){ + return itr != map->current_data.end(); + } + + bool Next(){ + if(map->pending_clear) + return false; + if(first_itr) + first_itr = false; + else + itr++; + if(itr != map->current_data.end()){ + first = itr->first; + second = itr->second; + map->PendingLock.lock(); + if(map->pending_remove.count(first) > 0){ + map->PendingLock.unlock(); + return Next(); + } + map->PendingLock.unlock(); + return true; + } + return false; + } + iterator* operator->() { + return this; + } + KeyT first; + ValueT second; + }; + int count(KeyT key, bool include_pending = false){ + while(changing){ + Sleep(1); + } + AddAccess(); + int ret = current_data.count(key); + if(include_pending){ + PendingLock.lock(); + ret += pending_add.count(key); + PendingLock.unlock(); + } + RemoveAccess(); + return ret; + } + void clear(bool delete_all = false){ + pending_clear = true; + if(delete_all){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + typename std::map::iterator itr; + for(itr = current_data.begin(); itr != current_data.end(); itr++){ + deleteData(itr->first, MUTEXMAP_DELETE_TYPE_VALUE); + } + PendingLock.unlock(); + RemoveAccess(); + SetNotChanging(); + } + update(); + } + unsigned int size(bool include_pending = false){ + if(include_pending) + return current_data.size() + pending_add.size(); + return current_data.size(); + } + void deleteData(KeyT key, int8 type, int32 erase_time = 0){ + DeleteData* del = new DeleteData(); + del->SetData(type, key, current_data[key], Timer::GetCurrentTime2() + erase_time); + pending_deletes[del] = true; + has_pending_deletes = true; + } + void erase(KeyT key, bool erase_key = false, bool erase_value = false, int32 erase_time = 0){ + while(changing){ + Sleep(1); + } + AddAccess(); + if(current_data.count(key) != 0){ + PendingLock.lock(); + pending_remove[key] = true; + if(erase_key || erase_value){ + int type = 0; + if(erase_key) + type = MUTEXMAP_DELETE_TYPE_KEY; + if(erase_value) + type += MUTEXMAP_DELETE_TYPE_VALUE; + deleteData(key, type, erase_time); + } + has_pending_data = true; + PendingLock.unlock(); + } + RemoveAccess(); + update(); + } + iterator begin(){ + return iterator(this); + } + void Put(KeyT key, ValueT value){ + while(changing){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + pending_add[key] = value; + has_pending_data = true; + PendingLock.unlock(); + RemoveAccess(); + update(); + } + ValueT& Get(KeyT key){ + while(changing){ + Sleep(1); + } + AddAccess(); + if(current_data.count(key) > 0 || pending_add.count(key) == 0){ + RemoveAccess(); + return current_data[key]; + } + RemoveAccess(); + return pending_add[key]; + } +private: + void AddAccess(){ + AccessLock.lock(); + ++access_count; + AccessLock.unlock(); + } + + void RemoveAccess(){ + AccessLock.lock(); + --access_count; + AccessLock.unlock(); + } + + void SetChanging(){ + ChangingLock.lock(); + changing = true; + } + + void SetNotChanging(){ + changing = false; + ChangingLock.unlock(); + } + + void update(bool force = false){ + if(pending_clear && (force || access_count == 0)){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + current_data.clear(); + has_pending_data = (pending_add.size() > 0 || pending_remove.size() > 0); + pending_clear = false; + PendingLock.unlock(); + RemoveAccess(); + SetNotChanging(); + } + if(!pending_clear && has_pending_data && (force || access_count == 0)){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + typename std::map::iterator remove_itr; + for(remove_itr = pending_remove.begin(); remove_itr != pending_remove.end(); remove_itr++){ + current_data.erase(remove_itr->first); + } + typename std::map::iterator add_itr; + for(add_itr = pending_add.begin(); add_itr != pending_add.end(); add_itr++){ + current_data[add_itr->first] = add_itr->second; + } + pending_add.clear(); + pending_remove.clear(); + has_pending_data = false; + PendingLock.unlock(); + RemoveAccess(); + SetNotChanging(); + } + if(has_pending_deletes && (force || (Timer::GetCurrentTime2() > next_delete_attempt && access_count == 0))){ + SetChanging(); + while(access_count > 0){ + Sleep(1); + } + AddAccess(); + PendingLock.lock(); + unsigned int time = Timer::GetCurrentTime2(); + typename std::list*> deleteData; + typename std::map*, bool>::iterator remove_itr; + for(remove_itr = pending_deletes.begin(); remove_itr != pending_deletes.end(); remove_itr++){ + if(force || time >= remove_itr->first->GetTime()) + deleteData.push_back(remove_itr->first); + } + DeleteData* data = 0; + typename std::list*>::iterator remove_data_itr; + for(remove_data_itr = deleteData.begin(); remove_data_itr != deleteData.end(); remove_data_itr++){ + data = *remove_data_itr; + if((data->GetType() & MUTEXMAP_DELETE_TYPE_KEY) == MUTEXMAP_DELETE_TYPE_KEY){ + data->DeleteKey(); + } + if((data->GetType() & MUTEXMAP_DELETE_TYPE_VALUE) == MUTEXMAP_DELETE_TYPE_VALUE){ + data->DeleteValue(); + } + pending_deletes.erase(data); + delete data; + } + next_delete_attempt = Timer::GetCurrentTime2() + 1000; + PendingLock.unlock(); + RemoveAccess(); + SetNotChanging(); + has_pending_deletes = (pending_deletes.size() > 0); + } + } + Locker PendingLock; + Locker AccessLock; + Locker ChangingLock; + std::map current_data; + std::map pending_add; + std::map*, bool > pending_deletes; + std::map pending_remove; + volatile unsigned int next_delete_attempt; + volatile int access_count; + volatile bool delete_all; + volatile bool changing; + volatile bool has_pending_data; + volatile bool has_pending_deletes; + volatile bool pending_clear; +}; +#endif diff --git a/source/WorldServer/MutexVector.h b/source/WorldServer/MutexVector.h new file mode 100644 index 0000000..1ca3c28 --- /dev/null +++ b/source/WorldServer/MutexVector.h @@ -0,0 +1,202 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef MUTEXVECTOR_H +#define MUTEXVECTOR_H +#include +#include "MutexHelper.h" + +#define MUTEXVECTOR_PENDING_ADD 1 +#define MUTEXVECTOR_PENDING_REMOVE 2 +#define MUTEXVECTOR_PENDING_DELETE 3 + +template +class MutexVector{ +public: + MutexVector(){ + has_pending_data = false; + pending_clear = false; + changing = false; + access_count = 0; + } + ~MutexVector(){ + + } + class iterator { + private: + typename std::vector::iterator itr; // Current element + MutexVector* vector; + bool first_itr; + public: + iterator(){ + } + iterator(MutexVector* vector){ + if(vector){ + this->vector = vector; + vector->update(); + ++this->vector->access_count; + first_itr = true; + itr = vector->current_data.begin(); + if(itr != vector->current_data.end()) + value = *itr; + } + else + this->vector = 0; + } + ~iterator(){ + if(vector) + --vector->access_count; + } + + bool HasNext(){ + return itr != vector->current_data.end(); + } + + bool Next(){ + if(vector->pending_clear) + return false; + if(first_itr) + first_itr = false; + else + itr++; + if(itr != vector->current_data.end()){ + value = *itr; + if(vector->pending_data.count(value) > 0 && vector->pending_data[value] == false) //pending delete + return Next(); + return true; + } + return false; + } + iterator* operator->() { + return this; + } + T value; +}; + void update(){ + //if(access_count > 5) + // cout << "Possible error.\n"; + while(changing){ + Sleep(1); + } + if(pending_clear && access_count == 0){ + changing = true; + while(access_count > 0){ + Sleep(1); + } + ++access_count; + current_data.clear(); + has_pending_data = (pending_data.size() > 0); + pending_clear = false; + --access_count; + changing = false; + } + if(!pending_clear && has_pending_data && access_count == 0){ + changing = true; + while(access_count > 0){ + Sleep(1); + } + ++access_count; + typename std::map::iterator pending_itr; + for(pending_itr = pending_data.begin(); pending_itr != pending_data.end(); pending_itr++){ + if(pending_itr->second) + current_data.push_back(pending_itr->first); + else{ + typename std::vector::iterator data_itr; + for(data_itr = current_data.begin(); data_itr != current_data.end(); data_itr++){ + if(*data_itr == pending_itr->first){ + current_data.erase(data_itr); + break; + } + } + } + } + pending_data.clear(); + has_pending_data = false; + --access_count; + changing = false; + } + handle_deletes.CheckDeletes(); + } + unsigned int size(bool include_pending = false){ + if(include_pending) + return current_data.size() + pending_data.size(); + return current_data.size(); + } + iterator begin(){ + return iterator(this); + } + void clear(){ + pending_clear = true; + update(); + } + + unsigned int count(T key){ + unsigned int ret = 0; + while(changing){ + Sleep(1); + } + ++access_count; + typename std::list::iterator iter; + for(iter = current_data.begin(); iter != current_data.end(); iter++){ + if(*iter == key) + ret++; + } + --access_count; + return ret; + } + + void Remove(T key, bool erase = false, unsigned int erase_time = 0){ + while(changing){ + Sleep(1); + } + ++access_count; + pending_data[key] = false; + if(erase) + handle_deletes.AddPendingDelete(key, erase_time); + has_pending_data = true; + --access_count; + update(); + } + + void Add(T key){ + while(changing){ + Sleep(1); + } + ++access_count; + pending_data[key] = true; + has_pending_data = true; + --access_count; + update(); + } + T Get(unsigned int index){ + while(changing){ + Sleep(1); + } + return current_data[index]; + } +private: + volatile int access_count; + std::vector current_data; + std::map pending_data; + HandleDeletes handle_deletes; + volatile bool changing; + volatile bool has_pending_data; + volatile bool pending_clear; +}; +#endif diff --git a/source/WorldServer/NPC.cpp b/source/WorldServer/NPC.cpp new file mode 100644 index 0000000..912cd46 --- /dev/null +++ b/source/WorldServer/NPC.cpp @@ -0,0 +1,1075 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "NPC.h" +#include "WorldDatabase.h" +#include +#include "client.h" +#include "World.h" +#include "races.h" +#include "../common/Log.h" +#include "NPC_AI.h" +#include "Appearances.h" +#include "SpellProcess.h" +#include "Skills.h" +#include "Rules/Rules.h" + +extern MasterSpellList master_spell_list; +extern ConfigReader configReader; +extern WorldDatabase database; +extern World world; +extern Races races; +extern Appearance master_appearance_list; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; + +NPC::NPC(){ + Initialize(); + if (GetMaxSpeed() > 0) + SetSpeed(GetMaxSpeed()); +} + +NPC::NPC(NPC* old_npc){ + Initialize(); + if(old_npc){ + if(old_npc->GetSizeOffset() > 0){ + int8 offset = old_npc->GetSizeOffset()+1; + sint32 tmp_size = old_npc->size + (rand()%offset - rand()%offset); + if(tmp_size < 0) + tmp_size = 1; + else if(tmp_size >= 0xFFFF) + tmp_size = 0xFFFF; + size = (int16)tmp_size; + } + else + size = old_npc->size; + SetCollector(old_npc->IsCollector()); + SetMerchantID(old_npc->GetMerchantID()); + SetMerchantType(old_npc->GetMerchantType()); + SetMerchantLevelRange(old_npc->GetMerchantMinLevel(), old_npc->GetMerchantMaxLevel()); + SetPrimaryCommands(&old_npc->primary_command_list); + SetSecondaryCommands(&old_npc->secondary_command_list); + appearance_id = old_npc->appearance_id; + database_id = old_npc->database_id; + primary_command_list_id = old_npc->primary_command_list_id; + secondary_command_list_id = old_npc->secondary_command_list_id; + this->SetInfoStruct(old_npc->GetInfoStruct()); + //memcpy(GetInfoStruct(), old_npc->GetInfoStruct(), sizeof(InfoStruct)); + memcpy(&appearance, &old_npc->appearance, sizeof(AppearanceData)); + memcpy(&features, &old_npc->features, sizeof(CharFeatures)); + memcpy(&equipment, &old_npc->equipment, sizeof(EQ2_Equipment)); + if(appearance.min_level < appearance.max_level) + SetLevel(appearance.min_level + rand()%((appearance.max_level - appearance.min_level)+1)); + target = 0; + SetTotalHPBase(old_npc->GetTotalHPBase()); + SetTotalHPBaseInstance(old_npc->GetTotalHPBase()); + SetTotalPowerBase(old_npc->GetTotalPowerBase()); + SetTotalPowerBaseInstance(old_npc->GetTotalPowerBase()); + faction_id = old_npc->faction_id; + movement_interrupted = false; + old_npc->SetQuestsRequired(this); + SetTransporterID(old_npc->GetTransporterID()); + SetAIStrategy(old_npc->GetAIStrategy()); + SetPrimarySpellList(old_npc->GetPrimarySpellList()); + SetSecondarySpellList(old_npc->GetSecondarySpellList()); + SetPrimarySkillList(old_npc->GetPrimarySkillList()); + SetSecondarySkillList(old_npc->GetSecondarySkillList()); + SetEquipmentListID(old_npc->GetEquipmentListID()); + SetAggroRadius(old_npc->GetBaseAggroRadius()); + SetCastPercentage(old_npc->GetCastPercentage()); + SetRandomize(old_npc->GetRandomize()); + if(appearance.randomize > 0) + Randomize(this, appearance.randomize); + CalculateBonuses(); + SetHP(GetTotalHP()); + SetPower(GetTotalPower()); + UpdateWeapons(); + SetSoundsDisabled(old_npc->IsSoundsDisabled()); + SetFlyingCreature(); + SetWaterCreature(); + SetOmittedByDBFlag(old_npc->IsOmittedByDBFlag()); + SetLootTier(old_npc->GetLootTier()); + SetLootDropType(old_npc->GetLootDropType()); + has_spells = old_npc->HasSpells(); + SetScaredByStrongPlayers(old_npc->IsScaredByStrongPlayers()); + } +} + +NPC::~NPC(){ + ResetMovement(); + if(skills){ + map::iterator skill_itr; + for(skill_itr = skills->begin(); skill_itr != skills->end(); skill_itr++){ + safe_delete(skill_itr->second); + } + safe_delete(skills); + } + if(spells) { + vector::iterator itr; + for(itr = spells->begin(); itr != spells->end(); itr++){ + safe_delete((*itr)); + } + spells->clear(); + } + safe_delete(spells); + MutexMap::iterator sb_itr = skill_bonus_list.begin(); + while (sb_itr.Next()) + RemoveSkillBonus(sb_itr.first); + safe_delete(runback); + safe_delete(m_brain); +} + +void NPC::Initialize(){ + ai_strategy = 0; + attack_type = 0; + movement_index = 0; + resume_movement = true; + movement_start_time = 0; + spawn_type = 2; + movement_interrupted = false; + attack_resume_needed = false; + MMovementLoop.SetName("NPC::MMovementLoop"); + last_movement_update = Timer::GetCurrentTime2(); + aggro_radius = 0.0f; + base_aggro_radius = 0.0f; + skills = 0; + spells = 0; + runback = 0; + m_brain = new ::Brain(this); + MBrain.SetName("NPC::m_brain"); + m_runningBack = false; + m_runbackHeadingDir1 = m_runbackHeadingDir2 = 0; + following = false; + SetFollowTarget(0); + m_petDismissing = false; + m_ShardID = 0; + m_ShardCharID = 0; + m_ShardCreatedTimestamp = 0; + m_call_runback = false; + has_spells = false; + cast_on_aggro_completed = false; +} + +EQ2Packet* NPC::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +void NPC::SetSkills(map* in_skills){ + if (skills) { + map::iterator skill_itr; + for(skill_itr = skills->begin(); skill_itr != skills->end(); skill_itr++){ + safe_delete(skill_itr->second); + } + + safe_delete(skills); + } + + skills = in_skills; +} + +void NPC::SetSpells(vector* in_spells){ + for(int i=0;i::iterator itr; + for(itr = spells->begin(); itr != spells->end(); itr++){ + safe_delete((*itr)); + } + } + safe_delete(spells); + + spells = in_spells; + + if(spells && spells->size() > 0) { + has_spells = true; + + vector::iterator itr; + for(itr = spells->begin(); itr != spells->end();){ + if((*itr)->cast_on_spawn) { + cast_on_spells[CAST_TYPE::CAST_ON_SPAWN].push_back((*itr)); + itr = spells->erase(itr); // we don't keep on the master list + continue; + } + if((*itr)->cast_on_initial_aggro) { + cast_on_spells[CAST_TYPE::CAST_ON_AGGRO].push_back((*itr)); + itr = spells->erase(itr); // we don't keep on the master list + continue; + } + + // didn't hit a continue case, iterate + itr++; + } + } + else { + has_spells = false; + } +} + +void NPC::SetRunbackLocation(float x, float y, float z, int32 gridid, bool set_hp_runback){ + safe_delete(runback); + runback = new MovementLocation; + runback->x = x; + runback->y = y; + runback->z = z; + runback->gridid = gridid; + runback->stage = 0; + runback->reset_hp_on_runback = set_hp_runback; +} + +MovementLocation* NPC::GetRunbackLocation(){ + return runback; +} + +float NPC::GetRunbackDistance(){ + if(!runback) + return 0; + return GetDistance(runback->x, runback->y, runback->z); +} + +void NPC::Runback(float distance, bool stopFollowing){ + if(!runback) + return; + + if ( distance == 0.0f ) + distance = GetRunbackDistance(); // gotta make sure its true, lua doesn't send the distance + + if(stopFollowing) + following = false; + + if (!m_runningBack) + { + ClearRunningLocations(); + GetZone()->movementMgr->StopNavigation((Entity*)this); + } + + m_runningBack = true; + SetSpeed(GetMaxSpeed()*2); + + if (CheckLoS(glm::vec3(runback->x, runback->z, runback->y + 1.0f), glm::vec3(GetX(), GetZ(), GetY() + 1.0f))) + { + FaceTarget(runback->x, runback->z); + ClearRunningLocations(); + GetZone()->movementMgr->DisruptNavigation((Entity*)this); + if (GetRunbackLocation()->gridid > 0) + SetLocation(GetRunbackLocation()->gridid); + AddRunningLocation(runback->x, runback->y, runback->z, GetSpeed(), 0, true, true, "", true); + } + else + GetZone()->movementMgr->NavigateTo((Entity*)this, runback->x, runback->y, runback->z); + + //AddRunningLocation(runback->x, runback->y, runback->z, GetSpeed(), 0, false); + last_movement_update = Timer::GetCurrentTime2(); +} + +void NPC::ClearRunback(){ + safe_delete(runback); + m_runningBack = false; + m_runbackHeadingDir1 = m_runbackHeadingDir2 = 0; + resume_movement = true; + NeedsToResumeMovement(false); +} + +void NPC::StartRunback(bool reset_hp_on_runback) +{ + if(GetRunbackLocation()) + return; + + SetRunbackLocation(GetX(), GetY(), GetZ(), GetLocation(), reset_hp_on_runback); + m_runbackHeadingDir1 = appearance.pos.Dir1; + m_runbackHeadingDir2 = appearance.pos.Dir2; +} + +bool NPC::PauseMovement(int32 period_of_time_ms) +{ + if(period_of_time_ms < 1) + period_of_time_ms = 1; + + if(HasMovementLoop() || HasMovementLocations()) + StartRunback(); + + RunToLocation(GetX(),GetY(),GetZ()); + + pause_timer.Start(period_of_time_ms, true); + + return true; +} + +bool NPC::IsPauseMovementTimerActive() +{ + if(pause_timer.Check()) + { + pause_timer.Disable(); + m_call_runback = true; + } + + return pause_timer.Enabled(); +} + +void NPC::InCombat(bool val){ + if (in_combat == val) + return; + + if(in_combat == false && val && GetZone()){ + LogWrite(NPC__DEBUG, 3, "NPC", "'%s' engaged in combat with '%s'", this->GetName(), ( GetTarget() ) ? GetTarget()->GetName() : "Unknown" ); + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_ATTACKED, GetTarget()); + SetTempActionState(0); // disable action states in combat + } + if(!in_combat && val){ + // if not a pet and no current run back location set then set one to the current location + bool hadRunback = (GetRunbackLocation() != nullptr); + if(hadRunback) { + pause_timer.Disable(); + if(!GetRunbackLocation()->reset_hp_on_runback) // if we aren't resetting hp it isn't a real reset point, just face target swings + ClearRunback(); + } + if(!IsPet()) { + StartRunback(true); + } + } + + int8 ruleAutoLockEncounter = rule_manager.GetGlobalRule(R_World, AutoLockEncounter)->GetInt8(); + in_combat = val; + if(val){ + LogWrite(NPC__DEBUG, 3, "NPC", "'%s' engaged in combat with '%s'", this->GetName(), ( GetTarget() ) ? GetTarget()->GetName() : "Unknown" ); + if(ruleAutoLockEncounter) { + SetLockedNoLoot(ENCOUNTER_STATE_LOCKED); + } + AddIconValue(64); + // In combat so lets set the NPC's speed to its max speed + if (GetMaxSpeed() > 0) + SetSpeed(GetMaxSpeed()); + } + else{ + SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE); + RemoveIconValue(64); + if (GetHP() > 0){ + SetTempActionState(-1); //re-enable action states on exiting combat + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_COMBAT_RESET); + // Stop all HO's attached to this NPC + GetZone()->GetSpellProcess()->KillHOBySpawnID(GetID()); + } + } + if(!MovementInterrupted() && val && GetSpeed() > 0 && movement_loop.size() > 0){ + CalculateRunningLocation(true); + } + MovementInterrupted(val); +} + +bool NPC::HandleUse(Client* client, string type){ + if(!client || type.length() == 0 || (appearance.show_command_icon == 0 && appearance.display_hand_icon == 0)) + return false; + EntityCommand* entity_command = FindEntityCommand(type); + if (entity_command) { + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + return true; + } + return false; + /*Spell* spell = master_spell_list.GetSpellByName((char*)type.c_str()); + if(spell) + client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()->GetTarget()); + else if(GetSpawnScript()) + client->GetCurrentZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, client->GetPlayer(), (char*)type.c_str()); + else + return false; + return true;*/ +} + + +bool NPC::CheckSameAppearance(string name, int16 id) +{ + // need to iterate through master_appearance_list finding if id contains name + return true; +} + + +void NPC::Randomize(NPC* npc, int32 flags) +{ + int8 random = 0; + int8 min_val = 0; + int8 max_val = 255; + + /* We need to check if gender is going to be randomized first because if the race is going to be + * randomized, we need to know its gender so we can choose the proper model. + * We also need to make sure the model gets set properly if the player chooses to randomize the gender + * and not the race. */ + int8 old_gender = npc->GetGender(); + + if (flags & RANDOMIZE_GENDER) + { + random = MakeRandomInt(1, 2); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Gender: %i", random); + npc->SetGender(random); + } + + if ((flags & RANDOMIZE_RACE) || (flags & RANDOMIZE_GENDER && old_gender != npc->GetGender()) || (flags & RANDOMIZE_MODEL_TYPE)) + { + string race_string = ""; + int8 gender = npc->GetGender(); + + if (gender == 1 || gender == 2) + { + + if (flags & RANDOMIZE_RACE) + { + if(npc->GetAlignment() == 1) // Good + random = races.GetRaceNameGood(); + else if(npc->GetAlignment() < 0) // Evil + random = races.GetRaceNameEvil(); + else // All + random = MakeRandomInt(0, 20); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Race: %s (%i)", races.GetRaceNameCase(random), random); + npc->SetRace(random); + } + + switch (npc->GetRace()) + { + case BARBARIAN: + // JA: If randomize has "4" (model) and race=0, barbarians show up in place of the real models. + // Started working on a solution (CheckSameAppearance) but cannot get it to work yet. + // Solution for now is to not randomize models for race=0. Set to race=255, or turn off model randomize + race_string = "/barbarian/barbarian"; + break; + case DARK_ELF: + race_string = "/darkelf/darkelf"; + break; + case DWARF: + race_string = "/dwarf/dwarf"; + break; + case ERUDITE: + race_string = "/erudite/erudite"; + break; + case FROGLOK: + race_string = "/froglok/froglok"; + break; + case GNOME: + race_string = "/gnome/gnome"; + break; + case HALF_ELF: + race_string = "/halfelf/halfelf"; + break; + case HALFLING: + race_string = "/halfling/halfling"; + break; + case HIGH_ELF: + race_string = "/highelf/highelf"; + break; + case HUMAN: + race_string = "/human/human"; + break; + case IKSAR: + race_string = "/iksar/iksar"; + break; + case KERRA: + race_string = "/kerra/kerra"; + break; + case OGRE: + race_string = "/ogre/ogre"; + break; + case RATONGA: + race_string = "/ratonga/ratonga"; + break; + case TROLL: + race_string = "/troll/troll"; + break; + case WOOD_ELF: + race_string = "/woodelf/woodelf"; + break; + case FAE: + race_string = "/fae/fae_light"; + break; + case ARASAI: + race_string = "/fae/fae_dark"; + break; + case SARNAK: + gender == 1 ? race_string = "01/sarnak_male/sarnak" : race_string = "01/sarnak_female/sarnak"; + break; + case VAMPIRE: + race_string = "/vampire/vampire"; + break; + case AERAKYN: + race_string = "/aerakyn/aerakyn"; + break; + } + + if (race_string.length() > 0) + { + string gender_string; + + gender == 1 ? gender_string = "male" : gender_string = "female"; + + vector* id_list = database.GetAppearanceIDsLikeName("ec/pc" + race_string + "_" + gender_string); + + if (id_list) + { + int32 index = MakeRandomInt(0, id_list->size() - 1); + npc->SetModelType(id_list->at(index)); + npc->SetSogaModelType(id_list->at(index)); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Model Type: %i", npc->GetModelType()); + int16 wing_type = 0; + + if (npc->GetRace() == FAE) + { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/fae_wings/fae_wing"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + else if (npc->GetRace() == ARASAI) + { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/fae_wings/fae_d_wing"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + else if (npc->GetRace() == AERAKYN) + { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/aerakyn/aerakyn_male_wings"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + if (wing_type > 0) + { + EQ2_Color color1; + EQ2_Color color2; + color1.red = MakeRandomInt(0, 255); + color1.green = MakeRandomInt(0, 255); + color1.blue = MakeRandomInt(0, 255); + color2.red = MakeRandomInt(0, 255); + color2.green = MakeRandomInt(0, 255); + color2.blue = MakeRandomInt(0, 255); + npc->SetWingColor1(color1); + npc->SetWingColor2(color2); + } + npc->SetWingType(wing_type); + safe_delete(id_list); + } + } + } + } + + if (flags & RANDOMIZE_FACIAL_HAIR_TYPE) { + vector* id_list = database.GetAppearanceIDsLikeName("accessories/hair/face"); + if (id_list) { + int32 index = MakeRandomInt(0, id_list->size() - 1); + npc->SetFacialHairType(id_list->at(index)); + npc->SetSogaFacialHairType(id_list->at(index)); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Facial Hair: %i", npc->GetFacialHairType()); + safe_delete(id_list); + } + } + if (flags & RANDOMIZE_HAIR_TYPE) { + vector* id_list = database.GetAppearanceIDsLikeName("accessories/hair/hair"); + if (id_list) { + int32 index = MakeRandomInt(0, id_list->size() - 1); + npc->SetHairType(id_list->at(index)); + npc->SetSogaHairType(id_list->at(index)); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair: %i", npc->GetHairType()); + safe_delete(id_list); + } + } + if (flags & RANDOMIZE_WING_TYPE) { + int16 wing_type = 0; + if (npc->GetRace() == FAE) { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/fae_wings/fae_wing"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + else if (npc->GetRace() == ARASAI) { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/fae_wings/fae_d_wing"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + else if (npc->GetRace() == AERAKYN) + { + vector* id_list_wings = database.GetAppearanceIDsLikeName("ec/pc/aerakyn/aerakyn_male_wings"); + if (id_list_wings) { + wing_type = id_list_wings->at(MakeRandomInt(0, id_list_wings->size() - 1)); + safe_delete(id_list_wings); + } + } + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Wing Type: %i", wing_type); + npc->SetWingType(wing_type); + } + + if (flags & RANDOMIZE_CHEEK_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Cheek[%i]: %i", i, random); + npc->features.cheek_type[i] = random; + } + } + if (flags & RANDOMIZE_CHIN_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Chin[%i]: %i", i, random); + npc->features.chin_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_EAR_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Ear[%i]: %i", i, random); + npc->features.ear_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_EYE_BROW_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Eyebrow[%i]: %i", i, random); + npc->features.eye_brow_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_EYE_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Eye[%i]: %i", i, random); + npc->features.eye_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_LIP_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Lip[%i]: %i", i, random); + npc->features.lip_type[i] = MakeRandomFloat(-100, 100); + } + } + if (flags & RANDOMIZE_NOSE_TYPE) { + for(int i=0;i<3;i++) { + random = MakeRandomFloat(-100, 100); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Nose[%i]: %i", i, random); + npc->features.nose_type[i] = MakeRandomFloat(-100, 100); + } + } + + /* Randomize Colors */ + random = MakeRandomInt(0, 255); + if(random > 30) { + min_val = random - MakeRandomInt(0, 30); + max_val = random + MakeRandomInt(0, 30); + } + if(max_val > 255) + max_val = 255; + + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Color Ranges, random: %i, min: %i, max: %i", random, min_val, max_val); + + if (flags & RANDOMIZE_EYE_COLOR) { + npc->features.eye_color.red = MakeRandomInt(min_val, max_val); + npc->features.eye_color.green = MakeRandomInt(min_val, max_val); + npc->features.eye_color.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Eye Color - R: %i, G: %i, B: %i", npc->features.eye_color.red, npc->features.eye_color.green, npc->features.eye_color.blue); + } + if (flags & RANDOMIZE_HAIR_COLOR1) { + npc->features.hair_color1.red = MakeRandomInt(min_val, max_val); + npc->features.hair_color1.green = MakeRandomInt(min_val, max_val); + npc->features.hair_color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Color 1 - R: %i, G: %i, B: %i", npc->features.hair_color1.red, npc->features.hair_color1.green, npc->features.hair_color1.blue); + } + if (flags & RANDOMIZE_HAIR_COLOR2) { + npc->features.hair_color2.red = MakeRandomInt(min_val, max_val); + npc->features.hair_color2.green = MakeRandomInt(min_val, max_val); + npc->features.hair_color2.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Color 2 - R: %i, G: %i, B: %i", npc->features.hair_color2.red, npc->features.hair_color2.green, npc->features.hair_color2.blue); + } + if (flags & RANDOMIZE_HAIR_HIGHLIGHT) { + npc->features.hair_highlight_color.red = MakeRandomInt(min_val, max_val); + npc->features.hair_highlight_color.green = MakeRandomInt(min_val, max_val); + npc->features.hair_highlight_color.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Highlight - R: %i, G: %i, B: %i", npc->features.hair_highlight_color.red, npc->features.hair_highlight_color.green, npc->features.hair_highlight_color.blue); + } + if (flags & RANDOMIZE_HAIR_FACE_COLOR) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Facial Hair Color - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetFacialHairColor(color1); + } + if (flags & RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Facial Hair Highlight - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetFacialHairHighlightColor(color1); + } + if (flags & RANDOMIZE_HAIR_TYPE_COLOR) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Type Color - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetHairColor(color1); + } + if (flags & RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Hair Type Highlight - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetHairTypeHighlightColor(color1); + } + if (flags & RANDOMIZE_SKIN_COLOR) { + npc->features.skin_color.red = MakeRandomInt(min_val, max_val); + npc->features.skin_color.green = MakeRandomInt(min_val, max_val); + npc->features.skin_color.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Skin Color - R: %i, G: %i, B: %i", npc->features.eye_color.red, npc->features.eye_color.green, npc->features.eye_color.blue); + } + if (flags & RANDOMIZE_WING_COLOR1) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Wing Color 1 - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetWingColor1(color1); + } + if (flags & RANDOMIZE_WING_COLOR2) { + EQ2_Color color1; + color1.red = MakeRandomInt(min_val, max_val); + color1.green = MakeRandomInt(min_val, max_val); + color1.blue = MakeRandomInt(min_val, max_val); + LogWrite(NPC__DEBUG, 5, "NPCs", "Randomizing Wing Color 2 - R: %i, G: %i, B: %i", color1.red, color1.green, color1.blue); + npc->SetWingColor2(color1); + } +} + +Skill* NPC::GetSkillByName(const char* name, bool check_update){ + if(skills && skills->count(name) > 0){ + Skill* ret = (*skills)[name]; + if(ret && check_update && ret->current_val < ret->max_val && (rand()%100) >= 90) + ret->current_val++; + return ret; + } + return 0; +} + +Skill* NPC::GetSkillByID(int32 id, bool check_update){ + Skill* skill = master_skill_list.GetSkill(id); + + if(skill && skills && skills->count(skill->name.data) > 0){ + Skill* ret = (*skills)[skill->name.data]; + if(ret && check_update && ret->current_val < ret->max_val && (rand()%100) >= 90) + ret->current_val++; + return ret; + } + return 0; +} + +int8 NPC::GetAttackType(){ + return attack_type; +} + +void NPC::SetAIStrategy(int8 strategy){ + ai_strategy = strategy; +} + +int8 NPC::GetAIStrategy(){ + return ai_strategy; +} + +void NPC::SetPrimarySpellList(int32 id){ + primary_spell_list = id; +} + +int32 NPC::GetPrimarySpellList(){ + return primary_spell_list; +} + +void NPC::SetSecondarySpellList(int32 id){ + secondary_spell_list = id; +} + +int32 NPC::GetSecondarySpellList(){ + return secondary_spell_list; +} + +void NPC::SetPrimarySkillList(int32 id){ + primary_skill_list = id; +} + +int32 NPC::GetPrimarySkillList(){ + return primary_skill_list; +} + +void NPC::SetSecondarySkillList(int32 id){ + secondary_skill_list = id; +} + +int32 NPC::GetSecondarySkillList(){ + return secondary_skill_list; +} + +void NPC::SetEquipmentListID(int32 id){ + equipment_list_id = id; +} + +int32 NPC::GetEquipmentListID(){ + return equipment_list_id; +} + +Spell* NPC::GetNextSpell(Spawn* target, float distance){ + if(!cast_on_aggro_completed) { + Spell* ret = nullptr; + Spell* tmpSpell = nullptr; + vector::iterator itr; + Spawn* tmpTarget = target; + for (itr = cast_on_spells[CAST_ON_AGGRO].begin(); itr != cast_on_spells[CAST_ON_AGGRO].end(); itr++) { + tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + + if(!tmpSpell) + continue; + if (tmpSpell->GetSpellData()->friendly_spell > 0) { + tmpTarget = (Spawn*)this; + } + if (tmpSpell->GetSpellData()) { + SpellEffects* effect = ((Entity*)tmpTarget)->GetSpellEffect(tmpSpell->GetSpellID()); + if (!effect) { + ret = tmpSpell; + + if (tmpSpell->GetSpellData()->friendly_spell > 0) { + tmpTarget = target; + } + break; + } + } + if (tmpSpell->GetSpellData()->friendly_spell > 0) { + tmpTarget = target; + } + } + + if(ret) { + return ret; + } + else { + cast_on_aggro_completed = true; + } + } + + int8 val = rand()%100; + if(ai_strategy == AI_STRATEGY_OFFENSIVE){ + if(val >= 20)//80% chance to cast offensive spell if Offensive AI + return GetNextSpell(distance, AI_STRATEGY_OFFENSIVE); + return GetNextSpell(distance, AI_STRATEGY_DEFENSIVE); + } + else if(ai_strategy == AI_STRATEGY_DEFENSIVE){ + if(val >= 20)//80% chance to cast defensive spell if Defensive AI + return GetNextSpell(distance, AI_STRATEGY_DEFENSIVE); + return GetNextSpell(distance, AI_STRATEGY_OFFENSIVE); + } + return GetNextSpell(distance, AI_STRATEGY_BALANCED); +} + +Spell* NPC::GetNextSpell(float distance, int8 type){ + Spell* ret = 0; + if(spells){ + if(distance < 0) + distance = 0; + Spell* tmpSpell = 0; + vector::iterator itr; + for(itr = spells->begin(); itr != spells->end(); itr++){ + // if positive, then say the hp ratio must be GREATER than OR EQUAL TO + if((*itr)->required_hp_ratio > 0 && (*itr)->required_hp_ratio < 101 && GetIntHPRatio() >= (*itr)->required_hp_ratio) + continue; + // if negative, then say the hp ratio must be LESS than OR EQUAL TO + if((*itr)->required_hp_ratio < 0 && (*itr)->required_hp_ratio > -101 && (-(*itr)->required_hp_ratio) >= GetIntHPRatio()) + continue; + tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + if(!tmpSpell || (type == AI_STRATEGY_OFFENSIVE && tmpSpell->GetSpellData()->friendly_spell > 0)) + continue; + if (tmpSpell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) + continue; + if(type == AI_STRATEGY_DEFENSIVE && tmpSpell->GetSpellData()->friendly_spell == 0) + continue; + if(distance <= tmpSpell->GetSpellData()->range && distance >= tmpSpell->GetSpellData()->min_range && GetPower() >= tmpSpell->GetPowerRequired(this)){ + ret = tmpSpell; + if((rand()%100) >= 70) //30% chance to stop after finding the first match, this will give the appearance of the NPC randomly choosing a spell to cast + break; + } + } + if(!ret && type != AI_STRATEGY_BALANCED) + ret = GetNextSpell(distance, AI_STRATEGY_BALANCED); //wasnt able to find a valid match, so find any spell that the NPC has + } + return ret; +} + +Spell* NPC::GetNextBuffSpell(Spawn* target) { + if(!target) { + target = (Spawn*)this; + } + + Spell* ret = 0; + + if(!target->IsEntity()) { + return ret; + } + + if (spells && GetZone()->GetSpellProcess()) { + Spell* tmpSpell = 0; + vector::iterator itr; + for (itr = cast_on_spells[CAST_ON_SPAWN].begin(); itr != cast_on_spells[CAST_ON_SPAWN].end(); itr++) { + tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + if (tmpSpell && tmpSpell->GetSpellData()) { + SpellEffects* effect = ((Entity*)target)->GetSpellEffect(tmpSpell->GetSpellID()); + if (effect) { + if (effect->tier < tmpSpell->GetSpellTier()) { + ret = tmpSpell; + break; + } + } + else { + ret = tmpSpell; + break; + } + } + } + + for (itr = spells->begin(); itr != spells->end(); itr++) { + // if positive, then say the hp ratio must be GREATER than OR EQUAL TO + if((*itr)->required_hp_ratio > 0 && (*itr)->required_hp_ratio < 101 && GetIntHPRatio() >= (*itr)->required_hp_ratio) + continue; + // if negative, then say the hp ratio must be LESS than OR EQUAL TO + if((*itr)->required_hp_ratio < 0 && (*itr)->required_hp_ratio > -101 && (-(*itr)->required_hp_ratio) >= GetIntHPRatio()) + continue; + tmpSpell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + if (tmpSpell && tmpSpell->GetSpellData() && tmpSpell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) { + SpellEffects* effect = ((Entity*)target)->GetSpellEffect(tmpSpell->GetSpellID()); + if (effect) { + if (effect->tier < tmpSpell->GetSpellTier()) { + ret = tmpSpell; + break; + } + } + else { + ret = tmpSpell; + break; + } + } + } + } + return ret; +} + +void NPC::SetAggroRadius(float radius, bool overrideBaseValue){ + if (base_aggro_radius == 0.0f || overrideBaseValue) + base_aggro_radius = radius; + + aggro_radius = radius; +} + +float NPC::GetAggroRadius(){ + return aggro_radius; +} + +void NPC::SetCastPercentage(int8 percentage){ + cast_percentage = percentage; +} + +int8 NPC::GetCastPercentage(){ + return cast_percentage; +} + +void NPC::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { + if (value != 0) { + SkillBonus* sb; + if (skill_bonus_list.count(spell_id) == 0) { + sb = new SkillBonus; + sb->spell_id = spell_id; + skill_bonus_list.Put(spell_id, sb); + } + else + sb = skill_bonus_list.Get(spell_id); + if (sb->skills[skill_id] == 0) { + SkillBonusValue* sbv = new SkillBonusValue; + sbv->skill_id = skill_id; + sbv->value = value; + sb->skills[skill_id] = sbv; + if (skills) { + map::iterator itr; + for (itr = skills->begin(); itr != skills->end(); itr++) { + Skill* skill = itr->second; + if (skill->skill_id == sbv->skill_id) { + skill->current_val += (int16)sbv->value; + skill->max_val += (int16)sbv->value; + } + } + } + } + } +} + +void NPC::RemoveSkillBonus(int32 spell_id) { + if (skill_bonus_list.count(spell_id) > 0) { + SkillBonus* sb = skill_bonus_list.Get(spell_id); + skill_bonus_list.erase(spell_id); + map::iterator itr; + for (itr = sb->skills.begin(); itr != sb->skills.end(); itr++) { + SkillBonusValue* sbv = itr->second; + if (skills) { + map::iterator skill_itr; + for (skill_itr = skills->begin(); skill_itr != skills->end(); skill_itr++) { + Skill* skill = skill_itr->second; + if (sbv->skill_id == skill->skill_id) { + skill->current_val -= (int16)sbv->value; + skill->max_val -= (int16)sbv->value; + } + } + } + safe_delete(sbv); + } + safe_delete(sb); + } +} + +void NPC::SetBrain(::Brain* brain) { + // Again, had to use the '::' to refer to the Brain class and not the function defined in the NPC class + MBrain.writelock(__FUNCTION__, __LINE__); + // Check to make sure the NPC the brain controls matches this npc + if (brain && brain->GetBody() != this) { + LogWrite(NPC_AI__ERROR, 0, "NPC_AI", "Brain body does not match the npc we tried to assign the brain to."); + MBrain.releasewritelock(__FUNCTION__, __LINE__); + return; + } + + // Store the old brain in a temp pointer so we can delete it later + ::Brain* old_brain = m_brain; + // Set the brain for this NPC to the new brain + m_brain = brain; + // Release the lock + MBrain.releasewritelock(__FUNCTION__, __LINE__); + // Delete the old brain + safe_delete(old_brain); +} + +void NPC::SetZone(ZoneServer* in_zone, int32 version) { + Spawn::SetZone(in_zone, version); + if (in_zone){ + GetZone()->SetNPCEquipment(this); + SetSkills(GetZone()->GetNPCSkills(primary_skill_list, secondary_skill_list)); + SetSpells(world.GetNPCSpells(primary_spell_list, secondary_spell_list)); + } +} diff --git a/source/WorldServer/NPC.h b/source/WorldServer/NPC.h new file mode 100644 index 0000000..22cca52 --- /dev/null +++ b/source/WorldServer/NPC.h @@ -0,0 +1,217 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_NPC__ +#define __EQ2_NPC__ +#include +#include "Entity.h" +#include "MutexMap.h" + +#define AI_STRATEGY_BALANCED 1 +#define AI_STRATEGY_OFFENSIVE 2 +#define AI_STRATEGY_DEFENSIVE 3 + + +// Randomize Appearances +#define RANDOMIZE_GENDER 1 +#define RANDOMIZE_RACE 2 +#define RANDOMIZE_MODEL_TYPE 4 + +// Randomize appearance id (spawn_npcs table values) +#define RANDOMIZE_FACIAL_HAIR_TYPE 8 // was RANDOMIZE_FACIAL_HAIR +#define RANDOMIZE_HAIR_TYPE 16 // was RANDOMIZE_HAIR +//#define RANDOMIZE_LEGS_TYPE 32 // spare! +#define RANDOMIZE_WING_TYPE 64 + +// Randomize parameters (npc_appearances, sInt values) +#define RANDOMIZE_CHEEK_TYPE 128 +#define RANDOMIZE_CHIN_TYPE 256 +#define RANDOMIZE_EAR_TYPE 512 +#define RANDOMIZE_EYE_BROW_TYPE 1024 +#define RANDOMIZE_EYE_TYPE 2048 +#define RANDOMIZE_LIP_TYPE 4096 +#define RANDOMIZE_NOSE_TYPE 8192 + +// Randomize colors/hues (npc_appearances, RGB values) +#define RANDOMIZE_EYE_COLOR 16384 +#define RANDOMIZE_HAIR_COLOR1 32768 +#define RANDOMIZE_HAIR_COLOR2 65536 +#define RANDOMIZE_HAIR_HIGHLIGHT 131072 +#define RANDOMIZE_HAIR_FACE_COLOR 262144 // was RANDOMIZE_FACIAL_HAIR_COLOR +#define RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR 524288 +#define RANDOMIZE_HAIR_TYPE_COLOR 1048576 // was RANDOMIZE_HAIR_COLOR +#define RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR 2097152 +#define RANDOMIZE_SKIN_COLOR 4194304 +#define RANDOMIZE_WING_COLOR1 8388608 +#define RANDOMIZE_WING_COLOR2 16777216 + +// All Flags On: 33554431 + +#define PET_TYPE_COMBAT 1 +#define PET_TYPE_CHARMED 2 +#define PET_TYPE_DEITY 3 +#define PET_TYPE_COSMETIC 4 +#define PET_TYPE_DUMBFIRE 5 + +enum CAST_TYPE { + CAST_ON_SPAWN=0, + CAST_ON_AGGRO=1, + MAX_CAST_TYPES=2 +}; +class Brain; + +class NPCSpell { +public: + NPCSpell() { + + } + + NPCSpell(NPCSpell* inherit) { + list_id = inherit->list_id; + spell_id = inherit->spell_id; + tier = inherit->tier; + cast_on_spawn = inherit->cast_on_spawn; + cast_on_initial_aggro = inherit->cast_on_initial_aggro; + required_hp_ratio = inherit->required_hp_ratio; + } + + int32 list_id; + int32 spell_id; + int8 tier; + bool cast_on_spawn; + bool cast_on_initial_aggro; + sint8 required_hp_ratio; +}; + +class NPC : public Entity { +public: + NPC(); + NPC(NPC* old_npc); + virtual ~NPC(); + void Initialize(); + EQ2Packet* serialize(Player* player, int16 version); + void SetAppearanceID(int32 id){ appearance_id = id; } + int32 GetAppearanceID(){ return appearance_id; } + bool IsNPC(){ return true; } + void StartRunback(bool reset_hp_on_runback = false); + void InCombat(bool val); + bool HandleUse(Client* client, string type); + void SetRandomize(int32 value) {appearance.randomize = value;} + void AddRandomize(sint32 value) {appearance.randomize += value;} + int32 GetRandomize() {return appearance.randomize;} + bool CheckSameAppearance(string name, int16 id); + void Randomize(NPC* npc, int32 flags); + Skill* GetSkillByName(const char* name, bool check_update = false); + Skill* GetSkillByID(int32 id, bool check_update = false); + int8 GetAttackType(); + void SetAIStrategy(int8 strategy); + int8 GetAIStrategy(); + void SetPrimarySpellList(int32 id); + int32 GetPrimarySpellList(); + void SetSecondarySpellList(int32 id); + int32 GetSecondarySpellList(); + void SetPrimarySkillList(int32 id); + int32 GetPrimarySkillList(); + void SetSecondarySkillList(int32 id); + int32 GetSecondarySkillList(); + void SetEquipmentListID(int32 id); + int32 GetEquipmentListID(); + Spell* GetNextSpell(Spawn* target, float distance); + virtual Spell* GetNextBuffSpell(Spawn* target = 0); + void SetAggroRadius(float radius, bool overrideBaseValue = false); + float GetAggroRadius(); + float GetBaseAggroRadius() { return base_aggro_radius; } + void SetCastPercentage(int8 percentage); + int8 GetCastPercentage(); + void SetSkills(map* in_skills); + void SetSpells(vector* in_spells); + void SetRunbackLocation(float x, float y, float z, int32 gridid, bool set_hp_runback = false); + MovementLocation* GetRunbackLocation(); + float GetRunbackDistance(); + void Runback(float distance=0.0f, bool stopFollowing = true); + void ClearRunback(); + + virtual bool PauseMovement(int32 period_of_time_ms); + virtual bool IsPauseMovementTimerActive(); + + void AddSkillBonus(int32 spell_id, int32 skill_id, float value); + virtual void RemoveSkillBonus(int32 spell_id); + virtual void SetZone(ZoneServer* zone, int32 version=0); + + void SetMaxPetLevel(int8 val) { m_petMaxLevel = val; } + int8 GetMaxPetLevel() { return m_petMaxLevel; } + + void ProcessCombat(); + + /// Sets the brain this NPC should use + /// The brain this npc should use + void SetBrain(Brain* brain); + /// Gets the current brain this NPC uses + /// The Brain this NPC uses + ::Brain* Brain() { return m_brain; } + bool m_runningBack; + sint16 m_runbackHeadingDir1; + sint16 m_runbackHeadingDir2; + + int32 GetShardID() { return m_ShardID; } + void SetShardID(int32 shardid) { m_ShardID = shardid; } + + int32 GetShardCharID() { return m_ShardCharID; } + void SetShardCharID(int32 charid) { m_ShardCharID = charid; } + + sint64 GetShardCreatedTimestamp() { return m_ShardCreatedTimestamp; } + void SetShardCreatedTimestamp(sint64 timestamp) { m_ShardCreatedTimestamp = timestamp; } + + bool HasSpells() { return has_spells; } + + std::atomic m_call_runback; + std::atomic cast_on_aggro_completed; +private: + MovementLocation* runback; + int8 cast_percentage; + float aggro_radius; + float base_aggro_radius; + Spell* GetNextSpell(float distance, int8 type); + map* skills; + vector* spells; + vector cast_on_spells[CAST_TYPE::MAX_CAST_TYPES]; + int32 primary_spell_list; + int32 secondary_spell_list; + int32 primary_skill_list; + int32 secondary_skill_list; + int32 equipment_list_id; + int8 attack_type; + int8 ai_strategy; + int32 appearance_id; + int32 npc_id; + MutexMap skill_bonus_list; + int8 m_petMaxLevel; + + // Because I named the get function Brain() as well we need to use '::' to specify we are refering to + // the brain class and not the function defined above + ::Brain* m_brain; + Mutex MBrain; + + int32 m_ShardID; + int32 m_ShardCharID; + sint64 m_ShardCreatedTimestamp; + bool has_spells; +}; +#endif + diff --git a/source/WorldServer/NPC_AI.cpp b/source/WorldServer/NPC_AI.cpp new file mode 100644 index 0000000..ff89588 --- /dev/null +++ b/source/WorldServer/NPC_AI.cpp @@ -0,0 +1,902 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "NPC_AI.h" +#include "Combat.h" +#include "zoneserver.h" +#include "Spells.h" +#include "../common/Log.h" +#include "LuaInterface.h" +#include "World.h" +#include "Rules/Rules.h" + +extern RuleManager rule_manager; + +extern LuaInterface* lua_interface; +extern World world; + +/* The NEW AI code */ + +Brain::Brain(NPC* npc) { + // Set the npc this brain will controll + m_body = npc; + // Set the default time between calls to think to 250 miliseconds (1/4 a second) + m_tick = 250; + m_lastTick = Timer::GetCurrentTime2(); + m_spellRecovery = 0; + m_playerInEncounter = false; + // Set up the mutex for the hate list + MHateList.SetName("Brain::m_hatelist"); + // Set up the mutex for the encounter list + MEncounter.SetName("Brain::m_encounter"); +} + +Brain::~Brain() { + +} + +void Brain::Think() { + if (m_body->IsPet() && m_body->GetOwner() && m_body->GetOwner()->IsPlayer()) { + Player* player = (Player*)m_body->GetOwner(); + if(player->GetInfoStruct()->get_pet_id() == 0) { + player->GetInfoStruct()->set_pet_id(player->GetIDWithPlayerSpawn(m_body)); + player->SetCharSheetChanged(true); + } + } + // Get the entity this NPC hates the most, + // GetMostHated() will handle dead spawns so no need to check the health in this function + Entity* target = GetMostHated(); + + // If mezzed, stunned or feared we can't do anything so skip + if (!m_body->IsMezzedOrStunned()) { + // Not mezzed or stunned + + // Get the distance to the runback location + float run_back_distance = m_body->GetRunbackDistance(); + + if (target) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s has %s targeted.", m_body->GetName(), target->GetName()); + // NPC has an entity that it hates + // Set the NPC's target to the most hated entity if it is not already. + if (m_body->GetTarget() != target) { + m_body->SetTarget(target); + } + m_body->FaceTarget(target, false); + // target needs to be set before in combat is engaged + + // If the NPC is not in combat then put them in combat + if (!m_body->EngagedInCombat()) { + m_body->ClearRunningLocations(); + m_body->InCombat(true); + m_body->cast_on_aggro_completed = false; + m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AGGRO, target); + } + + bool breakWaterPursuit = false; + if (m_body->IsWaterCreature() && !m_body->IsFlyingCreature() && !target->InWater()) + breakWaterPursuit = true; + // Check to see if the NPC has exceeded the max chase distance + if (run_back_distance > MAX_CHASE_DISTANCE || breakWaterPursuit) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Run back distance is greater then max chase distance, run_back_distance = %f", run_back_distance); + // Over the max chase distance, Check to see if the target is is a client + if (target->IsPlayer() && ((Player*)target)->GetClient()) + { + // Target is a client so send encounter break messages + if (m_body->HasSpawnGroup()) + ((Player*)target)->GetClient()->SimpleMessage(CHANNEL_NARRATIVE, "This encounter will no longer give encounter rewards."); + else + ((Player*)target)->GetClient()->Message(CHANNEL_NARRATIVE, "%s is no longer worth any experience or treasure.", m_body->GetName()); + } + + // Clear the hate list for this NPC + ClearHate(); + // Clear the encounter list + ClearEncounter(); + } + else { + // Still within max chase distance lets to the combat stuff now + + float distance = m_body->GetDistance(target); + + if(!m_body->IsCasting() && (!HasRecovered() || !ProcessSpell(target, distance))) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is attempting melee on %s.", m_body->GetName(), target->GetName()); + m_body->FaceTarget(target, false); + ProcessMelee(target, distance); + } + } + } + else { + // Nothing in the hate list + bool wasInCombat = m_body->EngagedInCombat(); + // Check to see if the NPC is still flagged as in combat for some reason + if (m_body->EngagedInCombat()) { + // If it is set the combat flag to false + m_body->InCombat(false); + + // Do not set a players pet to full health once they stop combat + if (!m_body->IsPet() || (m_body->IsPet() && m_body->GetOwner() && !m_body->GetOwner()->IsPlayer())) + m_body->SetHP(m_body->GetTotalHP()); + } + + CheckBuffs(); + + // If run back distance is greater then 0 then run back + if(!m_body->EngagedInCombat() && !m_body->IsPauseMovementTimerActive()) + { + if (run_back_distance > 1 || (m_body->m_call_runback && !m_body->following)) { + m_body->SetLockedNoLoot(ENCOUNTER_STATE_BROKEN); + m_body->UpdateEncounterState(ENCOUNTER_STATE_BROKEN); + m_body->GetZone()->AddChangedSpawn(m_body); + m_body->Runback(run_back_distance); + m_body->m_call_runback = false; + } + else if (m_body->GetRunbackLocation()) + { + switch(m_body->GetRunbackLocation()->stage) + { + case 0: + m_body->GetZone()->movementMgr->StopNavigation((Entity*)m_body); + m_body->ClearRunningLocations(); + m_body->SetX(m_body->GetRunbackLocation()->x,false); + m_body->SetZ(m_body->GetRunbackLocation()->z,false); + m_body->SetY(m_body->GetRunbackLocation()->y,false); + m_body->CalculateRunningLocation(true); + m_body->GetRunbackLocation()->stage = 1; + + m_body->GetZone()->AddChangedSpawn(m_body); + break; + case 6: // artificially 1500ms per 250ms Think() call + if (m_body->GetRunbackLocation()->gridid > 0) + m_body->SetLocation(m_body->GetRunbackLocation()->gridid); + if(m_body->GetTempActionState() == 0) + m_body->SetTempActionState(-1); + + m_body->SetHeading(m_body->m_runbackHeadingDir1,m_body->m_runbackHeadingDir2,false); + + if(m_body->GetRunbackLocation()->reset_hp_on_runback) + m_body->SetHP(m_body->GetTotalHP()); + + m_body->ClearRunback(); + + if(m_body->GetLockedNoLoot() != ENCOUNTER_STATE_AVAILABLE && m_body->Alive()) { + m_body->SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE); + m_body->UpdateEncounterState(ENCOUNTER_STATE_AVAILABLE); + } + + m_body->GetZone()->AddChangedSpawn(m_body); + break; + default: // captures case 1 up to case 5 to turn around / reset hp + m_body->GetRunbackLocation()->stage++; // artificially delay + break; + + } + } + } + // If encounter size is greater then 0 then clear it + if (GetEncounterSize() > 0) + ClearEncounter(); + } + } +} + +sint32 Brain::GetHate(Entity* entity) { + // We will use this variable to return the value, default to 0 + sint32 ret = 0; + + // Lock the hate list, not altering it so do a read lock + MHateList.readlock(__FUNCTION__, __LINE__); + + // First check to see if the given entity is even in the hate list + if (m_hatelist.count(entity->GetID()) > 0) + // Entity in the hate list so get the hate value for the entity + ret = m_hatelist[entity->GetID()]; + + // Unlock the hate list + MHateList.releasereadlock(__FUNCTION__, __LINE__); + + // return the hate + return ret; +} + +void Brain::AddHate(Entity* entity, sint32 hate) { + // do not aggro when running back, despite taking damage + if (m_body->IsNPC() && ((NPC*)m_body)->m_runningBack) + return; + else if (m_body->IsPet() && m_body->IsEntity() && ((Entity*)m_body)->GetOwner() == entity) + return; + + if(m_body->IsImmune(IMMUNITY_TYPE_TAUNT)) + { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is immune to taunt from entity %s.", m_body->GetName(), entity ? entity->GetName() : "(null)"); + if(entity && entity->IsPlayer()) + ((Player*)entity)->GetClient()->GetCurrentZone()->SendDamagePacket((Spawn*)entity, (Spawn*)m_body, DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG, DAMAGE_PACKET_RESULT_IMMUNE, 0, 0, "Hate"); + return; + } + + // Lock the hate list, we are altering the list so use write lock + MHateList.writelock(__FUNCTION__, __LINE__); + + if (m_hatelist.count(entity->GetID()) > 0) { + m_hatelist[entity->GetID()] += hate; + // take into consideration that 0 or negative hate is not valid, we need to properly reset the value + if(m_hatelist[entity->GetID()] < 1) { + m_hatelist[entity->GetID()] = 1; + } + } + else + m_hatelist.insert(std::pair(entity->GetID(), hate)); + + entity->MHatedBy.lock(); + if (entity->HatedBy.count(m_body->GetID()) == 0) + entity->HatedBy.insert(m_body->GetID()); + entity->MHatedBy.unlock(); + + // Unlock the list + bool ownerExistsAddHate = false; + + if(entity->IsPet() && entity->GetOwner()) { + map::iterator itr = m_hatelist.find(entity->GetOwner()->GetID()); + if(itr == m_hatelist.end()) { + ownerExistsAddHate = true; + } + } + MHateList.releasewritelock(__FUNCTION__, __LINE__); + if(ownerExistsAddHate) { + AddHate(entity->GetOwner(), 0); + } +} + +void Brain::ClearHate() { + // Lock the hate list, we are altering the list so use a write lock + MHateList.writelock(__FUNCTION__, __LINE__); + + map::iterator itr; + for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { + Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first); + if (spawn && spawn->IsEntity()) + { + ((Entity*)spawn)->MHatedBy.lock(); + ((Entity*)spawn)->HatedBy.erase(m_body->GetID()); + ((Entity*)spawn)->MHatedBy.unlock(); + } + } + + // Clear the list + m_hatelist.clear(); + // Unlock the hate list + MHateList.releasewritelock(__FUNCTION__, __LINE__); +} + +void Brain::ClearHate(Entity* entity) { + // Lock the hate list, we could potentially modify the list so use write lock + MHateList.writelock(__FUNCTION__, __LINE__); + + // Check to see if the given entity is in the hate list + if (m_hatelist.count(entity->GetID()) > 0) + // Erase the entity from the hate list + m_hatelist.erase(entity->GetID()); + + entity->MHatedBy.lock(); + entity->HatedBy.erase(m_body->GetID()); + entity->MHatedBy.unlock(); + + // Unlock the hate list + MHateList.releasewritelock(__FUNCTION__, __LINE__); +} + +Entity* Brain::GetMostHated() { + map::iterator itr; + int32 ret = 0; + sint32 hate = 0; + + // Lock the hate list, not going to alter it so use a read lock + MHateList.readlock(__FUNCTION__, __LINE__); + + if (m_hatelist.size() > 0) { + // Loop through the list looking for the entity that this NPC hates the most + for(itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { + // Compare the hate value for the current iteration to our stored highest value + if(itr->second > hate) { + // New high value store the entity + ret = itr->first; + // Store the value to compare with the rest of the entities + hate = itr->second; + } + } + } + // Unlock the list + MHateList.releasereadlock(__FUNCTION__, __LINE__); + Entity* hated = (Entity*)GetBody()->GetZone()->GetSpawnByID(ret); + // Check the reult to see if it is still alive + if(hated && hated->GetHP() <= 0) { + // Entity we got was dead so remove it from the list + ClearHate(hated); + // Call this function again now that we removed the dead entity + hated = GetMostHated(); + } + + // Return our result + return hated; +} + +sint8 Brain::GetHatePercentage(Entity* entity) { + float percentage = 0.0; + MHateList.readlock(__FUNCTION__, __LINE__); + if (entity && m_hatelist.count(entity->GetID()) > 0 && m_hatelist[entity->GetID()] > 0) { + sint32 total_hate = 0; + map::iterator itr; + for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) + total_hate += itr->second; + percentage = m_hatelist[entity->GetID()] / total_hate; + } + MHateList.releasereadlock(__FUNCTION__, __LINE__); + + return (sint8)(percentage * 100); +} + +void Brain::SendHateList(Client* client) { + MHateList.readlock(__FUNCTION__, __LINE__); + + client->Message(CHANNEL_COLOR_YELLOW, "%s's HateList", m_body->GetName()); + client->Message(CHANNEL_COLOR_YELLOW, "-------------------"); + map::iterator itr; + if (m_hatelist.size() > 0) { + // Loop through the list looking for the entity that this NPC hates the most + for(itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { + Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID(itr->first); + // Compare the hate value for the current iteration to our stored highest value + if(ent) { + client->Message(CHANNEL_COLOR_YELLOW, "%s : %i", ent->GetName(), itr->second); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "%u (cannot identity spawn id->entity) : %i", itr->first, itr->second); + } + } + } + client->Message(CHANNEL_COLOR_YELLOW, "-------------------"); + MHateList.releasereadlock(__FUNCTION__, __LINE__); +} + +vector* Brain::GetHateList() { + vector* ret = new vector; + map::iterator itr; + + // Lock the list + MHateList.readlock(__FUNCTION__, __LINE__); + // Loop over the list storing the values into the new list + for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) { + Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID(itr->first); + if (ent) + ret->push_back(ent); + } + // Unlock the list + MHateList.releasereadlock(__FUNCTION__, __LINE__); + + // Return the copy of the list + return ret; +} + +void Brain::MoveCloser(Spawn* target) { + if (target && m_body->GetFollowTarget() != target) + m_body->SetFollowTarget(target, rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()); + + if (m_body->GetFollowTarget() && !m_body->following) { + m_body->CalculateRunningLocation(true); + //m_body->ClearRunningLocations(); + m_body->following = true; + } +} + +bool Brain::ProcessSpell(Entity* target, float distance) { + if(rand()%100 > m_body->GetCastPercentage() || m_body->IsStifled() || m_body->IsFeared()) + return false; + Spell* spell = m_body->GetNextSpell(target, distance); + if(spell){ + Spawn* spell_target = 0; + if(spell->GetSpellData()->friendly_spell == 1){ + vector* group = m_body->GetSpawnGroup(); + if(group && group->size() > 0){ + vector::iterator itr; + for(itr = group->begin(); itr != group->end(); itr++){ + if((!spell_target && (*itr)->GetHP() > 0 && (*itr)->GetHP() < (*itr)->GetTotalHP()) || (spell_target && (*itr)->GetHP() > 0 && spell_target->GetHP() > (*itr)->GetHP())) + spell_target = *itr; + } + } + if(!spell_target) + spell_target = m_body; + + safe_delete(group); + } + else + spell_target = target; + + BrainCastSpell(spell, spell_target, false); + return true; + } + return false; +} + +bool Brain::BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc) { + if (spell) { + if(calculate_run_loc) { + m_body->CalculateRunningLocation(true); + } + m_body->GetZone()->ProcessSpell(spell, m_body, cast_on); + m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000); + return true; + } + return false; +} + +bool Brain::CheckBuffs() { + if (!m_body->GetZone()->GetSpellProcess() || m_body->EngagedInCombat() || m_body->IsCasting() || m_body->IsMezzedOrStunned() || !m_body->Alive() || m_body->IsStifled() || !HasRecovered()) + return false; + + Spell* spell = m_body->GetNextBuffSpell(m_body); + + bool casted_on = false; + if(!(casted_on = BrainCastSpell(spell, m_body)) && m_body->IsNPC() && ((NPC*)m_body)->HasSpells()) { + Spawn* target = nullptr; + + vector* group = m_body->GetSpawnGroup(); + if(group && group->size() > 0){ + vector::iterator itr; + for(itr = group->begin(); itr != group->end(); itr++){ + Spawn* spawn = (*itr); + if(spawn->IsEntity() && spawn != m_body) { + if(target) { + Spell* spell = m_body->GetNextBuffSpell(spawn); + SpellEffects* se = ((Entity*)spawn)->GetSpellEffect(spell->GetSpellData()->id); + if(!se && BrainCastSpell(spell, spawn)) { + casted_on = true; + break; + } + } + } + } + } + safe_delete(group); + } + return casted_on; +} + +void Brain::ProcessMelee(Entity* target, float distance) { + if(distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + MoveCloser((Spawn*)target); + else { + if (target) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is within melee range of %s.", m_body->GetName(), target->GetName()); + if (m_body->AttackAllowed(target)) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is allowed to attack %s.", m_body->GetName(), target->GetName()); + if (m_body->PrimaryWeaponReady() && !m_body->IsDazed() && !m_body->IsFeared()) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s swings its primary weapon at %s.", m_body->GetName(), target->GetName()); + m_body->SetPrimaryLastAttackTime(Timer::GetCurrentTime2()); + m_body->MeleeAttack(target, distance, true); + m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AUTO_ATTACK_TICK, target); + } + if (m_body->SecondaryWeaponReady() && !m_body->IsDazed()) { + m_body->SetSecondaryLastAttackTime(Timer::GetCurrentTime2()); + m_body->MeleeAttack(target, distance, false); + } + } + } + } +} + +bool Brain::HasRecovered() { + if(m_spellRecovery > Timer::GetCurrentTime2()) + return false; + + m_spellRecovery = 0; + return true; +} + +void Brain::AddToEncounter(Entity* entity) { + // If player pet then set the entity to the pets owner + if (entity->IsPet() && entity->GetOwner() && !entity->IsBot()) { + MEncounter.writelock(__FUNCTION__, __LINE__); + bool success = AddToEncounter(entity->GetID()); + MEncounter.releasewritelock(__FUNCTION__, __LINE__); + if(!success) + return; + entity = entity->GetOwner(); + } + else if(entity->HasPet() && entity->GetPet()) { + MEncounter.writelock(__FUNCTION__, __LINE__); + bool success = AddToEncounter(entity->GetPet()->GetID()); + MEncounter.releasewritelock(__FUNCTION__, __LINE__); + if(!success) + return; + } + + // If player or bot then get the group + int32 group_id = 0; + if (entity->IsPlayer() || entity->IsBot()) { + m_playerInEncounter = true; + if (entity->GetGroupMemberInfo()) + group_id = entity->GetGroupMemberInfo()->group_id; + } + + // Insert the entity into the encounter list, if there is a group add all group members as well + // TODO: add raid members + MEncounter.writelock(__FUNCTION__, __LINE__); + if (group_id > 0) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + if ((*itr)->member) + { + bool success = AddToEncounter((*itr)->member->GetID()); + if((*itr)->client && success) { + m_encounter_playerlist.insert(make_pair((*itr)->client->GetPlayer()->GetCharacterID(), (*itr)->client->GetPlayer()->GetID())); + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else { + bool success = AddToEncounter(entity->GetID()); + if (success && entity->IsPlayer()) + { + Player* plyr = (Player*)entity; + m_encounter_playerlist.insert(make_pair(plyr->GetCharacterID(), entity->GetID())); + } + } + MEncounter.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Brain::CheckLootAllowed(Entity* entity) { + bool ret = false; + vector::iterator itr; + + if (m_body) + { + if ((m_body->GetLootMethod() != GroupLootMethod::METHOD_LOTTO && m_body->GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->GetLooterSpawnID() > 0 && m_body->GetLooterSpawnID() != entity->GetID()) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter spawn id %u does not match received %s(%u)", GetBody()->GetName(), m_body->GetLooterSpawnID(), entity->GetName(), entity->GetID()); + return false; + } + if (rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByDropTime)->GetInt8() + && m_body->GetChestDropTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32() * 1000)) { + return true; + } + if (rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByTrapTime)->GetInt8() + && m_body->GetTrapOpenedTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32() * 1000)) { + return true; + } + if ((m_body->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || m_body->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->HasSpawnLootWindowCompleted(entity->GetID())) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter %s(%u) has already completed their lotto selections.", GetBody()->GetName(), entity->GetName(), entity->GetID()); + return false; + } + } + // Check the encounter list to see if the given entity is in it, if so return true. + MEncounter.readlock(__FUNCTION__, __LINE__); + if (entity->IsPlayer()) + { + Player* plyr = (Player*)entity; + + map::iterator itr = m_encounter_playerlist.find(plyr->GetCharacterID()); + if (itr != m_encounter_playerlist.end()) + { + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + return true; + } + + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + return false; + } + + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) { + if ((*itr) == entity->GetID()) { + // found the entity in the encounter list, set return value to true and break the loop + ret = true; + break; + } + } + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +int8 Brain::GetEncounterSize() { + int8 ret = 0; + + MEncounter.readlock(__FUNCTION__, __LINE__); + ret = (int8)m_encounter.size(); + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +vector* Brain::GetEncounter() { + vector* ret = new vector; + vector::iterator itr; + + // Lock the list + MEncounter.readlock(__FUNCTION__, __LINE__); + // Loop over the list storing the values into the new list + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) + ret->push_back(*itr); + // Unlock the list + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + + // Return the copy of the list + return ret; +} + +bool Brain::IsPlayerInEncounter(int32 char_id) { + bool ret = false; + MEncounter.readlock(__FUNCTION__, __LINE__); + std::map::iterator itr = m_encounter_playerlist.find(char_id); + if(itr != m_encounter_playerlist.end()) { + ret = true; + } + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool Brain::IsEntityInEncounter(int32 id, bool skip_read_lock) { + bool ret = false; + if(!skip_read_lock) { + MEncounter.readlock(__FUNCTION__, __LINE__); + } + vector::iterator itr; + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) { + if ((*itr) == id) { + // found the entity in the encounter list, set return value to true and break the loop + ret = true; + break; + } + } + if(!skip_read_lock) { + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +int32 Brain::CountPlayerBotInEncounter() { + int32 count = 0; + vector::iterator itr; + MEncounter.readlock(__FUNCTION__, __LINE__); + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) { + Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID((*itr)); + if (ent && (ent->IsPlayer() || ent->IsBot())) { + count++; + } + } + MEncounter.releasereadlock(__FUNCTION__, __LINE__); + return count; +} + +bool Brain::AddToEncounter(int32 id) { + if(!IsEntityInEncounter(id, true)) { + m_encounter.push_back(id); + return true; + } + return false; +} + +void Brain::ClearEncounter() { + MEncounter.writelock(__FUNCTION__, __LINE__); + if(m_body) { + m_body->RemoveSpells(true); + } + m_encounter.clear(); + m_encounter_playerlist.clear(); + m_playerInEncounter = false; + MEncounter.releasewritelock(__FUNCTION__, __LINE__); +} + +void Brain::SendEncounterList(Client* client) { + client->Message(CHANNEL_COLOR_YELLOW, "%s's EncounterList", m_body->GetName()); + client->Message(CHANNEL_COLOR_YELLOW, "-------------------"); + vector::iterator itr; + + // Check the encounter list to see if the given entity is in it, if so return true. + MEncounter.readlock(__FUNCTION__, __LINE__); + + for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) { + Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID((*itr)); + // Compare the hate value for the current iteration to our stored highest value + if(ent) { + client->Message(CHANNEL_COLOR_YELLOW, "%s", ent->GetName()); + } + else { + client->Message(CHANNEL_COLOR_YELLOW, "%u (cannot identity spawn id->entity)", (*itr)); + } + } + client->Message(CHANNEL_COLOR_YELLOW, "-------------------"); + MEncounter.releasereadlock(__FUNCTION__, __LINE__); +} + +/* Example of how to extend the default AI */ + + +CombatPetBrain::CombatPetBrain(NPC* body) : Brain(body) { + // Make sure to have the " : Brain(body)" so it calls the parent class constructor + // to set up the AI properly +} + +CombatPetBrain::~CombatPetBrain() { + +} + +void CombatPetBrain::Think() { + // We are extending the base brain so make sure to call the parent Think() function. + // If we want to override then we could remove Brain::Think() + Brain::Think(); + + // All this Brain does is make the pet follow its owner, the combat comes from the default brain + + if (GetBody()->EngagedInCombat() || !GetBody()->IsPet() || GetBody()->IsMezzedOrStunned()) + return; + + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Pet AI code called for %s", GetBody()->GetName()); + + // If owner is a player and player has stay set then return out + if (GetBody()->GetOwner() && GetBody()->GetOwner()->IsPlayer() && ((Player*)GetBody()->GetOwner())->GetInfoStruct()->get_pet_movement() == 1) + return; + + // Set target to owner + Entity* target = GetBody()->GetOwner(); + GetBody()->SetTarget(target); + + // Get distance from the owner + float distance = GetBody()->GetDistance(target); + + // If out of melee range then move closer + if (distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + MoveCloser((Spawn*)target); +} + +/* Example of how to override the default AI */ + + +NonCombatPetBrain::NonCombatPetBrain(NPC* body) : Brain(body) { + // Make sure to have the " : Brain(body)" so it calls the parent class constructor + // to set up the AI properly +} + +NonCombatPetBrain::~NonCombatPetBrain() { + +} + +void NonCombatPetBrain::Think() { + // All this Brain does is make the pet follow its owner + + if (!GetBody()->IsPet() || GetBody()->IsMezzedOrStunned()) + return; + + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Pet AI code called for %s", GetBody()->GetName()); + + // Set target to owner + Entity* target = GetBody()->GetOwner(); + GetBody()->SetTarget(target); + + // Get distance from the owner + float distance = GetBody()->GetDistance(target); + + // If out of melee range then move closer + if (distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + MoveCloser((Spawn*)target); +} + +BlankBrain::BlankBrain(NPC* body) : Brain(body) { + // Make sure to have the " : Brain(body)" so it calls the parent class constructor + // to set up the AI properly + SetTick(50000); +} + +BlankBrain::~BlankBrain() { + +} + +void BlankBrain::Think() { + +} + +LuaBrain::LuaBrain(NPC* body) : Brain(body) { + +} + +LuaBrain::~LuaBrain() { +} + +void LuaBrain::Think() { + if (!lua_interface) + return; + + const char* script = GetBody()->GetSpawnScript(); + if(script) { + if (!lua_interface->RunSpawnScript(script, "Think", GetBody(), GetBody()->GetTarget())) { + lua_interface->LogError("LUA LuaBrain error: was unable to call the Think function in the spawn script (%s)", script); + } + } + else { + LogWrite(NPC_AI__ERROR, 0, "NPC_AI", "Lua brain set on a spawn that doesn't have a script..."); + } +} + +DumbFirePetBrain::DumbFirePetBrain(NPC* body, Entity* target, int32 expire_time) : Brain(body) { + m_expireTime = Timer::GetCurrentTime2() + expire_time; + AddHate(target, INT_MAX); +} + +DumbFirePetBrain::~DumbFirePetBrain() { + +} + +void DumbFirePetBrain::AddHate(Entity* entity, sint32 hate) { + if (!GetMostHated()) + Brain::AddHate(entity, hate); +} + +void DumbFirePetBrain::Think() { + + Entity* target = GetMostHated(); + + if (target) { + if (!GetBody()->IsMezzedOrStunned()) { + // Set the NPC's target to the most hated entity if it is not already. + if (GetBody()->GetTarget() != target) { + GetBody()->SetTarget(target); + GetBody()->FaceTarget(target, false); + } + // target needs to be identified before combat setting + + // If the NPC is not in combat then put them in combat + if (!GetBody()->EngagedInCombat()) { + //GetBody()->ClearRunningLocations(); + GetBody()->CalculateRunningLocation(true); + GetBody()->InCombat(true); + } + + float distance = GetBody()->GetDistance(target); + + if(GetBody()->CheckLoS(target) && !GetBody()->IsCasting() && (!HasRecovered() || !ProcessSpell(target, distance))) { + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is attempting melee on %s.", GetBody()->GetName(), target->GetName()); + GetBody()->FaceTarget(target, false); + ProcessMelee(target, distance); + } + } + } + else { + // No hated target or time expired, kill this mob + if (GetBody()->GetHP() > 0) { + GetBody()->KillSpawn(GetBody()); + LogWrite(NPC_AI__DEBUG, 7, "NPC AI", "Dumbfire being killed because there is no target."); + } + } + + if (Timer::GetCurrentTime2() > m_expireTime) { + if (GetBody()->GetHP() > 0) { + GetBody()->KillSpawn(GetBody()); + LogWrite(NPC_AI__DEBUG, 7, "NPC AI", "Dumbfire being killed because timer expired."); + } + } +} diff --git a/source/WorldServer/NPC_AI.h b/source/WorldServer/NPC_AI.h new file mode 100644 index 0000000..cbb8166 --- /dev/null +++ b/source/WorldServer/NPC_AI.h @@ -0,0 +1,198 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __NPC_AI_H__ +#define __NPC_AI_H__ +#include "NPC.h" +#include +#include + +using namespace std; + +class Brain { +public: + Brain(NPC* npc); + virtual ~Brain(); + + /// The main loop for the brain. This will do all the AI work + virtual void Think(); + + /* Timer related functions */ + + /// Gets the time between calls to Think() + /// Time in miliseconds between calls to Think() + int16 Tick() { return m_tick; } + /// Sets the time between calls to Think() + /// Time in miliseconds + void SetTick(int16 time) { m_tick = time; } + /// Gets the timestamp of the last call to Think() + /// Timestamp of the last call to Think() + int32 LastTick() { return m_lastTick; } + /// Sets the last tick to the given time + /// The time to set the last tick to + void SetLastTick(int32 time) { m_lastTick = time; } + + /* Hate related functions */ + + /// Gets the amount of hate this npc has towards the given entity + /// The entity to check + /// The amount of hate towards the given entity + sint32 GetHate(Entity* entity); + /// Add hate for the given entity to this NPC + /// The entity we are adding to this NPC's hate list + /// The amount of hate to add + virtual void AddHate(Entity* entity, sint32 hate); + /// Completely clears the hate list for this npc + void ClearHate(); + /// Removes the given entity from this NPC's hate list + /// Entity to remove from this NPC's hate list + void ClearHate(Entity* entity); + /// Get the entity this NPC hates the most + /// The entity this NPC hates the most + Entity* GetMostHated(); + /// Gets a percentage of hate owned by the given entity + /// Entity to get the percentage for + /// Percentage of hate as a sint8 + sint8 GetHatePercentage(Entity* entity); + + void SendHateList(Client* client); + + ///Gets a list of all the entities in the hate list + vector* GetHateList(); + + /* Combat related functions */ + + bool BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc = true); + + /// + /// + /// + virtual bool ProcessSpell(Entity* target, float distance); + /// + /// True if a buff starts casting + bool CheckBuffs(); + + /// Has the NPC make a melee attack + /// The target to attack + /// The current distance from the target + void ProcessMelee(Entity* target, float distance); + + /* Encounter related functions */ + + /// Adds the given entity and its group and raid members to the encounter list + /// Entity we are adding to the encounter list + void AddToEncounter(Entity* entity); + /// Checks to see if the given entity can loot the corpse + /// Entity trying to loot + /// True if the entity can loot + bool CheckLootAllowed(Entity* entity); + /// Gets the size of the encounter list + /// The size of the list as an int8 + int8 GetEncounterSize(); + /// Clears the encounter list + void ClearEncounter(); + + void SendEncounterList(Client* client); + + /// Gets a copy of the encounter list + /// A copy of the encounter list as a vector* + vector* GetEncounter(); + /// Checks to see if a player is in the encounter + /// True if the encounter list contains a player + bool PlayerInEncounter() { return m_playerInEncounter; } + bool IsPlayerInEncounter(int32 char_id); + bool IsEntityInEncounter(int32 id, bool skip_read_lock = false); + int32 CountPlayerBotInEncounter(); + bool AddToEncounter(int32 id); + /* Helper functions*/ + + /// Gets the NPC this brain controls + /// The NPC this brain controls + NPC* GetBody() { return m_body; } + /// Checks to see if the NPC can cast + /// True if the NPC can cast + bool HasRecovered(); + /// Tells the NPC to move closer to the given target + /// The target to move closer to + void MoveCloser(Spawn* target); + +protected: + // m_body = the npc this brain controls + NPC* m_body; + // m_spellRecovery = time stamp for when the npc can cast again + int32 m_spellRecovery; +private: + // MHateList = mutex to lock and unlock the hate list + Mutex MHateList; + // m_hatelist = the list that stores all the hate, + // entity is the entity this npc hates and the int32 is the value for how much we hate the entity + map m_hatelist; + // m_lastTick = the last time we ran this brain + int32 m_lastTick; + // m_tick = the amount of time between Think() calls in milliseconds + int16 m_tick; + // m_encounter = list of players (entities) that will get a reward (xp/loot) for killing this npc + vector m_encounter; + map m_encounter_playerlist; + + // MEncounter = mutex to lock and unlock the encounter list + Mutex MEncounter; + //m_playerInEncounter = true if a player is added to the encounter + bool m_playerInEncounter; +}; + +// Extension of the default brain for combat pets +class CombatPetBrain : public Brain { +public: + CombatPetBrain(NPC* body); + virtual ~CombatPetBrain(); + void Think(); +}; + +class NonCombatPetBrain : public Brain { +public: + NonCombatPetBrain(NPC* body); + virtual ~NonCombatPetBrain(); + void Think(); +}; + +class BlankBrain : public Brain { +public: + BlankBrain(NPC* body); + virtual ~BlankBrain(); + void Think(); +}; + +class LuaBrain : public Brain { +public: + LuaBrain(NPC* body); + virtual ~LuaBrain(); + void Think(); +}; + +class DumbFirePetBrain : public Brain { +public: + DumbFirePetBrain(NPC* body, Entity* target, int32 expire_time); + virtual ~DumbFirePetBrain(); + void Think(); + void AddHate(Entity* entity, sint32 hate); +private: + int32 m_expireTime; +}; +#endif diff --git a/source/WorldServer/Object.cpp b/source/WorldServer/Object.cpp new file mode 100644 index 0000000..971e789 --- /dev/null +++ b/source/WorldServer/Object.cpp @@ -0,0 +1,99 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "World.h" +#include "Object.h" +#include "Spells.h" + +extern World world; +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; + +Object::Object(){ + clickable = false; + zone_name = 0; + packet_num = 0; + appearance.activity_status = 64; + appearance.pos.state = 1; + appearance.difficulty = 0; + spawn_type = 2; + m_deviceID = 0; +} +Object::~Object(){ + +} + +EQ2Packet* Object::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +void Object::HandleUse(Client* client, string command){ + vector destinations; + if(GetTransporterID() > 0) + GetZone()->GetTransporters(&destinations, client, GetTransporterID()); + if (destinations.size()) + { + client->SetTemporaryTransportID(0); + client->ProcessTeleport(this, &destinations, GetTransporterID()); + } + else if (client && command.length() > 0 && appearance.show_command_icon == 1 && MeetsSpawnAccessRequirements(client->GetPlayer())){ + EntityCommand* entity_command = FindEntityCommand(command); + if (entity_command) + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + } +} + +Object* Object::Copy(){ + Object* new_spawn = new Object(); + new_spawn->SetCollector(IsCollector()); + new_spawn->SetMerchantID(merchant_id); + new_spawn->SetMerchantType(merchant_type); + new_spawn->SetMerchantLevelRange(GetMerchantMinLevel(), GetMerchantMaxLevel()); + if(GetSizeOffset() > 0){ + int8 offset = GetSizeOffset()+1; + sint32 tmp_size = size + (rand()%offset - rand()%offset); + if(tmp_size < 0) + tmp_size = 1; + else if(tmp_size >= 0xFFFF) + tmp_size = 0xFFFF; + new_spawn->size = (int16)tmp_size; + } + else + new_spawn->size = size; + new_spawn->SetPrimaryCommands(&primary_command_list); + new_spawn->SetSecondaryCommands(&secondary_command_list); + new_spawn->database_id = database_id; + new_spawn->primary_command_list_id = primary_command_list_id; + new_spawn->secondary_command_list_id = secondary_command_list_id; + memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData)); + new_spawn->faction_id = faction_id; + new_spawn->target = 0; + new_spawn->SetTotalHP(GetTotalHP()); + new_spawn->SetTotalPower(GetTotalPower()); + new_spawn->SetHP(GetHP()); + new_spawn->SetPower(GetPower()); + SetQuestsRequired(new_spawn); + new_spawn->SetTransporterID(GetTransporterID()); + new_spawn->SetDeviceID(GetDeviceID()); + new_spawn->SetSoundsDisabled(IsSoundsDisabled()); + new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); + new_spawn->SetLootTier(GetLootTier()); + new_spawn->SetLootDropType(GetLootDropType()); + return new_spawn; +} diff --git a/source/WorldServer/Object.h b/source/WorldServer/Object.h new file mode 100644 index 0000000..1a6098b --- /dev/null +++ b/source/WorldServer/Object.h @@ -0,0 +1,49 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_OBJECT__ +#define __EQ2_OBJECT__ + +#include "Spawn.h" + +class Object : public Spawn{ +public: + Object(); + virtual ~Object(); + void SetClickable(bool click){ + clickable = click; + } + void SetZone(char* zone){ + zone_name = zone; + } + Object* Copy(); + bool IsObject(){ return true; } + void HandleUse(Client* client, string command); + bool clickable; + char* zone_name; + EQ2Packet* serialize(Player* player, int16 version); + + void SetDeviceID(int8 val) { m_deviceID = val; } + int8 GetDeviceID() { return m_deviceID; } + +private: + int8 m_deviceID; +}; +#endif + diff --git a/source/WorldServer/Player.cpp b/source/WorldServer/Player.cpp new file mode 100644 index 0000000..ade5fbe --- /dev/null +++ b/source/WorldServer/Player.cpp @@ -0,0 +1,7549 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Player.h" +#include "../common/MiscFunctions.h" +#include "World.h" +#include "WorldDatabase.h" +#include +#include "classes.h" +#include "LuaInterface.h" +#include "../common/Log.h" +#include "Rules/Rules.h" +#include "Titles.h" +#include "Languages.h" +#include "SpellProcess.h" +#include +#include +#include "ClientPacketFunctions.h" + +extern Classes classes; +extern WorldDatabase database; +extern World world; +extern ConfigReader configReader; +extern MasterSkillList master_skill_list; +extern MasterSpellList master_spell_list; +extern MasterQuestList master_quest_list; +extern Variables variables; +extern LuaInterface* lua_interface; +extern MasterItemList master_item_list; +extern RuleManager rule_manager; +extern MasterTitlesList master_titles_list; +extern MasterLanguagesList master_languages_list; + +Player::Player(){ + tutorial_step = 0; + char_id = 0; + group = 0; + appearance.pos.grid_id = 0; + spawn_index = 1; + info = 0; + movement_packet = 0; + last_movement_activity = 0; + //speed = 0; + packet_num = 0; + range_attack = false; + old_movement_packet = 0; + charsheet_changed = false; + quickbar_updated = false; + custNPC = false; + spawn_tmp_vis_xor_packet = 0; + spawn_tmp_pos_xor_packet = 0; + spawn_tmp_info_xor_packet = 0; + pending_collection_reward = 0; + pos_packet_speed = 0; + + appearance.display_name = 1; + appearance.show_command_icon = 1; + appearance.player_flag = 1; + appearance.targetable = 1; + appearance.show_level = 1; + spell_count = 0; + spell_orig_packet = 0; + spell_xor_packet = 0; + resurrecting = false; + spawn_id = 1; + spawn_type = 4; + player_spawn_id_map[1] = this; + player_spawn_reverse_id_map[this] = 1; + MPlayerQuests.SetName("Player::MPlayerQuests"); + test_time = 0; + returning_from_ld = false; + away_message = "Sorry, I am A.F.K. (Away From Keyboard)"; + AddSecondaryEntityCommand("Inspect", 10000, "inspect_player", "", 0, 0); + AddSecondaryEntityCommand("Who", 10000, "who", "", 0, 0); + // commented out commands a player canNOT use on themselves... move these to Client::HandleVerbRequest()? + //AddSecondaryEntityCommand("Assist", 10, "assist", "", 0, 0); + //AddSecondaryEntityCommand("Duel", 10, "duel", "", 0, 0); + //AddSecondaryEntityCommand("Duel Bet", 10, "duelbet", "", 0, 0); + //AddSecondaryEntityCommand("Trade", 10, "trade", "", 0, 0); + is_tracking = false; + guild = 0; + following = false; + combat_target = 0; + //InitXPTable(); + pending_deletion = false; + spawn_vis_struct = 0; + spawn_pos_struct = 0; + spawn_info_struct = 0; + spawn_header_struct = 0; + spawn_footer_struct = 0; + widget_footer_struct = 0; + sign_footer_struct = 0; + pos_xor_size = 0; + info_xor_size = 0; + vis_xor_size = 0; + pos_mutex.SetName("Player::pos_mutex"); + vis_mutex.SetName("Player::vis_mutex"); + info_mutex.SetName("Player::info_mutex"); + index_mutex.SetName("Player::index_mutex"); + spawn_mutex.SetName("Player::spawn_mutex"); + m_playerSpawnQuestsRequired.SetName("Player::player_spawn_quests_required"); + m_playerSpawnHistoryRequired.SetName("Player::player_spawn_history_required"); + gm_vision = false; + SetSaveSpellEffects(true); + reset_mentorship = false; + all_spells_locked = false; + current_language_id = 0; + active_reward = false; + + SortedTraitList = new map > >; + ClassTraining = new map >; + RaceTraits = new map >; + InnateRaceTraits = new map >; + FocusEffects = new map >; + need_trait_update = true; + active_food_unique_id = 0; + active_drink_unique_id = 0; +} +Player::~Player(){ + SetSaveSpellEffects(true); + for(int32 i=0;i*>::iterator itr; + for (itr = player_spawn_quests_required.begin(); itr != player_spawn_quests_required.end(); itr++){ + safe_delete(itr->second); + } + player_spawn_quests_required.clear(); + + for (itr = player_spawn_history_required.begin(); itr != player_spawn_history_required.end(); itr++){ + safe_delete(itr->second); + } + player_spawn_history_required.clear(); + + map > >::iterator itr1; + map >::iterator itr2; + vector::iterator itr3; + // Type + for (itr1 = m_characterHistory.begin(); itr1 != m_characterHistory.end(); itr1++) { + // Sub type + for (itr2 = itr1->second.begin(); itr2 != itr1->second.end(); itr2++) { + // vector of data + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) { + safe_delete(*itr3); + } + } + } + m_characterHistory.clear(); + + mLUAHistory.writelock(); + map::iterator itr4; + for (itr4 = m_charLuaHistory.begin(); itr4 != m_charLuaHistory.end(); itr4++) { + safe_delete(itr4->second); + } + m_charLuaHistory.clear(); + mLUAHistory.releasewritelock(); + + safe_delete_array(movement_packet); + safe_delete_array(old_movement_packet); + safe_delete_array(spawn_tmp_info_xor_packet); + safe_delete_array(spawn_tmp_vis_xor_packet); + safe_delete_array(spawn_tmp_pos_xor_packet); + safe_delete_array(spell_xor_packet); + safe_delete_array(spell_orig_packet); + DestroyQuests(); + WritePlayerStatistics(); + RemovePlayerStatistics(); + DeleteMail(); + world.RemoveLottoPlayer(GetCharacterID()); + safe_delete(info); + index_mutex.writelock(__FUNCTION__, __LINE__); + player_spawn_reverse_id_map.clear(); + player_spawn_id_map.clear(); + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + info_mutex.writelock(__FUNCTION__, __LINE__); + spawn_info_packet_list.clear(); + info_mutex.releasewritelock(__FUNCTION__, __LINE__); + vis_mutex.writelock(__FUNCTION__, __LINE__); + spawn_vis_packet_list.clear(); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + pos_mutex.writelock(__FUNCTION__, __LINE__); + spawn_pos_packet_list.clear(); + pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + + safe_delete(spawn_header_struct); + safe_delete(spawn_footer_struct); + safe_delete(sign_footer_struct); + safe_delete(widget_footer_struct); + safe_delete(spawn_info_struct); + safe_delete(spawn_vis_struct); + safe_delete(spawn_pos_struct); + ClearPendingSelectableItemRewards(0, true); + ClearPendingItemRewards(); + ClearEverything(); + + safe_delete(SortedTraitList); + safe_delete(ClassTraining); + safe_delete(RaceTraits); + safe_delete(InnateRaceTraits); + safe_delete(FocusEffects); + // leak fix on Language* pointer from Player::AddLanguage + player_languages_list.Clear(); +} + +EQ2Packet* Player::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +EQ2Packet* Player::Move(float x, float y, float z, int16 version, float heading){ + PacketStruct* packet = configReader.getStruct("WS_MoveClient", version); + if(packet){ + packet->setDataByName("x", x); + packet->setDataByName("y", y); + packet->setDataByName("z", z); + packet->setDataByName("unknown", 1); // 1 seems to force the client to re-render the zone at the new location + packet->setDataByName("location", 0xFFFFFFFF); //added in 869 + if (heading != -1.0f) + packet->setDataByName("heading", heading); + EQ2Packet* outapp = packet->serialize(); + safe_delete(packet); + return outapp; + } + return 0; +} + +void Player::DestroyQuests(){ + MPlayerQuests.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for(itr = completed_quests.begin(); itr != completed_quests.end(); itr++){ + if(itr->second) { + safe_delete(itr->second); + } + } + completed_quests.clear(); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second) { + safe_delete(itr->second); + } + } + player_quests.clear(); + for(itr = pending_quests.begin(); itr != pending_quests.end(); itr++){ + if(itr->second) { + safe_delete(itr->second); + } + } + pending_quests.clear(); + MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); +} + +PlayerInfo* Player::GetPlayerInfo(){ + if(info == 0) + info = new PlayerInfo(this); + return info; +} + +void PlayerInfo::CalculateXPPercentages(){ + int32 xp_needed = info_struct->get_xp_needed(); + if(xp_needed > 0){ + double div_percent = ((double)info_struct->get_xp() / xp_needed) * 100.0; + int16 percentage = (int16)(div_percent) * 10; + double whole, fractional = 0.0; + fractional = std::modf(div_percent, &whole); + info_struct->set_xp_yellow(percentage); + info_struct->set_xp_blue((int16)(fractional * 1000)); + + // vitality bars probably need a revisit + info_struct->set_xp_blue_vitality_bar(0); + info_struct->set_xp_yellow_vitality_bar(0); + if(player->GetXPVitality() > 0){ + float vitality_total = player->GetXPVitality()*10 + percentage; + vitality_total -= ((int)(percentage/100)*100); + if(vitality_total < 100){ //10% + info_struct->set_xp_blue_vitality_bar(info_struct->get_xp_blue() + (int16)(player->GetXPVitality() *10)); + } + else + info_struct->set_xp_yellow_vitality_bar(info_struct->get_xp_yellow() + (int16)(player->GetXPVitality() *10)); + } + } +} + +void PlayerInfo::CalculateTSXPPercentages(){ + int32 ts_xp_needed = info_struct->get_ts_xp_needed(); + if(ts_xp_needed > 0){ + float percentage = ((double)info_struct->get_ts_xp() / ts_xp_needed) * 1000; + info_struct->set_tradeskill_exp_yellow((int16)percentage); + info_struct->set_tradeskill_exp_blue((int16)((percentage - info_struct->get_tradeskill_exp_yellow()) * 1000)); + /*info_struct->xp_blue_vitality_bar = 0; + info_struct->xp_yellow_vitality_bar = 0; + if(player->GetXPVitality() > 0){ + float vitality_total = player->GetXPVitality()*10 + percentage; + vitality_total -= ((int)(percentage/100)*100); + if(vitality_total < 100){ //10% + info_struct->xp_blue_vitality_bar = info_struct->xp_blue + (int16)(player->GetXPVitality() *10); + } + else + info_struct->xp_yellow_vitality_bar = info_struct->xp_yellow + (int16)(player->GetXPVitality() *10); + }*/ + } +} + +void PlayerInfo::SetHouseZone(int32 id){ + house_zone_id = id; +} + +void PlayerInfo::SetBindZone(int32 id){ + bind_zone_id = id; +} + +void PlayerInfo::SetBindX(float x){ + bind_x = x; +} + +void PlayerInfo::SetBindY(float y){ + bind_y = y; +} + +void PlayerInfo::SetBindZ(float z){ + bind_z = z; +} + +void PlayerInfo::SetBindHeading(float heading){ + bind_heading = heading; +} + +int32 PlayerInfo::GetHouseZoneID(){ + return house_zone_id; +} + +int32 PlayerInfo::GetBindZoneID(){ + return bind_zone_id; +} + +float PlayerInfo::GetBindZoneX(){ + return bind_x; +} + +float PlayerInfo::GetBindZoneY(){ + return bind_y; +} + +float PlayerInfo::GetBindZoneZ(){ + return bind_z; +} + +float PlayerInfo::GetBindZoneHeading(){ + return bind_heading; +} + +PacketStruct* PlayerInfo::serialize2(int16 version){ + PacketStruct* packet = configReader.getStruct("WS_CharacterSheet", version); + if(packet){ + //TODO: 2021 FIX THIS CASTING + char deity[32]; + strncpy(deity, info_struct->get_deity().c_str(), 32); + packet->setDataByName("deity", deity); + + char name[40]; + strncpy(name, info_struct->get_name().c_str(), 40); + packet->setDataByName("character_name", name); + packet->setDataByName("race", info_struct->get_race()); + packet->setDataByName("gender", info_struct->get_gender()); + packet->setDataByName("class1", info_struct->get_class1()); + packet->setDataByName("class2", info_struct->get_class2()); + packet->setDataByName("class3", info_struct->get_class3()); + packet->setDataByName("tradeskill_class1", info_struct->get_tradeskill_class1()); + packet->setDataByName("tradeskill_class2", info_struct->get_tradeskill_class2()); + packet->setDataByName("tradeskill_class3", info_struct->get_tradeskill_class3()); + packet->setDataByName("level", info_struct->get_level()); + packet->setDataByName("effective_level", info_struct->get_effective_level() != 0 ? info_struct->get_effective_level() : info_struct->get_level()); + packet->setDataByName("tradeskill_level", info_struct->get_tradeskill_level()); + packet->setDataByName("account_age_base", info_struct->get_account_age_base()); + +// for(int8 i=0;i<19;i++) +// { +// packet->setDataByName("account_age_bonus", info_struct->get_account_age_bonus(i)); +// } + + // + packet->setDataByName("current_hp", player->GetHP()); + packet->setDataByName("max_hp",player-> GetTotalHP()); + packet->setDataByName("base_hp", player->GetTotalHPBase()); + float bonus_health = floor( (float)(info_struct->get_sta() * player->CalculateBonusMod())); + packet->setDataByName("bonus_health", bonus_health); + packet->setDataByName("stat_bonus_health", player->CalculateBonusMod()); + packet->setDataByName("current_power", player->GetPower()); + packet->setDataByName("max_power", player->GetTotalPower()); + packet->setDataByName("base_power", player->GetTotalPowerBase()); + packet->setDataByName("bonus_power", floor( (float)(player->GetPrimaryStat() * player->CalculateBonusMod()))); + packet->setDataByName("stat_bonus_power", player->CalculateBonusMod()); + packet->setDataByName("conc_used", info_struct->get_cur_concentration()); + packet->setDataByName("conc_max", info_struct->get_max_concentration()); + packet->setDataByName("attack", info_struct->get_cur_attack()); + packet->setDataByName("attack_base", info_struct->get_attack_base()); + packet->setDataByName("absorb", info_struct->get_absorb()); + packet->setDataByName("mitigation_skill1", info_struct->get_mitigation_skill1()); + packet->setDataByName("mitigation_skill2", info_struct->get_mitigation_skill2()); + packet->setDataByName("mitigation_skill3", info_struct->get_mitigation_skill3()); + CalculateXPPercentages(); + packet->setDataByName("exp_yellow", info_struct->get_xp_yellow()); + packet->setDataByName("exp_blue", info_struct->get_xp_blue()); + packet->setDataByName("tradeskill_exp_yellow", info_struct->get_tradeskill_exp_yellow()); + packet->setDataByName("tradeskill_exp_blue", info_struct->get_tradeskill_exp_blue()); + packet->setDataByName("flags", info_struct->get_flags()); + packet->setDataByName("flags2", info_struct->get_flags2()); + + packet->setDataByName("avoidance_pct", (int16)info_struct->get_avoidance_display()*10.0f);//avoidance_pct 192 = 19.2% // confirmed DoV + packet->setDataByName("avoidance_base", (int16)info_struct->get_avoidance_base()*10.0f); // confirmed DoV + packet->setDataByName("avoidance", info_struct->get_cur_avoidance()); + packet->setDataByName("base_avoidance_pct", info_struct->get_base_avoidance_pct());// confirmed DoV + float parry_pct = info_struct->get_parry(); // client works off of int16, but we use floats to track the actual x/100% + packet->setDataByName("parry",(int16)(parry_pct*10.0f));// confirmed DoV + + float block_pct = info_struct->get_block()*10.0f; + + packet->setDataByName("block", (int16)block_pct);// confirmed DoV + packet->setDataByName("uncontested_block", info_struct->get_uncontested_block());// confirmed DoV + + packet->setDataByName("str", info_struct->get_str()); + packet->setDataByName("sta", info_struct->get_sta()); + packet->setDataByName("agi", info_struct->get_agi()); + packet->setDataByName("wis", info_struct->get_wis()); + packet->setDataByName("int", info_struct->get_intel()); + packet->setDataByName("str_base", info_struct->get_str_base()); + packet->setDataByName("sta_base", info_struct->get_sta_base()); + packet->setDataByName("agi_base", info_struct->get_agi_base()); + packet->setDataByName("wis_base", info_struct->get_wis_base()); + packet->setDataByName("int_base", info_struct->get_intel_base()); + packet->setDataByName("mitigation_cur", info_struct->get_cur_mitigation()); + packet->setDataByName("mitigation_max", info_struct->get_max_mitigation()); + packet->setDataByName("mitigation_base", info_struct->get_mitigation_base()); + packet->setDataByName("heat", info_struct->get_heat()); + packet->setDataByName("cold", info_struct->get_cold()); + packet->setDataByName("magic", info_struct->get_magic()); + packet->setDataByName("mental", info_struct->get_mental()); + packet->setDataByName("divine", info_struct->get_divine()); + packet->setDataByName("disease", info_struct->get_disease()); + packet->setDataByName("poison", info_struct->get_poison()); + packet->setDataByName("heat_base", info_struct->get_heat_base()); + packet->setDataByName("cold_base", info_struct->get_cold_base()); + packet->setDataByName("magic_base", info_struct->get_magic_base()); + packet->setDataByName("mental_base", info_struct->get_mental_base()); + packet->setDataByName("divine_base", info_struct->get_divine_base()); + packet->setDataByName("disease_base", info_struct->get_disease_base()); + packet->setDataByName("poison_base", info_struct->get_poison_base()); + packet->setDataByName("mitigation_cur2", info_struct->get_cur_mitigation()); + packet->setDataByName("mitigation_max2", info_struct->get_max_mitigation()); + packet->setDataByName("mitigation_base2", info_struct->get_mitigation_base()); + packet->setDataByName("coins_copper", info_struct->get_coin_copper()); + packet->setDataByName("coins_silver", info_struct->get_coin_silver()); + packet->setDataByName("coins_gold", info_struct->get_coin_gold()); + packet->setDataByName("coins_plat", info_struct->get_coin_plat()); + packet->setDataByName("weight", info_struct->get_weight()); + packet->setDataByName("max_weight", info_struct->get_max_weight()); + + if(info_struct->get_pet_id() != 0xFFFFFFFF) { + char pet_name[32]; + strncpy(pet_name, info_struct->get_pet_name().c_str(), version <= 373 ? 16 : 32); + packet->setDataByName("pet_name", pet_name); + } + else { + packet->setDataByName("pet_name", "No Pet"); + } + + packet->setDataByName("pet_health_pct", info_struct->get_pet_health_pct()); + packet->setDataByName("pet_power_pct", info_struct->get_pet_power_pct()); + + packet->setDataByName("pet_movement", info_struct->get_pet_movement()); + packet->setDataByName("pet_behavior", info_struct->get_pet_behavior()); + + packet->setDataByName("status_points", info_struct->get_status_points()); + if(bind_zone_id > 0){ + string bind_name = database.GetZoneName(bind_zone_id); + if (bind_name.length() > 0) + packet->setDataByName("bind_zone", bind_name.c_str()); + } + else + packet->setDataByName("bind_zone", "None"); + if(house_zone_id > 0){ + string house_name = database.GetZoneName(house_zone_id); + if (house_name.length() > 0) + packet->setDataByName("house_zone", house_name.c_str()); + } + else + packet->setDataByName("house_zone", "None"); + //packet->setDataByName("account_age_base", 14); + packet->setDataByName("hp_regen", info_struct->get_hp_regen()); + packet->setDataByName("power_regen", info_struct->get_power_regen()); + /*packet->setDataByName("unknown11", -1, 0); + packet->setDataByName("unknown11", -1, 1); + packet->setDataByName("unknown13", 201, 0); + packet->setDataByName("unknown13", 201, 1); + packet->setDataByName("unknown13", 234, 2); + packet->setDataByName("unknown13", 201, 3); + packet->setDataByName("unknown13", 214, 4); + packet->setDataByName("unknown13", 234, 5); + packet->setDataByName("unknown13", 234, 6); + + packet->setDataByName("unknown14", 78); + */ + packet->setDataByName("adventure_exp_vitality", (int16)(player->GetXPVitality() *10)); + //packet->setDataByName("unknown15b", 9911); + packet->setDataByName("unknown15a", 78); + packet->setDataByName("xp_yellow_vitality_bar", info_struct->get_xp_yellow_vitality_bar()); + packet->setDataByName("xp_blue_vitality_bar", info_struct->get_xp_blue_vitality_bar()); + packet->setDataByName("tradeskill_exp_vitality", 100); + packet->setDataByName("unknown15c", 200); + + //packet->setDataByName("unknown15", 100, 10); + packet->setDataByName("unknown18", 16880, 1); + /*packet->setDataByName("unknown19", 1); + packet->setDataByName("unknown19", 3, 1); + packet->setDataByName("unknown19", 1074301064, 2); + packet->setDataByName("unknown19", 1, 3); + packet->setDataByName("unknown19", 3, 4); + packet->setDataByName("unknown19", 1074301064, 5); + packet->setDataByName("unknown19", 6, 6); + packet->setDataByName("unknown19", 14, 7); + packet->setDataByName("unknown19", 1083179008, 8);*/ + player->SetGroupInformation(packet); + packet->setDataByName("unknown20", 1, 107); + packet->setDataByName("unknown20", 1, 108); + packet->setDataByName("unknown20", 1, 109); + packet->setDataByName("unknown20", 1, 110); + packet->setDataByName("unknown20", 1, 111); + //packet->setDataByName("unknown20b", 255); + //packet->setDataByName("unknown20b", 255, 1); + //packet->setDataByName("unknown20b", 255, 2); + packet->setDataByName("unknown11", 123); + packet->setDataByName("unknown11", 234, 1); + + //packet->setDataByName("in_combat", 32768); + //make name flash red + /*packet->setDataByName("unknown20", 8); + packet->setDataByName("unknown20", 38, 70); + packet->setDataByName("unknown20", 17, 77); + packet->setDataByName("unknown20", 1, 112); //melee stats and such + packet->setDataByName("unknown20", 1, 113); + packet->setDataByName("unknown20", 1, 114); + packet->setDataByName("unknown20", 1, 115); + + packet->setDataByName("unknown20", 4294967295, 309); + packet->setDataByName("unknown22", 2, 4); + packet->setDataByName("unknown23", 2, 29); + */ + //packet->setDataByName("unknown20b", 1, i); // pet bar in here + // for(int i=0;i<19;i++) + // packet->setDataByName("unknown7", 257, i); + //packet->setDataByName("unknown21", info_struct->rain, 2); + packet->setDataByName("rain", info_struct->get_rain()); + packet->setDataByName("rain2", info_struct->get_wind()); //-102.24); + /*packet->setDataByName("unknown22", 3, 4); + packet->setDataByName("unknown23", 3, 161); + packet->setDataByName("unknown20", 103); + packet->setDataByName("unknown20", 1280, 70); + packet->setDataByName("unknown20", 9, 71); + packet->setDataByName("unknown20", 5, 72); + packet->setDataByName("unknown20", 4294967271, 73); + packet->setDataByName("unknown20", 5, 75); + packet->setDataByName("unknown20", 1051, 77); + packet->setDataByName("unknown20", 3, 78); + packet->setDataByName("unknown20", 6, 104); + packet->setDataByName("unknown20", 1, 105); + packet->setDataByName("unknown20", 20, 106); + packet->setDataByName("unknown20", 3, 107); + packet->setDataByName("unknown20", 1, 108); + packet->setDataByName("unknown20", 1, 109); + packet->setDataByName("unknown20", 4278190080, 494); + packet->setDataByName("unknown20b", 255); + packet->setDataByName("unknown20b", 255, 1); + packet->setDataByName("unknown20b", 255, 2); + packet->setDataByName("unknown20", 50, 75); + */ + //packet->setDataByName("rain2", -102.24); + for(int i=0;i<45;i++){ + if(i < 30){ + packet->setSubstructDataByName("maintained_effects", "name", info_struct->maintained_effects[i].name, i, 0); + packet->setSubstructDataByName("maintained_effects", "target", info_struct->maintained_effects[i].target, i, 0); + packet->setSubstructDataByName("maintained_effects", "spell_id", info_struct->maintained_effects[i].spell_id, i, 0); + packet->setSubstructDataByName("maintained_effects", "slot_pos", info_struct->maintained_effects[i].slot_pos, i, 0); + packet->setSubstructDataByName("maintained_effects", "icon", info_struct->maintained_effects[i].icon, i, 0); + packet->setSubstructDataByName("maintained_effects", "icon_type", info_struct->maintained_effects[i].icon_backdrop, i, 0); + packet->setSubstructDataByName("maintained_effects", "conc_used", info_struct->maintained_effects[i].conc_used, i, 0); + packet->setSubstructDataByName("maintained_effects", "unknown3", 1, i, 0); + packet->setSubstructDataByName("maintained_effects", "total_time", info_struct->maintained_effects[i].total_time, i, 0); + packet->setSubstructDataByName("maintained_effects", "expire_timestamp", info_struct->maintained_effects[i].expire_timestamp, i, 0); + } + else if(version < 942)//version 942 added 15 additional spell effect slots + break; + packet->setSubstructDataByName("spell_effects", "spell_id", info_struct->spell_effects[i].spell_id, i, 0); + if(info_struct->spell_effects[i].spell_id > 0 && info_struct->spell_effects[i].spell_id < 0xFFFFFFFF) + packet->setSubstructDataByName("spell_effects", "unknown2", 514, i, 0); + packet->setSubstructDataByName("spell_effects", "total_time", info_struct->spell_effects[i].total_time, i, 0); + packet->setSubstructDataByName("spell_effects", "expire_timestamp", info_struct->spell_effects[i].expire_timestamp, i, 0); + packet->setSubstructDataByName("spell_effects", "icon", info_struct->spell_effects[i].icon, i, 0); + packet->setSubstructDataByName("spell_effects", "icon_type", info_struct->spell_effects[i].icon_backdrop, i, 0); + } + return packet; + } + return 0; +} + +EQ2Packet* PlayerInfo::serialize3(PacketStruct* packet, int16 version){ + if(packet){ + string* data = packet->serializeString(); + int32 size = data->length(); + //DumpPacket((uchar*)data->c_str(), size); + uchar* tmp = new uchar[size]; + if(!changes){ + orig_packet = new uchar[size]; + changes = new uchar[size]; + memcpy(orig_packet, (uchar*)data->c_str(), size); + size = Pack(tmp, (uchar*)data->c_str(), size, size, version); + } + else{ + memcpy(changes, (uchar*)data->c_str(), size); + Encode(changes, orig_packet, size); + size = Pack(tmp, changes, size, size, version); + //cout << "INFO HERE:\n"; + //DumpPacket(tmp, size); + } + EQ2Packet* ret_packet = new EQ2Packet(OP_UpdateCharacterSheetMsg, tmp, size+4); + safe_delete_array(tmp); + safe_delete(packet); + return ret_packet; + } + return 0; +} + +void PlayerInfo::SetAccountAge(int32 age){ + info_struct->set_account_age_base(age); +} + +EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyValue) { + PacketStruct* packet = configReader.getStruct("WS_CharacterSheet", version); + //0-69, locked screen movement + //30-69 normal movement + //10-30 normal movement + + if (packet) { + char name[40]; + strncpy(name,info_struct->get_name().c_str(),40); + packet->setDataByName("character_name", name); + packet->setDataByName("race", info_struct->get_race()); + packet->setDataByName("gender", info_struct->get_gender()); + packet->setDataByName("exiled", 0); // need exiled data + packet->setDataByName("class1", info_struct->get_class1()); + packet->setDataByName("class2", info_struct->get_class2()); + packet->setDataByName("class3", info_struct->get_class3()); + packet->setDataByName("tradeskill_class1", info_struct->get_tradeskill_class1()); + packet->setDataByName("tradeskill_class2", info_struct->get_tradeskill_class2()); + packet->setDataByName("tradeskill_class3", info_struct->get_tradeskill_class3()); + packet->setDataByName("level", info_struct->get_level()); + packet->setDataByName("effective_level", info_struct->get_effective_level() != 0 ? info_struct->get_effective_level() : info_struct->get_level()); + packet->setDataByName("tradeskill_level", info_struct->get_tradeskill_level()); + packet->setDataByName("account_age_base", info_struct->get_account_age_base()); + + //TODO: 2021 FIX THIS CASTING + for (int8 i = 0; i < 19; i++) + packet->setDataByName("account_age_bonus", 0); + //TODO: 2021 FIX THIS CASTING + char deity[32]; + strncpy(deity, info_struct->get_deity().c_str(), 32); + packet->setDataByName("deity", deity); + + packet->setDataByName("last_name", player->GetLastName()); + packet->setDataByName("current_hp", player->GetHP()); + packet->setDataByName("max_hp", player->GetTotalHP()); + packet->setDataByName("base_hp", player->GetTotalHPBase()); + + packet->setDataByName("current_power", player->GetPower()); + packet->setDataByName("max_power", player->GetTotalPower()); + packet->setDataByName("base_power", player->GetTotalPowerBase()); + packet->setDataByName("conc_used", info_struct->get_cur_concentration()); + packet->setDataByName("conc_max", info_struct->get_max_concentration()); + packet->setDataByName("hp_regen", player->GetInfoStruct()->get_hp_regen()); + packet->setDataByName("power_regen", player->GetInfoStruct()->get_power_regen()); + + packet->setDataByName("stat_bonus_health", player->CalculateBonusMod());//bonus health and bonus power getting same value? + packet->setDataByName("stat_bonus_power", player->CalculateBonusMod());//bonus health and bonus power getting same value? + float bonus_health = floor((float)(info_struct->get_sta() * player->CalculateBonusMod())); + packet->setDataByName("bonus_health", bonus_health); + packet->setDataByName("bonus_power", floor((float)(player->GetPrimaryStat() * player->CalculateBonusMod()))); + packet->setDataByName("stat_bonus_damage", 95); //stat_bonus_damage + packet->setDataByName("mitigation_cur", info_struct->get_cur_mitigation());// confirmed DoV + packet->setDataByName("mitigation_base", info_struct->get_mitigation_base());// confirmed DoV + + packet->setDataByName("mitigation_pct_pve", info_struct->get_mitigation_pve()); // % calculation Mitigation % vs PvE 392 = 39.2%// confirmed DoV + packet->setDataByName("mitigation_pct_pvp", info_struct->get_mitigation_pvp()); // % calculation Mitigation % vs PvP 559 = 55.9%// confirmed DoV + packet->setDataByName("toughness", 0);//toughness// confirmed DoV + packet->setDataByName("toughness_resist_dmg_pvp", 0);//toughness_resist_dmg_pvp 73 = 7300% // confirmed DoV + packet->setDataByName("avoidance_pct", (int16)info_struct->get_avoidance_display()*10.0f);//avoidance_pct 192 = 19.2% // confirmed DoV + packet->setDataByName("avoidance_base", (int16)info_struct->get_avoidance_base()*10.0f); // confirmed DoV + packet->setDataByName("avoidance", info_struct->get_cur_avoidance()); + packet->setDataByName("base_avoidance_pct", info_struct->get_base_avoidance_pct());// confirmed DoV + float parry_pct = info_struct->get_parry(); // client works off of int16, but we use floats to track the actual x/100% + packet->setDataByName("parry",(int16)(parry_pct*10.0f));// confirmed DoV + + float block_pct = info_struct->get_block()*10.0f; + + packet->setDataByName("block", (int16)block_pct);// confirmed DoV + packet->setDataByName("uncontested_block", info_struct->get_uncontested_block());// confirmed DoV + packet->setDataByName("str", info_struct->get_str());// confirmed DoV + packet->setDataByName("sta", info_struct->get_sta());// confirmed DoV + packet->setDataByName("agi", info_struct->get_agi());// confirmed DoV + packet->setDataByName("wis", info_struct->get_wis());// confirmed DoV + packet->setDataByName("int", info_struct->get_intel());// confirmed DoV + packet->setDataByName("str_base", info_struct->get_str_base()); // confirmed DoV + packet->setDataByName("sta_base", info_struct->get_sta_base());// confirmed DoV + packet->setDataByName("agi_base", info_struct->get_agi_base());// confirmed DoV + packet->setDataByName("wis_base", info_struct->get_wis_base());// confirmed DoV + packet->setDataByName("int_base", info_struct->get_intel_base());// confirmed DoV + if (version <= 996) { + packet->setDataByName("heat", info_struct->get_heat()); + packet->setDataByName("cold", info_struct->get_cold()); + packet->setDataByName("magic", info_struct->get_magic()); + packet->setDataByName("mental", info_struct->get_mental()); + packet->setDataByName("divine", info_struct->get_divine()); + packet->setDataByName("disease", info_struct->get_disease()); + packet->setDataByName("poison", info_struct->get_poison()); + packet->setDataByName("heat_base", info_struct->get_heat_base()); + packet->setDataByName("cold_base", info_struct->get_cold_base()); + packet->setDataByName("magic_base", info_struct->get_magic_base()); + packet->setDataByName("mental_base", info_struct->get_mental_base()); + packet->setDataByName("divine_base", info_struct->get_divine_base()); + packet->setDataByName("disease_base", info_struct->get_disease_base()); + packet->setDataByName("poison_base", info_struct->get_poison_base()); + } + else { + packet->setDataByName("elemental", info_struct->get_heat());// confirmed DoV + packet->setDataByName("noxious", info_struct->get_poison());// confirmed DoV + packet->setDataByName("arcane", info_struct->get_magic());// confirmed DoV + packet->setDataByName("elemental_base", info_struct->get_elemental_base());// confirmed DoV + packet->setDataByName("noxious_base", info_struct->get_noxious_base());// confirmed DoV + packet->setDataByName("arcane_base", info_struct->get_arcane_base());// confirmed DoV + } + packet->setDataByName("elemental_absorb_pve", 0); //210 = 21.0% confirmed DoV + packet->setDataByName("noxious_absorb_pve", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("arcane_absorb_pve", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("elemental_absorb_pvp", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("noxious_absorb_pvp", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("arcane_absorb_pvp", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("elemental_dmg_reduction", 0);// confirmed DoV + packet->setDataByName("noxious_dmg_reduction", 0);// confirmed DoV + packet->setDataByName("arcane_dmg_reduction", 0);// confirmed DoV + packet->setDataByName("elemental_dmg_reduction_pct", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("noxious_dmg_reduction_pct", 0);//210 = 21.0% confirmed DoV + packet->setDataByName("arcane_dmg_reduction_pct", 0);//210 = 21.0% confirmed DoV + CalculateXPPercentages(); + packet->setDataByName("current_adv_xp", info_struct->get_xp()); // confirmed DoV + packet->setDataByName("needed_adv_xp", info_struct->get_xp_needed());// confirmed DoV + + if(version >= 60114) + { + // AoM ends up the debt_adv_xp field is the percentage of xp to the next level needed to advance out of debt (WHYY CANT THIS JUST BE A PERCENTAGE LIKE DOV!) + float currentPctOfLevel = (float)info_struct->get_xp() / (float)info_struct->get_xp_needed(); + float neededPctAdvanceOutOfDebt = currentPctOfLevel + (info_struct->get_xp_debt() / 100.0f); + packet->setDataByName("debt_adv_xp", neededPctAdvanceOutOfDebt); + } + else + { + double currentPctOfLevel = (double)info_struct->get_xp() / (double)info_struct->get_xp_needed(); + double neededPctAdvanceOutOfDebt = (currentPctOfLevel + ((double)info_struct->get_xp_debt() / 100.0)) * 1000.0; + packet->setDataByName("exp_debt", (int16)(neededPctAdvanceOutOfDebt));//95= 9500% //confirmed DoV + } + + packet->setDataByName("current_trade_xp", info_struct->get_ts_xp());// confirmed DoV + packet->setDataByName("needed_trade_xp", info_struct->get_ts_xp_needed());// confirmed DoV + + packet->setDataByName("debt_trade_xp", 0);//95= 9500% //confirmed DoV + packet->setDataByName("server_bonus", 0);//confirmed DoV + packet->setDataByName("adventure_vet_bonus", 145);//confirmed DoV + packet->setDataByName("tradeskill_vet_bonus", 123);//confirmed DoV + packet->setDataByName("recruit_friend", 110);// 110 = 11000% //confirmed DoV + packet->setDataByName("recruit_friend_bonus", 0);//confirmed DoV + + packet->setDataByName("adventure_vitality", (int16)(player->GetXPVitality() * 10)); // a %% + packet->setDataByName("adventure_vitality_yellow_arrow", info_struct->get_xp_yellow_vitality_bar()); //change info_struct to match struct + packet->setDataByName("adventure_vitality_blue_arrow", info_struct->get_xp_blue_vitality_bar()); //change info_struct to match struct + + packet->setDataByName("tradeskill_vitality", 300); //300 = 30% + + packet->setDataByName("tradeskill_vitality_purple_arrow", 0);// dov confirmed + packet->setDataByName("tradeskill_vitality_blue_arrow", 0);// dov confirmed + packet->setDataByName("mentor_bonus", 50);//mentor_bonus //this converts wrong says mentor bonus enabled but earning 0 + + packet->setDataByName("assigned_aa", player->GetAssignedAA()); + packet->setDataByName("max_aa", rule_manager.GetGlobalRule(R_Player, MaxAA)->GetInt16()); + packet->setDataByName("unassigned_aa", player->GetUnassignedAA()); // dov confirmed + packet->setDataByName("aa_green_bar", 0);// dov confirmed + packet->setDataByName("adv_xp_to_aa_xp_slider", 0); // aa slider max // dov confirmed + packet->setDataByName("adv_xp_to_aa_xp_max", 100); // aa slider position // dov confirmed + packet->setDataByName("aa_blue_bar", 0);// dov confirmed + packet->setDataByName("bonus_achievement_xp", 0); // dov confirmed + + packet->setDataByName("level_events", 32);// dov confirmed + packet->setDataByName("items_found", 62);// dov confirmed + packet->setDataByName("named_npcs_killed", 192);// dov confirmed + packet->setDataByName("quests_completed", 670);// dov confirmed + packet->setDataByName("exploration_events", 435);// dov confirmed + packet->setDataByName("completed_collections", 144);// dov confirmed + packet->setDataByName("unknown_1096_13_MJ", 80);//unknown_1096_13_MJ + packet->setDataByName("unknown_1096_14_MJ", 50);//unknown_1096_14_MJ + packet->setDataByName("coins_copper", info_struct->get_coin_copper());// dov confirmed + packet->setDataByName("coins_silver", info_struct->get_coin_silver());// dov confirmed + packet->setDataByName("coins_gold", info_struct->get_coin_gold());// dov confirmed + packet->setDataByName("coins_plat", info_struct->get_coin_plat());// dov confirmed + + Skill* skill = player->GetSkillByName("Swimming", false); + float breath_modifier = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMinBreathLength)->GetFloat(); + if(skill) { + int32 max_val = 450; + if(skill->max_val > 0) + max_val = skill->max_val; + float diff = (float)(skill->current_val + player->GetStat(ITEM_STAT_SWIMMING)) / (float)max_val; + float max_breath_mod = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMaxBreathLength)->GetFloat(); + float diff_mod = max_breath_mod * diff; + if(diff_mod > max_breath_mod) + breath_modifier = max_breath_mod; + else if(diff_mod > breath_modifier) + breath_modifier = diff_mod; + } + packet->setDataByName("breath", breath_modifier); + + packet->setDataByName("melee_pri_dmg_min", player->GetPrimaryWeaponMinDamage());// dov confirmed + packet->setDataByName("melee_pri_dmg_max", player->GetPrimaryWeaponMaxDamage());// dov confirmed + packet->setDataByName("melee_sec_dmg_min", player->GetSecondaryWeaponMinDamage());// dov confirmed + packet->setDataByName("melee_sec_dmg_max", player->GetSecondaryWeaponMaxDamage());// dov confirmed // this is off when using 2 handed weapon + packet->setDataByName("ranged_dmg_min", player->GetRangedWeaponMinDamage());// dov confirmed + packet->setDataByName("ranged_dmg_max", player->GetRangedWeaponMaxDamage());// dov confirmed + if (info_struct->get_attackspeed() > 0) { + packet->setDataByName("melee_pri_delay", (((float)player->GetPrimaryWeaponDelay() * 1.33) / player->CalculateAttackSpeedMod()) * .001);// dov confirmed + packet->setDataByName("melee_sec_delay", (((float)player->GetSecondaryWeaponDelay() * 1.33) / player->CalculateAttackSpeedMod()) * .001);// dov confirmed + packet->setDataByName("ranged_delay", (((float)player->GetRangeWeaponDelay() * 1.33) / player->CalculateAttackSpeedMod()) * .001);// dov confirmed + } + else { + packet->setDataByName("melee_pri_delay", (float)player->GetPrimaryWeaponDelay() * .001);// dov confirmed + packet->setDataByName("melee_sec_delay", (float)player->GetSecondaryWeaponDelay() * .001);// dov confirmed + packet->setDataByName("ranged_delay", (float)player->GetRangeWeaponDelay() * .001);// dov confirmed + } + + packet->setDataByName("ability_mod_pve", info_struct->get_ability_modifier());// dov confirmed + packet->setDataByName("base_melee_crit", 85);//85 = 8500% dov confirmed + packet->setDataByName("base_spell_crit", 84);// dov confirmed + packet->setDataByName("base_taunt_crit", 83);// dov confirmed + packet->setDataByName("base_heal_crit", 82);// dov confirmed + packet->setDataByName("flags", info_struct->get_flags()); + packet->setDataByName("flags2", info_struct->get_flags2()); + if (version == 546) { + if (player->get_character_flag(CF_ANONYMOUS)) + packet->setDataByName("flags_anonymous", 1); + if (player->get_character_flag(CF_ROLEPLAYING)) + packet->setDataByName("flags_roleplaying", 1); + if (player->get_character_flag(CF_AFK)) + packet->setDataByName("flags_afk", 1); + if (player->get_character_flag(CF_LFG)) + packet->setDataByName("flags_lfg", 1); + if (player->get_character_flag(CF_LFW)) + packet->setDataByName("flags_lfw", 1); + if (!player->get_character_flag(CF_HIDE_HOOD) && !player->get_character_flag(CF_HIDE_HELM)) + packet->setDataByName("flags_show_hood", 1); + if (player->get_character_flag(CF_SHOW_ILLUSION)) + packet->setDataByName("flags_show_illusion_form", 1); + if (player->get_character_flag(CF_ALLOW_DUEL_INVITES)) + packet->setDataByName("flags_show_duel_invites", 1); + if (player->get_character_flag(CF_ALLOW_TRADE_INVITES)) + packet->setDataByName("flags_show_trade_invites", 1); + if (player->get_character_flag(CF_ALLOW_GROUP_INVITES)) + packet->setDataByName("flags_show_group_invites", 1); + if (player->get_character_flag(CF_ALLOW_RAID_INVITES)) + packet->setDataByName("flags_show_raid_invites", 1); + if (player->get_character_flag(CF_ALLOW_GUILD_INVITES)) + packet->setDataByName("flags_show_guild_invites", 1); + } + + packet->setDataByName("haste", info_struct->get_haste());// dov confirmed + packet->setDataByName("drunk", info_struct->get_drunk());// dov confirmed + + packet->setDataByName("hate_mod", info_struct->get_hate_mod());// dov confirmed + packet->setDataByName("adventure_effects_bonus", 55);// NEED an adventure_effects_bonus// dov confirmed + packet->setDataByName("tradeskill_effects_bonus", 56);// NEED an tradeskill_effects_bonus// dov confirmed + packet->setDataByName("dps", info_struct->get_dps());// dov confirmed + packet->setDataByName("melee_ae", info_struct->get_melee_ae());// dov confirmed + packet->setDataByName("multi_attack", info_struct->get_multi_attack());// dov confirmed + packet->setDataByName("spell_multi_attack", info_struct->get_spell_multi_attack());// dov confirmed + packet->setDataByName("block_chance", info_struct->get_block_chance());// dov confirmed + packet->setDataByName("crit_chance", info_struct->get_crit_chance());// dov confirmed + packet->setDataByName("crit_bonus", info_struct->get_crit_bonus());// dov confirmed + + packet->setDataByName("potency", info_struct->get_potency());//info_struct->get_potency);// dov confirmed + + packet->setDataByName("reuse_speed", info_struct->get_reuse_speed());// dov confirmed + packet->setDataByName("recovery_speed", info_struct->get_recovery_speed());// dov confirmed + packet->setDataByName("casting_speed", info_struct->get_casting_speed());// dov confirmed + packet->setDataByName("spell_reuse_speed", info_struct->get_spell_reuse_speed());// dov confirmed + packet->setDataByName("strikethrough", info_struct->get_strikethrough());//dov confirmed + packet->setDataByName("accuracy", info_struct->get_accuracy());//dov confirmed + packet->setDataByName("critical_mit", info_struct->get_critical_mitigation());//dov /confirmed + + ((Entity*)player)->MStats.lock(); + packet->setDataByName("durability_mod", player->stats[ITEM_STAT_DURABILITY_MOD]);// dov confirmed + packet->setDataByName("durability_add", player->stats[ITEM_STAT_DURABILITY_ADD]);// dov confirmed + packet->setDataByName("progress_mod", player->stats[ITEM_STAT_PROGRESS_MOD]);// dov confirmed + packet->setDataByName("progress_add", player->stats[ITEM_STAT_PROGRESS_ADD]);// dov confirmed + packet->setDataByName("success_mod", player->stats[ITEM_STAT_SUCCESS_MOD]);// dov confirmed + packet->setDataByName("crit_success_mod", player->stats[ITEM_STAT_CRIT_SUCCESS_MOD]);// dov confirmed + ((Entity*)player)->MStats.unlock(); + + if (version <= 373 && info_struct->get_pet_id() == 0xFFFFFFFF) + packet->setDataByName("pet_id", 0); + else { + packet->setDataByName("pet_id", info_struct->get_pet_id()); + char pet_name[32]; + strncpy(pet_name, info_struct->get_pet_name().c_str(), version <= 373 ? 16 : 32); + packet->setDataByName("pet_name", pet_name); + } + + packet->setDataByName("pet_health_pct", info_struct->get_pet_health_pct()); + packet->setDataByName("pet_power_pct", info_struct->get_pet_power_pct()); + + packet->setDataByName("pet_movement", info_struct->get_pet_movement()); + packet->setDataByName("pet_behavior", info_struct->get_pet_behavior()); + packet->setDataByName("rain", info_struct->get_rain()); + packet->setDataByName("rain2", info_struct->get_wind()); //-102.24); + packet->setDataByName("status_points", info_struct->get_status_points()); + packet->setDataByName("guild_status", 888888); + + if (house_zone_id > 0){ + string house_name = database.GetZoneName(house_zone_id); + if(house_name.length() > 0) + packet->setDataByName("house_zone", house_name.c_str()); + } + else + packet->setDataByName("house_zone", "None"); + + if (bind_zone_id > 0){ + string bind_name = database.GetZoneName(bind_zone_id); + if(bind_name.length() > 0) + packet->setDataByName("bind_zone", bind_name.c_str()); + } + else + packet->setDataByName("bind_zone", "None"); + + + ((Entity*)player)->MStats.lock(); + packet->setDataByName("rare_harvest_chance", player->stats[ITEM_STAT_RARE_HARVEST_CHANCE]); + packet->setDataByName("max_crafting", player->stats[ITEM_STAT_MAX_CRAFTING]); + packet->setDataByName("component_refund", player->stats[ITEM_STAT_COMPONENT_REFUND]); + packet->setDataByName("ex_durability_mod", player->stats[ITEM_STAT_EX_DURABILITY_MOD]); + packet->setDataByName("ex_durability_add", player->stats[ITEM_STAT_EX_DURABILITY_ADD]); + packet->setDataByName("ex_crit_success_mod", player->stats[ITEM_STAT_EX_CRIT_SUCCESS_MOD]); + packet->setDataByName("ex_crit_failure_mod", player->stats[ITEM_STAT_EX_CRIT_FAILURE_MOD]); + packet->setDataByName("ex_progress_mod", player->stats[ITEM_STAT_EX_PROGRESS_MOD]); + packet->setDataByName("ex_progress_add", player->stats[ITEM_STAT_EX_PROGRESS_ADD]); + packet->setDataByName("ex_success_mod", player->stats[ITEM_STAT_EX_SUCCESS_MOD]); + ((Entity*)player)->MStats.unlock(); + + packet->setDataByName("flurry", info_struct->get_flurry()); + packet->setDataByName("unknown153", 153); + packet->setDataByName("bountiful_harvest", 0); // need bountiful harvest + + packet->setDataByName("unknown156", 156); + packet->setDataByName("unknown157", 157); + + packet->setDataByName("unknown159", 159); + packet->setDataByName("unknown160", 160); + + + packet->setDataByName("unknown163", 163); + + + packet->setDataByName("unknown168", 168); + packet->setDataByName("decrease_falling_dmg", 169); + + if (version <= 561) { + packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10); + packet->setDataByName("exp_blue", info_struct->get_xp_blue()/10); + } + else { + packet->setDataByName("exp_yellow", info_struct->get_xp_yellow()); + packet->setDataByName("exp_blue", info_struct->get_xp_blue()); + } + + if (version <= 561) { + packet->setDataByName("tradeskill_exp_yellow", info_struct->get_tradeskill_exp_yellow() / 10); + packet->setDataByName("tradeskill_exp_blue", info_struct->get_tradeskill_exp_blue() / 10); + } + else { + packet->setDataByName("tradeskill_exp_yellow", info_struct->get_tradeskill_exp_yellow()); + packet->setDataByName("tradeskill_exp_blue", info_struct->get_tradeskill_exp_blue()); + } + + packet->setDataByName("attack", info_struct->get_cur_attack()); + packet->setDataByName("attack_base", info_struct->get_attack_base()); + packet->setDataByName("absorb", info_struct->get_absorb()); + packet->setDataByName("mitigation_skill1", info_struct->get_mitigation_skill1()); + packet->setDataByName("mitigation_skill2", info_struct->get_mitigation_skill2()); + packet->setDataByName("mitigation_skill3", info_struct->get_mitigation_skill3()); + + packet->setDataByName("mitigation_max", info_struct->get_max_mitigation()); + + packet->setDataByName("savagery", 250); + packet->setDataByName("max_savagery", 500); + packet->setDataByName("savagery_level", 1); + packet->setDataByName("max_savagery_level", 5); + packet->setDataByName("dissonance", 5000); + packet->setDataByName("max_dissonance", 10000); + + packet->setDataByName("mitigation_cur2", info_struct->get_cur_mitigation()); + packet->setDataByName("mitigation_max2", info_struct->get_max_mitigation()); + packet->setDataByName("mitigation_base2", info_struct->get_mitigation_base()); + + packet->setDataByName("weight", info_struct->get_weight()); + packet->setDataByName("max_weight", info_struct->get_max_weight()); + packet->setDataByName("unknownint32a", 777777); + packet->setDataByName("unknownint32b", 666666); + packet->setDataByName("mitigation2_cur", 2367); + packet->setDataByName("uncontested_riposte", info_struct->get_uncontested_riposte()); + packet->setDataByName("uncontested_dodge", info_struct->get_uncontested_dodge()); + packet->setDataByName("uncontested_parry", info_struct->get_uncontested_parry()); //???? + packet->setDataByName("uncontested_riposte_pve", 0); //???? + packet->setDataByName("uncontested_parry_pve", 0); //???? + packet->setDataByName("total_prestige_points", player->GetPrestigeAA()); + packet->setDataByName("unassigned_prestige_points", player->GetUnassignedPretigeAA()); + packet->setDataByName("total_tradeskill_points", player->GetTradeskillAA()); + packet->setDataByName("unassigned_tradeskill_points", player->GetUnassignedTradeskillAA()); + packet->setDataByName("total_tradeskill_prestige_points", player->GetTradeskillPrestigeAA()); + packet->setDataByName("unassigned_tradeskill_prestige_points", player->GetUnassignedTradeskillPrestigeAA()); + + // unknown14c = percent aa exp to next level + packet->setDataByName("unknown14d", 100, 0); + packet->setDataByName("unknown20", 1084227584, 72); + packet->setDataByName("unknown15c", 200); + + player->SetGroupInformation(packet); + + packet->setDataByName("in_combat_movement_speed", 125); + + packet->setDataByName("increase_max_power", 127); + packet->setDataByName("increase_max_power2", 128); + + packet->setDataByName("vision", info_struct->get_vision()); + packet->setDataByName("breathe_underwater", info_struct->get_breathe_underwater()); + + int32 expireTimestamp = 0; + Spawn* maintained_target = 0; + player->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); + player->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + for (int i = 0; i < 45; i++) { + if (i < 30) { + maintained_target = player->GetZone()->GetSpawnByID(info_struct->maintained_effects[i].target); + packet->setSubstructDataByName("maintained_effects", "name", info_struct->maintained_effects[i].name, i, 0); + if (maintained_target) + packet->setSubstructDataByName("maintained_effects", "target", player->GetIDWithPlayerSpawn(maintained_target), i, 0); + packet->setSubstructDataByName("maintained_effects", "target_type", info_struct->maintained_effects[i].target_type, i, 0); + packet->setSubstructDataByName("maintained_effects", "spell_id", info_struct->maintained_effects[i].spell_id, i, 0); + packet->setSubstructDataByName("maintained_effects", "slot_pos", info_struct->maintained_effects[i].slot_pos, i, 0); + packet->setSubstructDataByName("maintained_effects", "icon", info_struct->maintained_effects[i].icon, i, 0); + packet->setSubstructDataByName("maintained_effects", "icon_type", info_struct->maintained_effects[i].icon_backdrop, i, 0); + packet->setSubstructDataByName("maintained_effects", "conc_used", info_struct->maintained_effects[i].conc_used, i, 0); + packet->setSubstructDataByName("maintained_effects", "unknown3", 1, i, 0); + packet->setSubstructDataByName("maintained_effects", "total_time", info_struct->maintained_effects[i].total_time, i, 0); + expireTimestamp = info_struct->maintained_effects[i].expire_timestamp; + if (expireTimestamp == 0xFFFFFFFF) + expireTimestamp = 0; + packet->setSubstructDataByName("maintained_effects", "expire_timestamp", expireTimestamp, i, 0); + } + else if (version < 942)//version 942 added 15 additional spell effect slots + break; + packet->setSubstructDataByName("spell_effects", "spell_id", info_struct->spell_effects[i].spell_id, i, 0); + packet->setSubstructDataByName("spell_effects", "total_time", info_struct->spell_effects[i].total_time, i, 0); + expireTimestamp = info_struct->spell_effects[i].expire_timestamp; + if (expireTimestamp == 0xFFFFFFFF) + expireTimestamp = 0; + packet->setSubstructDataByName("spell_effects", "expire_timestamp", expireTimestamp, i, 0); + packet->setSubstructDataByName("spell_effects", "icon", info_struct->spell_effects[i].icon, i, 0); + packet->setSubstructDataByName("spell_effects", "icon_type", info_struct->spell_effects[i].icon_backdrop, i, 0); + if(info_struct->spell_effects[i].spell && info_struct->spell_effects[i].spell->spell && info_struct->spell_effects[i].spell->spell->GetSpellData()->friendly_spell == 1) + packet->setSubstructDataByName("spell_effects", "cancellable", 1, i); + } + player->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + + int8 det_count = 0; + //Send detriment counts as 255 if all dets of that type are incurable + det_count = player->GetTraumaCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_TRAUMA)) + det_count = 255; + } + packet->setDataByName("trauma_count", det_count); + + det_count = player->GetArcaneCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_ARCANE)) + det_count = 255; + } + packet->setDataByName("arcane_count", det_count); + + det_count = player->GetNoxiousCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_NOXIOUS)) + det_count = 255; + } + packet->setDataByName("noxious_count", det_count); + + det_count = player->GetElementalCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_ELEMENTAL)) + det_count = 255; + } + packet->setDataByName("elemental_count", det_count); + + det_count = player->GetCurseCount(); + if (det_count > 0) { + if (!player->HasCurableDetrimentType(DET_TYPE_CURSE)) + det_count = 255; + } + packet->setDataByName("curse_count", det_count); + + player->GetDetrimentMutex()->readlock(__FUNCTION__, __LINE__); + vector* det_list = player->GetDetrimentalSpellEffects(); + DetrimentalEffects det; + int32 i = 0; + for (i = 0; i < det_list->size(); i++) { + det = det_list->at(i); + packet->setSubstructDataByName("detrimental_spell_effects", "spell_id", det.spell_id, i); + packet->setSubstructDataByName("detrimental_spell_effects", "total_time", det.total_time, i); + packet->setSubstructDataByName("detrimental_spell_effects", "icon", det.icon, i); + packet->setSubstructDataByName("detrimental_spell_effects", "icon_type", det.icon_backdrop, i); + expireTimestamp = det.expire_timestamp; + if (expireTimestamp == 0xFFFFFFFF) + expireTimestamp = 0; + packet->setSubstructDataByName("detrimental_spell_effects", "expire_timestamp", expireTimestamp, i); + packet->setSubstructDataByName("detrimental_spell_effects", "unknown2", 2, i); + if (i == 30) { + if (version < 942) + break; + } + else if (i == 45) + break; + } + if (version < 942) { + while (i < 30) { + packet->setSubstructDataByName("detrimental_spell_effects", "spell_id", 0xFFFFFFFF, i); + i++; + } + } + else { + while (i < 45) { + packet->setSubstructDataByName("detrimental_spell_effects", "spell_id", 0xFFFFFFFF, i); + i++; + } + } + player->GetDetrimentMutex()->releasereadlock(__FUNCTION__, __LINE__); + + // disabling as not in use right now + //packet->setDataByName("spirit_rank", 2); + //packet->setDataByName("spirit", 1); + //packet->setDataByName("spirit_progress", .67); + + packet->setDataByName("combat_exp_enabled", 1); + + string* data = packet->serializeString(); + int32 size = data->length(); + + printf("CharSheet size: %u for version %u\n", size, version); + //DumpPacket((uchar*)data->c_str(), data->size()); + //packet->PrintPacket(); + uchar* tmp = new uchar[size]; + bool reverse = version > 373; + if (!changes) { + orig_packet = new uchar[size]; + changes = new uchar[size]; + memcpy(orig_packet, (uchar*)data->c_str(), size); + size = Pack(tmp, orig_packet, size, size, version, reverse); + } + else { + memcpy(changes, (uchar*)data->c_str(), size); + if (modifyPos > 0) { + uchar* ptr2 = (uchar*)changes; + ptr2 += modifyPos - 1; + if (modifyValue > 0xFFFF) { + memcpy(ptr2, (uchar*)&modifyValue, 4); + } + else if (modifyValue > 0xFF) { + memcpy(ptr2, (uchar*)&modifyValue, 2); + } + else + memcpy(ptr2, (uchar*)&modifyValue, 1); + } + Encode(changes, orig_packet, size); + if (modifyPos > 0) { + uchar* ptr2 = (uchar*)orig_packet; + if (modifyPos > 64) + ptr2 += modifyPos - 64; + int16 tmpsize = modifyPos + 128; + if (tmpsize > size) + tmpsize = size; + } + size = Pack(tmp, changes, size, size, version, reverse); + } + + if (version >= 546 && player->GetClient()) { + player->GetClient()->SendControlGhost(); + } + + EQ2Packet* ret_packet = new EQ2Packet(OP_UpdateCharacterSheetMsg, tmp, size); + safe_delete(packet); + safe_delete_array(tmp); + return ret_packet; + } + return 0; +} + +EQ2Packet* PlayerInfo::serializePet(int16 version) { + PacketStruct* packet = configReader.getStruct("WS_CharacterPet", version); + if(packet) { + Spawn* pet = 0; + pet = player->GetPet(); + if (!pet) + pet = player->GetCharmedPet(); + + if (pet) { + packet->setDataByName("current_hp", pet->GetHP()); + packet->setDataByName("max_hp", pet->GetTotalHP()); + packet->setDataByName("base_hp", pet->GetTotalHPBase()); + + packet->setDataByName("current_power", pet->GetPower()); + packet->setDataByName("max_power", pet->GetTotalPower()); + packet->setDataByName("base_power", pet->GetTotalPowerBase()); + + packet->setDataByName("spawn_id", info_struct->get_pet_id()); + packet->setDataByName("spawn_id2", info_struct->get_pet_id()); + + if(info_struct->get_pet_id() != 0xFFFFFFFF) { + packet->setDataByName("pet_id", info_struct->get_pet_id()); + char pet_name[32]; + strncpy(pet_name, info_struct->get_pet_name().c_str(), 32); + packet->setDataByName("name", pet_name); + } + else { + packet->setDataByName("name", "No Pet"); + packet->setDataByName("no_pet", "No Pet"); + } + + if (version >= 57000) { + packet->setDataByName("current_power3", pet->GetPower()); + packet->setDataByName("max_power3", pet->GetTotalPower()); + packet->setDataByName("health_pct_tooltip", (double)info_struct->get_pet_health_pct()); + packet->setDataByName("health_pct_bar", (double)info_struct->get_pet_health_pct()); + } + else { + packet->setDataByName("health_pct_tooltip", info_struct->get_pet_health_pct()); + packet->setDataByName("health_pct_bar", info_struct->get_pet_health_pct()); + } + packet->setDataByName("power_pct_tooltip", info_struct->get_pet_power_pct()); + packet->setDataByName("power_pct_bar", info_struct->get_pet_power_pct()); + packet->setDataByName("unknown5", 255); // Hate % maybe + packet->setDataByName("movement", info_struct->get_pet_movement()); + packet->setDataByName("behavior", info_struct->get_pet_behavior()); + } + else { + packet->setDataByName("current_hp", 0); + packet->setDataByName("max_hp", 0); + packet->setDataByName("base_hp", 0); + packet->setDataByName("current_power", 0); + packet->setDataByName("max_power", 0); + packet->setDataByName("base_power", 0); + + packet->setDataByName("spawn_id", 0); + packet->setDataByName("spawn_id2", 0xFFFFFFFF); + packet->setDataByName("name", ""); + packet->setDataByName("no_pet", "No Pet"); + packet->setDataByName("health_pct_tooltip", 0); + packet->setDataByName("health_pct_bar", 0); + packet->setDataByName("power_pct_tooltip", 0); + packet->setDataByName("power_pct_bar", 0); + packet->setDataByName("unknown5", 0); + packet->setDataByName("movement", 0); + packet->setDataByName("behavior", 0); + } + + + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* tmp = new uchar[size]; + // if this is the first time sending this packet create the buffers + if(!pet_changes){ + pet_orig_packet = new uchar[size]; + pet_changes = new uchar[size]; + // copy the packet into the pet_orig_packet so we can xor against it in the future + memcpy(pet_orig_packet, (uchar*)data->c_str(), size); + // pack the packet, result ends up in tmp + size = Pack(tmp, (uchar*)data->c_str(), size, size, version); + } + else{ + // copy the packet into pet_changes + memcpy(pet_changes, (uchar*)data->c_str(), size); + // XOR's the packet to the original, stores the new packet in the orig packet (will xor against that for the next update) + // puts the xor packet into pet_changes. + Encode(pet_changes, pet_orig_packet, size); + // Pack the pet_changes packet, will put the packed size at the start, result ends up in tmp + size = Pack(tmp, pet_changes, size, size, version); + } + + // Create the packet that we will send + EQ2Packet* ret_packet = new EQ2Packet(OP_CharacterPet, tmp, size+4); + // Clean up + safe_delete_array(tmp); + safe_delete(packet); + // Return the packet that will be sent to the client + return ret_packet; + } + return 0; +} + +bool Player::DamageEquippedItems(int8 amount, Client* client) { + bool ret = false; + int8 item_type; + Item* item = 0; + equipment_list.MEquipmentItems.readlock(__FUNCTION__, __LINE__); + for(int8 i=0;igeneric_info.item_type; + if (item->details.item_id > 0 && item_type != ITEM_TYPE_FOOD && item_type != ITEM_TYPE_BAUBLE && item_type != ITEM_TYPE_THROWN && + !item->CheckFlag2(INDESTRUCTABLE)){ + ret = true; + if((item->generic_info.condition - amount) > 0) + item->generic_info.condition -= amount; + else + item->generic_info.condition = 0; + item->save_needed = true; + if (client) + client->QueuePacket(item->serialize(client->GetVersion(), false, this)); + } + } + } + equipment_list.MEquipmentItems.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +int16 Player::ConvertSlotToClient(int8 slot, int16 version) { + if (version <= 373) { + if (slot == EQ2_FOOD_SLOT) + slot = EQ2_ORIG_FOOD_SLOT; + else if (slot == EQ2_DRINK_SLOT) + slot = EQ2_ORIG_DRINK_SLOT; + else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) + slot -= 1; + } + else if (version <= 561) { + if (slot == EQ2_FOOD_SLOT) + slot = EQ2_DOF_FOOD_SLOT; + else if (slot == EQ2_DRINK_SLOT) + slot = EQ2_DOF_DRINK_SLOT; + else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) + slot -= 1; + } + return slot; +} + +int16 Player::ConvertSlotFromClient(int8 slot, int16 version) { + if (version <= 373) { + if (slot == EQ2_ORIG_FOOD_SLOT) + slot = EQ2_FOOD_SLOT; + else if (slot == EQ2_ORIG_DRINK_SLOT) + slot = EQ2_DRINK_SLOT; + else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) + slot += 1; + } + else if (version <= 561) { + if (slot == EQ2_DOF_FOOD_SLOT) + slot = EQ2_FOOD_SLOT; + else if (slot == EQ2_DOF_DRINK_SLOT) + slot = EQ2_DRINK_SLOT; + else if (slot > EQ2_EARS_SLOT_1 && slot <= EQ2_WAIST_SLOT) + slot += 1; + } + return slot; +} + +int16 Player::GetNumSlotsEquip(int16 version) { + if(version <= 561) { + return CLASSIC_NUM_SLOTS; + } + + return NUM_SLOTS; +} + +int8 Player::GetMaxBagSlots(int16 version) { + if(version <= 373) { + return CLASSIC_EQ_MAX_BAG_SLOTS; + } + else if(version <= 561) { + return DOF_EQ_MAX_BAG_SLOTS; + } + + return 255; +} + +vector Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type, bool send_item_updates) { + vector packets; + EquipmentItemList* equipList = &equipment_list; + + if(appearance_type) + equipList = &appearance_equipment_list; + + if(index >= NUM_SLOTS) { + LogWrite(PLAYER__ERROR, 0, "Player", "%u index is out of range for equip items, bag_id: %i, slot: %u, version: %u, appearance: %u", index, bag_id, slot, version, appearance_type); + return packets; + } + equipList->MEquipmentItems.readlock(__FUNCTION__, __LINE__); + Item* item = equipList->items[index]; + + if(item && !IsAllowedCombatEquip(item->details.slot_id, true)) { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempt to unequip item %s (%u) FAILED in combat!", item->name.c_str(), item->details.item_id); + equipList->MEquipmentItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + equipList->MEquipmentItems.releasereadlock(__FUNCTION__, __LINE__); + + if (item && bag_id == -999) { + int8 old_slot = item->details.slot_id; + if(item->details.equip_slot_id) { + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "item_unequipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id); + item->save_needed = true; + EQ2Packet* outapp = item_list.serialize(this, version); + if (outapp) { + packets.push_back(outapp); + packets.push_back(item->serialize(version, false)); + EQ2Packet* bag_packet = SendBagUpdate(item->details.inv_slot_id, version); + if (bag_packet) + packets.push_back(bag_packet); + } + sint16 equip_slot_id = item->details.equip_slot_id; + item->details.equip_slot_id = 0; + equipList->RemoveItem(index); + SetEquippedItemAppearances(); + packets.push_back(equipList->serialize(version, this)); + SetCharSheetChanged(true); + SetEquipment(0, equip_slot_id ? equip_slot_id : old_slot); + } + else if (item_list.AssignItemToFreeSlot(item)) { + if(appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "item_unequipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id); + item->save_needed = true; + EQ2Packet* outapp = item_list.serialize(this, version); + if (outapp) { + packets.push_back(outapp); + packets.push_back(item->serialize(version, false)); + EQ2Packet* bag_packet = SendBagUpdate(item->details.inv_slot_id, version); + if (bag_packet) + packets.push_back(bag_packet); + } + equipList->RemoveItem(index); + SetEquippedItemAppearances(); + packets.push_back(equipList->serialize(version, this)); + SetCharSheetChanged(true); + SetEquipment(0, old_slot); + } + else { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Unable to unequip item: no free inventory locations."); + packet->setDataByName("unknown02", 0x00ff); + packets.push_back(packet->serialize()); + safe_delete(packet); + } + } + } + else if (item) { + Item* to_item = 0; + if(appearance_type && slot == 255) + { + sint16 tmpSlot = 0; + item_list.GetFirstFreeSlot(&bag_id, &tmpSlot); + if(tmpSlot >= 0 && tmpSlot < 255) + slot = tmpSlot; + else + bag_id = 0; + } + + item_list.MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (item_list.items.count(bag_id) > 0 && item_list.items[bag_id][BASE_EQUIPMENT].count(slot) > 0) + to_item = item_list.items[bag_id][BASE_EQUIPMENT][slot]; + + bool canEquipToSlot = false; + if (to_item && equipList->CanItemBeEquippedInSlot(to_item, item->details.slot_id)) { + canEquipToSlot = true; + } + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + + if (canEquipToSlot) { + equipList->RemoveItem(index); + if(item->details.appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + database.DeleteItem(GetCharacterID(), to_item, "NOT-EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + + if (to_item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(to_item->GetItemScript(), "equipped", to_item, this); + + if(item->IsBag() && ( item->details.inv_slot_id != bag_id || item->details.slot_id != slot)) { + item_list.EraseItem(item); + } + item_list.RemoveItem(to_item); + equipList->SetItem(item->details.slot_id, to_item); + to_item->save_needed = true; + packets.push_back(to_item->serialize(version, false)); + SetEquipment(to_item); + item->details.inv_slot_id = bag_id; + item->details.slot_id = slot; + item->details.appearance_type = 0; + item->details.equip_slot_id = 0; + + if(!item->IsBag() && item_list.AddItem(item)) { // bags are omitted because they are equipped while remaining in inventory + item->save_needed = true; + SetEquippedItemAppearances(); + // SerializeItemPackets serves item and equipList in opposite order is why we don't use that function here.. + packets.push_back(item->serialize(version, false)); + packets.push_back(equipList->serialize(version, this)); + packets.push_back(item_list.serialize(this, version)); + } + else if(item->IsBag()) { + // already in inventory + } + else { + LogWrite(PLAYER__ERROR, 0, "Player", "failed to add item to item_list during UnequipItem, index %u, bag id %i, slot %u, version %u, appearance type %u", index, bag_id, slot, version, appearance_type); + } + } + else if (to_item && to_item->IsBag() && to_item->details.num_slots > 0) { + bool free_slot = false; + for (int8 i = 0; i < to_item->details.num_slots; i++) { + item_list.MPlayerItems.readlock(__FUNCTION__, __LINE__); + int32 count = item_list.items[to_item->details.bag_id][appearance_type].count(i); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + if (count == 0) { + SetEquipment(0, item->details.equip_slot_id ? item->details.equip_slot_id : item->details.slot_id); + + if(item->details.appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + + if(item->IsBag() && item != to_item) { + item_list.EraseItem(item); + } + + equipList->RemoveItem(index); + if(!item->IsBag()) { + item->details.inv_slot_id = to_item->details.bag_id; + item->details.slot_id = i; + item->details.appearance_type = to_item->details.appearance_type; + } + else { + item->details.appearance_type = 0; + } + item->details.equip_slot_id = 0; + + SerializeItemPackets(equipList, &packets, item, version, to_item); + free_slot = true; + break; + } + } + if (!free_slot) { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Unable to unequip item: no free space in the bag."); + packet->setDataByName("unknown02", 0x00ff); + packets.push_back(packet->serialize()); + safe_delete(packet); + } + } + } + else if (to_item) { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Unable to swap items: that item cannot be equipped there."); + packet->setDataByName("unknown02", 0x00ff); + packets.push_back(packet->serialize()); + safe_delete(packet); + } + } + else { + if ((bag_id == 0 && slot < NUM_INV_SLOTS) || (bag_id == -3 && slot < NUM_BANK_SLOTS) || (bag_id == -4 && slot < NUM_SHARED_BANK_SLOTS)) { + if (bag_id == -4 && item->CheckFlag(NO_TRADE)) { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Unable to unequip item: that item cannot be traded."); + packet->setDataByName("unknown02", 0x00ff); + packets.push_back(packet->serialize()); + safe_delete(packet); + } + } + else { + // need to check if appearance slot vs equipped + SetEquipment(0, item->details.equip_slot_id ? item->details.equip_slot_id : item->details.slot_id); + if(item->details.appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + + if(item->IsBag() && (item->details.inv_slot_id != bag_id || item->details.slot_id != slot)) { + item_list.EraseItem(item); + } + equipList->RemoveItem(index); + item->details.inv_slot_id = bag_id; + item->details.slot_id = slot; + item->details.appearance_type = 0; + item->details.equip_slot_id = 0; + SerializeItemPackets(equipList, &packets, item, version); + } + } + else { + Item* bag = item_list.GetItemFromUniqueID(bag_id, true); + if (bag && bag->IsBag() && slot < bag->details.num_slots) { + SetEquipment(0, item->details.equip_slot_id ? item->details.equip_slot_id : item->details.slot_id); + if(item->details.appearance_type) + database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); + else + database.DeleteItem(GetCharacterID(), item, "EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "unequipped", item, this); + + if(item->IsBag() && ( item->details.inv_slot_id != bag_id || item->details.slot_id != slot)) { + item_list.EraseItem(item); + } + equipList->RemoveItem(index); + item->details.inv_slot_id = bag_id; + item->details.slot_id = slot; + item->details.appearance_type = 0; + item->details.equip_slot_id = 0; + SerializeItemPackets(equipList, &packets, item, version); + } + } + } + Item* bag = item_list.GetItemFromUniqueID(bag_id, true); + if (bag && bag->IsBag()) + packets.push_back(bag->serialize(version, false, this)); + } + + if(send_item_updates && GetClient()) + { + GetClient()->UpdateSentSpellList(); + GetClient()->ClearSentSpellList(); + } + + return packets; +} + +map* Player::GetItemList(){ + return item_list.GetAllItems(); +} + +vector* Player::GetEquippedItemList(){ + return equipment_list.GetAllEquippedItems(); +} + +vector* Player::GetAppearanceEquippedItemList(){ + return appearance_equipment_list.GetAllEquippedItems(); +} + +EQ2Packet* Player::SendBagUpdate(int32 bag_unique_id, int16 version){ + Item* bag = 0; + if(bag_unique_id > 0) + bag = item_list.GetItemFromUniqueID(bag_unique_id, true); + + if(bag && bag->IsBag()) + return bag->serialize(version, false, this); + return 0; +} + +void Player::SetEquippedItemAppearances(){ + vector* items = GetEquipmentList()->GetAllEquippedItems(); + vector* appearance_items = GetAppearanceEquipmentList()->GetAllEquippedItems(); + if(items){ + for(int32 i=0;isize();i++) + SetEquipment(items->at(i)); + + // just have appearance items brute force replace the slots after the fact + for(int32 i=0;isize();i++) + SetEquipment(appearance_items->at(i)); + } + safe_delete(items); + safe_delete(appearance_items); + info_changed = true; + GetZone()->SendSpawnChanges(this); +} + +EQ2Packet* Player::SwapEquippedItems(int8 slot1, int8 slot2, int16 version, int16 equip_type){ + EquipmentItemList* equipList = &equipment_list; + + // right now client seems to pass 3 for this? Not sure why when other fields has appearance equipment as type 1 + if(equip_type == 3) + equipList = &appearance_equipment_list; + + equipList->MEquipmentItems.readlock(__FUNCTION__, __LINE__); + Item* item_from = equipList->items[slot1]; + Item* item_to = equipList->items[slot2]; + equipList->MEquipmentItems.releasereadlock(__FUNCTION__, __LINE__); + + if(item_from && equipList->CanItemBeEquippedInSlot(item_from, slot2)){ + if(item_to){ + if(!equipList->CanItemBeEquippedInSlot(item_to, slot1)) + return 0; + } + equipList->MEquipmentItems.writelock(__FUNCTION__, __LINE__); + equipList->items[slot1] = nullptr; + equipList->MEquipmentItems.releasewritelock(__FUNCTION__, __LINE__); + equipList->SetItem(slot2, item_from); + if(item_to) + { + equipList->SetItem(slot1, item_to); + item_to->save_needed = true; + } + item_from->save_needed = true; + + if (GetClient()) + { + //EquipmentItemList* equipList = &equipment_list; + + //if(appearance_type) + // equipList = &appearance_equipment_list; + + if(item_to) + GetClient()->QueuePacket(item_to->serialize(version, false, this)); + GetClient()->QueuePacket(item_from->serialize(version, false, this)); + GetClient()->QueuePacket(item_list.serialize(this, version)); + } + return equipList->serialize(version, this); + } + return 0; +} +bool Player::CanEquipItem(Item* item, int8 slot) { + if(client && client->GetVersion() <= 561 && slot == EQ2_EARS_SLOT_2) + return false; + + if (item) { + Client* client = GetClient(); + if (client) { + if (item->IsWeapon() && slot == 1) { + bool dwable = item->IsDualWieldAble(client, item, slot); + + if (dwable == 0) { + return false; + } + } + + if (item->CheckFlag(EVIL_ONLY) && GetAlignment() != ALIGNMENT_EVIL) { + client->Message(0, "%s requires an evil race.", item->name.c_str()); + } + else if (item->CheckFlag(GOOD_ONLY) && GetAlignment() != ALIGNMENT_GOOD) { + client->Message(0, "%s requires a good race.", item->name.c_str()); + } + else if (item->IsArmor() || item->IsWeapon() || item->IsFood() || item->IsRanged() || item->IsShield() || item->IsBauble() || item->IsAmmo() || item->IsThrown()) { + if (((item->generic_info.skill_req1 == 0 || item->generic_info.skill_req1 == 0xFFFFFFFF || skill_list.HasSkill(item->generic_info.skill_req1)) && (item->generic_info.skill_req2 == 0 || item->generic_info.skill_req2 == 0xFFFFFFFF || skill_list.HasSkill(item->generic_info.skill_req2)))) { + int16 override_level = item->GetOverrideLevel(GetAdventureClass(), GetTradeskillClass()); + if (override_level > 0 && override_level <= GetLevel()) + return true; + if (item->CheckClass(GetAdventureClass(), GetTradeskillClass())) + if (item->CheckLevel(GetAdventureClass(), GetTradeskillClass(), GetLevel())) + return true; + else + client->Message(CHANNEL_COLOR_RED, "You must be at least level %u to equip %s.", item->generic_info.adventure_default_level, item->CreateItemLink(client->GetVersion()).c_str()); + else + client->Message(CHANNEL_COLOR_RED, "Your class may not equip %s.", item->CreateItemLink(client->GetVersion()).c_str()); + } + else { + Skill* firstSkill = master_skill_list.GetSkill(item->generic_info.skill_req1); + Skill* secondSkill = master_skill_list.GetSkill(item->generic_info.skill_req2); + std::string msg(""); + if(GetClient()->GetAdminStatus() >= 200) { + if(firstSkill && !skill_list.HasSkill(item->generic_info.skill_req1)) { + msg += "(" + std::string(firstSkill->name.data.c_str()); + } + + if(secondSkill && !skill_list.HasSkill(item->generic_info.skill_req2)) { + if(msg.length() > 0) { + msg += ", "; + } + else { + msg = "("; + } + msg += std::string(secondSkill->name.data.c_str()); + } + + if(msg.length() > 0) { + msg += ") "; + } + } + client->Message(0, "You lack the skill %srequired to equip this item.",msg.c_str()); + } + } + else + client->Message(0, "Item %s isn't equipable.", item->name.c_str()); + } + } + return false; +} + +vector Player::EquipItem(int16 index, int16 version, int8 appearance_type, int8 slot_id) { + + EquipmentItemList* equipList = &equipment_list; + if(appearance_type) + equipList = &appearance_equipment_list; + + vector packets; + item_list.MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (item_list.indexed_items.count(index) == 0) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + Item* item = item_list.indexed_items[index]; + int8 orig_slot_id = slot_id; + int8 slot = 255; + if (item) { + if(orig_slot_id == 255 && item->CheckFlag2(APPEARANCE_ONLY)) { + appearance_type = 1; + equipList = &appearance_equipment_list; + } + if (slot_id != 255 && !item->HasSlot(slot_id)) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + slot = equipList->GetFreeSlot(item, slot_id, version); + + bool canEquip = CanEquipItem(item,slot); + int32 conflictSlot = 0; + + if(canEquip && !appearance_type && item->CheckFlag2(APPEARANCE_ONLY)) + { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + if(GetClient()) { + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "This item is for appearance slots only."); + } + return packets; + } + else if(canEquip && (conflictSlot = equipList->CheckSlotConflict(item)) > 0) { + bool abort = true; + switch(conflictSlot) { + case LORE: + if(GetClient()) + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "Lore conflict, cannot equip this item."); + break; + case LORE_EQUIP: + if(GetClient()) + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "You already have this item equipped, you cannot equip another."); + break; + case STACK_LORE: + if(GetClient()) + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "Cannot equip as it exceeds lore stack."); + break; + default: + abort = false; + break; + } + if(abort) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + } + else if (canEquip && item->CheckFlag(ATTUNEABLE)) { + PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", version); + char text[255]; + sprintf(text, "%s must be attuned before it can be equipped. Would you like to attune it now?", item->name.c_str()); + char accept_command[25]; + sprintf(accept_command, "attune_inv %i 1 0 -1", index); + packet->setDataByName("text", text); + packet->setDataByName("accept_text", "Attune"); + packet->setDataByName("accept_command", accept_command); + packet->setDataByName("cancel_text", "Cancel"); + // No clue if we even need the following 2 unknowns, just added them so the packet matches what live sends + packet->setDataByName("max_length", 50); + packet->setDataByName("unknown4", 1); + packets.push_back(packet->serialize()); + safe_delete(packet); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + if (canEquip && slot == 255) + { + if (slot_id == 255) { + if(item->slot_data.size() > 0) { + slot = item->slot_data.at(0); + if(!IsAllowedCombatEquip(slot, true)) { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempt to equip item %s (%u) with FAILED in combat!", item->name.c_str(), item->details.item_id); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + } + else { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempt to equip item %s (%u) with auto equip FAILED, no slot_data exists! Check items table, 'slots' column value should not be 0.", item->name.c_str(), item->details.item_id); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + } + else + slot = slot_id; + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + packets = UnequipItem(slot, item->details.inv_slot_id, item->details.slot_id, version, appearance_type, false); + // grab player items lock again and assure item still present + item_list.MPlayerItems.readlock(__FUNCTION__, __LINE__); + if (item_list.indexed_items.count(index) == 0) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + // If item is a 2handed weapon and something is in the secondary, unequip the secondary + if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipList->GetItem(EQ2_SECONDARY_SLOT) != 0) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + vector tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type, false); + //packets.reserve(packets.size() + tmp_packets.size()); + packets.insert(packets.end(), tmp_packets.begin(), tmp_packets.end()); + } + else { + // release for delete item / scripting etc + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + } + else if (canEquip && slot < 255) { + + if(!IsAllowedCombatEquip(slot, true)) { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempt to equip item %s (%u) with auto equip FAILED in combat!", item->name.c_str(), item->details.item_id); + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + return packets; + } + // If item is a 2handed weapon and something is in the secondary, unequip the secondary + if (item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && equipList->GetItem(EQ2_SECONDARY_SLOT) != 0) { + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + vector tmp_packets = UnequipItem(EQ2_SECONDARY_SLOT, -999, 0, version, appearance_type, false); + //packets.reserve(packets.size() + tmp_packets.size()); + packets.insert(packets.end(), tmp_packets.begin(), tmp_packets.end()); + } + else { + // release for delete item / scripting etc + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + + database.DeleteItem(GetCharacterID(), item, "NOT-EQUIPPED"); + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "equipped", item, this); + + if(!item->IsBag()) { + item_list.RemoveItem(item); + } + equipList->SetItem(slot, item); + item->save_needed = true; + packets.push_back(item->serialize(version, false)); + SetEquipment(item); + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "item_equipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id); + int32 bag_id = item->details.inv_slot_id; + if (item->generic_info.condition == 0) { + Client* client = GetClient(); + if (client) { + string popup_text = "Your "; + string popup_item = item->CreateItemLink(client->GetVersion(), true).c_str(); + string popup_textcont = " is worn out and will not be effective until repaired."; + popup_text.append(popup_item); + popup_text.append(popup_textcont); + //devn00b: decided to use "crimson" for the color. (220,20,60 rgb) + client->SendPopupMessage(10, popup_text.c_str(), "", 5, 0xDC, 0x14, 0x3C); + client->Message(CHANNEL_COLOR_RED, "Your %s is worn out and will not be effective until repaired.", item->CreateItemLink(client->GetVersion(), true).c_str()); + } + } + SetEquippedItemAppearances(); + packets.push_back(equipList->serialize(version, this)); + EQ2Packet* outapp = item_list.serialize(this, version); + if (outapp) { + packets.push_back(outapp); + EQ2Packet* bag_packet = SendBagUpdate(bag_id, version); + if (bag_packet) + packets.push_back(bag_packet); + } + SetCharSheetChanged(true); + } + else { + // clear items lock + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + } + else { + // clear items lock + item_list.MPlayerItems.releasereadlock(__FUNCTION__, __LINE__); + } + + if(slot < 255) { + if (slot == EQ2_FOOD_SLOT && item->IsFoodFood() && get_character_flag(CF_FOOD_AUTO_CONSUME)) { + Item* item = GetEquipmentList()->GetItem(EQ2_FOOD_SLOT); + if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false)) + GetClient()->ConsumeFoodDrink(item, EQ2_FOOD_SLOT); + + if(item) + SetActiveFoodUniqueID(item->details.unique_id); + } + else if (slot == EQ2_DRINK_SLOT && item->IsFoodDrink() && get_character_flag(CF_DRINK_AUTO_CONSUME)) { + Item* item = GetEquipmentList()->GetItem(EQ2_DRINK_SLOT); + if(item && GetClient() && GetClient()->CheckConsumptionAllowed(slot, false)) + GetClient()->ConsumeFoodDrink(item, EQ2_DRINK_SLOT); + + if(item) + SetActiveDrinkUniqueID(item->details.unique_id); + } + } + + client->UpdateSentSpellList(); + client->ClearSentSpellList(); + + return packets; +} +bool Player::AddItem(Item* item, AddItemType type) { + int32 conflictItemList = 0, conflictequipmentList = 0, conflictAppearanceEquipmentList = 0; + int16 lore_stack_count = 0; + if (item && item->details.item_id > 0) { + if( ((conflictItemList = item_list.CheckSlotConflict(item, true, true, &lore_stack_count)) == LORE || + (conflictequipmentList = equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE || + (conflictAppearanceEquipmentList = appearance_equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE) && !item->CheckFlag(STACK_LORE)) { + + switch(type) + { + case AddItemType::BUY_FROM_BROKER: + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You already own this item and cannot have another."); + break; + default: + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You cannot obtain %s due to lore conflict.", item->name.c_str()); + break; + } + safe_delete(item); + return false; + } + else if(conflictItemList == STACK_LORE || conflictequipmentList == STACK_LORE || + conflictAppearanceEquipmentList == STACK_LORE) { + switch(type) + { + default: + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You already have one stack of the LORE item: %s.", item->name.c_str()); + break; + } + safe_delete(item); + return false; + } + else if (item_list.AssignItemToFreeSlot(item)) { + item->save_needed = true; + CalculateApplyWeight(); + return true; + } + else if (item_list.AddOverflowItem(item)) { + CalculateApplyWeight(); + return true; + } + } + return false; +} +bool Player::AddItemToBank(Item* item) { + + if (item && item->details.item_id > 0) { + + sint32 bag = -3; + sint16 slot = -1; + if (item_list.GetFirstFreeBankSlot(&bag, &slot)) { + item->details.inv_slot_id = bag; + item->details.slot_id = slot; + item->save_needed = true; + + return item_list.AddItem(item); + } + else if (item_list.AddOverflowItem(item)) + return true; + } + return false; +} +EQ2Packet* Player::SendInventoryUpdate(int16 version) { + // assure any inventory updates are reflected in sell window + if(GetClient() && GetClient()->GetMerchantTransaction()) + GetClient()->SendSellMerchantList(); + + return item_list.serialize(this, version); +} + +void Player::UpdateInventory(int32 bag_id) { + + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + client->QueuePacket(outapp); + + outapp = client->GetPlayer()->SendBagUpdate(bag_id, client->GetVersion()); + + if (outapp) + client->QueuePacket(outapp); + +} +EQ2Packet* Player::MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version) { + Item* item = item_list.GetItemFromIndex(from_index); + int8 result = item_list.MoveItem(to_bag_id, from_index, new_slot, appearance_type, charges); + if (result == 1) { + if (item) { + if (!item->needs_deletion) + item->save_needed = true; + else if (item->needs_deletion) { + database.DeleteItem(GetCharacterID(), item, 0); + client->GetPlayer()->item_list.DestroyItem(from_index); + client->GetPlayer()->UpdateInventory(to_bag_id); + if(item_deleted) + *item_deleted = true; + } + } + return item_list.serialize(this, version); + } + else { + PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); + if (packet) { + packet->setDataByName("color", CHANNEL_COLOR_YELLOW); + packet->setMediumStringByName("text", "Could not move item to that location."); + packet->setDataByName("unknown02", 0x00ff); + EQ2Packet* outapp = packet->serialize(); + safe_delete(packet); + return outapp; + } + } + return 0; +} + +int32 Player::GetCoinsCopper(){ + return GetInfoStruct()->get_coin_copper(); +} + +int32 Player::GetCoinsSilver(){ + return GetInfoStruct()->get_coin_silver(); +} + +int32 Player::GetCoinsGold(){ + return GetInfoStruct()->get_coin_gold(); +} + +int32 Player::GetCoinsPlat(){ + return GetInfoStruct()->get_coin_plat(); +} + +int32 Player::GetBankCoinsCopper(){ + return GetInfoStruct()->get_bank_coin_copper(); +} + +int32 Player::GetBankCoinsSilver(){ + return GetInfoStruct()->get_bank_coin_silver(); +} + +int32 Player::GetBankCoinsGold(){ + return GetInfoStruct()->get_bank_coin_gold(); +} + +int32 Player::GetBankCoinsPlat(){ + return GetInfoStruct()->get_bank_coin_plat(); +} + +int32 Player::GetStatusPoints(){ + return GetInfoStruct()->get_status_points(); +} + +vector* Player::GetQuickbar(){ + return &quickbar_items; +} + +bool Player::UpdateQuickbarNeeded(){ + return quickbar_updated; +} + +void Player::ResetQuickbarNeeded(){ + quickbar_updated = false; +} + +void Player::AddQuickbarItem(int32 bar, int32 slot, int32 type, int16 icon, int16 icon_type, int32 id, int8 tier, int32 unique_id, const char* text, bool update){ + RemoveQuickbarItem(bar, slot, false); + QuickBarItem* ability = new QuickBarItem; + ability->deleted = false; + ability->hotbar = bar; + ability->slot = slot; + ability->type = type; + ability->icon = icon; + ability->tier = tier; + ability->icon_type = icon_type; + ability->id = id; + if(unique_id == 0) + unique_id = database.NextUniqueHotbarID(); + ability->unique_id = unique_id; + if(type == QUICKBAR_TEXT_CMD && text){ + ability->text.data = string(text); + ability->text.size = ability->text.data.length(); + } + else + ability->text.size = 0; + quickbar_items.push_back(ability); + if(update) + quickbar_updated = true; +} + +void Player::RemoveQuickbarItem(int32 bar, int32 slot, bool update){ + vector::iterator itr; + QuickBarItem* qbi = 0; + for(itr=quickbar_items.begin();itr!=quickbar_items.end();itr++){ + qbi = *itr; + if(qbi && qbi->deleted == false && qbi->hotbar == bar && qbi->slot == slot){ + qbi->deleted = true; + break; + } + } + if(update) + quickbar_updated = true; +} + +void Player::ClearQuickbarItems(){ + quickbar_items.clear(); +} + +EQ2Packet* Player::GetQuickbarPacket(int16 version){ + PacketStruct* packet = configReader.getStruct("WS_QuickBarInit", version); + if(packet){ + vector::iterator itr; + packet->setArrayLengthByName("num_abilities", quickbar_items.size()); + int16 i=0; + for(itr=quickbar_items.begin();itr != quickbar_items.end(); itr++){ + QuickBarItem* ability = *itr; + if(!ability || ability->deleted) + continue; + packet->setArrayDataByName("hotbar", ability->hotbar, i); + packet->setArrayDataByName("slot", ability->slot, i); + packet->setArrayDataByName("type", ability->type, i); + packet->setArrayDataByName("icon", ability->icon, i); + packet->setArrayDataByName("icon_type", ability->icon_type, i); + packet->setArrayDataByName("id", ability->id, i); + packet->setArrayDataByName("unique_id", ability->tier, i); + packet->setArrayDataByName("text", &ability->text, i); + i++; + } + EQ2Packet* app = packet->serialize(); + safe_delete(packet); + return app; + } + return 0; +} + +void Player::AddSpellBookEntry(int32 spell_id, int8 tier, sint32 slot, int32 type, int32 timer, bool save_needed){ + SpellBookEntry* spell = new SpellBookEntry; + spell->status = 169; + spell->slot = slot; + spell->spell_id = spell_id; + spell->type = type; + spell->tier = tier; + spell->timer = timer; + spell->save_needed = save_needed; + spell->recast = 0; + spell->recast_available = 0; + spell->player = this; + spell->visible = true; + spell->in_use = false; + spell->in_remiss = false; + MSpellsBook.lock(); + spells.push_back(spell); + MSpellsBook.unlock(); + + if (type == SPELL_BOOK_TYPE_NOT_SHOWN) + AddPassiveSpell(spell_id, tier); +} + +void Player::DeleteSpellBook(int8 type_selection){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end();){ + spell = *itr; + if((type_selection & DELETE_TRADESKILLS) == 0 && spell->type == SPELL_BOOK_TYPE_TRADESKILL) { + itr++; + continue; + } + else if((type_selection & DELETE_SPELLS) == 0 && spell->type == SPELL_BOOK_TYPE_SPELL) { + itr++; + continue; + } + else if((type_selection & DELETE_COMBAT_ART) == 0 && spell->type == SPELL_BOOK_TYPE_COMBAT_ART) { + itr++; + continue; + } + else if((type_selection & DELETE_ABILITY) == 0 && spell->type == SPELL_BOOK_TYPE_ABILITY) { + itr++; + continue; + } + else if((type_selection & DELETE_NOT_SHOWN) == 0 && spell->type == SPELL_BOOK_TYPE_NOT_SHOWN) { + itr++; + continue; + } + database.DeleteCharacterSpell(GetCharacterID(), spell->spell_id); + if (spell->type == SPELL_BOOK_TYPE_NOT_SHOWN) + RemovePassive(spell->spell_id, spell->tier, true); + itr = spells.erase(itr); + } + MSpellsBook.unlock(); +} + +void Player::RemoveSpellBookEntry(int32 spell_id, bool remove_passives_from_list){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->spell_id == spell_id){ + if (spell->type == SPELL_BOOK_TYPE_NOT_SHOWN) + RemovePassive(spell->spell_id, spell->tier, remove_passives_from_list); + spells.erase(itr); + break; + } + } + MSpellsBook.unlock(); +} + +void Player::ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 maxlvl_only, int32 book_type) +{ + //sort_by : 0 - alpha, 1 - level, 2 - category + //order : 0 - ascending, 1 - descending + //pattern : 0 - zigzag, 1 - down, 2 - across + MSpellsBook.lock(); + + std::vector sort_spells(spells); + + if (!maxlvl_only) + { + switch (sort_by) + { + case 0: + if (!order) + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByName); + else + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByNameReverse); + break; + case 1: + if (!order) + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByLevel); + else + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByLevelReverse); + break; + case 2: + if (!order) + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByCategory); + else + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByCategoryReverse); + break; + } + } + + vector::iterator itr; + SpellBookEntry* spell = 0; + map tmpSpells; + vector resultSpells; + + int32 i = 0; + int8 page_book_count = 0; + int32 last_start_point = 0; + + for (itr = sort_spells.begin(); itr != sort_spells.end(); itr++) { + spell = *itr; + + if (spell->type != book_type) + continue; + + if (maxlvl_only) + { + Spell* actual_spell = 0; + actual_spell = master_spell_list.GetSpell(spell->spell_id, spell->tier); + if(!actual_spell) { + // we have a spell that doesn't exist here! + continue; + } + std::regex re("^(.*?)(\\s(I{1,}[VX]{0,}|V{1,}[IVX]{0,})|X{1,}[IVX]{0,})$"); + std::string output = std::regex_replace(string(actual_spell->GetName()), re, "$1", std::regex_constants::format_no_copy); + + if ( output.size() < 1 ) + output = string(actual_spell->GetName()); + + map::iterator tmpItr = tmpSpells.find(output); + if (tmpItr != tmpSpells.end()) + { + Spell* tmpSpell = master_spell_list.GetSpell(tmpItr->second->spell_id, tmpItr->second->tier); + if (actual_spell->GetLevelRequired(this) > tmpSpell->GetLevelRequired(this)) + { + tmpItr->second->visible = false; + tmpItr->second->slot = 0xFFFF; + + std::vector::iterator it; + it = find(resultSpells.begin(), resultSpells.end(), (SpellBookEntry*)tmpItr->second); + if (it != resultSpells.end()) + resultSpells.erase(it); + + tmpSpells.erase(tmpItr); + } + else + continue; // leave as-is we have the newer spell + } + + spell->visible = true; + tmpSpells.insert(make_pair(output, spell)); + resultSpells.push_back(spell); + } + spell->slot = i; + + GetSpellBookSlotSort(pattern, &i, &page_book_count, &last_start_point); + } // end for loop for setting slots + + if (maxlvl_only) + { + switch (sort_by) + { + case 0: + if (!order) + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByName); + else + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByNameReverse); + break; + case 1: + if (!order) + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByLevel); + else + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByLevelReverse); + break; + case 2: + if (!order) + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByCategory); + else + stable_sort(resultSpells.begin(), resultSpells.end(), SortSpellEntryByCategoryReverse); + break; + } + + i = 0; + page_book_count = 0; + last_start_point = 0; + vector::iterator tmpItr; + for (tmpItr = resultSpells.begin(); tmpItr != resultSpells.end(); tmpItr++) { + ((SpellBookEntry*)*tmpItr)->slot = i; + GetSpellBookSlotSort(pattern, &i, &page_book_count, &last_start_point); + } + } + + MSpellsBook.unlock(); +} + +bool Player::SortSpellEntryByName(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + return (string(spell1->GetName()) < string(spell2->GetName())); +} + +bool Player::SortSpellEntryByCategory(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + return (spell1->GetSpellIconBackdrop() < spell2->GetSpellIconBackdrop()); +} + +bool Player::SortSpellEntryByLevel(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + int16 lvl1 = spell1->GetLevelRequired(s1->player); + int16 lvl2 = spell2->GetLevelRequired(s2->player); + if (lvl1 == 0xFFFF) + lvl1 = 0; + if (lvl2 == 0xFFFF) + lvl2 = 0; + + return (lvl1 < lvl2); +} + +bool Player::SortSpellEntryByNameReverse(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + return (string(spell2->GetName()) < string(spell1->GetName())); +} + +bool Player::SortSpellEntryByCategoryReverse(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + if (!spell1 || !spell2) + return false; + return (spell2->GetSpellIconBackdrop() < spell1->GetSpellIconBackdrop()); +} + +bool Player::SortSpellEntryByLevelReverse(SpellBookEntry* s1, SpellBookEntry* s2) +{ + Spell* spell1 = master_spell_list.GetSpell(s1->spell_id, s1->tier); + Spell* spell2 = master_spell_list.GetSpell(s2->spell_id, s2->tier); + + if (!spell1 || !spell2) + return false; + + int16 lvl1 = spell1->GetLevelRequired(s1->player); + int16 lvl2 = spell2->GetLevelRequired(s2->player); + if (lvl1 == 0xFFFF) + lvl1 = 0; + if (lvl2 == 0xFFFF) + lvl2 = 0; + + return (lvl2 < lvl1); +} + +int8 Player::GetSpellSlot(int32 spell_id){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->spell_id == spell_id) + { + int8 slot = spell->slot; + MSpellsBook.unlock(); + return slot; + } + } + MSpellsBook.unlock(); + return 0; +} + +void Player::AddSkill(int32 skill_id, int16 current_val, int16 max_val, bool save_needed){ + Skill* master_skill = master_skill_list.GetSkill(skill_id); + if (master_skill) { + Skill* skill = new Skill(master_skill); + skill->current_val = current_val; + skill->previous_val = current_val; + skill->max_val = max_val; + if (save_needed) + skill->save_needed = true; + skill_list.AddSkill(skill); + } +} + +void Player::RemovePlayerSkill(int32 skill_id, bool save) { + Skill* skill = skill_list.GetSkill(skill_id); + if (skill) + RemoveSkillFromDB(skill, save); +} + +void Player::RemoveSkillFromDB(Skill* skill, bool save) { + skill_list.RemoveSkill(skill); + if (save) + database.DeleteCharacterSkill(GetCharacterID(), skill); +} + +int16 Player::GetSpellSlotMappingCount(){ + int16 ret = 0; + MSpellsBook.lock(); + for(int32 i=0;islot >= 0 && spell->spell_id > 0 && spell->type != SPELL_BOOK_TYPE_NOT_SHOWN) + ret++; + } + MSpellsBook.unlock(); + return ret; +} + +int8 Player::GetSpellTier(int32 id){ + int8 ret = 0; + MSpellsBook.lock(); + for(int32 i=0;ispell_id == id){ + ret = spell->tier; + break; + } + } + MSpellsBook.unlock(); + return ret; +} + +int16 Player::GetSpellPacketCount(){ + int16 ret = 0; + MSpellsBook.lock(); + for(int32 i=0;ispell_id > 0 && spell->type != SPELL_BOOK_TYPE_NOT_SHOWN) + ret++; + } + MSpellsBook.unlock(); + return ret; +} + +void Player::LockAllSpells() { + vector::iterator itr; + + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + if ((*itr)->type != SPELL_BOOK_TYPE_TRADESKILL) + RemoveSpellStatus((*itr), SPELL_STATUS_LOCK, false); + } + + all_spells_locked = true; + + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::UnlockAllSpells(bool modify_recast, Spell* exception) { + vector::iterator itr; + int32 exception_spell_id = 0; + if (exception) + exception_spell_id = exception->GetSpellID(); + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + MaintainedEffects* effect = 0; + if((effect = GetMaintainedSpell((*itr)->spell_id)) && effect->spell->spell->GetSpellData()->duration_until_cancel) + continue; + + if ((*itr)->in_use == false && + (((*itr)->spell_id != exception_spell_id || + (*itr)->timer > 0 && (*itr)->timer != exception->GetSpellData()->linked_timer) + && (*itr)->type != SPELL_BOOK_TYPE_TRADESKILL)) { + AddSpellStatus((*itr), SPELL_STATUS_LOCK, modify_recast); + (*itr)->recast_available = 0; + } + else if((*itr)->in_remiss) + { + AddSpellStatus((*itr), SPELL_STATUS_LOCK); + (*itr)->recast_available = 0; + (*itr)->in_remiss = false; + } + } + + all_spells_locked = false; + + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::LockSpell(Spell* spell, int16 recast) { + vector::iterator itr; + SpellBookEntry* spell2; + + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + spell2 = *itr; + if (spell2->spell_id == spell->GetSpellID() || (spell->GetSpellData()->linked_timer > 0 && spell->GetSpellData()->linked_timer == spell2->timer)) + { + spell2->in_use = true; + RemoveSpellStatus(spell2, SPELL_STATUS_LOCK, true, recast); + } + else if(spell2->in_use) + RemoveSpellStatus(spell2, SPELL_STATUS_LOCK, false, 0); + } + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::UnlockSpell(Spell* spell) { + if (spell->GetStayLocked()) + return; + vector::iterator itr; + SpellBookEntry* spell2; + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + spell2 = *itr; + if (spell2->spell_id == spell->GetSpellID() || (spell->GetSpellData()->linked_timer > 0 && spell->GetSpellData()->linked_timer == spell2->timer)) + { + spell2->in_use = false; + spell2->recast_available = 0; + if(all_spells_locked) + spell2->in_remiss = true; + else + AddSpellStatus(spell2, SPELL_STATUS_LOCK, false); + } + } + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::LockTSSpells() { + vector::iterator itr; + + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + if ((*itr)->type == SPELL_BOOK_TYPE_TRADESKILL) + RemoveSpellStatus(*itr, SPELL_STATUS_LOCK); + } + + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); + // Unlock all other types + UnlockAllSpells(); +} + +void Player::UnlockTSSpells() { + vector::iterator itr; + + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + if ((*itr)->type == SPELL_BOOK_TYPE_TRADESKILL) + AddSpellStatus(*itr, SPELL_STATUS_LOCK); + } + + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); + // Lock all other types + LockAllSpells(); +} + +void Player::QueueSpell(Spell* spell) { + vector::iterator itr; + SpellBookEntry* spell2; + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + spell2 = *itr; + if (spell2->spell_id == spell->GetSpellID()) + AddSpellStatus(spell2, SPELL_STATUS_QUEUE, false); + } + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::UnQueueSpell(Spell* spell) { + vector::iterator itr; + SpellBookEntry* spell2; + MSpellsBook.writelock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + spell2 = *itr; + if (spell2->spell_id == spell->GetSpellID()) + RemoveSpellStatus(spell2, SPELL_STATUS_QUEUE, false); + } + MSpellsBook.releasewritelock(__FUNCTION__, __LINE__); +} + +vector Player::GetSpellBookSpellsByTimer(Spell* spell, int32 timerID) { + vector ret; + vector::iterator itr; + MSpellsBook.readlock(__FUNCTION__, __LINE__); + for (itr = spells.begin(); itr != spells.end(); itr++) { + if ((*itr)->timer == timerID && spell->GetSpellID() != (*itr)->spell_id) + ret.push_back(master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier)); + } + MSpellsBook.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void Player::ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) { + SetSpellEntryRecast(spell, modify_recast, recast); + if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) { + spell->status += value; // use set/remove spell status now + } +} + +void Player::AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) { + SetSpellEntryRecast(spell, modify_recast, recast); + if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) { + spell->status = spell->status | value; + } +} + +void Player::RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) { + SetSpellEntryRecast(spell, modify_recast, recast); + if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) { + spell->status = spell->status & ~value; + } +} + +void Player::SetSpellStatus(Spell* spell, int8 status){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell2 = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell2 = *itr; + if(spell2->spell_id == spell->GetSpellData()->id){ + spell2->status = spell2->status | status; + break; + } + } + MSpellsBook.unlock(); +} + +void Player::SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast) { + if (modify_recast) { + spell->recast = recast / 100; + Spell* spell_ = master_spell_list.GetSpell(spell->spell_id, spell->tier); + if(spell_) { + float override_recast = 0.0f; + if(recast > 0) { + override_recast = static_cast(recast); + } + int32 recast_time = spell_->CalculateRecastTimer(this, override_recast); + + spell->recast = recast_time / 100; + spell->recast_available = Timer::GetCurrentTime2() + recast_time; + } + else { + spell->recast_available = Timer::GetCurrentTime2() + recast; + } + } +} + +vector* Player::GetSpellsSaveNeeded(){ + vector* ret = 0; + vector::iterator itr; + MSpellsBook.lock(); + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->save_needed){ + if(!ret) + ret = new vector; + ret->push_back(spell); + } + } + MSpellsBook.unlock(); + return ret; +} + +int16 Player::GetTierUp(int16 tier) +{ + switch(tier) + { + case 0: + break; + case 7: + case 9: + tier -= 2; + break; + default: + tier -= 1; + break; + } + + return tier; +} +bool Player::HasSpell(int32 spell_id, int8 tier, bool include_higher_tiers, bool include_possible_scribe){ + bool ret = false; + vector::iterator itr; + MSpellsBook.lock(); + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->spell_id == spell_id && (tier == 255 || spell->tier == tier || (include_higher_tiers && spell->tier > tier) || (include_possible_scribe && tier <= spell->tier))){ + ret = true; + break; + } + } + MSpellsBook.unlock(); + return ret; +} + +sint32 Player::GetFreeSpellBookSlot(int32 type){ + sint32 ret = 0; + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->type == type && spell->slot > ret) //get last slot (add 1 to it on return) + ret = spell->slot; + } + MSpellsBook.unlock(); + return ret+1; +} + +SpellBookEntry* Player::GetSpellBookSpell(int32 spell_id){ + MSpellsBook.lock(); + vector::iterator itr; + SpellBookEntry* ret = 0; + SpellBookEntry* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = *itr; + if(spell->spell_id == spell_id){ + ret = spell; + break; + } + } + MSpellsBook.unlock(); + return ret; +} + +vector Player::GetSpellBookSpellIDBySkill(int32 skill_id) { + vector ret; + + MSpellsBook.readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + Spell* spell = 0; + for(itr = spells.begin(); itr != spells.end(); itr++){ + spell = master_spell_list.GetSpell((*itr)->spell_id, (*itr)->tier); + if(spell && spell->GetSpellData()->mastery_skill == skill_id) + ret.push_back(spell->GetSpellData()->id); + } + MSpellsBook.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + + +EQ2Packet* Player::GetSpellSlotMappingPacket(int16 version){ + PacketStruct* packet = configReader.getStruct("WS_SpellSlotMapping", version); + if(packet){ + int16 count = GetSpellSlotMappingCount(); + int16 ptr = 0; + if(count > 0){ + packet->setArrayLengthByName("spell_count", count); + MSpellsBook.lock(); + for(int32 i=0;itype == SPELL_BOOK_TYPE_NOT_SHOWN || spell->slot < 0 || spell->spell_id == 0) + continue; + packet->setArrayDataByName("spell_id", spell->spell_id, ptr); + packet->setArrayDataByName("slot_id", (int16)spell->slot, ptr); + ptr++; + } + MSpellsBook.unlock(); + EQ2Packet* ret = packet->serialize(); + safe_delete(packet); + return ret; + } + safe_delete(packet); + } + return 0; +} + +EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) { + std::unique_lock lock(spell_packet_update_mutex); + PacketStruct* packet = configReader.getStruct("WS_UpdateSpellBook", version); + EQ2Packet* ret = 0; + if (packet) { + Spell* spell = 0; + SpellBookEntry* spell_entry = 0; + int16 count = GetSpellPacketCount(); + int16 ptr = 0; + // Get the packet size + PacketStruct* packet2 = configReader.getStruct("SubStruct_UpdateSpellBook", version); + int32 total_bytes = packet2->GetTotalPacketSize(); + safe_delete(packet2); + packet->setArrayLengthByName("spell_count", count); + + LogWrite(PLAYER__DEBUG, 5, "Player", "%s: GetSpellBookUpdatePacket Spell Count: %u, Spell Entry Book Size: %u", GetName(), count, total_bytes); + + if (count > 0) { + if (count > spell_count) { + uchar* tmp = 0; + if (spell_orig_packet) { + tmp = new uchar[count * total_bytes]; + memset(tmp, 0, total_bytes * count); + memcpy(tmp, spell_orig_packet, spell_count * total_bytes); + safe_delete_array(spell_orig_packet); + safe_delete_array(spell_xor_packet); + spell_orig_packet = tmp; + } + else { + spell_orig_packet = new uchar[count * total_bytes]; + memset(spell_orig_packet, 0, total_bytes * count); + } + spell_xor_packet = new uchar[count * total_bytes]; + memset(spell_xor_packet, 0, count * total_bytes); + } + spell_count = count; + MSpellsBook.lock(); + for (int32 i = 0; i < spells.size(); i++) { + spell_entry = (SpellBookEntry*)spells[i]; + if (spell_entry->spell_id == 0 || spell_entry->type == SPELL_BOOK_TYPE_NOT_SHOWN) + continue; + spell = master_spell_list.GetSpell(spell_entry->spell_id, spell_entry->tier); + if (spell) { + if (spell_entry->recast_available == 0 || Timer::GetCurrentTime2() > spell_entry->recast_available) { + packet->setSubstructArrayDataByName("spells", "available", 1, 0, ptr); + } + LogWrite(PLAYER__DEBUG, 9, "Player", "%s: GetSpellBookUpdatePacket Send Spell %u in position %u\n",GetName(), spell_entry->spell_id, ptr); + packet->setSubstructArrayDataByName("spells", "spell_id", spell_entry->spell_id, 0, ptr); + packet->setSubstructArrayDataByName("spells", "type", spell_entry->type, 0, ptr); + packet->setSubstructArrayDataByName("spells", "recast_available", spell_entry->recast_available, 0, ptr); + packet->setSubstructArrayDataByName("spells", "recast_time", spell_entry->recast, 0, ptr); + packet->setSubstructArrayDataByName("spells", "status", spell_entry->status, 0, ptr); + packet->setSubstructArrayDataByName("spells", "icon", (spell->TranslateClientSpellIcon(version) * -1) - 1, 0, ptr); + packet->setSubstructArrayDataByName("spells", "icon_type", spell->GetSpellIconBackdrop(), 0, ptr); + packet->setSubstructArrayDataByName("spells", "icon2", spell->GetSpellIconHeroicOp(), 0, ptr); + packet->setSubstructArrayDataByName("spells", "unique_id", (spell_entry->tier + 1) * -1, 0, ptr); //this is actually GetSpellNameCrc(spell->GetName()), but hijacking it for spell tier + packet->setSubstructArrayDataByName("spells", "charges", 255, 0, ptr); + // Beastlord and Channeler spell support + if (spell->GetSpellData()->savage_bar == 1) + packet->setSubstructArrayDataByName("spells", "unknown6", 32, 0, ptr); // advantages + else if (spell->GetSpellData()->savage_bar == 2) + packet->setSubstructArrayDataByName("spells", "unknown6", 64, 0, ptr); // primal + else if (spell->GetSpellData()->savage_bar == 3) { + packet->setSubstructArrayDataByName("spells", "unknown6", 6, 1, ptr); // 6 = channeler + // Slot req for channelers + // bitmask for slots 1 = slot 1, 2 = slot 2, 4 = slot 3, 8 = slot 4, 16 = slot 5, 32 = slot 6, 64 = slot 7, 128 = slot 8 + packet->setSubstructArrayDataByName("spells", "savage_bar_slot", spell->GetSpellData()->savage_bar_slot, 0, ptr); + } + + ptr++; + } + } + MSpellsBook.unlock(); + } + ret = packet->serializeCountPacket(version, 0, spell_orig_packet, spell_xor_packet); + //packet->PrintPacket(); + //DumpPacket(ret); + safe_delete(packet); + } + return ret; +} + +PlayerInfo::~PlayerInfo(){ + RemoveOldPackets(); +} + +PlayerInfo::PlayerInfo(Player* in_player){ + orig_packet = 0; + changes = 0; + pet_orig_packet = 0; + pet_changes = 0; + player = in_player; + info_struct = player->GetInfoStruct(); + info_struct->set_name(std::string(player->GetName())); + info_struct->set_deity(std::string("None")); + + info_struct->set_class1(classes.GetBaseClass(player->GetAdventureClass())); + info_struct->set_class2(classes.GetSecondaryBaseClass(player->GetAdventureClass())); + info_struct->set_class3(player->GetAdventureClass()); + + info_struct->set_race(player->GetRace()); + info_struct->set_gender(player->GetGender()); + info_struct->set_level(player->GetLevel()); + info_struct->set_tradeskill_level(player->GetTSLevel()); + info_struct->set_tradeskill_class1(classes.GetTSBaseClass(player->GetTradeskillClass())); + info_struct->set_tradeskill_class2(classes.GetSecondaryTSBaseClass(player->GetTradeskillClass())); + info_struct->set_tradeskill_class3(player->GetTradeskillClass()); + + for(int i=0;i<45;i++){ + if(i<30){ + info_struct->maintained_effects[i].spell_id = 0xFFFFFFFF; + info_struct->maintained_effects[i].icon = 0xFFFF; + info_struct->maintained_effects[i].spell = nullptr; + } + info_struct->spell_effects[i].spell_id = 0xFFFFFFFF; + info_struct->spell_effects[i].spell = nullptr; + } + + house_zone_id = 0; + bind_zone_id = 0; + bind_x = 0; + bind_y = 0; + bind_z = 0; + bind_heading = 0; + boat_x_offset = 0; + boat_y_offset = 0; + boat_z_offset = 0; + boat_spawn = 0; +} + +MaintainedEffects* Player::GetFreeMaintainedSpellSlot(){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + for(int i=0;imaintained_effects[i].spell_id == 0xFFFFFFFF){ + ret = &info->maintained_effects[i]; + ret->spell_id = 0; + ret->slot_pos = i; + break; + } + } + GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +MaintainedEffects* Player::GetMaintainedSpell(int32 id){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + for(int i=0;imaintained_effects[i].spell_id == id){ + ret = &info->maintained_effects[i]; + break; + } + } + GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +MaintainedEffects* Player::GetMaintainedSpellBySlot(int8 slot){ + MaintainedEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + for(int i=0;imaintained_effects[i].slot_pos == slot){ + ret = &info->maintained_effects[i]; + break; + } + } + GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +MaintainedEffects* Player::GetMaintainedSpells() { + return GetInfoStruct()->maintained_effects; +} + +SpellEffects* Player::GetFreeSpellEffectSlot(){ + SpellEffects* ret = 0; + InfoStruct* info = GetInfoStruct(); + GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); + for(int i=0;i<45;i++){ + if(info->spell_effects[i].spell_id == 0xFFFFFFFF){ + ret = &info->spell_effects[i]; + ret->spell_id = 0; + break; + } + } + GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +SpellEffects* Player::GetSpellEffects() { + return GetInfoStruct()->spell_effects; +} + +// call inside info_mutex +void Player::ClearRemovalTimers(){ + map::iterator itr; + for(itr = spawn_state_list.begin(); itr != spawn_state_list.end();) { + SpawnQueueState* sr = itr->second; + itr = spawn_state_list.erase(itr); + safe_delete(sr); + } +} + +void Player::ClearEverything(){ + index_mutex.writelock(__FUNCTION__, __LINE__); + player_spawn_id_map.clear(); + player_spawn_reverse_id_map.clear(); + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + map*>::iterator itr; + m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__); + for (itr = player_spawn_quests_required.begin(); itr != player_spawn_quests_required.end(); itr++){ + safe_delete(itr->second); + } + player_spawn_quests_required.clear(); + m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__); + + m_playerSpawnHistoryRequired.writelock(__FUNCTION__, __LINE__); + for (itr = player_spawn_history_required.begin(); itr != player_spawn_history_required.end(); itr++){ + safe_delete(itr->second); + } + player_spawn_history_required.clear(); + m_playerSpawnHistoryRequired.releasewritelock(__FUNCTION__, __LINE__); + + spawn_mutex.writelock(__FUNCTION__, __LINE__); + ClearRemovalTimers(); + spawn_packet_sent.clear(); + spawn_mutex.releasewritelock(__FUNCTION__, __LINE__); + + info_mutex.writelock(__FUNCTION__, __LINE__); + spawn_info_packet_list.clear(); + info_mutex.releasewritelock(__FUNCTION__, __LINE__); + + vis_mutex.writelock(__FUNCTION__, __LINE__); + spawn_vis_packet_list.clear(); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + pos_mutex.writelock(__FUNCTION__, __LINE__); + spawn_pos_packet_list.clear(); + pos_mutex.releasewritelock(__FUNCTION__, __LINE__); +} +bool Player::IsResurrecting(){ + return resurrecting; +} +void Player::SetResurrecting(bool val){ + resurrecting = val; +} +void Player::AddMaintainedSpell(LuaSpell* luaspell){ + if(!luaspell) + return; + + Spell* spell = luaspell->spell; + MaintainedEffects* effect = GetFreeMaintainedSpellSlot(); + int32 target_type = 0; + Spawn* spawn = 0; + + if(effect && luaspell->caster && luaspell->caster->GetZone()){ + GetMaintainedMutex()->writelock(__FUNCTION__, __LINE__); + strcpy(effect->name, spell->GetSpellData()->name.data.c_str()); + effect->target = luaspell->initial_target; + + spawn = luaspell->caster->GetZone()->GetSpawnByID(luaspell->initial_target); + if (spawn){ + if (spawn == this) + target_type = 0; + else if (GetPet() == spawn || GetCharmedPet() == spawn) + target_type = 1; + else + target_type = 2; + } + effect->target_type = target_type; + + effect->spell = luaspell; + if(!luaspell->slot_pos) + luaspell->slot_pos = effect->slot_pos; + effect->spell_id = spell->GetSpellData()->id; + LogWrite(PLAYER__DEBUG, 5, "Player", "AddMaintainedSpell Spell ID: %u, req concentration: %u", spell->GetSpellData()->id, spell->GetSpellData()->req_concentration); + effect->icon = spell->GetSpellData()->icon; + effect->icon_backdrop = spell->GetSpellData()->icon_backdrop; + effect->conc_used = spell->GetSpellData()->req_concentration; + effect->total_time = spell->GetSpellDuration()/10; + effect->tier = spell->GetSpellData()->tier; + if (spell->GetSpellData()->duration_until_cancel) + effect->expire_timestamp = 0xFFFFFFFF; + else + effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100); + GetMaintainedMutex()->releasewritelock(__FUNCTION__, __LINE__); + charsheet_changed = true; + } +} +void Player::AddSpellEffect(LuaSpell* luaspell, int32 override_expire_time){ + if(!luaspell || !luaspell->caster) + return; + + Spell* spell = luaspell->spell; + SpellEffects* old_effect = GetSpellEffect(spell->GetSpellID(), luaspell->caster); + SpellEffects* effect = 0; + if (old_effect){ + GetZone()->RemoveTargetFromSpell(old_effect->spell, this); + RemoveSpellEffect(old_effect->spell); + } + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s AddSpellEffect %s (%u).", spell->GetName(), GetName(), GetID()); + + effect = GetFreeSpellEffectSlot(); + + if(effect){ + GetSpellEffectMutex()->writelock(__FUNCTION__, __LINE__); + effect->spell = luaspell; + effect->spell_id = spell->GetSpellData()->id; + effect->caster = luaspell->caster; + effect->total_time = spell->GetSpellDuration()/10; + if (spell->GetSpellData()->duration_until_cancel) + effect->expire_timestamp = 0xFFFFFFFF; + else if(override_expire_time) + effect->expire_timestamp = Timer::GetCurrentTime2() + override_expire_time; + else + effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100); + effect->icon = spell->GetSpellData()->icon; + effect->icon_backdrop = spell->GetSpellData()->icon_backdrop; + effect->tier = spell->GetSpellTier(); + GetSpellEffectMutex()->releasewritelock(__FUNCTION__, __LINE__); + charsheet_changed = true; + + if(luaspell->caster && luaspell->caster->IsPlayer() && luaspell->caster != this) + { + if(GetClient()) { + GetClient()->TriggerSpellSave(); + } + if(((Player*)luaspell->caster)->GetClient()) { + ((Player*)luaspell->caster)->GetClient()->TriggerSpellSave(); + } + } + } +} + +void Player::RemoveMaintainedSpell(LuaSpell* luaspell){ + if(!luaspell) + return; + + bool found = false; + Client* client = GetClient(); + LuaSpell* old_spell = 0; + LuaSpell* current_spell = 0; + GetMaintainedMutex()->writelock(__FUNCTION__, __LINE__); + for(int i=0;i<30;i++){ + // If we already found the spell then we are bumping all other up one so there are no gaps in the ui + // This check needs to be first so found can never be true on the first iteration (i = 0) + if (found) { + old_spell = GetInfoStruct()->maintained_effects[i - 1].spell; + current_spell = GetInfoStruct()->maintained_effects[i].spell; + + //Update the maintained window uses_remaining and damage_remaining values + if (current_spell && current_spell->num_triggers > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, i - 1, current_spell->num_triggers, 0); + else if (current_spell && current_spell->damage_remaining > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, i - 1, current_spell->damage_remaining, 1); + else if (old_spell && old_spell->had_triggers) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, i - 1, 0, 0); + else if (old_spell && old_spell->had_dmg_remaining) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, i - 1, 0, 1); + + + GetInfoStruct()->maintained_effects[i].slot_pos = i - 1; + GetInfoStruct()->maintained_effects[i - 1] = GetInfoStruct()->maintained_effects[i]; + if (current_spell) + current_spell->slot_pos = i - 1; + } + // Compare spells, if we found a match set the found flag + if(GetInfoStruct()->maintained_effects[i].spell == luaspell) + found = true; + } + // if we found the spell in the array then we need to flag the char sheet as changed and set the last element to empty + if (found) { + memset(&GetInfoStruct()->maintained_effects[29], 0, sizeof(MaintainedEffects)); + GetInfoStruct()->maintained_effects[29].spell_id = 0xFFFFFFFF; + GetInfoStruct()->maintained_effects[29].icon = 0xFFFF; + GetInfoStruct()->maintained_effects[29].spell = nullptr; + charsheet_changed = true; + } + GetMaintainedMutex()->releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::RemoveSpellEffect(LuaSpell* spell){ + bool found = false; + GetSpellEffectMutex()->writelock(__FUNCTION__, __LINE__); + for(int i=0;i<45;i++){ + if (found) { + GetInfoStruct()->spell_effects[i-1] = GetInfoStruct()->spell_effects[i]; + } + if(GetInfoStruct()->spell_effects[i].spell == spell) + found = true; + } + if (found) { + memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects)); + GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF; + changed = true; + info_changed = true; + AddChangedZoneSpawn(); + charsheet_changed = true; + } + GetSpellEffectMutex()->releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version) +{ + if(GetClient() && GetClient()->IsReloadingZone()) + return; + + LogWrite(PLAYER__DEBUG, 7, "Player", "Enter: %s", __FUNCTION__); // trace + + // XML structs may be to slow to use in this portion of the code as a single + // client sends a LOT of these packets when they are moving. I have commented + // out all the code for xml structs, to switch to it just uncomment + // the code and comment the 2 if/else if/else blocks, both have a comment + // above them to let you know wich ones they are. + + //PacketStruct* update = configReader.getStruct("WS_PlayerPosUpdate", version); + int16 total_bytes; // = update->GetTotalPacketSize(); + + // Comment out this if/else if/else block if you switch to xml structs + if (version >= 1144) + total_bytes = sizeof(Player_Update1144); + else if (version >= 1096) + total_bytes = sizeof(Player_Update1096); + else if (version <= 373) + total_bytes = sizeof(Player_Update283); + else + total_bytes = sizeof(Player_Update); + + if (!movement_packet) + movement_packet = new uchar[total_bytes]; + else if (!old_movement_packet) + old_movement_packet = new uchar[total_bytes]; + if (movement_packet && old_movement_packet) + memcpy(old_movement_packet, movement_packet, total_bytes); + bool reverse = version > 373; + Unpack(len, data, movement_packet, total_bytes, 0, reverse); + if (!movement_packet || !old_movement_packet) + return; + Decode(movement_packet, old_movement_packet, total_bytes); + + //update->LoadPacketData(movement_packet, total_bytes); + + int32 activity; // = update->getType_int32_ByName("activity"); + int32 grid_id; // = update->getType_int32_ByName("grid_location"); + float direction1; // = update->getType_float_ByName("direction1"); + float direction2; // = update->getType_float_ByName("direction2");; + float speed; // = update->getType_float_ByName("speed");; + float side_speed; + float vert_speed; + float x; // = update->getType_float_ByName("x");; + float y; // = update->getType_float_ByName("y");; + float z; // = update->getType_float_ByName("z");; + float x_speed; + float y_speed; + float z_speed; + float client_pitch; + + // comment out this if/else if/else block if you use xml structs + if (version >= 1144) { + Player_Update1144* update = (Player_Update1144*)movement_packet; + activity = update->activity; + grid_id = update->grid_location; + direction1 = update->direction1; + direction2 = update->direction2; + speed = update->speed; + side_speed = update->side_speed; + vert_speed = update->vert_speed; + x = update->x; + y = update->y; + z = update->z; + x_speed = update->speed_x; + y_speed = update->speed_y; + z_speed = update->speed_z; + client_pitch = update->pitch; + + SetPitch(180 + update->pitch); + } + else if (version >= 1096) { + Player_Update1096* update = (Player_Update1096*)movement_packet; + activity = update->activity; + grid_id = update->grid_location; + direction1 = update->direction1; + direction2 = update->direction2; + speed = update->speed; + side_speed = update->side_speed; + vert_speed = update->vert_speed; + x = update->x; + y = update->y; + z = update->z; + x_speed = update->speed_x; + y_speed = update->speed_y; + z_speed = update->speed_z; + client_pitch = update->pitch; + + SetPitch(180 + update->pitch); + } + else if (version <= 373) { + Player_Update283* update = (Player_Update283*)movement_packet; + activity = update->activity; + grid_id = update->grid_location; + direction1 = update->direction1; + direction2 = update->direction2; + speed = update->speed; + side_speed = update->side_speed; + vert_speed = update->vert_speed; + client_pitch = update->pitch; + + x = update->x; + y = update->y; + z = update->z; + x_speed = update->speed_x; + y_speed = update->speed_y; + z_speed = update->speed_z; + appearance.pos.X2 = update->orig_x; + appearance.pos.Y2 = update->orig_y; + appearance.pos.Z2 = update->orig_z; + appearance.pos.X3 = update->orig_x2; + appearance.pos.Y3 = update->orig_y2; + appearance.pos.Z3 = update->orig_z2; + if (update->pitch != 0) + SetPitch(180 + update->pitch); + } + else { + Player_Update* update = (Player_Update*)movement_packet; + activity = update->activity; + grid_id = update->grid_location; + direction1 = update->direction1; + direction2 = update->direction2; + speed = update->speed; + side_speed = update->side_speed; + vert_speed = update->vert_speed; + x = update->x; + y = update->y; + z = update->z; + x_speed = update->speed_x; + y_speed = update->speed_y; + z_speed = update->speed_z; + appearance.pos.X2 = update->orig_x; + appearance.pos.Y2 = update->orig_y; + appearance.pos.Z2 = update->orig_z; + appearance.pos.X3 = update->orig_x2; + appearance.pos.Y3 = update->orig_y2; + appearance.pos.Z3 = update->orig_z2; + client_pitch = update->pitch; + + SetPitch(180 + update->pitch); + } + + SetHeading((sint16)(direction1 * 64), (sint16)(direction2 * 64)); + + if (activity != last_movement_activity) { + switch(activity) { + case UPDATE_ACTIVITY_RUNNING: + case UPDATE_ACTIVITY_RUNNING_AOM: + case UPDATE_ACTIVITY_IN_WATER_ABOVE: + case UPDATE_ACTIVITY_IN_WATER_BELOW: + case UPDATE_ACTIVITY_MOVE_WATER_ABOVE_AOM: + case UPDATE_ACTIVITY_MOVE_WATER_BELOW_AOM: { + if(GetZone() && GetZone()->GetDrowningVictim(this)) + GetZone()->RemoveDrowningVictim(this); + + break; + } + case UPDATE_ACTIVITY_DROWNING: + case UPDATE_ACTIVITY_DROWNING2: + case UPDATE_ACTIVITY_DROWNING_AOM: + case UPDATE_ACTIVITY_DROWNING2_AOM: { + if(GetZone() && !GetInvulnerable()) { + GetZone()->AddDrowningVictim(this); + } + break; + } + case UPDATE_ACTIVITY_JUMPING: + case UPDATE_ACTIVITY_JUMPING_AOM: + case UPDATE_ACTIVITY_FALLING: + case UPDATE_ACTIVITY_FALLING_AOM: { + if(IsCasting()) { + GetZone()->Interrupted(this, 0, SPELL_ERROR_INTERRUPTED, false, true); + } + if(GetInitialState() != 1024) { + SetInitialState(1024); + } + else if(GetInitialState() == 1024) { + if(activity == UPDATE_ACTIVITY_JUMPING_AOM) { + SetInitialState(UPDATE_ACTIVITY_JUMPING_AOM); + } + else { + SetInitialState(16512); + } + } + break; + } + } + + last_movement_activity = activity; + } + //Player is riding a lift, update lift XYZ offsets and the lift's spawn pointer + if (activity & UPDATE_ACTIVITY_RIDING_BOAT) { + Spawn* boat = 0; + + float boat_x = x; + float boat_y = y; + float boat_z = z; + + if (GetBoatSpawn() == 0 && GetZone()) { + boat = GetZone()->GetClosestTransportSpawn(GetX(), GetY(), GetZ()); + SetBoatSpawn(boat); + if(boat) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Set Player %s (%u) on Boat: %s", + GetName(), GetCharacterID(), boat ? boat->GetName() : "notset"); + boat->AddRailPassenger(GetCharacterID()); + GetZone()->CallSpawnScript(boat, SPAWN_SCRIPT_BOARD, this); + } + } + + if (boat || (GetBoatSpawn() && GetZone())) { + if (!boat) + boat = GetZone()->GetSpawnByID(GetBoatSpawn()); + + if (boat && boat->IsWidget() && ((Widget*)boat)->GetMultiFloorLift()) { + boat_x -= boat->GetX(); + boat_y -= boat->GetY(); + boat_z -= boat->GetZ(); + } + } + + SetBoatX(boat_x); + SetBoatY(boat_y); + SetBoatZ(boat_z); + pos_packet_speed = speed; + grid_id = GetLocation(); + } + else if (GetBoatSpawn() > 0 && !lift_cooldown.Enabled()) + { + lift_cooldown.Start(100, true); + } + else if(lift_cooldown.Check()) + { + if(GetBoatSpawn()) + { + Spawn* boat = GetZone()->GetSpawnByID(GetBoatSpawn()); + if(boat) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Remove Player %s (%u) from Boat: %s", + GetName(), GetCharacterID(), boat ? boat->GetName() : "notset"); + boat->RemoveRailPassenger(GetCharacterID()); + GetZone()->CallSpawnScript(boat, SPAWN_SCRIPT_DEBOARD, this); + } + } + SetBoatSpawn(0); + lift_cooldown.Disable(); + } + + if (!IsResurrecting() && !GetBoatSpawn()) + { + if (!IsRooted() && !IsMezzedOrStunned()) { + SetX(x); + SetY(y, true, true); + SetZ(z); + SetSpeedX(x_speed); + SetSpeedY(y_speed); + SetSpeedZ(z_speed); + SetSideSpeed(side_speed); + SetVertSpeed(vert_speed); + SetClientHeading1(direction1); + SetClientHeading2(direction2); + SetClientPitch(client_pitch); + if(version > 373) { + pos_packet_speed = speed; + } + } + else { + SetSpeedX(0.0f); + SetSpeedY(0.0f); + SetSpeedZ(0.0f); + SetSideSpeed(0.0f); + SetVertSpeed(0.0f); + SetClientHeading1(direction1); + SetClientHeading2(direction2); + SetClientPitch(client_pitch); + pos_packet_speed = 0; + } + } + + if (GetLocation() != grid_id) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s left grid %u and entered grid %u", appearance.name, GetLocation(), grid_id); + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "leave_location", GetZone(), this, GetLocation()); + } + + SetLocation(grid_id); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "enter_location", GetZone(), this, grid_id); + } + } + if (activity == UPDATE_ACTIVITY_IN_WATER_ABOVE || activity == UPDATE_ACTIVITY_IN_WATER_BELOW || + activity == UPDATE_ACTIVITY_MOVE_WATER_BELOW_AOM || activity == UPDATE_ACTIVITY_MOVE_WATER_ABOVE_AOM) { + if (MakeRandomFloat(0, 100) < 25 && InWater()) + GetSkillByName("Swimming", true); + } + // don't have to uncomment the print packet but you MUST uncomment the safe_delete() for xml structs + //update->PrintPacket(); + //safe_delete(update); + + LogWrite(PLAYER__DEBUG, 7, "Player", "Exit: %s", __FUNCTION__); // trace +} + +int16 Player::GetLastMovementActivity(){ + return last_movement_activity; +} + +void Player::AddSpawnInfoPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size){ + spawn_info_packet_list[spawn_id] = string((char*)packet, packet_size); +} + +void Player::AddSpawnPosPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size){ + spawn_pos_packet_list[spawn_id] = string((char*)packet, packet_size); +} + +uchar* Player::GetSpawnPosPacketForXOR(int32 spawn_id){ + uchar* ret = 0; + if(spawn_pos_packet_list.count(spawn_id) == 1) + ret = (uchar*)spawn_pos_packet_list[spawn_id].c_str(); + return ret; +} +uchar* Player::GetSpawnInfoPacketForXOR(int32 spawn_id){ + uchar* ret = 0; + if(spawn_info_packet_list.count(spawn_id) == 1) + ret = (uchar*)spawn_info_packet_list[spawn_id].c_str(); + return ret; +} +void Player::AddSpawnVisPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size){ + spawn_vis_packet_list[spawn_id] = string((char*)packet, packet_size); +} + +uchar* Player::GetSpawnVisPacketForXOR(int32 spawn_id){ + uchar* ret = 0; + if(spawn_vis_packet_list.count(spawn_id) == 1) + ret = (uchar*)spawn_vis_packet_list[spawn_id].c_str(); + return ret; +} + +uchar* Player::GetTempInfoPacketForXOR(){ + return spawn_tmp_info_xor_packet; +} + +uchar* Player::GetTempVisPacketForXOR(){ + return spawn_tmp_vis_xor_packet; +} + +uchar* Player::GetTempPosPacketForXOR(){ + return spawn_tmp_pos_xor_packet; +} + +uchar* Player::SetTempInfoPacketForXOR(int16 size){ + spawn_tmp_info_xor_packet = new uchar[size]; + info_xor_size = size; + return spawn_tmp_info_xor_packet; +} + +uchar* Player::SetTempVisPacketForXOR(int16 size){ + spawn_tmp_vis_xor_packet = new uchar[size]; + vis_xor_size = size; + return spawn_tmp_vis_xor_packet; +} + +uchar* Player::SetTempPosPacketForXOR(int16 size){ + spawn_tmp_pos_xor_packet = new uchar[size]; + pos_xor_size = size; + return spawn_tmp_pos_xor_packet; +} + +bool Player::CheckPlayerInfo(){ + return info != 0; +} + +bool Player::SetSpawnSentState(Spawn* spawn, SpawnState state) { + bool val = true; + spawn_mutex.writelock(__FUNCTION__, __LINE__); + int16 index = GetIndexForSpawn(spawn); + if(index > 0 && (state == SpawnState::SPAWN_STATE_SENDING)) { + LogWrite(PLAYER__WARNING, 0, "Player", "Spawn ALREADY INDEXED for Player %s (%u). Spawn %s (index %u) attempted to state %u.", + GetName(), GetCharacterID(), spawn->GetName(), index, state); + if(GetClient() && GetClient()->IsReloadingZone()) { + spawn_packet_sent.insert(make_pair(spawn->GetID(), state)); + val = false; + } + // we don't do anything this spawn is already populated by the player + } + else { + LogWrite(PLAYER__DEBUG, 0, "Player", "Spawn for Player %s (%u). Spawn %s (index %u) in state %u.", + GetName(), GetCharacterID(), spawn->GetName(), index, state); + + map::iterator itr = spawn_packet_sent.find(spawn->GetID()); + if(itr != spawn_packet_sent.end()) + itr->second = state; + else + spawn_packet_sent.insert(make_pair(spawn->GetID(), state)); + if(state == SPAWN_STATE_SENT_WAIT) { + map::iterator state_itr; + if((state_itr = spawn_state_list.find(spawn->GetID())) != spawn_state_list.end()) { + safe_delete(state_itr->second); + spawn_state_list.erase(state_itr); + } + + SpawnQueueState* removal = new SpawnQueueState; + removal->index_id = index; + removal->spawn_state_timer = Timer(500, true); + removal->spawn_state_timer.Start(); + spawn_state_list.insert(make_pair(spawn->GetID(),removal)); + } + else if(state == SpawnState::SPAWN_STATE_REMOVING && + spawn_state_list.count(spawn->GetID()) == 0) { + SpawnQueueState* removal = new SpawnQueueState; + removal->index_id = index; + removal->spawn_state_timer = Timer(1000, true); + removal->spawn_state_timer.Start(); + spawn_state_list.insert(make_pair(spawn->GetID(),removal)); + } + } + spawn_mutex.releasewritelock(__FUNCTION__, __LINE__); + return val; +} + +void Player::CheckSpawnStateQueue() { + if(!GetClient() || !GetClient()->IsReadyForUpdates()) + return; + + spawn_mutex.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for(itr = spawn_state_list.begin(); itr != spawn_state_list.end();) { + if(itr->second->spawn_state_timer.Check()) { + map::iterator sent_itr = spawn_packet_sent.find(itr->first); + LogWrite(PLAYER__DEBUG, 0, "Player", "Spawn for Player %s (%u). Spawn index %u in state %u.", + GetName(), GetCharacterID(), itr->second->index_id, sent_itr->second); + switch(sent_itr->second) { + case SpawnState::SPAWN_STATE_SENT_WAIT: { + sent_itr->second = SpawnState::SPAWN_STATE_SENT; + SpawnQueueState* sr = itr->second; + itr = spawn_state_list.erase(itr); + safe_delete(sr); + break; + } + case SpawnState::SPAWN_STATE_REMOVING: { + if(itr->first == GetID() && GetClient()->IsReloadingZone()) { + itr->second->spawn_state_timer.Disable(); + continue; + } + + if(itr->second->index_id) { + PacketStruct* packet = packet = configReader.getStruct("WS_DestroyGhostCmd", GetClient()->GetVersion()); + packet->setDataByName("spawn_index", itr->second->index_id); + packet->setDataByName("delete", 1); + GetClient()->QueuePacket(packet->serialize()); + safe_delete(packet); + } + sent_itr->second = SpawnState::SPAWN_STATE_REMOVING_SLEEP; + itr++; + break; + } + case SpawnState::SPAWN_STATE_REMOVING_SLEEP: { + map::iterator sent_itr = spawn_packet_sent.find(itr->first); + sent_itr->second = SpawnState::SPAWN_STATE_REMOVED; + SpawnQueueState* sr = itr->second; + itr = spawn_state_list.erase(itr); + safe_delete(sr); + break; + } + default: { + // reset + itr->second->spawn_state_timer.Disable(); + break; + } + } + } + else + itr++; + } + spawn_mutex.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Player::WasSentSpawn(int32 spawn_id){ + if(GetID() == spawn_id) + return true; + + bool ret = false; + spawn_mutex.readlock(__FUNCTION__, __LINE__); + map::iterator itr = spawn_packet_sent.find(spawn_id); + if(itr != spawn_packet_sent.end() && itr->second == SpawnState::SPAWN_STATE_SENT) { + ret = true; + } + spawn_mutex.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool Player::IsSendingSpawn(int32 spawn_id){ + bool ret = false; + spawn_mutex.readlock(__FUNCTION__, __LINE__); + map::iterator itr = spawn_packet_sent.find(spawn_id); + if(itr != spawn_packet_sent.end() && (itr->second == SpawnState::SPAWN_STATE_SENDING || itr->second == SPAWN_STATE_SENT_WAIT)) { + ret = true; + } + spawn_mutex.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool Player::IsRemovingSpawn(int32 spawn_id){ + bool ret = false; + spawn_mutex.readlock(__FUNCTION__, __LINE__); + map::iterator itr = spawn_packet_sent.find(spawn_id); + if(itr != spawn_packet_sent.end() && + (itr->second == SpawnState::SPAWN_STATE_REMOVING || itr->second == SpawnState::SPAWN_STATE_REMOVING_SLEEP)) { + ret = true; + } + spawn_mutex.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +PlayerSkillList* Player::GetSkills(){ + return &skill_list; +} + +void Player::InCombat(bool val, bool range) { + if (val) + GetInfoStruct()->set_flags(GetInfoStruct()->get_flags() | (1 << (range?CF_RANGED_AUTO_ATTACK:CF_AUTO_ATTACK))); + else + GetInfoStruct()->set_flags(GetInfoStruct()->get_flags() & ~(1 << (range?CF_RANGED_AUTO_ATTACK:CF_AUTO_ATTACK))); + + bool changeCombatState = false; + + if((in_combat && !val) || (!in_combat && val)) + changeCombatState = true; + + in_combat = val; + if(in_combat) + AddIconValue(64); + else + RemoveIconValue(64); + + bool update_regen = false; + if(GetInfoStruct()->get_engaged_encounter()) { + if(!IsAggroed() || !IsEngagedInEncounter()) { + GetInfoStruct()->set_engaged_encounter(0); + update_regen = true; + } + } + + if(changeCombatState || update_regen) + SetRegenValues((GetInfoStruct()->get_effective_level() > 0) ? GetInfoStruct()->get_effective_level() : GetLevel()); + + charsheet_changed = true; + info_changed = true; +} + +void Player::SetCharSheetChanged(bool val){ + charsheet_changed = val; +} + +bool Player::GetCharSheetChanged(){ + return charsheet_changed; +} + +bool Player::AdventureXPEnabled(){ + return (GetInfoStruct()->get_flags() & (1 << CF_COMBAT_EXPERIENCE_ENABLED)); +} + +bool Player::TradeskillXPEnabled() { + // TODO: need to identify the flag to togle tradeskill xp + return true; +} + +void Player::set_character_flag(int flag){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Flag: %u", flag); + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags before: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); + + if (flag > CF_MAXIMUM_FLAG) return; + if (flag < 32) GetInfoStruct()->set_flags(GetInfoStruct()->get_flags() | (1 << flag)); + else GetInfoStruct()->set_flags2(GetInfoStruct()->get_flags2() | (1 << (flag - 32))); + charsheet_changed = true; + info_changed = true; + + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags after: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); +} + +void Player::reset_character_flag(int flag){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Flag: %u", flag); + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags before: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); + + if (flag > CF_MAXIMUM_FLAG) return; + if (flag < 32) + { + int8 origflag = GetInfoStruct()->get_flags(); + GetInfoStruct()->set_flags(origflag &= ~(1 << flag)); + } + else + { + int8 flag2 = GetInfoStruct()->get_flags2(); + GetInfoStruct()->set_flags2(flag2 &= ~(1 << (flag - 32))); + } + charsheet_changed = true; + info_changed = true; + + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags after: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); +} + +void Player::toggle_character_flag(int flag){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Flag: %u", flag); + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags before: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); + + if (flag > CF_MAXIMUM_FLAG) return; + if (flag < 32) + { + int32 origflag = GetInfoStruct()->get_flags(); + GetInfoStruct()->set_flags(origflag ^= (1 << flag)); + } + else + { + int32 flag2 = GetInfoStruct()->get_flags2(); + GetInfoStruct()->set_flags2(flag2 ^= (1 << (flag - 32))); + } + charsheet_changed = true; + info_changed = true; + + LogWrite(PLAYER__DEBUG, 0, "Player", "Flags after: %u, Flags2: %u", GetInfoStruct()->get_flags(), GetInfoStruct()->get_flags2()); +} + +bool Player::get_character_flag(int flag){ + bool ret = false; + + if (flag > CF_MAXIMUM_FLAG){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Player::get_character_flag error: attempted to check flag %i", flag); + return ret; + } + if (flag < 32) ret = ((GetInfoStruct()->get_flags()) >> flag & 1); + else ret = ((GetInfoStruct()->get_flags2()) >> (flag - 32) & 1); + + return ret; +} + +float Player::GetXPVitality(){ + return GetInfoStruct()->get_xp_vitality(); +} + +float Player::GetTSXPVitality() { + return GetInfoStruct()->get_tradeskill_xp_vitality(); +} + +bool Player::DoubleXPEnabled(){ + return GetInfoStruct()->get_xp_vitality() > 0; +} + +void Player::SetCharacterID(int32 new_id){ + char_id = new_id; +} + +int32 Player::GetCharacterID(){ + return char_id; +} + +float Player::CalculateXP(Spawn* victim){ + if(AdventureXPEnabled() == false || !victim) + return 0; + float multiplier = 0; + + float zone_xp_modifier = 1; // let's be safe!! + if( GetZone()->GetXPModifier() != 0 ) { + zone_xp_modifier = GetZone()->GetXPModifier(); + LogWrite(PLAYER__DEBUG, 5, "XP", "Zone XP Modifier = %.2f", zone_xp_modifier); + } + + switch(GetArrowColor(victim->GetLevel())){ + case ARROW_COLOR_GRAY: + LogWrite(PLAYER__DEBUG, 5, "XP", "Gray Arrow = No XP"); + return 0.0f; + break; + case ARROW_COLOR_GREEN: + multiplier = 3.25; + LogWrite(PLAYER__DEBUG, 5, "XP", "Green Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_BLUE: + multiplier = 3.5; + LogWrite(PLAYER__DEBUG, 5, "XP", "Blue Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_WHITE: + multiplier = 4; + LogWrite(PLAYER__DEBUG, 5, "XP", "White Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_YELLOW: + multiplier = 4.25; + LogWrite(PLAYER__DEBUG, 5, "XP", "Yellow Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_ORANGE: + multiplier = 4.5; + LogWrite(PLAYER__DEBUG, 5, "XP", "Orange Arrow Multiplier = %.2f", multiplier); + break; + case ARROW_COLOR_RED: + multiplier = 6; + LogWrite(PLAYER__DEBUG, 5, "XP", "Red Arrow Multiplier = %.2f", multiplier); + break; + } + float total = multiplier * 8; + LogWrite(PLAYER__DEBUG, 5, "XP", "Multiplier * 8 = %.2f", total); + + if(victim->GetDifficulty() > 6) { // no need to multiply by 1 if this is a normal mob + total *= (victim->GetDifficulty() - 5); + LogWrite(PLAYER__DEBUG, 5, "XP", "Encounter > 6, total = %.2f", total); + } + else if(victim->GetDifficulty() <= 5) { + total /= (7 - victim->GetDifficulty()); //1 down mobs are worth half credit, 2 down worth .25, etc + LogWrite(PLAYER__DEBUG, 5, "XP", "Encounter <= 5, total = %.2f", total); + } + + if(victim->GetHeroic() > 1) { + total *= victim->GetHeroic(); + LogWrite(PLAYER__DEBUG, 5, "XP", "Heroic, total = %.2f", total); + } + if(DoubleXPEnabled()) { + LogWrite(PLAYER__DEBUG, 5, "XP", "Calculating Double XP!"); + + float percent = (((float)(total))/GetNeededXP()) *100; + LogWrite(PLAYER__DEBUG, 5, "XP", "Percent of total / XP Needed * 100, percent = %.2f", percent); + float xp_vitality = GetXPVitality(); + if(xp_vitality >= percent) { + GetInfoStruct()->set_xp_vitality(xp_vitality - percent); + total *= 2; + LogWrite(PLAYER__DEBUG, 5, "XP", "Vitality >= Percent, total = %.2f", total); + } + else { + total += ((GetXPVitality() / percent) *2)*total; + GetInfoStruct()->set_xp_vitality(0); + LogWrite(PLAYER__DEBUG, 5, "XP", "Vitality < Percent, total = %.2f", total); + } + } + LogWrite(PLAYER__DEBUG, 5, "XP", "Final total = %.2f", (total * world.GetXPRate() * zone_xp_modifier)); + return total * world.GetXPRate() * zone_xp_modifier; +} + +float Player::CalculateTSXP(int8 level){ + if(TradeskillXPEnabled() == false) + return 0; + float multiplier = 0; + + float zone_xp_modifier = 1; // let's be safe!! + if( GetZone()->GetXPModifier() != 0 ) { + zone_xp_modifier = GetZone()->GetXPModifier(); + LogWrite(PLAYER__DEBUG, 5, "XP", "Zone XP Modifier = %.2f", zone_xp_modifier); + } + + sint16 diff = level - GetTSLevel(); + if(GetTSLevel() < 10) + diff *= 3; + else if(GetTSLevel() <= 20) + diff *= 2; + if(diff >= 9) + multiplier = 6; + else if(diff >= 5) + multiplier = 4.5; + else if(diff >= 1) + multiplier = 4.25; + else if(diff == 0) + multiplier = 4; + else if(diff <= -11) + multiplier = 0; + else if(diff <= -6) + multiplier = 3.25; + else //if(diff < 0) + multiplier = 3.5; + + + float total = multiplier * 8; + LogWrite(PLAYER__DEBUG, 5, "XP", "Multiplier * 8 = %.2f", total); + + if(DoubleXPEnabled()) { + LogWrite(PLAYER__DEBUG, 5, "XP", "Calculating Double XP!"); + + float percent = (((float)(total))/GetNeededTSXP()) *100; + LogWrite(PLAYER__DEBUG, 5, "XP", "Percent of total / XP Needed * 100, percent = %.2f", percent); + + float ts_xp_vitality = GetTSXPVitality(); + if(ts_xp_vitality >= percent) { + GetInfoStruct()->set_tradeskill_xp_vitality(ts_xp_vitality - percent); + total *= 2; + LogWrite(PLAYER__DEBUG, 5, "XP", "Vitality >= Percent, total = %.2f", total); + } + else { + total += ((GetTSXPVitality() / percent) *2)*total; + GetInfoStruct()->set_tradeskill_xp_vitality(0); + LogWrite(PLAYER__DEBUG, 5, "XP", "Vitality < Percent, total = %.2f", total); + } + } + LogWrite(PLAYER__DEBUG, 5, "XP", "Final total = %.2f", (total * world.GetXPRate() * zone_xp_modifier)); + return total * world.GetXPRate() * zone_xp_modifier; +} + +void Player::CalculateOfflineDebtRecovery(int32 unix_timestamp) +{ + float xpDebt = GetXPDebt(); + // not a real timestamp to work with + if(unix_timestamp < 1 || xpDebt == 0.0f) + return; + + uint32 diff = (Timer::GetUnixTimeStamp() - unix_timestamp)/1000; + + float recoveryDebtPercentage = rule_manager.GetGlobalRule(R_Combat, ExperienceDebtRecoveryPercent)->GetFloat()/100.0f; + int32 recoveryPeriodSeconds = rule_manager.GetGlobalRule(R_Combat, ExperienceDebtRecoveryPeriod)->GetInt32(); + if(recoveryDebtPercentage == 0.0f || recoveryPeriodSeconds < 1) + return; + + + float periodsPassed = (float)diff/(float)recoveryPeriodSeconds; + + // not enough time passed to calculate debt xp recovered + if(periodsPassed < 1.0f) + return; + + float debtToSubtract = xpDebt * ((recoveryDebtPercentage*periodsPassed)/100.0f); + + if(debtToSubtract >= xpDebt) + GetInfoStruct()->set_xp_debt(0.0f); + else + GetInfoStruct()->set_xp_debt(xpDebt - debtToSubtract); +} + +void Player::SetNeededXP(int32 val){ + GetInfoStruct()->set_xp_needed(val); +} + +void Player::SetNeededXP(){ + //GetInfoStruct()->xp_needed = GetLevel() * 100; + // Get xp needed to get to the next level + int16 level = GetLevel() + 1; + // If next level is beyond what we have in the map multiply the last value we have by how many levels we are over plus one + if (level > 95) + SetNeededXP(database.GetMysqlExpCurve(95)* ((level - 95) + 1)); + else + SetNeededXP(database.GetMysqlExpCurve(level)); +} + +void Player::SetXP(int32 val){ + GetInfoStruct()->set_xp(val); +} + +void Player::SetNeededTSXP(int32 val) { + GetInfoStruct()->set_ts_xp_needed(val); +} + +void Player::SetNeededTSXP() { + GetInfoStruct()->set_ts_xp_needed(GetTSLevel() * 100); +} + +void Player::SetTSXP(int32 val) { + GetInfoStruct()->set_ts_xp(val); +} + +float Player::GetXPDebt(){ + return GetInfoStruct()->get_xp_debt(); +} + +int32 Player::GetNeededXP(){ + return GetInfoStruct()->get_xp_needed(); +} + +int32 Player::GetXP(){ + return GetInfoStruct()->get_xp(); +} + +int32 Player::GetNeededTSXP() { + return GetInfoStruct()->get_ts_xp_needed(); +} + +int32 Player::GetTSXP() { + return GetInfoStruct()->get_ts_xp(); +} + +bool Player::AddXP(int32 xp_amount){ + if(!GetClient()) // potential linkdead player + return false; + + MStats.lock(); + xp_amount += (int32)(((float)xp_amount) * stats[ITEM_STAT_COMBATEXPMOD]) / 100; + MStats.unlock(); + + if(GetInfoStruct()->get_xp_debt()) + { + float expRatioToDebt = rule_manager.GetGlobalRule(R_Combat, ExperienceToDebt)->GetFloat()/100.0f; + int32 amountToTakeFromDebt = (int32)((float)expRatioToDebt * (float)xp_amount); + int32 amountRequiredClearDebt = (GetInfoStruct()->get_xp_debt()/100.0f) * xp_amount; + + if(amountToTakeFromDebt > amountRequiredClearDebt) + { + GetInfoStruct()->set_xp_debt(0.0f); + if(amountRequiredClearDebt > xp_amount) + xp_amount = 0; + else + xp_amount -= amountRequiredClearDebt; + } + else + { + float amountRemovedPct = ((float)amountToTakeFromDebt/(float)amountRequiredClearDebt); + GetInfoStruct()->set_xp_debt(GetInfoStruct()->get_xp_debt()-amountRemovedPct); + if(amountToTakeFromDebt > xp_amount) + xp_amount = 0; + else + xp_amount -= amountToTakeFromDebt; + } + } + + // used up in xp debt + if(!xp_amount) { + SetCharSheetChanged(true); + return true; + } + + int32 prev_level = GetLevel(); + float current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100; + float miniding_min_percent = ((int)(current_xp_percent/10)+1)*10; + while((xp_amount + GetXP()) >= GetNeededXP()){ + if (!CheckLevelStatus(GetLevel() + 1)) { + if(GetClient()) { + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "You do not have the required status to level up anymore!"); + } + SetCharSheetChanged(true); + return false; + } + xp_amount -= GetNeededXP() - GetXP(); + SetLevel(GetLevel() + 1); + } + SetXP(GetXP() + xp_amount); + GetPlayerInfo()->CalculateXPPercentages(); + current_xp_percent = ((float)GetXP()/(float)GetNeededXP())*100; + if(current_xp_percent >= miniding_min_percent){ + SetHP(GetTotalHP()); + SetPower(GetTotalPower()); + GetZone()->SendCastSpellPacket(332, this, this); //send mini level up spell effect + } + + if(GetClient()) { + GetClient()->Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp_amount); + + if (prev_level != GetLevel()) + GetClient()->ChangeLevel(prev_level, GetLevel()); + } + + SetCharSheetChanged(true); + return true; +} + +bool Player::AddTSXP(int32 xp_amount){ + MStats.lock(); + xp_amount += ((xp_amount)*stats[ITEM_STAT_TRADESKILLEXPMOD]) / 100; + MStats.unlock(); + + float current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100; + float miniding_min_percent = ((int)(current_xp_percent/10)+1)*10; + while((xp_amount + GetTSXP()) >= GetNeededTSXP()){ + if (!CheckLevelStatus(GetTSLevel() + 1)) { + if(GetClient()) { + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "You do not have the required status to level up anymore!"); + } + return false; + } + xp_amount -= GetNeededTSXP() - GetTSXP(); + SetTSLevel(GetTSLevel() + 1); + SetTSXP(0); + SetNeededTSXP(); + } + SetTSXP(GetTSXP() + xp_amount); + GetPlayerInfo()->CalculateXPPercentages(); + current_xp_percent = ((float)GetTSXP()/(float)GetNeededTSXP())*100; + if(current_xp_percent >= miniding_min_percent){ + SetHP(GetTotalHP()); + SetPower(GetTotalPower()); + } + + if (GetTradeskillClass() == 0){ + SetTradeskillClass(1); + GetInfoStruct()->set_tradeskill_class1(1); + GetInfoStruct()->set_tradeskill_class2(1); + GetInfoStruct()->set_tradeskill_class3(1); + } + return true; +} + +void Player::CalculateLocation(){ + if(GetSpeed() > 0 ){ + if(GetHeading() >= 270 && GetHeading() <= 360){ + SetX(GetX() + (GetSpeed()*.5)*((360-GetHeading())/90)); + SetZ(GetZ() - (GetSpeed()*.5)*((GetHeading()-270)/90)); + } + else if(GetHeading() >= 180 && GetHeading() < 270){ + SetX(GetX() + (GetSpeed()*.5)*((GetHeading()-180)/90)); + SetZ(GetZ() + (GetSpeed()*.5)*((270-GetHeading())/90)); + } + else if(GetHeading() >= 90 && GetHeading() < 180){ + SetX(GetX() - (GetSpeed()*.5)*((180-GetHeading())/90)); + SetZ(GetZ() + (GetSpeed()*.5)*((GetHeading()-90)/90)); + } + else if(GetHeading() >= 0 && GetHeading() < 90){ + SetX(GetX() - (GetSpeed()*.5)*(GetHeading()/90)); + SetZ(GetZ() - (GetSpeed()*.5)*((90-GetHeading())/90)); + } + } +} + +Spawn* Player::GetSpawnByIndex(int16 index){ + Spawn* spawn = 0; + + index_mutex.readlock(__FUNCTION__, __LINE__); + if(player_spawn_id_map.count(index) > 0) + spawn = player_spawn_id_map[index]; + index_mutex.releasereadlock(__FUNCTION__, __LINE__); + + return spawn; +} + +int16 Player::GetIndexForSpawn(Spawn* spawn) { + int16 val = 0; + + index_mutex.readlock(__FUNCTION__, __LINE__); + if(player_spawn_reverse_id_map.count(spawn) > 0) + val = player_spawn_reverse_id_map[spawn]; + index_mutex.releasereadlock(__FUNCTION__, __LINE__); + + return val; +} + +bool Player::WasSpawnRemoved(Spawn* spawn){ + bool wasRemoved = false; + + if(IsRemovingSpawn(spawn->GetID())) + return false; + + spawn_mutex.readlock(__FUNCTION__, __LINE__); + map::iterator itr = spawn_packet_sent.find(spawn_id); + if(itr != spawn_packet_sent.end() && itr->second == SpawnState::SPAWN_STATE_REMOVED) { + wasRemoved = true; + } + spawn_mutex.releasereadlock(__FUNCTION__, __LINE__); + + return wasRemoved; +} + +void Player::RemoveSpawn(Spawn* spawn, bool delete_spawn) +{ + LogWrite(PLAYER__DEBUG, 3, "Player", "Remove Spawn '%s' (%u)", spawn->GetName(), spawn->GetID()); + + SetSpawnSentState(spawn, delete_spawn ? SpawnState::SPAWN_STATE_REMOVING : SpawnState::SPAWN_STATE_REMOVING_SLEEP); + + info_mutex.writelock(__FUNCTION__, __LINE__); + vis_mutex.writelock(__FUNCTION__, __LINE__); + pos_mutex.writelock(__FUNCTION__, __LINE__); + + index_mutex.writelock(__FUNCTION__, __LINE__); + + if (player_spawn_reverse_id_map[spawn] && player_spawn_id_map.count(player_spawn_reverse_id_map[spawn]) > 0) + player_spawn_id_map.erase(player_spawn_reverse_id_map[spawn]); + + if (player_spawn_reverse_id_map.count(spawn) > 0) + player_spawn_reverse_id_map.erase(spawn); + + if (player_spawn_id_map.count(spawn->GetID()) && player_spawn_id_map[spawn->GetID()] == spawn) + player_spawn_id_map.erase(spawn->GetID()); + + int32 id = spawn->GetID(); + if (spawn_info_packet_list.count(id)) + spawn_info_packet_list.erase(id); + + if (spawn_pos_packet_list.count(id)) + spawn_pos_packet_list.erase(id); + + if (spawn_vis_packet_list.count(id)) + spawn_vis_packet_list.erase(id); + + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + info_mutex.releasewritelock(__FUNCTION__, __LINE__); + pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); +} + +vector Player::GetQuestIDs(){ + vector ret; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second) + ret.push_back(itr->second->GetQuestID()); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +vector* Player::CheckQuestsItemUpdate(Item* item){ + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestItemUpdate(item->details.item_id, item->details.count)){ + if(!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +void Player::CheckQuestsCraftUpdate(Item* item, int32 qty){ + map::iterator itr; + vector* update_list = new vector; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second){ + if(item && qty > 0){ + if(itr->second->CheckQuestRefIDUpdate(item->details.item_id, qty)){ + update_list->push_back(itr->second); + } + } + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + if(update_list && update_list->size() > 0){ + Client* client = GetClient(); + if(client){ + for(int8 i=0;isize(); i++){ + client->SendQuestUpdate(update_list->at(i)); + client->SendQuestFailure(update_list->at(i)); + } + } + } + update_list->clear(); + safe_delete(update_list); +} + +void Player::CheckQuestsHarvestUpdate(Item* item, int32 qty){ + map::iterator itr; + vector* update_list = new vector; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second){ + if(item && qty > 0){ + if(itr->second->CheckQuestRefIDUpdate(item->details.item_id, qty)){ + update_list->push_back(itr->second); + } + } + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + if(update_list && update_list->size() > 0){ + Client* client = GetClient(); + if(client){ + for(int8 i=0;isize(); i++){ + client->SendQuestUpdate(update_list->at(i)); + client->SendQuestFailure(update_list->at(i)); + } + } + } + update_list->clear(); + safe_delete(update_list); +} + +vector* Player::CheckQuestsSpellUpdate(Spell* spell) { + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for (itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if (itr->second && itr->second->CheckQuestSpellUpdate(spell)) { + if (!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +PacketStruct* Player::GetQuestJournalPacket(bool all_quests, int16 version, int32 crc, int32 current_quest_id, bool updated){ + PacketStruct* packet = configReader.getStruct("WS_QuestJournalUpdate", version); + Quest* quest = 0; + if(packet){ + int16 total_quests_num = 0; + int16 total_completed_quests = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + map total_quests = player_quests; + if(all_quests && completed_quests.size() > 0) + total_quests.insert(completed_quests.begin(), completed_quests.end()); + if(total_quests.size() > 0){ + map quest_types; + map::iterator itr; + int16 zone_id = 0; + for(itr = total_quests.begin(); itr != total_quests.end(); itr++){ + if(itr->first && itr->second){ + if(current_quest_id == 0 && itr->second->GetTurnedIn() == false) + current_quest_id = itr->first; + if(itr->second->GetTurnedIn()) + total_completed_quests++; + if(itr->second->GetType()){ + if(quest_types.count(itr->second->GetType()) == 0){ + quest_types[itr->second->GetType()] = zone_id; + zone_id++; + } + } + if(itr->second->GetZone()){ + if(quest_types.count(itr->second->GetZone()) == 0){ + quest_types[itr->second->GetZone()] = zone_id; // Fix #490 - incorrect ordering of quests in journal + zone_id++; + } + } + total_quests_num++; + } + else + continue; + } + packet->setArrayLengthByName("num_quests", total_quests_num); + int16 i = 0; + for(itr = total_quests.begin(); itr != total_quests.end(); itr++){ + if(i == 0 && quest_types.size() > 0){ + packet->setArrayLengthByName("num_quest_zones", quest_types.size()); + map::iterator type_itr; + int16 x = 0; + for(type_itr = quest_types.begin(); type_itr != quest_types.end(); type_itr++){ + packet->setArrayDataByName("quest_zones_zone", type_itr->first.c_str(), x); + packet->setArrayDataByName("quest_zones_zone_id", type_itr->second, x); + x++; + } + } + if(itr->first == 0 || !itr->second) + continue; + if(!all_quests && !itr->second->GetUpdateRequired()) + continue; + quest = itr->second; + if(!quest->GetDeleted()) + packet->setArrayDataByName("active", 1, i); + packet->setArrayDataByName("name", quest->GetName(), i); + packet->setArrayDataByName("quest_type", quest->GetType(), i); + packet->setArrayDataByName("quest_zone", quest->GetZone(), i); + int8 display_status = QUEST_DISPLAY_STATUS_SHOW; + if(itr->second->GetCompleted()) + packet->setArrayDataByName("completed", 1, i); + if(itr->second->GetTurnedIn()){ + packet->setArrayDataByName("turned_in", 1, i); + packet->setArrayDataByName("completed", 1, i); + packet->setArrayDataByName("visible", 1, i); + packet->setArrayDataByName("unknown3", 1, i); + display_status += QUEST_DISPLAY_STATUS_COMPLETED; + } + if (updated) { + packet->setArrayDataByName("quest_updated", 1, i); + packet->setArrayDataByName("journal_updated", 1, i); + } + packet->setArrayDataByName("quest_id", quest->GetQuestID(), i); + packet->setArrayDataByName("day", quest->GetDay(), i); + packet->setArrayDataByName("month", quest->GetMonth(), i); + packet->setArrayDataByName("year", quest->GetYear(), i); + packet->setArrayDataByName("level", quest->GetQuestLevel(), i); + int8 difficulty = 0; + string category = quest->GetType(); + if(category == "Tradeskill") + difficulty = GetTSArrowColor(quest->GetQuestLevel()); + else + difficulty = GetArrowColor(quest->GetQuestLevel()); + packet->setArrayDataByName("difficulty", difficulty, i); + if (itr->second->GetEncounterLevel() > 4) + packet->setArrayDataByName("encounter_level", quest->GetEncounterLevel(), i); + else + packet->setArrayDataByName("encounter_level", 4, i); + if(version >= 931 && quest_types.count(quest->GetType()) > 0) + packet->setArrayDataByName("zonetype_id", quest_types[quest->GetType()], i); + if(version >= 931 && quest_types.count(quest->GetZone()) > 0) + packet->setArrayDataByName("zone_id", quest_types[quest->GetZone()], i); + if(version >= 931 && quest->GetVisible()){ + if (quest->GetCompletedFlag()) + display_status += QUEST_DISPLAY_STATUS_COMPLETE_FLAG; + else if (quest->IsRepeatable()) + display_status += QUEST_DISPLAY_STATUS_REPEATABLE; + if (quest->GetYellowName() || quest->CheckCategoryYellow()) + display_status += QUEST_DISPLAY_STATUS_YELLOW; + + if (quest->IsTracked()) + display_status += QUEST_DISPLAY_STATUS_CHECK; + else + display_status += QUEST_DISPLAY_STATUS_NO_CHECK; + + if (quest->IsHidden() && !quest->GetTurnedIn()) { + display_status += QUEST_DISPLAY_STATUS_HIDDEN; + display_status -= QUEST_DISPLAY_STATUS_SHOW; + } + + if(quest->CanShareQuestCriteria(GetClient(),false)) { + display_status += QUEST_DISPLAY_STATUS_CAN_SHARE; + } + } + else + packet->setArrayDataByName("visible", quest->GetVisible(), i); + if (itr->second->IsRepeatable()) + packet->setArrayDataByName("repeatable", 1, i); + + packet->setArrayDataByName("display_status", display_status, i); + i++; + } + //packet->setDataByName("unknown4", 0); + packet->setDataByName("visible_quest_id", current_quest_id); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + packet->setDataByName("player_crc", crc); + packet->setDataByName("player_name", GetName()); + packet->setDataByName("used_quests", total_quests_num - total_completed_quests); + packet->setDataByName("max_quests", 75); + + LogWrite(PLAYER__PACKET, 0, "Player", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + } + return packet; +} + +PacketStruct* Player::GetQuestJournalPacket(Quest* quest, int16 version, int32 crc, bool updated) { + if (!quest) + return 0; + + PacketStruct* packet = configReader.getStruct("WS_QuestJournalUpdate", version); + if (packet) { + packet->setArrayLengthByName("num_quests", 1); + packet->setArrayLengthByName("num_quest_zones", 1); + packet->setArrayDataByName("quest_zones_zone", quest->GetType()); + packet->setArrayDataByName("quest_zones_zone_id", 0); + + if(!quest->GetDeleted() && !quest->GetCompleted()) + packet->setArrayDataByName("active", 1); + + packet->setArrayDataByName("name", quest->GetName()); + // don't see these two in the struct + packet->setArrayDataByName("quest_type", quest->GetType()); + packet->setArrayDataByName("quest_zone", quest->GetZone()); + + int8 display_status = QUEST_DISPLAY_STATUS_SHOW; + if(quest->GetCompleted()) + packet->setArrayDataByName("completed", 1); + if(quest->GetTurnedIn()) { + packet->setArrayDataByName("turned_in", 1); + packet->setArrayDataByName("completed", 1); + packet->setArrayDataByName("visible", 1); + display_status += QUEST_DISPLAY_STATUS_COMPLETED; + } + packet->setArrayDataByName("quest_id", quest->GetQuestID()); + packet->setArrayDataByName("day", quest->GetDay()); + packet->setArrayDataByName("month", quest->GetMonth()); + packet->setArrayDataByName("year", quest->GetYear()); + packet->setArrayDataByName("level", quest->GetQuestLevel()); + int8 difficulty = 0; + string category = quest->GetType(); + if(category == "Tradeskill") + difficulty = GetTSArrowColor(quest->GetQuestLevel()); + else + difficulty = GetArrowColor(quest->GetQuestLevel()); + + packet->setArrayDataByName("difficulty", difficulty); + if (quest->GetEncounterLevel() > 4) + packet->setArrayDataByName("encounter_level", quest->GetEncounterLevel()); + else + packet->setArrayDataByName("encounter_level", 4); + + if (version >= 931) { + packet->setArrayDataByName("zonetype_id", 0); + packet->setArrayDataByName("zone_id", 0); + } + if(version >= 931 && quest->GetVisible()){ + if (quest->GetCompletedFlag()) + display_status += QUEST_DISPLAY_STATUS_COMPLETE_FLAG; + else if (quest->IsRepeatable()) + display_status += QUEST_DISPLAY_STATUS_REPEATABLE; + if (quest->GetYellowName() || quest->CheckCategoryYellow()) + display_status += QUEST_DISPLAY_STATUS_YELLOW; + + if (quest->IsTracked()) + display_status += QUEST_DISPLAY_STATUS_CHECK; + else + display_status += QUEST_DISPLAY_STATUS_NO_CHECK; + + if (quest->IsHidden() && !quest->GetTurnedIn()) { + display_status += QUEST_DISPLAY_STATUS_HIDDEN; + display_status -= QUEST_DISPLAY_STATUS_SHOW; + } + + if(quest->CanShareQuestCriteria(GetClient(),false)) { + display_status += QUEST_DISPLAY_STATUS_CAN_SHARE; + } + } + else + packet->setArrayDataByName("visible", quest->GetVisible()); + if (quest->IsRepeatable()) + packet->setArrayDataByName("repeatable", 1); + + packet->setArrayDataByName("display_status", display_status); + if (updated) { + packet->setArrayDataByName("quest_updated", 1); + packet->setArrayDataByName("journal_updated", 1); + } + if(version >= 546) + packet->setDataByName("unknown3", 1); + packet->setDataByName("visible_quest_id", quest->GetQuestID()); + packet->setDataByName("player_crc", crc); + packet->setDataByName("player_name", GetName()); + packet->setDataByName("used_quests", player_quests.size()); + packet->setDataByName("unknown4a", 1); + packet->setDataByName("max_quests", 75); + } + + return packet; +} + +Quest* Player::SetStepComplete(int32 id, int32 step){ + Quest* ret = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(id) > 0){ + if(player_quests[id] && player_quests[id]->SetStepComplete(step)) + ret = player_quests[id]; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +Quest* Player::AddStepProgress(int32 quest_id, int32 step, int32 progress) { + Quest* ret = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if (player_quests.count(quest_id) > 0) { + if (player_quests[quest_id] && player_quests[quest_id]->AddStepProgress(step, progress)) + ret = player_quests[quest_id]; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int32 Player::GetStepProgress(int32 quest_id, int32 step_id) { + int32 ret = 0; + + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if (player_quests.count(quest_id) > 0 && player_quests[quest_id]) + ret = player_quests[quest_id]->GetStepProgress(step_id); + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void Player::RemoveQuest(int32 id, bool delete_quest){ + MPlayerQuests.writelock(__FUNCTION__, __LINE__); + map::iterator itr = player_quests.find(id); + if(itr != player_quests.end()) { + player_quests.erase(itr); + } + + if(delete_quest){ + safe_delete(player_quests[id]); + } + + MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + SendQuestRequiredSpawns(id); +} + +vector* Player::CheckQuestsLocationUpdate(){ + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestLocationUpdate(GetX(), GetY(), GetZ(), (GetZone() ? GetZone()->GetZoneID() : 0))){ + if(!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +vector* Player::CheckQuestsFailures(){ + vector* quest_failures = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->GetQuestFailures()->size() > 0){ + if(!quest_failures) + quest_failures = new vector(); + quest_failures->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_failures; +} + +vector* Player::CheckQuestsKillUpdate(Spawn* spawn, bool update){ + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestKillUpdate(spawn, update)){ + if(!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +bool Player::HasQuestUpdateRequirement(Spawn* spawn){ + bool reqMet = false; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestReferencedSpawns(spawn)){ + reqMet = true; + break; + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return reqMet; +} + +vector* Player::CheckQuestsChatUpdate(Spawn* spawn){ + vector* quest_updates = 0; + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + if(itr->second && itr->second->CheckQuestChatUpdate(spawn->GetDatabaseID())){ + if(!quest_updates) + quest_updates = new vector(); + quest_updates->push_back(itr->second); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return quest_updates; +} + +int16 Player::GetTaskGroupStep(int32 quest_id){ + Quest* quest = 0; + int16 step = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0){ + quest = player_quests[quest_id]; + if(quest) { + step = quest->GetTaskGroupStep(); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return step; +} + +bool Player::GetQuestStepComplete(int32 quest_id, int32 step_id){ + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0){ + Quest* quest = player_quests[quest_id]; + if ( quest != NULL ) + ret = quest->GetQuestStepCompleted(step_id); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +int16 Player::GetQuestStep(int32 quest_id){ + Quest* quest = 0; + int16 step = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0){ + quest = player_quests[quest_id]; + if(quest) { + step = quest->GetQuestStep(); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return step; +} + +map* Player::GetPlayerQuests(){ + return &player_quests; +} + +map* Player::GetCompletedPlayerQuests(){ + return &completed_quests; +} + +Quest* Player::GetAnyQuest(int32 quest_id) { + if(player_quests.count(quest_id) > 0) + return player_quests[quest_id]; + if(completed_quests.count(quest_id) > 0) + return completed_quests[quest_id]; + + return 0; +} +Quest* Player::GetCompletedQuest(int32 quest_id){ + if(completed_quests.count(quest_id) > 0) + return completed_quests[quest_id]; + return 0; +} + +bool Player::HasQuestBeenCompleted(int32 quest_id){ + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(completed_quests.count(quest_id) > 0 && completed_quests[quest_id]) + ret = true; + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Player::HasActiveQuest(int32 quest_id){ + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0 && player_quests[quest_id]) + ret = true; + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool Player::HasAnyQuest(int32 quest_id){ + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quest_id) > 0) + ret = true; + if(completed_quests.count(quest_id) > 0) + ret = true; + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +int32 Player::GetQuestCompletedCount(int32 quest_id) { + int32 count = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetCompletedQuest(quest_id); + if(quest) { + count = quest->GetCompleteCount(); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return count; +} + +Quest* Player::GetQuest(int32 quest_id){ + if(player_quests.count(quest_id) > 0) + return player_quests[quest_id]; + return 0; +} + +void Player::AddCompletedQuest(Quest* quest){ + Quest* existing = GetCompletedQuest(quest->GetQuestID()); + MPlayerQuests.writelock(__FUNCTION__, __LINE__); + completed_quests[quest->GetQuestID()] = quest; + if(existing && existing != quest) { + safe_delete(existing); + } + + quest->SetSaveNeeded(true); + quest->SetTurnedIn(true); + if(quest->GetCompletedDescription()) + quest->SetDescription(string(quest->GetCompletedDescription())); + quest->SetUpdateRequired(true); + MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Player::CheckQuestRemoveFlag(Spawn* spawn){ + if(current_quest_flagged.count(spawn) > 0){ + current_quest_flagged.erase(spawn); + return true; + } + return false; +} + +bool Player::CheckQuestRequired(Spawn* spawn){ + if(spawn) + return spawn->MeetsSpawnAccessRequirements(this); + return false; +} + +int8 Player::CheckQuestFlag(Spawn* spawn){ + int8 ret = 0; + + if (!spawn) { + LogWrite(PLAYER__ERROR, 0, "Player", "CheckQuestFlag() called with an invalid spawn"); + return ret; + } + if(spawn->HasProvidedQuests()){ + vector* quests = spawn->GetProvidedQuests(); + Quest* quest = 0; + for(int32 i=0;isize();i++){ + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + if(player_quests.count(quests->at(i)) > 0){ + if(player_quests[quests->at(i)] && player_quests[quests->at(i)]->GetCompleted() && player_quests[quests->at(i)]->GetQuestReturnNPC() == spawn->GetDatabaseID()){ + ret = 2; + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + break; + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + int8 flag = 0; + if (CanReceiveQuest(quests->at(i), &flag)){ + if(flag) { + ret = flag; + break; + } + master_quest_list.LockQuests(); + quest = master_quest_list.GetQuest(quests->at(i), false); + master_quest_list.UnlockQuests(); + if(quest){ + int8 color = quest->GetFeatherColor(); + // purple + if (color == 1) + ret = 16; + // green + else if (color == 2) + ret = 32; + // blue + else if (color == 3) + ret = 64; + // normal + else + ret = 1; + break; + } + } + } + } + map::iterator itr; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for(itr = player_quests.begin(); itr != player_quests.end(); itr++){ + // must make sure the quest ptr is alive or nullptr + if(itr->second && itr->second->CheckQuestChatUpdate(spawn->GetDatabaseID(), false)) + ret = 2; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + if(ret > 0) + current_quest_flagged[spawn] = true; + return ret; +} + +bool Player::CanReceiveQuest(int32 quest_id, int8* ret){ + bool passed = true; + int32 x; + master_quest_list.LockQuests(); + Quest* quest = master_quest_list.GetQuest(quest_id, false); + master_quest_list.UnlockQuests(); + if (!quest) + passed = false; + //check if quest is already completed, and not repeatable + else if (HasQuestBeenCompleted(quest_id) && !quest->IsRepeatable()) + passed = false; + //check if the player already has this quest + else if (player_quests.count(quest_id) > 0) + passed = false; + //Check Prereq Adv Levels + else if (quest->GetPrereqLevel() > GetLevel()) + passed = false; + else if (quest->GetPrereqMaxLevel() > 0){ + if (GetLevel() > quest->GetPrereqMaxLevel()) + passed = false; + } + //Check Prereq TS Levels + else if (quest->GetPrereqTSLevel() > GetTSLevel()) + passed = false; + else if (quest->GetPrereqMaxTSLevel() > 0){ + if (GetTSLevel() > quest->GetPrereqMaxLevel()) + passed = false; + } + + + // Check quest pre req + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + vector* prereq_quests = quest->GetPrereqQuests(); + if(passed && prereq_quests && prereq_quests->size() > 0){ + for(int32 x=0;xsize();x++){ + if(completed_quests.count(prereq_quests->at(x)) == 0){ + passed = false; + break; + } + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + //Check Prereq Classes + vector* prereq_classes = quest->GetPrereqClasses(); + if(passed && prereq_classes && prereq_classes->size() > 0){ + for(int32 x=0;xsize();x++){ + if(prereq_classes->at(x) == GetAdventureClass()){ + passed = true; + break; + } + else + passed = false; + } + } + + //Check Prereq TS Classes + vector* prereq_tsclasses = quest->GetPrereqTradeskillClasses(); + if(passed && prereq_tsclasses && prereq_tsclasses->size() > 0){ + for( x=0;xsize();x++){ + if(prereq_tsclasses->at(x) == GetTradeskillClass()){ + passed = true; + break; + } + else + passed = false; + } + } + + + // Check model prereq + vector* prereq_model_types = quest->GetPrereqModelTypes(); + if(passed && prereq_model_types && prereq_model_types->size() > 0){ + for(x=0;xsize();x++){ + if(prereq_model_types->at(x) == GetModelType()){ + passed = true; + break; + } + else + passed = false; + } + } + + + // Check faction pre req + vector* prereq_factions = quest->GetPrereqFactions(); + if(passed && prereq_factions && prereq_factions->size() > 0){ + sint32 val = 0; + for(x=0;xsize();x++){ + val = GetFactions()->GetFactionValue(prereq_factions->at(x).faction_id); + if(val >= prereq_factions->at(x).min && (prereq_factions->at(x).max == 0 || val <= prereq_factions->at(x).max)){ + passed = true; + break; + } + else + passed = false; + } + } + + LogWrite(MISC__TODO, 1, "TODO", "Check prereq items\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + // Check race pre req + vector* prereq_races = quest->GetPrereqRaces(); + if(passed && prereq_races && prereq_races->size() > 0){ + for(x=0;xsize();x++){ + if(prereq_races->at(x) == GetRace()){ + passed = true; + break; + } + else + passed = false; + } + } + + int32 flag = 0; + if(lua_interface->CallQuestFunction(quest, "ReceiveQuestCriteria", this, 0xFFFFFFFF, &flag)) { + if(ret) + *ret = flag; + if(!flag) { + passed = false; + } + else { + passed = true; + } + } + + return passed; +} + +bool Player::UpdateQuestReward(int32 quest_id, QuestRewardData* qrd) { + if(!GetClient()) + return false; + + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + + if(!quest) { + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return false; + } + + quest->SetQuestTemporaryState(qrd->is_temporary, qrd->description); + if(qrd->is_temporary) { + quest->SetStatusTmpReward(qrd->tmp_status); + quest->SetCoinTmpReward(qrd->tmp_coin); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + GetClient()->GiveQuestReward(quest, qrd->has_displayed); + SetActiveReward(true); + + return true; +} + + +Quest* Player::PendingQuestAcceptance(int32 quest_id, int32 item_id, bool* quest_exists) { + vector* items = 0; + bool ret = false; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + if(!quest) { + if(quest_exists) { + *quest_exists = false; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return nullptr; + } + + if(quest_exists) { + *quest_exists = true; + } + if(quest->GetQuestTemporaryState()) + items = quest->GetTmpRewardItems(); + else + items = quest->GetRewardItems(); + if (item_id == 0) { + ret = true; + } + else { + items = quest->GetSelectableRewardItems(); + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) { + if (items->at(i)->details.item_id == item_id) { + ret = true; + break; + } + } + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return quest; +} + + +bool Player::AcceptQuestReward(int32 item_id, int32 selectable_item_id) { + if(!GetClient()) { + return false; + } + + Collection *collection = 0; + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = client->GetPendingQuestAcceptance(item_id); + if(quest){ + GetClient()->AcceptQuestReward(quest, item_id); + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return true; + } + bool collectedItems = false; + if (client->GetPlayer()->HasPendingItemRewards()) { + vector items = client->GetPlayer()->GetPendingItemRewards(); + if (items.size() > 0) { + collectedItems = true; + for (int i = 0; i < items.size(); i++) { + client->GetPlayer()->AddItem(new Item(items[i])); + } + client->GetPlayer()->ClearPendingItemRewards(); + client->GetPlayer()->SetActiveReward(false); + } + map selectable_item = client->GetPlayer()->GetPendingSelectableItemReward(item_id); + if (selectable_item.size() > 0) { + collectedItems = true; + map::iterator itr; + for (itr = selectable_item.begin(); itr != selectable_item.end(); itr++) { + client->GetPlayer()->AddItem(new Item(itr->second)); + client->GetPlayer()->ClearPendingSelectableItemRewards(itr->first); + } + client->GetPlayer()->SetActiveReward(false); + } + } + else if (collection = GetPendingCollectionReward()) + { + client->AcceptCollectionRewards(collection, (selectable_item_id > 0) ? selectable_item_id : item_id); + collectedItems = true; + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + return collectedItems; +} + + +bool Player::SendQuestStepUpdate(int32 quest_id, int32 quest_step_id, bool display_quest_helper) { + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + if(!quest) { + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + return false; + } + + QuestStep* quest_step = quest->GetQuestStep(quest_step_id); + if (quest_step) { + if(GetClient()) { + GetClient()->QueuePacket(quest->QuestJournalReply(GetClient()->GetVersion(), GetClient()->GetNameCRC(), this, quest_step, 1, false, false, display_quest_helper)); + } + quest_step->WasUpdated(false); + } + bool turnedIn = quest->GetTurnedIn(); + + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + if(turnedIn && GetClient()) //update the journal so the old quest isn't the one displayed in the client's quest helper + GetClient()->SendQuestJournal(); + + return true; +} + +void Player::SendQuest(int32 quest_id) { + if(!GetClient()) { + return; + } + + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetQuest(quest_id); + if (quest) + GetClient()->QueuePacket(quest->QuestJournalReply(GetClient()->GetVersion(), GetClient()->GetNameCRC(), this)); + else { + quest = GetCompletedQuest(quest_id); + + if (quest) + GetClient()->QueuePacket(quest->QuestJournalReply(GetClient()->GetVersion(), GetClient()->GetNameCRC(), this, 0, 1, true)); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); +} + + +void Player::UpdateQuestCompleteCount(int32 quest_id) { + if(!GetClient()) { + return; + } + + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + // If character has already completed this quest once update the given date in the database + Quest* quest = GetQuest(id); + Quest* quest2 = GetCompletedQuest(id); + if (quest && quest2) { + quest->SetCompleteCount(quest2->GetCompleteCount()); + database.SaveCharRepeatableQuest(GetClient(), id, quest->GetCompleteCount()); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::GetQuestTemporaryRewards(int32 quest_id, std::vector* items) { + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + if(quest) { + quest->GetTmpRewardItemsByID(items); + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::AddQuestTemporaryReward(int32 quest_id, int32 item_id, int16 item_count) { + MPlayerQuests.readlock(__FUNCTION__, __LINE__); + Quest* quest = GetAnyQuest(quest_id); + if(quest) { + Item* item = master_item_list.GetItem(item_id); + if(item) { + Item* tmpItem = new Item(item); + tmpItem->details.count = item_count; + quest->AddTmpRewardItem(tmpItem); + } + } + MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); +} + +bool Player::ShouldSendSpawn(Spawn* spawn){ + if(spawn->IsDeletedSpawn() || IsSendingSpawn(spawn->GetID()) || IsRemovingSpawn(spawn->GetID())) + return false; + else if((WasSentSpawn(spawn->GetID()) == false) && (!spawn->IsPrivateSpawn() || spawn->AllowedAccess(this))) + return true; + + return false; +} + +int8 Player::GetTSArrowColor(int8 level){ + int8 color = 0; + sint16 diff = level - GetTSLevel(); + if(GetLevel() < 10) + diff *= 3; + else if(GetLevel() <= 20) + diff *= 2; + if(diff >= 9) + color = ARROW_COLOR_RED; + else if(diff >= 5) + color = ARROW_COLOR_ORANGE; + else if(diff >= 1) + color = ARROW_COLOR_YELLOW; + else if(diff == 0) + color = ARROW_COLOR_WHITE; + else if(diff <= -11) + color = ARROW_COLOR_GRAY; + else if(diff <= -6) + color = ARROW_COLOR_GREEN; + else //if(diff < 0) + color = ARROW_COLOR_BLUE; + return color; +} + +void Player::AddCoins(int64 val){ + int32 tmp = 0; + UpdatePlayerStatistic(STAT_PLAYER_TOTAL_WEALTH, (GetCoinsCopper() + (GetCoinsSilver() * 100) + (GetCoinsGold() * 10000) + (GetCoinsPlat() * 1000000)) + val, true); + if(val >= 1000000){ + tmp = val / 1000000; + val -= tmp*1000000; + GetInfoStruct()->add_coin_plat(tmp); + } + if(val >= 10000){ + tmp = val / 10000; + val -= tmp*10000; + GetInfoStruct()->add_coin_gold(tmp); + } + if(val >= 100){ + tmp = val / 100; + val -= tmp*100; + GetInfoStruct()->add_coin_silver(tmp); + } + GetInfoStruct()->add_coin_copper(val); + int32 new_copper_value = GetInfoStruct()->get_coin_copper(); + if(new_copper_value >= 100){ + tmp = new_copper_value/100; + GetInfoStruct()->set_coin_copper(new_copper_value - (100 * tmp)); + GetInfoStruct()->add_coin_silver(tmp); + } + int32 new_silver_value = GetInfoStruct()->get_coin_silver(); + if(new_silver_value >= 100){ + tmp = new_silver_value/100; + GetInfoStruct()->set_coin_silver(new_silver_value - (100 * tmp)); + GetInfoStruct()->add_coin_gold(tmp); + } + int32 new_gold_value = GetInfoStruct()->get_coin_gold(); + if(new_gold_value >= 100){ + tmp = new_gold_value/100; + GetInfoStruct()->set_coin_gold(new_gold_value - (100 * tmp)); + GetInfoStruct()->add_coin_plat(tmp); + } + charsheet_changed = true; +} + +bool Player::RemoveCoins(int64 val){ + int64 total_coins = GetInfoStruct()->get_coin_plat()*1000000; + total_coins += GetInfoStruct()->get_coin_gold()*10000; + total_coins += GetInfoStruct()->get_coin_silver()*100; + total_coins += GetInfoStruct()->get_coin_copper(); + if(total_coins >= val){ + total_coins -= val; + GetInfoStruct()->set_coin_plat(0); + GetInfoStruct()->set_coin_gold(0); + GetInfoStruct()->set_coin_silver(0); + GetInfoStruct()->set_coin_copper(0); + AddCoins(total_coins); + return true; + } + return false; +} + +bool Player::HasCoins(int64 val) { + int64 total_coins = GetInfoStruct()->get_coin_plat()*1000000; + total_coins += GetInfoStruct()->get_coin_gold()*10000; + total_coins += GetInfoStruct()->get_coin_silver()*100; + total_coins += GetInfoStruct()->get_coin_copper(); + if(total_coins >= val) + return true; + + return false; +} + +bool Player::HasPendingLootItems(int32 id){ + return (pending_loot_items.count(id) > 0 && pending_loot_items[id].size() > 0); +} + +bool Player::HasPendingLootItem(int32 id, int32 item_id){ + return (pending_loot_items.count(id) > 0 && pending_loot_items[id].count(item_id) > 0); +} +vector* Player::GetPendingLootItems(int32 id){ + vector* ret = 0; + if(pending_loot_items.count(id) > 0){ + ret = new vector(); + map::iterator itr; + for(itr = pending_loot_items[id].begin(); itr != pending_loot_items[id].end(); itr++){ + if(master_item_list.GetItem(itr->first)) + ret->push_back(master_item_list.GetItem(itr->first)); + } + } + return ret; +} + +void Player::RemovePendingLootItem(int32 id, int32 item_id){ + if(pending_loot_items.count(id) > 0){ + pending_loot_items[id].erase(item_id); + if(pending_loot_items[id].size() == 0) + pending_loot_items.erase(id); + } +} + +void Player::RemovePendingLootItems(int32 id){ + if(pending_loot_items.count(id) > 0) + pending_loot_items.erase(id); +} + +void Player::AddPendingLootItems(int32 id, vector* items){ + if(items){ + Item* item = 0; + for(int32 i=0;isize();i++){ + item = items->at(i); + if(item) + pending_loot_items[id][item->details.item_id] = true; + } + } +} + +void Player::AddPlayerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date) { + if (statistics.count(stat_id) == 0) { + Statistic* stat = new Statistic; + stat->stat_id = stat_id; + stat->stat_value = stat_value; + stat->stat_date = stat_date; + stat->save_needed = false; + statistics[stat_id] = stat; + } +} + +void Player::UpdatePlayerStatistic(int32 stat_id, sint32 stat_value, bool overwrite) { + if (statistics.count(stat_id) == 0) + AddPlayerStatistic(stat_id, 0, 0); + Statistic* stat = statistics[stat_id]; + overwrite == true ? stat->stat_value = stat_value : stat->stat_value += stat_value; + stat->stat_date = Timer::GetUnixTimeStamp(); + stat->save_needed = true; +} + +void Player::WritePlayerStatistics() { + map::iterator stat_itr; + for (stat_itr = statistics.begin(); stat_itr != statistics.end(); stat_itr++) { + Statistic* stat = stat_itr->second; + if (stat->save_needed) { + stat->save_needed = false; + database.WritePlayerStatistic(this, stat); + } + } +} + +sint64 Player::GetPlayerStatisticValue(int32 stat_id) { + if (stat_id >= 0 && statistics.count(stat_id) > 0) + return statistics[stat_id]->stat_value; + return 0; +} + +void Player::RemovePlayerStatistics() { + map::iterator stat_itr; + for (stat_itr = statistics.begin(); stat_itr != statistics.end(); stat_itr++) + safe_delete(stat_itr->second); + statistics.clear(); +} + +void Player::SetGroup(PlayerGroup* new_group){ + group = new_group; +} + +/*PlayerGroup* Player::GetGroup(){ + return group; +}*/ + +bool Player::IsGroupMember(Entity* player) { + bool ret = false; + if (GetGroupMemberInfo() && player) { + //world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + ret = world.GetGroupManager()->IsInGroup(GetGroupMemberInfo()->group_id, player); + + /*deque::iterator itr; + deque* members = world.GetGroupManager()->GetGroupMembers(GetGroupMemberInfo()->group_id); + for (itr = members->begin(); itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client && gmi->client->GetPlayer() == player) { + ret = true; + break; + } + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);*/ + } + return ret; +} + + + + + +void Player::SetGroupInformation(PacketStruct* packet){ + int8 det_count = 0; + Entity* member = 0; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + if (GetGroupMemberInfo()) { + PlayerGroup* group = world.GetGroupManager()->GetGroup(GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + GroupMemberInfo* info = 0; + int x = 0; + + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + + if (info == GetGroupMemberInfo()) { + if (info->leader) + packet->setDataByName("group_leader_id", 0xFFFFFFFF); // If this player is the group leader then fill this element with FF FF FF FF + + continue; + } + else { + if (info->leader) + packet->setDataByName("group_leader_id", x); // If leader is some one else then fill with the slot number they are in + } + + member = info->member; + + if (member && member->GetZone() == GetZone()) { + packet->setSubstructDataByName("group_members", "spawn_id", GetIDWithPlayerSpawn(member), x); + + if (member->HasPet()) { + if (member->GetPet()) + packet->setSubstructDataByName("group_members", "pet_id", GetIDWithPlayerSpawn(member->GetPet()), x); + else + packet->setSubstructDataByName("group_members", "pet_id", GetIDWithPlayerSpawn(member->GetCharmedPet()), x); + } + else + packet->setSubstructDataByName("group_members", "pet_id", 0xFFFFFFFF, x); + + //Send detriment counts as 255 if all dets of that type are incurable + det_count = member->GetTraumaCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_TRAUMA)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "trauma_count", det_count, x); + + det_count = member->GetArcaneCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_ARCANE)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "arcane_count", det_count, x); + + det_count = member->GetNoxiousCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_NOXIOUS)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "noxious_count", det_count, x); + + det_count = member->GetElementalCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_ELEMENTAL)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "elemental_count", det_count, x); + + det_count = member->GetCurseCount(); + if (det_count > 0) { + if (!member->HasCurableDetrimentType(DET_TYPE_CURSE)) + det_count = 255; + } + packet->setSubstructDataByName("group_members", "curse_count", det_count, x); + + packet->setSubstructDataByName("group_members", "zone_status", 1, x); + } + else { + packet->setSubstructDataByName("group_members", "pet_id", 0xFFFFFFFF, x); + //packet->setSubstructDataByName("group_members", "unknown5", 1, x, 1); // unknown5 > 1 = name is blue + packet->setSubstructDataByName("group_members", "zone_status", 2, x); + } + + packet->setSubstructDataByName("group_members", "name", info->name.c_str(), x); + packet->setSubstructDataByName("group_members", "hp_current", info->hp_current, x); + packet->setSubstructDataByName("group_members", "hp_max", info->hp_max, x); + packet->setSubstructDataByName("group_members", "power_current", info->power_current, x); + packet->setSubstructDataByName("group_members", "power_max", info->power_max, x); + packet->setSubstructDataByName("group_members", "level_current", info->level_current, x); + packet->setSubstructDataByName("group_members", "level_max", info->level_max, x); + packet->setSubstructDataByName("group_members", "zone", info->zone.c_str(), x); + packet->setSubstructDataByName("group_members", "race_id", info->race_id, x); + packet->setSubstructDataByName("group_members", "class_id", info->class_id, x); + + x++; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + //packet->PrintPacket(); + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); +} + +PlayerItemList* Player::GetPlayerItemList(){ + return &item_list; +} + +void Player::ResetSavedSpawns(){ + spawn_mutex.writelock(__FUNCTION__, __LINE__); + ClearRemovalTimers(); + spawn_packet_sent.clear(); + spawn_mutex.releasewritelock(__FUNCTION__, __LINE__); + + info_mutex.writelock(__FUNCTION__, __LINE__); + spawn_info_packet_list.clear(); + info_mutex.releasewritelock(__FUNCTION__, __LINE__); + + vis_mutex.writelock(__FUNCTION__, __LINE__); + spawn_vis_packet_list.clear(); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + pos_mutex.writelock(__FUNCTION__, __LINE__); + spawn_pos_packet_list.clear(); + pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + + index_mutex.writelock(__FUNCTION__, __LINE__); + player_spawn_reverse_id_map.clear(); + player_spawn_id_map.clear(); + player_spawn_id_map[1] = this; + player_spawn_reverse_id_map[this] = 1; + spawn_index = 1; + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__); + player_spawn_quests_required.clear(); + m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__); + if(info) + info->RemoveOldPackets(); + + safe_delete_array(movement_packet); + safe_delete_array(old_movement_packet); +} + +void Player::SetReturningFromLD(bool val){ + std::unique_lock lock(spell_packet_update_mutex); + if(val && val != returning_from_ld) + { + if(GetPlayerItemList()) + GetPlayerItemList()->ResetPackets(); + + GetEquipmentList()->ResetPackets(); + GetAppearanceEquipmentList()->ResetPackets(); + skill_list.ResetPackets(); + safe_delete_array(spell_orig_packet); + safe_delete_array(spell_xor_packet); + spell_orig_packet=0; + spell_xor_packet=0; + spell_count = 0; + + reset_character_flag(CF_IS_SITTING); + if (GetActivityStatus() & ACTIVITY_STATUS_CAMPING) + SetActivityStatus(GetActivityStatus() - ACTIVITY_STATUS_CAMPING); + + if (GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) + SetActivityStatus(GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD); + + SetTempVisualState(0); + + safe_delete_array(spawn_tmp_info_xor_packet); + safe_delete_array(spawn_tmp_vis_xor_packet); + safe_delete_array(spawn_tmp_pos_xor_packet); + spawn_tmp_info_xor_packet = 0; + spawn_tmp_vis_xor_packet = 0; + spawn_tmp_pos_xor_packet = 0; + pos_xor_size = 0; + info_xor_size = 0; + vis_xor_size = 0; + + index_mutex.writelock(__FUNCTION__, __LINE__); + player_spawn_id_map[1] = this; + player_spawn_reverse_id_map[this] = 1; + spawn_index = 1; + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + } + + returning_from_ld = val; +} + +bool Player::IsReturningFromLD(){ + return returning_from_ld; +} + +void Player::AddFriend(const char* name, bool save){ + if(name){ + if(save) + friend_list[name] = 1; + else + friend_list[name] = 0; + } +} + +bool Player::IsFriend(const char* name){ + if(name && friend_list.count(name) > 0 && friend_list[name] < 2) + return true; + return false; +} + +void Player::RemoveFriend(const char* name){ + if(friend_list.count(name) > 0) + friend_list[name] = 2; +} + +map* Player::GetFriends(){ + return &friend_list; +} + +void Player::AddIgnore(const char* name, bool save){ + if(name){ + if(save) + ignore_list[name] = 1; + else + ignore_list[name] = 0; + } +} + +bool Player::IsIgnored(const char* name){ + if(name && ignore_list.count(name) > 0 && ignore_list[name] < 2) + return true; + return false; +} + +void Player::RemoveIgnore(const char* name){ + if(name && ignore_list.count(name) > 0) + ignore_list[name] = 2; +} + +map* Player::GetIgnoredPlayers(){ + return &ignore_list; +} + +bool Player::CheckLevelStatus(int16 new_level) { + int16 LevelCap = rule_manager.GetGlobalRule(R_Player, MaxLevel)->GetInt16(); + int16 LevelCapOverrideStatus = rule_manager.GetGlobalRule(R_Player, MaxLevelOverrideStatus)->GetInt16(); + if ( GetClient() && (LevelCap < new_level) && (LevelCapOverrideStatus > GetClient()->GetAdminStatus()) ) + return false; + return true; +} + +Skill* Player::GetSkillByName(const char* name, bool check_update){ + Skill* ret = skill_list.GetSkillByName(name); + if(check_update) + { + if(skill_list.CheckSkillIncrease(ret)) + CalculateBonuses(); + } + return ret; +} + +Skill* Player::GetSkillByID(int32 id, bool check_update){ + Skill* ret = skill_list.GetSkill(id); + if(check_update) + { + if(skill_list.CheckSkillIncrease(ret)) + CalculateBonuses(); + } + return ret; +} + +void Player::SetRangeAttack(bool val){ + range_attack = val; +} + +bool Player::GetRangeAttack(){ + return range_attack; +} + +bool Player::AddMail(Mail* mail) { + bool ret = false; + if (mail) { + mail_list.Put(mail->mail_id, mail); + ret = true; + } + return ret; +} + +MutexMap* Player::GetMail() { + return &mail_list; +} + +Mail* Player::GetMail(int32 mail_id) { + Mail* mail = 0; + if (mail_list.count(mail_id) > 0) + mail = mail_list.Get(mail_id); + return mail; +} + +void Player::DeleteMail(bool from_database) { + MutexMap::iterator itr = mail_list.begin(); + while (itr.Next()) + DeleteMail(itr->first, from_database); + mail_list.clear(); +} + +void Player::DeleteMail(int32 mail_id, bool from_database) { + if (mail_list.count(mail_id) > 0) { + if (from_database) + database.DeletePlayerMail(mail_list.Get(mail_id)); + mail_list.erase(mail_id, true, true); // need to delete the mail ptr + } +} + +ZoneServer* Player::GetGroupMemberInZone(int32 zone_id) { + ZoneServer* ret = nullptr; + + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + // If the player has a group and destination zone id + if (gmi && zone_id) { + deque::iterator itr; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + // Loop through the group members + for (itr = members->begin(); itr != members->end(); itr++) { + // If a group member matches a target + if ((*itr)->member && (*itr)->member != this && (*itr)->member->GetZone() && (*itr)->member->GetZone()->GetInstanceID() > 0 && + (*itr)->member->GetZone()->GetZoneID() == zone_id) { + // toggle the flag and break the loop + ret = (*itr)->member->GetZone(); + break; + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + return ret; +} + + +/* CharacterInstances */ + +CharacterInstances::CharacterInstances() { + m_instanceList.SetName("Mutex::m_instanceList"); +} + +CharacterInstances::~CharacterInstances() { + RemoveInstances(); +} + +void CharacterInstances::AddInstance(int32 db_id, int32 instance_id, int32 last_success_timestamp, int32 last_failure_timestamp, int32 success_lockout_time, int32 failure_lockout_time, int32 zone_id, int8 zone_instancetype, string zone_name) { + InstanceData data; + data.db_id = db_id; + data.instance_id = instance_id; + data.zone_id = zone_id; + data.zone_instance_type = zone_instancetype; + data.zone_name = zone_name; + data.last_success_timestamp = last_success_timestamp; + data.last_failure_timestamp = last_failure_timestamp; + data.success_lockout_time = success_lockout_time; + data.failure_lockout_time = failure_lockout_time; + instanceList.push_back(data); +} + +void CharacterInstances::RemoveInstances() { + instanceList.clear(); +} + +bool CharacterInstances::RemoveInstanceByZoneID(int32 zone_id) { + vector::iterator itr; + m_instanceList.writelock(__FUNCTION__, __LINE__); + for(itr = instanceList.begin(); itr != instanceList.end(); itr++) { + InstanceData* data = &(*itr); + if (data->zone_id == zone_id) { + instanceList.erase(itr); + m_instanceList.releasewritelock(__FUNCTION__, __LINE__); + return true; + } + } + m_instanceList.releasewritelock(__FUNCTION__, __LINE__); + return false; +} + +bool CharacterInstances::RemoveInstanceByInstanceID(int32 instance_id) { + vector::iterator itr; + m_instanceList.writelock(__FUNCTION__, __LINE__); + for(itr = instanceList.begin(); itr != instanceList.end(); itr++) { + InstanceData* data = &(*itr); + if (data->instance_id == instance_id) { + instanceList.erase(itr); + m_instanceList.releasewritelock(__FUNCTION__, __LINE__); + return true; + } + } + m_instanceList.releasewritelock(__FUNCTION__, __LINE__); + return false; +} + +InstanceData* CharacterInstances::FindInstanceByZoneID(int32 zone_id) { + m_instanceList.readlock(__FUNCTION__, __LINE__); + for(int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->zone_id == zone_id) { + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return data; + } + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + + return 0; +} + +InstanceData* CharacterInstances::FindInstanceByDBID(int32 db_id) { + m_instanceList.readlock(__FUNCTION__, __LINE__); + for(int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->db_id == db_id) { + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return data; + } + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + + return 0; +} + +InstanceData* CharacterInstances::FindInstanceByInstanceID(int32 instance_id) { + m_instanceList.readlock(__FUNCTION__, __LINE__); + for(int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->instance_id == instance_id) { + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return data; + } + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + + return 0; +} +vector CharacterInstances::GetLockoutInstances() { + vector ret; + m_instanceList.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->zone_instance_type == SOLO_LOCKOUT_INSTANCE || data->zone_instance_type == GROUP_LOCKOUT_INSTANCE || data->zone_instance_type == RAID_LOCKOUT_INSTANCE) + ret.push_back(*data); + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +vector CharacterInstances::GetPersistentInstances() { + vector ret; + m_instanceList.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + if (data->zone_instance_type == SOLO_PERSIST_INSTANCE || data->zone_instance_type == GROUP_PERSIST_INSTANCE || data->zone_instance_type == RAID_PERSIST_INSTANCE) + ret.push_back(*data); + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void CharacterInstances::ProcessInstanceTimers(Player* player) { + + // Only need to check persistent instances here, lockout should be handled by zone shutting down + + // Check instance id, if > 0 check timers, if timers expired set instance id to 0 and update the db `character_instance` to instance id 0, + // delete instance from `instances` if no more characters assigned to it + + m_instanceList.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < instanceList.size(); i++) { + InstanceData* data = &instanceList.at(i); + + // Check to see if we have a valid instance and if it is persistant + if (data->instance_id > 0) { + + if (data->zone_instance_type == SOLO_PERSIST_INSTANCE || data->zone_instance_type == GROUP_PERSIST_INSTANCE || data->zone_instance_type == RAID_PERSIST_INSTANCE) { + // Check max duration (last success + success time) + if (Timer::GetUnixTimeStamp() >= (data->last_success_timestamp + data->success_lockout_time)) { + // Max duration has passed, instance has expired lets remove the player from it, + // this will also delete the instace if all players have been removed from it + database.DeleteCharacterFromInstance(player->GetCharacterID(), data->instance_id); + data->instance_id = 0; + } + } + + if (data->zone_instance_type == SOLO_LOCKOUT_INSTANCE || data->zone_instance_type == GROUP_LOCKOUT_INSTANCE || data->zone_instance_type == RAID_LOCKOUT_INSTANCE) { + // Need to check lockout instance ids to ensure they are still valid. + if (!database.VerifyInstanceID(player->GetCharacterID(), data->instance_id)) + data->instance_id = 0; + } + } + } + m_instanceList.releasereadlock(__FUNCTION__, __LINE__); + + /*for(int32 i=0;isize();i++) + { + bool valuesUpdated = false; + InstanceData data = instanceList->at(i); + if ( data.grant_reenter_time_left > 0 ) + { + if ( ( data.grant_reenter_time_left - msDiff ) < 1 ) + data.grant_reenter_time_left = 0; + else + data.grant_reenter_time_left -= msDiff; + + valuesUpdated = true; + } + if ( data.grant_reset_time_left > 0 ) + { + if ( ( data.grant_reset_time_left - msDiff ) < 1 ) + data.grant_reset_time_left = 0; + else + data.grant_reset_time_left -= msDiff; + + valuesUpdated = true; + } + if ( data.lockout_time > 0 ) + { + if ( ( data.lockout_time - msDiff ) < 1 ) + { + data.lockout_time = 0; + // this means that their timer ran out and we need to clear it from db and player + RemoveInstanceByInstanceID(data.instance_id); + database.DeleteCharacterFromInstance(player->GetCharacterID(),data.instance_id); + } + else + data.lockout_time -= msDiff; + + valuesUpdated = true; + } + + if ( valuesUpdated ) + database.UpdateCharacterInstanceTimers(player->GetCharacterID(),data.instance_id,data.lockout_time,data.grant_reset_time_left,data.grant_reenter_time_left); + }*/ +} + +int32 CharacterInstances::GetInstanceCount() { + return instanceList.size(); +} + +void Player::SetPlayerAdventureClass(int8 new_class){ + SetAdventureClass(new_class); + GetInfoStruct()->set_class1(classes.GetBaseClass(new_class)); + GetInfoStruct()->set_class2(classes.GetSecondaryBaseClass(new_class)); + GetInfoStruct()->set_class3(new_class); + charsheet_changed = true; + if(GetZone()) + GetZone()->TriggerCharSheetTimer(); +} + +void Player::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { + GetSkills()->AddSkillBonus(spell_id, skill_id, value); +} + +SkillBonus* Player::GetSkillBonus(int32 spell_id) { + return GetSkills()->GetSkillBonus(spell_id); +} + +void Player::RemoveSkillBonus(int32 spell_id) { + GetSkills()->RemoveSkillBonus(spell_id); +} + +bool Player::HasFreeBankSlot() { + return item_list.HasFreeBankSlot(); +} + +int8 Player::FindFreeBankSlot() { + return item_list.FindFreeBankSlot(); +} + +void Player::AddTitle(sint32 title_id, const char *name, int8 prefix, bool save_needed){ + Title* new_title = new Title; + new_title->SetID(title_id); + new_title->SetName(name); + new_title->SetPrefix(prefix); + new_title->SetSaveNeeded(save_needed); + player_titles_list.Add(new_title); +} + +void Player::AddAAEntry(int16 template_id, int8 tab_id, int32 aa_id, int16 order,int8 treeid) { + + + +} +void Player::AddLanguage(int32 id, const char *name, bool save_needed){ + Skill* skill = master_skill_list.GetSkillByName(name); + if(skill && !GetSkills()->HasSkill(skill->skill_id)) { + AddSkill(skill->skill_id, 1, skill->max_val, true); + } + // Check to see if the player already has the language + if (HasLanguage(id)) + return; + + // Doesn't already have the language so add it + Language* new_language = new Language; + new_language->SetID(id); + new_language->SetName(name); + player_languages_list.Add(new_language); + + if (save_needed) + database.SaveCharacterLang(GetCharacterID(), id); +} + +bool Player::HasLanguage(int32 id){ + list* languages = player_languages_list.GetAllLanguages(); + list::iterator itr; + Language* language = 0; + bool ret = false; + for(itr = languages->begin(); itr != languages->end(); itr++){ + language = *itr; + if(language->GetID() == id){ + ret = true; + break; + } + } + return ret; +} + +bool Player::HasLanguage(const char* name){ + list* languages = player_languages_list.GetAllLanguages(); + list::iterator itr; + Language* language = 0; + bool ret = false; + for(itr = languages->begin(); itr != languages->end(); itr++){ + language = *itr; + if(!strncmp(language->GetName(), name, strlen(name))){ + ret = true; + break; + } + } + return ret; +} + +void Player::AddPassiveSpell(int32 id, int8 tier) +{ + // Add the spell to the list of passives this player currently has + // list is used for quickly going over only the passive spells the + // player has instead of going through all their spells. + passive_spells.push_back(id); + + Client* client = GetClient(); + + // Don not apply passives if the client is null, zoning, or reviving + if (client == NULL || client->IsZoning() || IsResurrecting()) + return; + + Spell* spell = 0; + spell = master_spell_list.GetSpell(id, tier); + if (spell) { + SpellProcess* spellProcess = 0; + // Get the current zones spell process + spellProcess = GetZone()->GetSpellProcess(); + // Cast the spell, CastPassives() bypasses the spell queue + spellProcess->CastPassives(spell, this); + } +} + +void Player::ApplyPassiveSpells() +{ + Spell* spell = 0; + SpellBookEntry* spell2 = 0; + vector::iterator itr; + SpellProcess* spellProcess = 0; + spellProcess = GetZone()->GetSpellProcess(); + + for (itr = passive_spells.begin(); itr != passive_spells.end(); itr++) + { + spell2 = GetSpellBookSpell((*itr)); + spell = master_spell_list.GetSpell(spell2->spell_id, spell2->tier); + if (spell) { + spellProcess->CastPassives(spell, this); + } + } +} + +void Player::RemovePassive(int32 id, int8 tier, bool remove_from_list) +{ + Spell* spell = 0; + spell = master_spell_list.GetSpell(id, tier); + if (spell) { + SpellProcess* spellProcess = 0; + spellProcess = GetZone()->GetSpellProcess(); + spellProcess->CastPassives(spell, this, true); + + if (remove_from_list) { + vector::iterator remove; + remove = find(passive_spells.begin(), passive_spells.end(), id); + if (remove != passive_spells.end()) + passive_spells.erase(remove); + } + + database.DeleteCharacterSpell(GetCharacterID(), spell->GetSpellID()); + } +} + +void Player::RemoveAllPassives() +{ + vector::iterator itr; + for (itr = passive_spells.begin(); itr != passive_spells.end(); itr++) + RemoveSpellBookEntry((*itr), false); + + passive_spells.clear(); +} + +void Player::ResetPetInfo() { + GetInfoStruct()->set_pet_id(0xFFFFFFFF); + GetInfoStruct()->set_pet_movement(0); + GetInfoStruct()->set_pet_behavior(0); + GetInfoStruct()->set_pet_health_pct(0.0f); + GetInfoStruct()->set_pet_power_pct(0.0f); + // Make sure the values get sent to the client + SetCharSheetChanged(true); +} + +bool Player::HasRecipeBook(int32 recipe_id){ + return recipebook_list.HasRecipeBook(recipe_id); +} + +bool Player::DiscoveredLocation(int32 locationID) { + bool ret = false; + + // No discovery type entry then return false + if (m_characterHistory.count(HISTORY_TYPE_DISCOVERY) == 0) + return false; + + // Is a discovery type entry but not a location subtype return false + if (m_characterHistory[HISTORY_TYPE_DISCOVERY].count(HISTORY_SUBTYPE_LOCATION) == 0) + return false; + + vector::iterator itr; + + for (itr = m_characterHistory[HISTORY_TYPE_DISCOVERY][HISTORY_SUBTYPE_LOCATION].begin(); itr != m_characterHistory[HISTORY_TYPE_DISCOVERY][HISTORY_SUBTYPE_LOCATION].end(); itr++) { + if ((*itr)->Value == locationID) { + ret = true; + break; + } + } + + return ret; +} + +void Player::UpdatePlayerHistory(int8 type, int8 subtype, int32 value, int32 value2) { + switch (type) { + case HISTORY_TYPE_NONE: + HandleHistoryNone(subtype, value, value2); + break; + case HISTORY_TYPE_DEATH: + HandleHistoryDeath(subtype, value, value2); + break; + case HISTORY_TYPE_DISCOVERY: + HandleHistoryDiscovery(subtype, value, value2); + break; + case HISTORY_TYPE_XP: + HandleHistoryXP(subtype, value, value2); + break; + default: + // Not a valid history event so return out before trying to save into the db + return; + } +} + +void Player::HandleHistoryNone(int8 subtype, int32 value, int32 value2) { +} + +void Player::HandleHistoryDeath(int8 subtype, int32 value, int32 value2) { +} + +void Player::HandleHistoryDiscovery(int8 subtype, int32 value, int32 value2) { + switch (subtype) { + case HISTORY_SUBTYPE_NONE: + break; + case HISTORY_SUBTYPE_ADVENTURE: + break; + case HISTORY_SUBTYPE_TRADESKILL: + break; + case HISTORY_SUBTYPE_QUEST: + break; + case HISTORY_SUBTYPE_AA: + break; + case HISTORY_SUBTYPE_ITEM: + break; + case HISTORY_SUBTYPE_LOCATION: { + HistoryData* hd = new HistoryData; + hd->Value = value; + hd->Value2 = value2; + hd->EventDate = Timer::GetUnixTimeStamp(); + strcpy(hd->Location, GetZone()->GetZoneName()); + hd->needs_save = true; + + m_characterHistory[HISTORY_TYPE_DISCOVERY][HISTORY_SUBTYPE_LOCATION].push_back(hd); + break; + } + default: + // Invalid subtype, default to NONE + break; + } +} + +void Player::HandleHistoryXP(int8 subtype, int32 value, int32 value2) { + switch (subtype) { + case HISTORY_SUBTYPE_NONE: + break; + case HISTORY_SUBTYPE_ADVENTURE: { + HistoryData* hd = new HistoryData; + hd->Value = value; + hd->Value2 = value2; + hd->EventDate = Timer::GetUnixTimeStamp(); + strcpy(hd->Location, GetZone()->GetZoneName()); + hd->needs_save = true; + + m_characterHistory[HISTORY_TYPE_XP][HISTORY_SUBTYPE_ADVENTURE].push_back(hd); + } + break; + case HISTORY_SUBTYPE_TRADESKILL: + break; + case HISTORY_SUBTYPE_QUEST: + break; + case HISTORY_SUBTYPE_AA: + break; + case HISTORY_SUBTYPE_ITEM: + break; + case HISTORY_SUBTYPE_LOCATION: + break; + default: + // Invalid subtype, default to NONE + break; + } +} + +void Player::LoadPlayerHistory(int8 type, int8 subtype, HistoryData* hd) { + m_characterHistory[type][subtype].push_back(hd); +} + +void Player::SaveHistory() { + LogWrite(PLAYER__DEBUG, 0, "Player", "Saving History for Player: '%s'", GetName()); + + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + for (itr = m_characterHistory.begin(); itr != m_characterHistory.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) { + + if((*itr3)->needs_save) { + database.SaveCharacterHistory(this, itr->first, itr2->first, (*itr3)->Value, (*itr3)->Value2, (*itr3)->Location, (*itr3)->EventDate); + (*itr3)->needs_save = false; + } + } + } + } +} + +void Player::InitXPTable() { + int i = 2; + while (i >= 2 && i <= 95) { + m_levelXPReq[i] = database.GetMysqlExpCurve(i); + i++; + } +} + +void Player::SendQuestRequiredSpawns(int32 quest_id){ + bool locked = true; + m_playerSpawnQuestsRequired.readlock(__FUNCTION__, __LINE__); + if (player_spawn_quests_required.size() > 0 ) { + ZoneServer* zone = GetZone(); + Client* client = GetClient(); + if (!client){ + m_playerSpawnQuestsRequired.releasereadlock(__FUNCTION__, __LINE__); + return; + } + int xxx = player_spawn_quests_required.count(quest_id); + if (player_spawn_quests_required.count(quest_id) > 0){ + vector spawns = *player_spawn_quests_required[quest_id]; + m_playerSpawnQuestsRequired.releasereadlock(__FUNCTION__, __LINE__); + Spawn* spawn = 0; + vector::iterator itr; + for (itr = spawns.begin(); itr != spawns.end();){ + spawn = zone->GetSpawnByID(*itr); + if (spawn) + zone->SendSpawnChanges(spawn, client, false, true); + else { + itr = spawns.erase(itr); + continue; + } + itr++; + } + locked = false; + } + } + if (locked) + m_playerSpawnQuestsRequired.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::SendHistoryRequiredSpawns(int32 event_id){ + bool locked = true; + m_playerSpawnHistoryRequired.readlock(__FUNCTION__, __LINE__); + if (player_spawn_history_required.size() > 0) { + ZoneServer* zone = GetZone(); + Client* client = GetClient(); + if (!client){ + m_playerSpawnHistoryRequired.releasereadlock(__FUNCTION__, __LINE__); + return; + } + if (player_spawn_history_required.count(event_id) > 0){ + vector spawns = *player_spawn_history_required[event_id]; + m_playerSpawnHistoryRequired.releasereadlock(__FUNCTION__, __LINE__); + Spawn* spawn = 0; + vector::iterator itr; + for (itr = spawns.begin(); itr != spawns.end();){ + spawn = zone->GetSpawnByID(*itr); + if (spawn) + zone->SendSpawnChanges(spawn, client, false, true); + else { + itr = spawns.erase(itr); + continue; + } + itr++; + } + locked = false; + } + } + if (locked) + m_playerSpawnHistoryRequired.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::AddQuestRequiredSpawn(Spawn* spawn, int32 quest_id){ + if(!spawn || !quest_id) + return; + m_playerSpawnQuestsRequired.writelock(__FUNCTION__, __LINE__); + if(player_spawn_quests_required.count(quest_id) == 0) + player_spawn_quests_required[quest_id] = new vector; + vector* quest_spawns = player_spawn_quests_required[quest_id]; + int32 current_spawn = 0; + for(int32 i=0;isize();i++){ + current_spawn = quest_spawns->at(i); + if (current_spawn == spawn->GetID()){ + m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__); + return; + } + } + player_spawn_quests_required[quest_id]->push_back(spawn->GetID()); + m_playerSpawnQuestsRequired.releasewritelock(__FUNCTION__, __LINE__); +} + +void Player::AddHistoryRequiredSpawn(Spawn* spawn, int32 event_id){ + if (!spawn || !event_id) + return; + m_playerSpawnHistoryRequired.writelock(__FUNCTION__, __LINE__); + if (player_spawn_history_required.count(event_id) == 0) + player_spawn_history_required[event_id] = new vector; + vector* history_spawns = player_spawn_history_required[event_id]; + vector::iterator itr = find(history_spawns->begin(), history_spawns->end(), spawn->GetID()); + if (itr == history_spawns->end()) + history_spawns->push_back(spawn->GetID()); + m_playerSpawnHistoryRequired.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 PlayerInfo::GetBoatSpawn(){ + return boat_spawn; +} + +void PlayerInfo::SetBoatSpawn(Spawn* spawn){ + if(spawn) + boat_spawn = spawn->GetID(); + else + boat_spawn = 0; +} + +void PlayerInfo::RemoveOldPackets(){ + safe_delete_array(changes); + safe_delete_array(orig_packet); + safe_delete_array(pet_changes); + safe_delete_array(pet_orig_packet); + changes = 0; + orig_packet = 0; + pet_changes = 0; + pet_orig_packet = 0; +} + +PlayerControlFlags::PlayerControlFlags(){ + MControlFlags.SetName("PlayerControlFlags::MControlFlags"); + MFlagChanges.SetName("PlayerControlFlags::MFlagChanges"); + flags_changed = false; + flag_changes.clear(); + current_flags.clear(); +} + +PlayerControlFlags::~PlayerControlFlags(){ + flag_changes.clear(); + current_flags.clear(); +} + +bool PlayerControlFlags::ControlFlagsChanged(){ + bool ret; + MFlagChanges.writelock(__FUNCTION__, __LINE__); + ret = flags_changed; + MFlagChanges.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +void PlayerControlFlags::SetPlayerControlFlag(int8 param, int8 param_value, bool is_active){ + if (!param || !param_value) + return; + + bool active_changed = false; + MControlFlags.writelock(__FUNCTION__, __LINE__); + active_changed = (current_flags[param][param_value] != is_active); + if (active_changed){ + current_flags[param][param_value] = is_active; + MFlagChanges.writelock(__FUNCTION__, __LINE__); + flag_changes[param][param_value] = is_active ? 1 : 0; + flags_changed = true; + MFlagChanges.releasewritelock(__FUNCTION__, __LINE__); + } + MControlFlags.releasewritelock(__FUNCTION__, __LINE__); +} + +void PlayerControlFlags::SendControlFlagUpdates(Client* client){ + if (!client) + return; + + map* ptr = 0; + map >::iterator itr; + map::iterator itr2; + + MFlagChanges.writelock(__FUNCTION__, __LINE__); + for (itr = flag_changes.begin(); itr != flag_changes.end(); itr++){ + ptr = &itr->second; + for (itr2 = ptr->begin(); itr2 != ptr->end(); itr2++){ + int32 param = itr2->first; + if(client->GetVersion() <= 561) { + if(itr->first == 1) { // first set of flags DoF only supports these + bool skip = false; + switch(itr2->first) { + case 1: // flymode for DoF + case 2: // no clip mode for DoF + case 4: // we don't know + case 32: { // safe fall (DoF is low gravity for this parameter) + skip = true; + break; + } + } + + if(skip) { + continue; + } + } + + bool bypassFlag = true; + // remap control effects to old DoF from AoM + switch(itr->first) { + case 4: { + if(itr2->first == 64) { // stun + ClientPacketFunctions::SendServerControlFlagsClassic(client, 8, itr2->second); + param = 16; + bypassFlag = false; + } + break; + } + } + if(itr->first > 1 && bypassFlag) { + continue; // we don't support flag sets higher than 1 for DoF + } + ClientPacketFunctions::SendServerControlFlagsClassic(client, param, itr2->second); + } + else { + ClientPacketFunctions::SendServerControlFlags(client, itr->first, itr2->first, itr2->second); + } + } + } + flag_changes.clear(); + flags_changed = false; + MFlagChanges.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Player::ControlFlagsChanged(){ + return control_flags.ControlFlagsChanged(); +} + +void Player::SetPlayerControlFlag(int8 param, int8 param_value, bool is_active){ + control_flags.SetPlayerControlFlag(param, param_value, is_active); +} + +void Player::SendControlFlagUpdates(Client* client){ + control_flags.SendControlFlagUpdates(client); +} + +void Player::LoadLUAHistory(int32 event_id, LUAHistory* history) { + mLUAHistory.writelock(); + if (m_charLuaHistory.count(event_id) > 0) { + LogWrite(PLAYER__ERROR, 0, "Player", "Attempted to added a dupicate event (%u) to character LUA history", event_id); + safe_delete(history); + mLUAHistory.releasewritelock(); + return; + } + + m_charLuaHistory.insert(make_pair(event_id,history)); + mLUAHistory.releasewritelock(); +} + +void Player::SaveLUAHistory() { + mLUAHistory.readlock(); + LogWrite(PLAYER__DEBUG, 0, "Player", "Saving LUA History for Player: '%s'", GetName()); + + map::iterator itr; + for (itr = m_charLuaHistory.begin(); itr != m_charLuaHistory.end(); itr++) { + if (itr->second->SaveNeeded) { + database.SaveCharacterLUAHistory(this, itr->first, itr->second->Value, itr->second->Value2); + itr->second->SaveNeeded = false; + } + } + mLUAHistory.releasereadlock(); +} + +void Player::UpdateLUAHistory(int32 event_id, int32 value, int32 value2) { + mLUAHistory.writelock(); + LUAHistory* hd = 0; + + if (m_charLuaHistory.count(event_id) > 0) + hd = m_charLuaHistory[event_id]; + else { + hd = new LUAHistory; + m_charLuaHistory.insert(make_pair(event_id,hd)); + } + + hd->Value = value; + hd->Value2 = value2; + hd->SaveNeeded = true; + mLUAHistory.releasewritelock(); + // release the mLUAHistory lock, we will maintain a readlock to avoid any further writes until we complete SendHistoryRequiredSpawns + // through Spawn::SendSpawnChanges -> Spawn::InitializeVisPacketData -> Spawn::MeetsSpawnAccessRequirements-> Player::GetLUAHistory (this was causing a deadlock) + mLUAHistory.readlock(); + SendHistoryRequiredSpawns(event_id); + mLUAHistory.releasereadlock(); +} + +LUAHistory* Player::GetLUAHistory(int32 event_id) { + LUAHistory* ret = 0; + + mLUAHistory.readlock(); + + if (m_charLuaHistory.count(event_id) > 0) + ret = m_charLuaHistory[event_id]; + + mLUAHistory.releasereadlock(); + + return ret; +} + +bool Player::CanSeeInvis(Entity* target) +{ + if (!target->IsStealthed() && !target->IsInvis()) + return true; + if (target->IsStealthed() && HasSeeHideSpell()) + return true; + else if (target->IsInvis() && HasSeeInvisSpell()) + return true; + + sint32 radius = rule_manager.GetGlobalRule(R_PVP, InvisPlayerDiscoveryRange)->GetSInt32(); + + if (radius == 0) // radius of 0 is always seen + return true; + // radius of -1 is never seen except through items/spells, radius > -1 means we will show the player if they get into the inner radius + else if (radius > -1 && this->GetDistance((Spawn*)target) < (float)radius) + return true; + + // TODO: Implement See Invis Spells! http://cutpon.com:3000/devn00b/EQ2EMu/issues/43 + + Item* item = 0; + vector* equipped_list = GetEquippedItemList(); + bool seeInvis = false; + bool seeStealth = false; + for (int32 i = 0; i < equipped_list->size(); i++) + { + item = equipped_list->at(i); + seeInvis = item->HasStat(ITEM_STAT_SEEINVIS); + seeStealth = item->HasStat(ITEM_STAT_SEESTEALTH); + if (target->IsStealthed() && seeStealth) + return true; + else if (target->IsInvis() && seeInvis) + return true; + } + + return false; +} + +// returns true if we need to update target info due to see invis status change +bool Player::CheckChangeInvisHistory(Entity* target) +{ + std::map::iterator it; + + it = target_invis_history.find(target->GetID()); + if (it != target_invis_history.end()) + { + //canSeeStatus + if (it->second) + { + if (!this->CanSeeInvis(target)) + { + UpdateTargetInvisHistory(target->GetID(), false); + return true; + } + else + return false; + } + else + { + if (this->CanSeeInvis(target)) + { + UpdateTargetInvisHistory(target->GetID(), true); + return true; + } + else + return false; + } + } + + if (!this->CanSeeInvis(target)) + UpdateTargetInvisHistory(target->GetID(), false); + else + UpdateTargetInvisHistory(target->GetID(), true); + + return true; +} + +void Player::UpdateTargetInvisHistory(int32 targetID, bool canSeeStatus) +{ + target_invis_history[targetID] = canSeeStatus; +} + +void Player::RemoveTargetInvisHistory(int32 targetID) +{ + target_invis_history.erase(targetID); +} + +int16 Player::GetNextSpawnIndex(Spawn* spawn, bool set_lock) +{ + if(set_lock) + index_mutex.writelock(__FUNCTION__, __LINE__); + int16 next_index = 0; + int16 max_count = 0; + bool not_found = true; + do { + next_index = (spawn_index++); + max_count++; + if(max_count > 0xFFFE) { + LogWrite(PLAYER__ERROR, 0, "Player", "%s: This is bad we ran out of spawn indexes!", GetName()); + break; + } + if(next_index == 1 && spawn != this) { // only self can occupy index 1 + continue; + } + if(next_index == 0 || next_index == 255) { // avoid 0 and overloads (255) + continue; + } + Spawn* tmp_spawn = nullptr; + if(player_spawn_id_map.count(next_index) > 0) + tmp_spawn = player_spawn_id_map[next_index]; + + if(tmp_spawn && tmp_spawn != spawn) { // spawn index already taken and it is not this spawn + continue; + } + not_found = false; + } + while(not_found); + + if(set_lock) + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + return next_index; +} + +bool Player::SetSpawnMap(Spawn* spawn) +{ + if(!client->GetPlayer()->SetSpawnSentState(spawn, SpawnState::SPAWN_STATE_SENDING)) { + return false; + } + + index_mutex.writelock(__FUNCTION__, __LINE__); + int32 tmp_id = GetNextSpawnIndex(spawn, false); + + player_spawn_id_map[tmp_id] = spawn; + + if(player_spawn_reverse_id_map.count(spawn)) + player_spawn_reverse_id_map.erase(spawn); + + player_spawn_reverse_id_map.insert(make_pair(spawn,tmp_id)); + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + return true; +} + +int16 Player::SetSpawnMapAndIndex(Spawn* spawn) +{ + index_mutex.writelock(__FUNCTION__, __LINE__); + int32 new_index = GetNextSpawnIndex(spawn, false); + + player_spawn_id_map[new_index] = spawn; + player_spawn_reverse_id_map[spawn] = new_index; + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + + return new_index; +} + +NPC* Player::InstantiateSpiritShard(float origX, float origY, float origZ, float origHeading, int32 origGridID, ZoneServer* origZone) +{ + NPC* npc = new NPC(); + string newName(GetName()); + newName.append("'s spirit shard"); + + strcpy(npc->appearance.name, newName.c_str()); + /*vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(9)); + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(10)); + if(primary_command_list){ + npc->SetPrimaryCommands(primary_command_list); + npc->primary_command_list_id = result.GetInt32(9); + } + if(secondary_command_list){ + npc->SetSecondaryCommands(secondary_command_list); + npc->secondary_command_list_id = result.GetInt32(10); + }*/ + npc->appearance.level = GetLevel(); + npc->appearance.race = GetRace(); + npc->appearance.gender = GetGender(); + npc->appearance.adventure_class = GetAdventureClass(); + + npc->appearance.model_type = GetModelType(); + npc->appearance.soga_model_type = GetSogaModelType(); + npc->appearance.display_name = 1; + npc->features.hair_type = GetHairType(); + npc->features.hair_face_type = GetFacialHairType(); + npc->features.wing_type = GetWingType(); + npc->features.chest_type = GetChestType(); + npc->features.legs_type = GetLegsType(); + npc->features.soga_hair_type = GetSogaHairType(); + npc->features.soga_hair_face_type = GetSogaFacialHairType(); + npc->appearance.attackable = 0; + npc->appearance.show_level = 1; + npc->appearance.targetable = 1; + npc->appearance.show_command_icon = 1; + npc->appearance.display_hand_icon = 0; + npc->appearance.hide_hood = GetHideHood(); + npc->size = GetSize(); + npc->appearance.pos.collision_radius = appearance.pos.collision_radius; + npc->appearance.action_state = appearance.action_state; + npc->appearance.visual_state = 6193; // ghostly look + npc->appearance.mood_state = appearance.mood_state; + npc->appearance.emote_state = appearance.emote_state; + npc->appearance.pos.state = appearance.pos.state; + npc->appearance.activity_status = appearance.activity_status; + strncpy(npc->appearance.sub_title, appearance.sub_title, sizeof(npc->appearance.sub_title)); + npc->SetPrefixTitle(GetPrefixTitle()); + npc->SetSuffixTitle(GetSuffixTitle()); + npc->SetLastName(GetLastName()); + npc->SetX(origX); + npc->SetY(origY); + npc->SetZ(origZ); + npc->SetHeading(origHeading); + npc->SetSpawnOrigX(origX); + npc->SetSpawnOrigY(origY); + npc->SetSpawnOrigZ(origZ); + npc->SetSpawnOrigHeading(origHeading); + npc->SetLocation(origGridID); + npc->SetAlive(false); + const char* script = rule_manager.GetGlobalRule(R_Combat, SpiritShardSpawnScript)->GetString(); + + int32 dbid = database.CreateSpiritShard(newName.c_str(), GetLevel(), GetRace(), GetGender(), GetAdventureClass(), GetModelType(), GetSogaModelType(), + GetHairType(), GetFacialHairType(), GetWingType(), GetChestType(), GetLegsType(), GetSogaHairType(), GetSogaFacialHairType(), GetHideHood(), + GetSize(), npc->appearance.pos.collision_radius, npc->appearance.action_state, npc->appearance.visual_state, npc->appearance.mood_state, + npc->appearance.emote_state, npc->appearance.pos.state, npc->appearance.activity_status, npc->appearance.sub_title, GetPrefixTitle(), GetSuffixTitle(), + GetLastName(), origX, origY, origZ, origHeading, origGridID, GetCharacterID(), origZone->GetZoneID(), origZone->GetInstanceID()); + + npc->SetShardID(dbid); + npc->SetShardCharID(GetCharacterID()); + npc->SetShardCreatedTimestamp(Timer::GetCurrentTime2()); + + if(script) + npc->SetSpawnScript(script); + + return npc; +} + +void Player::SaveSpellEffects() +{ + if(stop_save_spell_effects) + { + LogWrite(PLAYER__WARNING, 0, "Player", "SaveSpellEffects called while player constructing / deconstructing!"); + return; + } + + SpellProcess* spellProcess = 0; + // Get the current zones spell process + spellProcess = GetZone()->GetSpellProcess(); + + Query savedEffects; + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effects where charid=%u", GetCharacterID()); + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_spell_effect_targets where caster_char_id=%u", GetCharacterID()); + InfoStruct* info = GetInfoStruct(); + MSpellEffects.readlock(__FUNCTION__, __LINE__); + MMaintainedSpells.readlock(__FUNCTION__, __LINE__); + for(int i = 0; i < 45; i++) { + if(info->spell_effects[i].spell_id != 0xFFFFFFFF) + { + Spawn* spawn = nullptr; + int32 target_char_id = 0; + if(info->spell_effects[i].spell->initial_target_char_id != 0) + target_char_id = info->spell_effects[i].spell->initial_target_char_id; + else if((spawn = GetZone()->GetSpawnByID(info->spell_effects[i].spell->initial_target)) != nullptr && spawn->IsPlayer()) + target_char_id = ((Player*)spawn)->GetCharacterID(); + + int32 timestamp = 0xFFFFFFFF; + if(info->spell_effects[i].spell->spell->GetSpellData() && !info->spell_effects[i].spell->spell->GetSpellData()->duration_until_cancel) + timestamp = info->spell_effects[i].expire_timestamp - Timer::GetCurrentTime2(); + + int32 caster_char_id = info->spell_effects[i].spell->initial_caster_char_id; + + if(caster_char_id == 0) + continue; + + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, + "insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')", + database.getSafeEscapeString(info->spell_effects[i].spell->spell->GetName()).c_str(), caster_char_id, + target_char_id, 0 /*no target_type for spell_effects*/, DB_TYPE_SPELLEFFECTS /* db_effect_type for spell_effects */, info->spell_effects[i].spell->spell->IsCopiedSpell() ? info->spell_effects[i].spell->spell->GetSpellData()->inherited_spell_id : info->spell_effects[i].spell_id, i, info->spell_effects[i].spell->slot_pos, + info->spell_effects[i].icon, info->spell_effects[i].icon_backdrop, 0 /* no conc_used for spell_effects */, info->spell_effects[i].tier, + info->spell_effects[i].total_time, timestamp, database.getSafeEscapeString(info->spell_effects[i].spell->file_name.c_str()).c_str(), info->spell_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), + info->spell_effects[i].spell->damage_remaining, info->spell_effects[i].spell->effect_bitmask, info->spell_effects[i].spell->num_triggers, info->spell_effects[i].spell->had_triggers, info->spell_effects[i].spell->cancel_after_all_triggers, + info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, info->spell_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str()); + } + if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell_id != 0xFFFFFFFF){ + Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target); + + int32 target_char_id = 0; + + if(info->maintained_effects[i].spell->initial_target_char_id != 0) + target_char_id = info->maintained_effects[i].spell->initial_target_char_id; + else if(!info->maintained_effects[i].spell->initial_target) + target_char_id = GetCharacterID(); + else if(spawn && spawn->IsPlayer()) + target_char_id = ((Player*)spawn)->GetCharacterID(); + else if (spawn && spawn->IsPet() && ((Entity*)spawn)->GetOwner() == (Entity*)this) + target_char_id = 0xFFFFFFFF; + + int32 caster_char_id = (info->maintained_effects[i].spell->caster && info->maintained_effects[i].spell->caster->IsPlayer()) ? ((Player*)info->maintained_effects[i].spell->caster)->GetCharacterID() : 0; + + int32 timestamp = 0xFFFFFFFF; + if(info->maintained_effects[i].spell->spell->GetSpellData() && !info->maintained_effects[i].spell->spell->GetSpellData()->duration_until_cancel) + timestamp = info->maintained_effects[i].expire_timestamp - Timer::GetCurrentTime2(); + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, + "insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')", + database.getSafeEscapeString(info->maintained_effects[i].name).c_str(), caster_char_id, target_char_id, info->maintained_effects[i].target_type, DB_TYPE_MAINTAINEDEFFECTS /* db_effect_type for maintained_effects */, info->maintained_effects[i].spell->spell->IsCopiedSpell() ? info->maintained_effects[i].spell->spell->GetSpellData()->inherited_spell_id : info->maintained_effects[i].spell_id, i, info->maintained_effects[i].slot_pos, + info->maintained_effects[i].icon, info->maintained_effects[i].icon_backdrop, info->maintained_effects[i].conc_used, info->maintained_effects[i].tier, + info->maintained_effects[i].total_time, timestamp, database.getSafeEscapeString(info->maintained_effects[i].spell->file_name.c_str()).c_str(), info->maintained_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), + info->maintained_effects[i].spell->damage_remaining, info->maintained_effects[i].spell->effect_bitmask, info->maintained_effects[i].spell->num_triggers, info->maintained_effects[i].spell->had_triggers, info->maintained_effects[i].spell->cancel_after_all_triggers, + info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, info->maintained_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str()); + + info->maintained_effects[i].spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + std::string insertTargets = string("insert into character_spell_effect_targets (caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos) values "); + bool firstTarget = true; + map targetsInserted; + for (int8 t = 0; t < info->maintained_effects[i].spell->targets.size(); t++) { + int32 spawn_id = info->maintained_effects[i].spell->targets.at(t); + Spawn* spawn = GetZone()->GetSpawnByID(spawn_id); + LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %u to identify for spell %s", GetName(), spawn_id, info->maintained_effects[i].spell->spell->GetName()); + if(spawn && (spawn->IsPlayer() || spawn->IsPet())) + { + int32 tmpCharID = 0; + int8 type = 0; + + if(targetsInserted.find(spawn) != targetsInserted.end()) + continue; + + if(spawn->IsPlayer()) + tmpCharID = ((Player*)spawn)->GetCharacterID(); + else if (spawn->IsPet() && ((Entity*)spawn)->GetOwner() == (Entity*)this) + { + tmpCharID = 0xFFFFFFFF; + } + else if(spawn->IsPet() && ((Entity*)spawn)->GetOwner() && + ((Entity*)spawn)->GetOwner()->IsPlayer()) + { + type = ((Entity*)spawn)->GetPetType(); + Player* petOwner = (Player*)((Entity*)spawn)->GetOwner(); + tmpCharID = petOwner->GetCharacterID(); + } + + if(!firstTarget) + insertTargets.append(", "); + + targetsInserted.insert(make_pair(spawn, true)); + + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %s (%u) added to spell %s", GetName(), spawn ? spawn->GetName() : "NA", tmpCharID, info->maintained_effects[i].spell->spell->GetName()); + insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(tmpCharID) + ", " + std::to_string(type) + ", " + + std::to_string(DB_TYPE_MAINTAINEDEFFECTS) + ", " + std::to_string(info->maintained_effects[i].spell_id) + ", " + std::to_string(i) + + ", " + std::to_string(info->maintained_effects[i].slot_pos) + ")"); + firstTarget = false; + } + } + multimap::iterator entries; + for(entries = info->maintained_effects[i].spell->char_id_targets.begin(); entries != info->maintained_effects[i].spell->char_id_targets.end(); entries++) + { + if(!firstTarget) + insertTargets.append(", "); + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %s (%u) added to spell %s", GetName(), spawn ? spawn->GetName() : "NA", entries->first, info->maintained_effects[i].spell->spell->GetName()); + insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(entries->first) + ", " + std::to_string(entries->second) + ", " + + std::to_string(DB_TYPE_MAINTAINEDEFFECTS) + ", " + std::to_string(info->maintained_effects[i].spell_id) + ", " + std::to_string(i) + + ", " + std::to_string(info->maintained_effects[i].slot_pos) + ")"); + + firstTarget = false; + } + info->maintained_effects[i].spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + if(!firstTarget) { + savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, insertTargets.c_str()); + } + } + } + MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__); + MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); +} + +void Player::MentorTarget() +{ + if(client->GetPlayer()->GetGroupMemberInfo() && client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id) + { + client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id = 0; + reset_mentorship = true; + client->Message(CHANNEL_COMMAND_TEXT, "You stop mentoring, and return to level %u.", client->GetPlayer()->GetLevel()); + } + else if(!reset_mentorship && client->GetPlayer()->GetTarget()) + { + if(client->GetPlayer()->GetTarget()->IsPlayer()) + { + Player* tmpPlayer = (Player*)client->GetPlayer()->GetTarget(); + if(tmpPlayer->GetGroupMemberInfo() && tmpPlayer->GetGroupMemberInfo()->mentor_target_char_id) + { + client->Message(CHANNEL_COMMAND_TEXT, "You cannot mentor %s at this time.",tmpPlayer->GetName()); + return; + } + if(client->GetPlayer()->group_id > 0 && client->GetPlayer()->GetTarget()->group_id == client->GetPlayer()->group_id) + { + if(client->GetPlayer()->GetGroupMemberInfo() && !client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id && client->GetPlayer()->GetZone() == client->GetPlayer()->GetTarget()->GetZone() && client->GetPlayer()->GetTarget()->GetName() != client->GetPlayer()->GetName()) + { + SetMentorStats(client->GetPlayer()->GetTarget()->GetLevel(), tmpPlayer->GetCharacterID()); + client->Message(CHANNEL_COMMAND_TEXT, "You are now mentoring %s, reducing your effective level to %u.",client->GetPlayer()->GetTarget()->GetName(), client->GetPlayer()->GetTarget()->GetLevel()); + } + if(client->GetPlayer()->GetTarget()->GetName() == client->GetPlayer()->GetName()) { + client->Message(CHANNEL_COMMAND_TEXT, "You cannot mentor yourself."); + } + } + } + } +} + +void Player::SetMentorStats(int32 effective_level, int32 target_char_id, bool update_stats) +{ + if(update_stats) { + RemoveSpells(); + } + if(client->GetPlayer()->GetGroupMemberInfo()) + client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id = target_char_id; + InfoStruct* info = GetInfoStruct(); + info->set_effective_level(effective_level); + CalculatePlayerHPPower(effective_level); + client->GetPlayer()->CalculateBonuses(); + if(update_stats) { + client->GetPlayer()->SetHP(GetTotalHP()); + client->GetPlayer()->SetPower(GetTotalPower()); + } + /*info->set_agi_base(effective_level * 2 + 15); + info->set_intel_base(effective_level * 2 + 15); + info->set_wis_base(effective_level * 2 + 15); + info->set_str_base(effective_level * 2 + 15); + info->set_sta_base(effective_level * 2 + 15); + info->set_cold_base((int16)(effective_level * 1.5 + 10)); + info->set_heat_base((int16)(effective_level * 1.5 + 10)); + info->set_disease_base((int16)(effective_level * 1.5 + 10)); + info->set_mental_base((int16)(effective_level * 1.5 + 10)); + info->set_magic_base((int16)(effective_level * 1.5 + 10)); + info->set_divine_base((int16)(effective_level * 1.5 + 10)); + info->set_poison_base((int16)(effective_level * 1.5 + 10));*/ + GetClient()->ClearSentItemDetails(); + if(GetClient()) + { + EQ2Packet* app = GetEquipmentList()->serialize(GetClient()->GetVersion(), this); + if (app) { + GetClient()->QueuePacket(app); + } + } + GetEquipmentList()->SendEquippedItems(this); +} + +void Player::SetLevel(int16 level, bool setUpdateFlags) { + if(!GetGroupMemberInfo() || GetGroupMemberInfo()->mentor_target_char_id == 0) { + GetInfoStruct()->set_effective_level(level); + } + SetInfo(&appearance.level, level, setUpdateFlags); + SetXP(0); + SetNeededXP(); +} + +bool Player::SerializeItemPackets(EquipmentItemList* equipList, vector* packets, Item* item, int16 version, Item* to_item) { + if(item_list.AddItem(item)) { + item->save_needed = true; + SetEquippedItemAppearances(); + packets->push_back(equipList->serialize(version, this)); + packets->push_back(item->serialize(version, false)); + if(to_item) + packets->push_back(to_item->serialize(version, false, this)); + packets->push_back(item_list.serialize(this, version)); + return true; + } + else { + LogWrite(PLAYER__ERROR, 0, "Player", "failed to add item to item_list"); + } + return false; +} + +void Player::AddGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, int16 visual_tag) { + if(MatchGMVisualFilter(filter_type, filter_value, filter_search_str) > 0) + return; + + vis_mutex.writelock(__FUNCTION__, __LINE__); + GMTagFilter filter; + filter.filter_type = filter_type; + filter.filter_value = filter_value; + memset(filter.filter_search_criteria, 0, sizeof(filter.filter_search_criteria)); + if(filter_search_str) + memcpy(&filter.filter_search_criteria, filter_search_str, strnlen(filter_search_str, sizeof(filter.filter_search_criteria))); + + filter.visual_tag = visual_tag; + gm_visual_filters.push_back(filter); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); +} + +int16 Player::MatchGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, bool in_vismutex_lock) { + if(!in_vismutex_lock) + vis_mutex.readlock(__FUNCTION__, __LINE__); + int16 tag_id = 0; + vector::iterator itr = gm_visual_filters.begin(); + for(;itr != gm_visual_filters.end();itr++) { + if(itr->filter_type == filter_type && itr->filter_value == filter_value) { + if(filter_search_str && !strcasecmp(filter_search_str, itr->filter_search_criteria)) { + tag_id = itr->visual_tag; + break; + } + } + } + if(!in_vismutex_lock) + vis_mutex.releasereadlock(__FUNCTION__, __LINE__); + return tag_id; +} +void Player::ClearGMVisualFilters() { + vis_mutex.writelock(__FUNCTION__, __LINE__); + gm_visual_filters.clear(); + vis_mutex.releasewritelock(__FUNCTION__, __LINE__); +} + +int Player::GetPVPAlignment(){ + int bind_zone = GetPlayerInfo()->GetBindZoneID(); + int alignment = 0; + + if(bind_zone && bind_zone != 0){ + //0 is good. + //1 is evil. + //2 is neutral aka haven players. + switch(bind_zone){ + //good zones + case 114: //Gfay + case 221: //Qeynos Harbor + case 222: //North Qeynos + case 231: //South Qeynos + case 233: //Nettleville + case 234: //Starcrest + case 235: //Graystone + case 236: //CastleView + case 237: //Willowood + case 238: //Baubbleshire + case 470: //Frostfang + case 589: //Qeynos Combined 1 + case 660: //Qeynos Combined 2 + alignment = 0; //good + break; + //evil zones + case 128: //East Freeport + case 134: //Big Bend + case 135: //Stonestair + case 136: //Temple St. + case 137: //Beggars Ct. + case 138: //Longshadow + case 139: //Scale Yard + case 144: //North Freeport + case 166: //South Freeport + case 168: //West Freeport + case 184: //Neriak + case 644: //BigBend2 + case 645: //Stonestair2 + case 646: //Temple St2 + case 647: //Beggars Ct2 + case 648: //LongShadow2 + case 649: //Scale Yard2 + alignment = 1; //evil + break; + //Neutral (MajDul?) + case 45: //haven + case 46: //MajDul + alignment = 2; + break; + + default: + alignment = -1; //error + } + //return -1 (error), 0 (good), 1 (evil), or 2 (Neutral) + return alignment; + } + return -1; //error +} + +void Player::GetSpellBookSlotSort(int32 pattern, int32* i, int8* page_book_count, int32* last_start_point) { + switch(pattern) { + case 1: { // down + *i = (*i) + 2; + (*page_book_count)++; + if(*page_book_count > 3) { + if(((*i) % 2) == 0) { + (*i) = (*last_start_point) + 1; + } + else { + (*last_start_point) = (*last_start_point) + 8; + (*i) = (*last_start_point); + } + (*page_book_count) = 0; + } + break; + } + case 2: { // across + (*page_book_count)++; + switch(*page_book_count) { + case 1: + case 3: { + (*i)++; + break; + } + case 2: { + (*i) = (*i) + 7; + break; + } + case 4: { + (*last_start_point) = (*last_start_point) + 2; + (*i) = (*last_start_point); + (*page_book_count) = 0; + break; + } + } + break; + } + default: { // zig-zag + (*i)++; + break; + } + } +} + + +bool Player::IsSpawnInRangeList(int32 spawn_id) { + std::shared_lock lock(spawn_aggro_range_mutex); + map::iterator spawn_itr = player_aggro_range_spawns.find(spawn_id); + if(spawn_itr != player_aggro_range_spawns.end()) { + return spawn_itr->second; + } + return false; +} + +void Player::SetSpawnInRangeList(int32 spawn_id, bool in_range) { + std::unique_lock lock(spawn_aggro_range_mutex); + player_aggro_range_spawns[spawn_id] = in_range; +} + +void Player::ProcessSpawnRangeUpdates() { + std::unique_lock lock(spawn_aggro_range_mutex); + if(GetClient()->GetCurrentZone() == nullptr) { + return; + } + + map::iterator spawn_itr; + for(spawn_itr = player_aggro_range_spawns.begin(); spawn_itr != player_aggro_range_spawns.end();) { + if(spawn_itr->second) { + Spawn* spawn = GetClient()->GetCurrentZone()->GetSpawnByID(spawn_itr->first); + if(spawn && spawn->IsNPC() && (GetDistance(spawn)) > ((NPC*)spawn)->GetAggroRadius()) { + GetClient()->GetCurrentZone()->SendSpawnChanges((NPC*)spawn, GetClient(), true, true); + spawn_itr->second = false; + spawn_itr = player_aggro_range_spawns.erase(spawn_itr); + continue; + } + } + spawn_itr++; + } +} + +void Player::CalculatePlayerHPPower(int16 new_level) { + if(IsPlayer()) { + int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel(); + if(new_level < 1) { + new_level = effective_level; + } + + float hp_rule_mod = rule_manager.GetGlobalRule(R_Player, StartHPLevelMod)->GetFloat(); + float power_rule_mod = rule_manager.GetGlobalRule(R_Player, StartPowerLevelMod)->GetFloat(); + + sint32 base_hp = rule_manager.GetGlobalRule(R_Player, StartHPBase)->GetFloat(); + sint32 base_power = rule_manager.GetGlobalRule(R_Player, StartPowerBase)->GetSInt32(); + + sint32 new_hp = (sint32)((float)new_level * (float)new_level * hp_rule_mod + base_hp); + sint32 new_power = (sint32)((float)new_level * (float)new_level * power_rule_mod + base_power); + + if(new_hp < 1) { + LogWrite(PLAYER__WARNING, 0, "Player", "Player HP Calculation for %s too low at level %u due to ruleset, StartPowerLevelMod %f, BasePower %i", GetName(), new_level, hp_rule_mod, base_hp); + new_hp = 1; + } + if(new_power < 1) { + LogWrite(PLAYER__WARNING, 0, "Player", "Player Power Calculations for %s too low at level %u due to ruleset, StartPowerLevelMod %f, BasePower %i", GetName(), new_level, power_rule_mod, base_power); + new_power = 1; + } + + SetTotalHPBase(new_hp); + SetTotalHPBaseInstance(new_hp); // we need the hp base to override the instance as the new default + + SetTotalPowerBase(new_power); + SetTotalPowerBaseInstance(new_power); // we need the hp base to override the instance as the new default + + LogWrite(PLAYER__INFO, 0, "Player", "Player %s: Level %u, Set Base HP %i, Set Base Power: %i", GetName(), new_level, power_rule_mod, base_power); + } +} + +bool Player::IsAllowedCombatEquip(int8 slot, bool send_message) { + bool rule_pass = true; + if(EngagedInCombat() && rule_manager.GetGlobalRule(R_Player, AllowPlayerEquipCombat)->GetInt8() == 0) { + switch(slot) { + case EQ2_PRIMARY_SLOT: + case EQ2_SECONDARY_SLOT: + case EQ2_RANGE_SLOT: + case EQ2_AMMO_SLOT: { + // good to go! + break; + } + default: { + if(send_message && GetClient()) { + GetClient()->SimpleMessage(CHANNEL_COLOR_RED, "You may not unequip/equip items while in combat."); + } + rule_pass = false; + break; + } + } + } + return rule_pass; +} + +void Player::SetActiveFoodUniqueID(int32 unique_id, bool update_db) { + active_food_unique_id = unique_id; + if(update_db) { + database.insertCharacterProperty(client, CHAR_PROPERTY_SETACTIVEFOOD, (char*)std::to_string(unique_id).c_str()); + } +} + +void Player::SetActiveDrinkUniqueID(int32 unique_id, bool update_db) { + active_drink_unique_id = unique_id; + if(update_db) { + database.insertCharacterProperty(client, CHAR_PROPERTY_SETACTIVEDRINK, (char*)std::to_string(unique_id).c_str()); + } +} + \ No newline at end of file diff --git a/source/WorldServer/Player.h b/source/WorldServer/Player.h new file mode 100644 index 0000000..b1491ac --- /dev/null +++ b/source/WorldServer/Player.h @@ -0,0 +1,1258 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_PLAYER__ +#define __EQ2_PLAYER__ + +#include "Entity.h" +#include "Items/Items.h" +#include "Factions.h" +#include "Skills.h" +#include "Quests.h" +#include "MutexMap.h" +#include "Guilds/Guild.h" +#include "Collections/Collections.h" +#include "Recipes/Recipe.h" +#include "Titles.h" +#include "Languages.h" +#include "Achievements/Achievements.h" +#include "Traits/Traits.h" +#include +#include + +#define CF_COMBAT_EXPERIENCE_ENABLED 0 +#define CF_ENABLE_CHANGE_LASTNAME 1 +#define CF_FOOD_AUTO_CONSUME 2 +#define CF_DRINK_AUTO_CONSUME 3 +#define CF_AUTO_ATTACK 4 +#define CF_RANGED_AUTO_ATTACK 5 +#define CF_QUEST_EXPERIENCE_ENABLED 6 +#define CF_CHASE_CAMERA_MAYBE 7 +#define CF_100 8 +#define CF_200 9 +#define CF_IS_SITTING 10 /*CAN'T CAST OR ATTACK*/ +#define CF_800 11 +#define CF_ANONYMOUS 12 +#define CF_ROLEPLAYING 13 +#define CF_AFK 14 +#define CF_LFG 15 +#define CF_LFW 16 +#define CF_HIDE_HOOD 17 +#define CF_HIDE_HELM 18 +#define CF_SHOW_ILLUSION 19 +#define CF_ALLOW_DUEL_INVITES 20 +#define CF_ALLOW_TRADE_INVITES 21 +#define CF_ALLOW_GROUP_INVITES 22 +#define CF_ALLOW_RAID_INVITES 23 +#define CF_ALLOW_GUILD_INVITES 24 +#define CF_2000000 25 +#define CF_4000000 26 +#define CF_DEFENSE_SKILLS_AT_MAX_QUESTIONABLE 27 +#define CF_SHOW_GUILD_HERALDRY 28 +#define CF_SHOW_CLOAK 29 +#define CF_IN_PVP 30 +#define CF_IS_HATED 31 +#define CF2_1 32 +#define CF2_2 33 +#define CF2_4 34 +#define CF2_ALLOW_LON_INVITES 35 +#define CF2_SHOW_RANGED 36 +#define CF2_ALLOW_VOICE_INVITES 37 +#define CF2_CHARACTER_BONUS_EXPERIENCE_ENABLED 38 +#define CF2_80 39 +#define CF2_100 40 /* hide achievments*/ +#define CF2_200 41 +#define CF2_400 42 +#define CF2_800 43 /* enable facebook updates*/ +#define CF2_1000 44 /* enable twitter updates*/ +#define CF2_2000 45 /* enable eq2 player updates */ +#define CF2_4000 46 /*eq2 players, link to alt chars */ +#define CF2_8000 47 +#define CF2_10000 48 +#define CF2_20000 49 +#define CF2_40000 50 +#define CF2_80000 51 +#define CF2_100000 52 +#define CF2_200000 53 +#define CF2_400000 54 +#define CF2_800000 55 +#define CF2_1000000 56 +#define CF2_2000000 57 +#define CF2_4000000 58 +#define CF2_8000000 59 +#define CF2_10000000 60 +#define CF2_20000000 61 +#define CF2_40000000 62 +#define CF2_80000000 63 +#define CF_MAXIMUM_FLAG 63 +#define CF_HIDE_STATUS 49 /* !!FORTESTING ONLY!! */ +#define CF_GM_HIDDEN 50 /* !!FOR TESTING ONLY!! */ + +#define UPDATE_ACTIVITY_FALLING 0 +#define UPDATE_ACTIVITY_RUNNING 128 +#define UPDATE_ACTIVITY_RIDING_BOAT 256 +#define UPDATE_ACTIVITY_JUMPING 1024 +#define UPDATE_ACTIVITY_IN_WATER_ABOVE 6144 +#define UPDATE_ACTIVITY_IN_WATER_BELOW 6272 +#define UPDATE_ACTIVITY_SITING 6336 +#define UPDATE_ACTIVITY_DROWNING 14464 +#define UPDATE_ACTIVITY_DROWNING2 14336 + + + +#define UPDATE_ACTIVITY_FALLING_AOM 16384 +#define UPDATE_ACTIVITY_RIDING_BOAT_AOM 256 +#define UPDATE_ACTIVITY_RUNNING_AOM 16512 +#define UPDATE_ACTIVITY_JUMPING_AOM 17408 +#define UPDATE_ACTIVITY_MOVE_WATER_BELOW_AOM 22528 +#define UPDATE_ACTIVITY_MOVE_WATER_ABOVE_AOM 22656 +#define UPDATE_ACTIVITY_SITTING_AOM 22720 +#define UPDATE_ACTIVITY_DROWNING_AOM 30720 +#define UPDATE_ACTIVITY_DROWNING2_AOM 30848 + +#define NUM_MAINTAINED_EFFECTS 30 +#define NUM_SPELL_EFFECTS 45 + +/* Character History Type Defines */ +#define HISTORY_TYPE_NONE 0 +#define HISTORY_TYPE_DEATH 1 +#define HISTORY_TYPE_DISCOVERY 2 +#define HISTORY_TYPE_XP 3 + +/* Spell Status */ +#define SPELL_STATUS_QUEUE 4 +#define SPELL_STATUS_LOCK 66 + +/* Character History Sub Type Defines */ +#define HISTORY_SUBTYPE_NONE 0 +#define HISTORY_SUBTYPE_ADVENTURE 1 +#define HISTORY_SUBTYPE_TRADESKILL 2 +#define HISTORY_SUBTYPE_QUEST 3 +#define HISTORY_SUBTYPE_AA 4 +#define HISTORY_SUBTYPE_ITEM 5 +#define HISTORY_SUBTYPE_LOCATION 6 + +/// Character history data, should match the `character_history` table in the DB +struct HistoryData { + int32 Value; + int32 Value2; + char Location[200]; + int32 EventID; + int32 EventDate; + bool needs_save; +}; + +/// History set through the LUA system +struct LUAHistory { + int32 Value; + int32 Value2; + bool SaveNeeded; +}; + +struct SpellBookEntry{ + int32 spell_id; + int8 tier; + int32 type; + sint32 slot; + int32 recast_available; + int8 status; + int16 recast; + int32 timer; + bool save_needed; + bool in_use; + bool in_remiss; + Player* player; + bool visible; +}; + +struct GMTagFilter { + int32 filter_type; + int32 filter_value; + char filter_search_criteria[256]; + int16 visual_tag; +}; + +enum GMTagFilterType { + GMFILTERTYPE_NONE=0, + GMFILTERTYPE_FACTION=1, + GMFILTERTYPE_SPAWNGROUP=2, + GMFILTERTYPE_RACE=3, + GMFILTERTYPE_GROUNDSPAWN=4 +}; +enum SpawnState{ + SPAWN_STATE_NONE=0, + SPAWN_STATE_SENDING=1, + SPAWN_STATE_SENT_WAIT=2, + SPAWN_STATE_SENT=3, + SPAWN_STATE_REMOVING=4, + SPAWN_STATE_REMOVING_SLEEP=5, + SPAWN_STATE_REMOVED=6 +}; +#define QUICKBAR_NORMAL 1 +#define QUICKBAR_INV_SLOT 2 +#define QUICKBAR_MACRO 3 +#define QUICKBAR_TEXT_CMD 4 +#define QUICKBAR_ITEM 6 + +#define EXP_DISABLED_STATE 0 +#define EXP_ENABLED_STATE 1 +#define MELEE_COMBAT_STATE 16 +#define RANGE_COMBAT_STATE 32 + +struct QuickBarItem{ + bool deleted; + int32 hotbar; + int32 slot; + int32 type; + int16 icon; + int16 icon_type; + int32 id; + int8 tier; + int32 unique_id; + EQ2_16BitString text; +}; + +struct LoginAppearances { + bool deleted; + int16 equip_type; + int8 red; + int8 green; + int8 blue; + int8 h_red; + int8 h_green; + int8 h_blue; + bool update_needed; +}; + +struct SpawnQueueState { + Timer spawn_state_timer; + int16 index_id; +}; + +class PlayerLoginAppearance { +public: + PlayerLoginAppearance() { appearanceList = new map; } + ~PlayerLoginAppearance() { } + + void AddEquipmentToUpdate(int8 slot_id, LoginAppearances* equip) + { + //LoginAppearances data; + //data.equip_type = equip->equip_type; + //appearanceList[slot_id] = data; + } + + void DeleteEquipmentFromUpdate(int8 slot_id, LoginAppearances* equip) + { + //LoginAppearances data; + //data.deleted = equip->deleted; + //data.update_needed = true; + //appearanceList[slot_id] = data; + } + + void RemoveEquipmentUpdates() + { + appearanceList->clear(); + safe_delete(appearanceList); + } + +private: + map* appearanceList; +}; + + +struct InstanceData{ + int32 db_id; + int32 instance_id; + int32 zone_id; + int8 zone_instance_type; + string zone_name; + int32 last_success_timestamp; + int32 last_failure_timestamp; + int32 success_lockout_time; + int32 failure_lockout_time; +}; + +class CharacterInstances { +public: + CharacterInstances(); + ~CharacterInstances(); + + ///Adds an instance data to the player with the given data + ///The unique id for this record in the database + ///The id of the instance + ///The success timestamp + ///The failure timestamp + ///The lockout time, in secs, for completing the instance + ///The lockout time, in secs, for failing the instance + ///The id of the zone + ///The type of instance of the zone + ///The name of the zone + void AddInstance(int32 db_id, int32 instance_id, int32 last_success_timestamp, int32 last_failure_timestamp, int32 success_lockout_time, int32 failure_lockout_time, int32 zone_id, int8 zone_instancetype, string zone_name); + + ///Clears all instance data + void RemoveInstances(); + + ///Removes the instace with the given zone id + ///The zone id of the instance to remove + ///True if the instance was found and removed + bool RemoveInstanceByZoneID(int32 zone_id); + + ///Removes the instance with the given instance id + ///the instance id of the instance to remove + ///True if instance was found and removed + bool RemoveInstanceByInstanceID(int32 instance_id); + + ///Gets the instance with the given zone id + ///The zone id of the instance to get + ///InstanceData* of the instance record for the given zone id + InstanceData* FindInstanceByZoneID(int32 zone_id); + + ///Gets the instance with the given database id + ///The database id of the instance to get + ///InstanceData* of the instance record for the given database id + InstanceData* FindInstanceByDBID(int32 db_id); + + ///Gets the instance with the given instance id + ///The instance id of the instance to get + ///InstanceData* of the instance record for the given instance id + InstanceData* FindInstanceByInstanceID(int32 instance_id); + + ///Gets a list of all the lockout instances + vector GetLockoutInstances(); + + ///Gets a list of all the persistent instances + vector GetPersistentInstances(); + + ///Check the timers for the instances + ///player we are checking the timers for + void ProcessInstanceTimers(Player* player); + + ///Gets the total number of instances + int32 GetInstanceCount(); +private: + vector instanceList; + Mutex m_instanceList; +}; + +class Player; +struct PlayerGroup; +struct GroupMemberInfo; +struct Statistic; +struct Mail; +class PlayerInfo { +public: + ~PlayerInfo(); + PlayerInfo(Player* in_player); + + EQ2Packet* serialize(int16 version, int16 modifyPos = 0, int32 modifyValue = 0); + PacketStruct* serialize2(int16 version); + EQ2Packet* serialize3(PacketStruct* packet, int16 version); + EQ2Packet* serializePet(int16 version); + void CalculateXPPercentages(); + void CalculateTSXPPercentages(); + void SetHouseZone(int32 id); + void SetBindZone(int32 id); + void SetBindX(float x); + void SetBindY(float y); + void SetBindZ(float z); + void SetBindHeading(float heading); + void SetAccountAge(int32 days); + int32 GetHouseZoneID(); + int32 GetBindZoneID(); + float GetBindZoneX(); + float GetBindZoneY(); + float GetBindZoneZ(); + float GetBindZoneHeading(); + float GetBoatX() { return boat_x_offset; } + float GetBoatY() { return boat_y_offset; } + float GetBoatZ() { return boat_z_offset; } + int32 GetBoatSpawn(); + void SetBoatX(float x) { boat_x_offset = x; } + void SetBoatY(float y) { boat_y_offset = y; } + void SetBoatZ(float z) { boat_z_offset = z; } + void SetBoatSpawn(Spawn* boat); + void RemoveOldPackets(); + +private: + int32 house_zone_id; + int32 bind_zone_id; + float bind_x; + float bind_y; + float bind_z; + float bind_heading; + uchar* changes; + uchar* orig_packet; + uchar* pet_changes; + uchar* pet_orig_packet; + InfoStruct* info_struct; + Player* player; + float boat_x_offset; + float boat_y_offset; + float boat_z_offset; + int32 boat_spawn; +}; + +class PlayerControlFlags{ +public: + PlayerControlFlags(); + ~PlayerControlFlags(); + + void SetPlayerControlFlag(int8 param, int8 param_value, bool is_active); + bool ControlFlagsChanged(); + void SendControlFlagUpdates(Client* client); +private: + bool flags_changed; + map > flag_changes; + map > current_flags; + Mutex MControlFlags; + Mutex MFlagChanges; +}; + +class Player : public Entity{ +public: + Player(); + virtual ~Player(); + EQ2Packet* serialize(Player* player, int16 version); + //int8 GetMaxArtLevel(){ return info->GetInfo()->max_art_level; } + //int8 GetArtLevel(){ return info->GetInfo()->art_level; } + + Client* GetClient() { return client; } + void SetClient(Client* client) { this->client = client; } + PlayerInfo* GetPlayerInfo(); + void SetCharSheetChanged(bool val); + bool GetCharSheetChanged(); + void AddFriend(const char* name, bool save); + bool IsFriend(const char* name); + void RemoveFriend(const char* name); + map* GetFriends(); + void AddIgnore(const char* name, bool save); + bool IsIgnored(const char* name); + void RemoveIgnore(const char* name); + map* GetIgnoredPlayers(); + + // JA: POI Discoveries + map >* GetPlayerDiscoveredPOIs(); + void AddPlayerDiscoveredPOI(int32 location_id); + // + + EQ2Packet* Move(float x, float y, float z, int16 version, float heading = -1.0f); + + /*void SetMaxArtLevel(int8 new_max){ + max_art_level = new_max; + } + void SetArtLevel(int8 new_lvl){ + art_level = new_lvl; + }*/ + bool WasSentSpawn(int32 spawn_id); + bool IsSendingSpawn(int32 spawn_id); + bool IsRemovingSpawn(int32 spawn_id); + bool SetSpawnSentState(Spawn* spawn, SpawnState state); + void CheckSpawnStateQueue(); + void SetSideSpeed(float side_speed, bool updateFlags = true) { + SetPos(&appearance.pos.SideSpeed, side_speed, updateFlags); + } + float GetSideSpeed() { + return appearance.pos.SideSpeed; + } + void SetVertSpeed(float vert_speed, bool updateFlags = true) { + SetPos(&appearance.pos.VertSpeed, vert_speed, updateFlags); + } + float GetVertSpeed() { + return appearance.pos.VertSpeed; + } + + void SetClientHeading1(float heading, bool updateFlags = true) { + SetPos(&appearance.pos.ClientHeading1, heading, updateFlags); + } + float GetClientHeading1() { + return appearance.pos.ClientHeading1; + } + + void SetClientHeading2(float heading, bool updateFlags = true) { + SetPos(&appearance.pos.ClientHeading2, heading, updateFlags); + } + float GetClientHeading2() { + return appearance.pos.ClientHeading2; + } + + void SetClientPitch(float pitch, bool updateFlags = true) { + SetPos(&appearance.pos.ClientPitch, pitch, updateFlags); + } + float GetClientPitch() { + return appearance.pos.ClientPitch; + } + + int8 GetTutorialStep() { + return tutorial_step; + } + void SetTutorialStep(int8 val) { + tutorial_step = val; + } + void AddMaintainedSpell(LuaSpell* spell); + void AddSpellEffect(LuaSpell* spell, int32 override_expire_time = 0); + void RemoveMaintainedSpell(LuaSpell* spell); + void RemoveSpellEffect(LuaSpell* spell); + void AddQuickbarItem(int32 bar, int32 slot, int32 type, int16 icon, int16 icon_type, int32 id, int8 tier, int32 unique_id, const char* text, bool update = true); + void RemoveQuickbarItem(int32 bar, int32 slot, bool update = true); + void MoveQuickbarItem(int32 id, int32 new_slot); + void ClearQuickbarItems(); + PlayerItemList* GetPlayerItemList(); + PlayerItemList item_list; + PlayerSkillList skill_list; + Skill* GetSkillByName(const char* name, bool check_update = false); + Skill* GetSkillByID(int32 skill_id, bool check_update = false); + PlayerSkillList* GetSkills(); + bool DamageEquippedItems(int8 amount = 10, Client* client = 0); + vector EquipItem(int16 index, int16 version, int8 appearance_type, int8 slot_id = 255); + bool CanEquipItem(Item* item, int8 slot); + void SetEquippedItemAppearances(); + vector UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type = 0, bool send_item_updates = true); + int16 ConvertSlotToClient(int8 slot, int16 version); + int16 ConvertSlotFromClient(int8 slot, int16 version); + int16 GetNumSlotsEquip(int16 version); + int8 GetMaxBagSlots(int16 version); + EQ2Packet* SwapEquippedItems(int8 slot1, int8 slot2, int16 version, int16 equiptype); + EQ2Packet* RemoveInventoryItem(int8 bag_slot, int8 slot); + EQ2Packet* SendInventoryUpdate(int16 version); + EQ2Packet* SendBagUpdate(int32 bag_unique_id, int16 version); + void SendQuestRequiredSpawns(int32 quest_id); + void SendHistoryRequiredSpawns(int32 event_id); + map* GetItemList(); + map* GetBankItemList(); + vector* GetEquippedItemList(); + vector* GetAppearanceEquippedItemList(); + Quest* SetStepComplete(int32 quest_id, int32 step); + Quest* AddStepProgress(int32 quest_id, int32 step, int32 progress); + int32 GetStepProgress(int32 quest_id, int32 step_id); + Quest* GetQuestByPositionID(int32 list_position_id); + bool AddItem(Item* item, AddItemType type = AddItemType::NOT_SET); + bool AddItemToBank(Item* item); + int16 GetSpellSlotMappingCount(); + int16 GetSpellPacketCount(); + Quest* GetQuest(int32 quest_id); + bool GetQuestStepComplete(int32 quest_id, int32 step_id); + int16 GetQuestStep(int32 quest_id); + int16 GetTaskGroupStep(int32 quest_id); + int8 GetSpellTier(int32 id); + void SetSpellStatus(Spell* spell, int8 status); + void RemoveSpellStatus(Spell* spell, int8 status); + EQ2Packet* GetSpellBookUpdatePacket(int16 version); + EQ2Packet* GetSpellSlotMappingPacket(int16 version); + int32 GetCharacterID(); + void SetCharacterID(int32 new_id); + EQ2Packet* GetQuickbarPacket(int16 version); + vector* GetQuickbar(); + bool UpdateQuickbarNeeded(); + void ResetQuickbarNeeded(); + void set_character_flag(int flag); + void reset_character_flag(int flag); + void toggle_character_flag(int flag); + bool get_character_flag(int flag); + void AddCoins(int64 val); + bool RemoveCoins(int64 val); + /// Checks to see if the player has the given amount of coins + /// Amount of coins to check + /// True if the player has enough coins + bool HasCoins(int64 val); + void AddSkill(int32 skill_id, int16 current_val, int16 max_val, bool save_needed = false); + void RemovePlayerSkill(int32 skill_id, bool save = false); + void RemoveSkillFromDB(Skill* skill, bool save = false); + void AddSpellBookEntry(int32 spell_id, int8 tier, sint32 slot, int32 type, int32 timer, bool save_needed = false); + SpellBookEntry* GetSpellBookSpell(int32 spell_id); + vector* GetSpellsSaveNeeded(); + sint32 GetFreeSpellBookSlot(int32 type); + /// Get a vector of spell ids for all spells in the spell book for the given skill + /// The id of the skill to check + /// A vector of int32's of the spell id's + vector GetSpellBookSpellIDBySkill(int32 skill_id); + void UpdateInventory(int32 bag_id); + EQ2Packet* MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version = 1); + bool IsPlayer(){ return true; } + MaintainedEffects* GetFreeMaintainedSpellSlot(); + MaintainedEffects* GetMaintainedSpell(int32 id); + MaintainedEffects* GetMaintainedSpellBySlot(int8 slot); + MaintainedEffects* GetMaintainedSpells(); + SpellEffects* GetFreeSpellEffectSlot(); + SpellEffects* GetSpellEffects(); + int32 GetCoinsCopper(); + int32 GetCoinsSilver(); + int32 GetCoinsGold(); + int32 GetCoinsPlat(); + int32 GetBankCoinsCopper(); + int32 GetBankCoinsSilver(); + int32 GetBankCoinsGold(); + int32 GetBankCoinsPlat(); + int32 GetStatusPoints(); + float GetXPVitality(); + float GetTSXPVitality(); + bool AdventureXPEnabled(); + bool TradeskillXPEnabled(); + void SetNeededXP(int32 val); + void SetNeededXP(); + void SetXP(int32 val); + void SetNeededTSXP(int32 val); + void SetNeededTSXP(); + void SetTSXP(int32 val); + int32 GetNeededXP(); + float GetXPDebt(); + int32 GetXP(); + int32 GetNeededTSXP(); + int32 GetTSXP(); + bool AddXP(int32 xp_amount); + bool AddTSXP(int32 xp_amount); + bool DoubleXPEnabled(); + float CalculateXP(Spawn* victim); + float CalculateTSXP(int8 level); + void CalculateOfflineDebtRecovery(int32 unix_timestamp); + void InCombat(bool val, bool range = false); + void PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version); + uchar* GetMovementPacketData(){ + return movement_packet; + } + void AddSpawnInfoPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size); + uchar* GetSpawnInfoPacketForXOR(int32 spawn_id); + void AddSpawnVisPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size); + uchar* GetSpawnVisPacketForXOR(int32 spawn_id); + void AddSpawnPosPacketForXOR(int32 spawn_id, uchar* packet, int16 packet_size); + uchar* GetSpawnPosPacketForXOR(int32 spawn_id); + uchar* GetTempInfoPacketForXOR(); + uchar* GetTempVisPacketForXOR(); + uchar* GetTempPosPacketForXOR(); + uchar* SetTempInfoPacketForXOR(int16 size); + uchar* SetTempVisPacketForXOR(int16 size); + uchar* SetTempPosPacketForXOR(int16 size); + int32 GetTempInfoXorSize() { return info_xor_size; } + int32 GetTempVisXorSize() { return vis_xor_size; } + int32 GetTempPosXorSize() { return pos_xor_size; } + bool CheckPlayerInfo(); + void CalculateLocation(); + void SetSpawnDeleteTime(int32 id, int32 time); + int32 GetSpawnDeleteTime(int32 id); + void ClearRemovalTimers(); + void ClearEverything(); + bool IsResurrecting(); + void SetResurrecting(bool val); + int8 GetTSArrowColor(int8 level); + Spawn* GetSpawnByIndex(int16 index); + int16 GetIndexForSpawn(Spawn* spawn); + bool WasSpawnRemoved(Spawn* spawn); + void RemoveSpawn(Spawn* spawn, bool delete_spawn = true); + bool ShouldSendSpawn(Spawn* spawn); + Client* client = 0; + void SetLevel(int16 level, bool setUpdateFlags = true); + + Spawn* GetSpawnWithPlayerID(int32 id){ + Spawn* spawn = 0; + + index_mutex.readlock(__FUNCTION__, __LINE__); + if (player_spawn_id_map.count(id) > 0) + spawn = player_spawn_id_map[id]; + index_mutex.releasereadlock(__FUNCTION__, __LINE__); + return spawn; + } + int32 GetIDWithPlayerSpawn(Spawn* spawn){ + int32 id = 0; + + index_mutex.readlock(__FUNCTION__, __LINE__); + if (player_spawn_reverse_id_map.count(spawn) > 0) + id = player_spawn_reverse_id_map[spawn]; + index_mutex.releasereadlock(__FUNCTION__, __LINE__); + + return id; + } + + int16 GetNextSpawnIndex(Spawn* spawn, bool set_lock = true); + bool SetSpawnMap(Spawn* spawn); + + void SetSpawnMapIndex(Spawn* spawn, int32 index) + { + index_mutex.writelock(__FUNCTION__, __LINE__); + if (player_spawn_id_map.count(index)) + player_spawn_id_map[index] = spawn; + else + player_spawn_id_map[index] = spawn; + index_mutex.releasewritelock(__FUNCTION__, __LINE__); + } + + int16 SetSpawnMapAndIndex(Spawn* spawn); + + PacketStruct* GetQuestJournalPacket(bool all_quests, int16 version, int32 crc, int32 current_quest_id, bool updated = true); + void RemoveQuest(int32 id, bool delete_quest); + vector* CheckQuestsChatUpdate(Spawn* spawn); + vector* CheckQuestsItemUpdate(Item* item); + vector* CheckQuestsLocationUpdate(); + vector* CheckQuestsKillUpdate(Spawn* spawn,bool update = true); + bool HasQuestUpdateRequirement(Spawn* spawn); + vector* CheckQuestsSpellUpdate(Spell* spell); + void CheckQuestsCraftUpdate(Item* item, int32 qty); + void CheckQuestsHarvestUpdate(Item* item, int32 qty); + vector* CheckQuestsFailures(); + bool CheckQuestRemoveFlag(Spawn* spawn); + int8 CheckQuestFlag(Spawn* spawn); + bool UpdateQuestReward(int32 quest_id, QuestRewardData* qrd); + Quest* PendingQuestAcceptance(int32 quest_id, int32 item_id, bool* quest_exists); + bool AcceptQuestReward(int32 item_id, int32 selectable_item_id); + + bool SendQuestStepUpdate(int32 quest_id, int32 quest_step_id, bool display_quest_helper); + void SendQuest(int32 quest_id); + void UpdateQuestCompleteCount(int32 quest_id); + void GetQuestTemporaryRewards(int32 quest_id, std::vector* items); + void AddQuestTemporaryReward(int32 quest_id, int32 item_id, int16 item_count); + + bool CheckQuestRequired(Spawn* spawn); + void AddQuestRequiredSpawn(Spawn* spawn, int32 quest_id); + void AddHistoryRequiredSpawn(Spawn* spawn, int32 event_id); + int16 spawn_index; + int32 spawn_id; + int8 tutorial_step; + map*> player_spawn_quests_required; + map*> player_spawn_history_required; + Mutex m_playerSpawnQuestsRequired; + Mutex m_playerSpawnHistoryRequired; + bool HasQuestBeenCompleted(int32 quest_id); + int32 GetQuestCompletedCount(int32 quest_id); + void AddCompletedQuest(Quest* quest); + bool HasActiveQuest(int32 quest_id); + bool HasAnyQuest(int32 quest_id); + map pending_quests; + map player_quests; + map* GetPlayerQuests(); + map* GetCompletedPlayerQuests(); + void SetFactionValue(int32 faction_id, sint32 value){ + factions.SetFactionValue(faction_id, value); + } + PlayerFaction* GetFactions(){ + return &factions; + } + vector GetQuestIDs(); + map macro_icons; + + bool HasPendingLootItems(int32 id); + bool HasPendingLootItem(int32 id, int32 item_id); + vector* GetPendingLootItems(int32 id); + void RemovePendingLootItem(int32 id, int32 item_id); + void RemovePendingLootItems(int32 id); + void AddPendingLootItems(int32 id, vector* items); + int16 GetTierUp(int16 tier); + bool HasSpell(int32 spell_id, int8 tier = 255, bool include_higher_tiers = false, bool include_possible_scribe = false); + bool HasRecipeBook(int32 recipe_id); + void AddPlayerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date); + void UpdatePlayerStatistic(int32 stat_id, sint32 stat_value, bool overwrite = false); + sint64 GetPlayerStatisticValue(int32 stat_id); + void WritePlayerStatistics(); + + + + //PlayerGroup* GetGroup(); + void SetGroup(PlayerGroup* group); + bool IsGroupMember(Entity* player); + void SetGroupInformation(PacketStruct* packet); + + + void ResetSavedSpawns(); + bool IsReturningFromLD(); + void SetReturningFromLD(bool val); + bool CheckLevelStatus(int16 new_level); + int16 GetLastMovementActivity(); + void DestroyQuests(); + string GetAwayMessage() const { return away_message; } + void SetAwayMessage(string val) { away_message = val; } + void SetRangeAttack(bool val); + bool GetRangeAttack(); + ZoneServer* GetGroupMemberInZone(int32 zone_id); + bool AddMail(Mail* mail); + MutexMap* GetMail(); + Mail* GetMail(int32 mail_id); + void DeleteMail(bool from_database = false); + void DeleteMail(int32 mail_id, bool from_database = false); + CharacterInstances* GetCharacterInstances() { return &character_instances; } + void SetIsTracking(bool val) { is_tracking = val; } + bool GetIsTracking() const { return is_tracking; } + void SetBiography(string new_biography) { biography = new_biography; } + string GetBiography() const { return biography; } + void SetPlayerAdventureClass(int8 new_class); + void SetGuild(Guild* new_guild) { guild = new_guild; } + Guild* GetGuild() { return guild; } + void AddSkillBonus(int32 spell_id, int32 skill_id, float value); + SkillBonus* GetSkillBonus(int32 spell_id); + virtual void RemoveSkillBonus(int32 spell_id); + + virtual bool CanSeeInvis(Entity* target); + bool CheckChangeInvisHistory(Entity* target); + void UpdateTargetInvisHistory(int32 targetID, bool canSeeStatus); + void RemoveTargetInvisHistory(int32 targetID); + + bool HasFreeBankSlot(); + int8 FindFreeBankSlot(); + PlayerCollectionList * GetCollectionList() { return &collection_list; } + PlayerRecipeList * GetRecipeList() { return &recipe_list; } + PlayerRecipeBookList * GetRecipeBookList() { return &recipebook_list; } + PlayerAchievementList * GetAchievementList() { return &achievement_list; } + PlayerAchievementUpdateList * GetAchievementUpdateList() { return &achievement_update_list; } + void SetPendingCollectionReward(Collection *collection) { pending_collection_reward = collection; } + Collection * GetPendingCollectionReward() { return pending_collection_reward; } + void AddPendingSelectableItemReward(int32 source_id, Item* item) { + if (pending_selectable_item_rewards.count(source_id) == 0) + pending_selectable_item_rewards[source_id] = vector(); + pending_selectable_item_rewards[source_id].push_back(item); + } + void AddPendingItemReward(Item* item) { + pending_item_rewards.push_back(item); + } + bool HasPendingItemRewards() { return (pending_item_rewards.size() > 0 || pending_selectable_item_rewards.size() > 0); } + vector GetPendingItemRewards() { return pending_item_rewards; } + map GetPendingSelectableItemReward(int32 item_id) { //since the client sends the selected item id, we need to have the associated source and remove all of them. Yes, there is an edge case if multiple sources have the same Item in them, but limited on what the client sends (just a single item id) + map ret; + if (pending_selectable_item_rewards.size() > 0) { + map>::iterator map_itr; + for (map_itr = pending_selectable_item_rewards.begin(); map_itr != pending_selectable_item_rewards.end(); map_itr++) { + vector::iterator itr; + for (itr = map_itr->second.begin(); itr != map_itr->second.end(); itr++) { + if ((*itr)->details.item_id == item_id) { + ret[map_itr->first] = *itr; + break; + } + } + if (ret.size() > 0) + break; + } + } + return map(); + } + void ClearPendingSelectableItemRewards(int32 source_id, bool all = false) { + if (pending_selectable_item_rewards.size() > 0) { + map>::iterator map_itr; + if (all) { + for (map_itr = pending_selectable_item_rewards.begin(); map_itr != pending_selectable_item_rewards.end(); map_itr++) { + vector::iterator itr; + for (itr = map_itr->second.begin(); itr != map_itr->second.end(); itr++) { + safe_delete(*itr); + } + } + pending_selectable_item_rewards.clear(); + } + else { + if (pending_selectable_item_rewards.count(source_id) > 0) { + vector::iterator itr; + for (itr = pending_selectable_item_rewards[source_id].begin(); itr != pending_selectable_item_rewards[source_id].end(); itr++) { + safe_delete(*itr); + } + pending_selectable_item_rewards.erase(source_id); + } + } + } + } + void ClearPendingItemRewards() { //the client doesn't send any reference to where the pending rewards came from, so if they collect one, we should just them all of them at once + if (pending_item_rewards.size() > 0) { + vector::iterator itr; + for (itr = pending_item_rewards.begin(); itr != pending_item_rewards.end(); itr++) { + safe_delete(*itr); + } + pending_item_rewards.clear(); + } + } + + enum DELETE_BOOK_TYPE { + DELETE_TRADESKILLS = 1, + DELETE_SPELLS = 2, + DELETE_COMBAT_ART = 4, + DELETE_ABILITY = 8, + DELETE_NOT_SHOWN = 16 + }; + void DeleteSpellBook(int8 type_selection = 0); + void RemoveSpellBookEntry(int32 spell_id, bool remove_passives_from_list = true); + void ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 maxlvl_only, int32 book_type); + void GetSpellBookSlotSort(int32 pattern, int32* i, int8* page_book_count, int32* last_start_point); + static bool SortSpellEntryByName(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByCategory(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByLevel(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByNameReverse(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByCategoryReverse(SpellBookEntry* s1, SpellBookEntry* s2); + static bool SortSpellEntryByLevelReverse(SpellBookEntry* s1, SpellBookEntry* s2); + + int8 GetSpellSlot(int32 spell_id); + void AddTitle(sint32 title_id, const char *name, int8 prefix, bool save_needed = false); + void AddAAEntry(int16 template_id, int8 tab_id, int32 aa_id, int16 order, int8 treeid); + PlayerTitlesList* GetPlayerTitles() { return &player_titles_list; } + void AddLanguage(int32 id, const char *name, bool save_needed = false); + PlayerLanguagesList* GetPlayerLanguages() { return &player_languages_list; } + bool HasLanguage(int32 id); + bool HasLanguage(const char* name); + bool CanReceiveQuest(int32 quest_id, int8* ret = 0); + float GetBoatX() { if (info) return info->GetBoatX(); return 0; } + float GetBoatY() { if (info) return info->GetBoatY(); return 0; } + float GetBoatZ() { if (info) return info->GetBoatZ(); return 0; } + int32 GetBoatSpawn() { if (info) return info->GetBoatSpawn(); return 0; } + void SetBoatX(float x) { if (info) info->SetBoatX(x); } + void SetBoatY(float y) { if (info) info->SetBoatY(y); } + void SetBoatZ(float z) { if (info) info->SetBoatZ(z); } + void SetBoatSpawn(Spawn* boat) { if (info) info->SetBoatSpawn(boat); } + Mutex* GetGroupBuffMutex(); + void SetPendingDeletion(bool val) { pending_deletion = val; } + bool GetPendingDeletion() { return pending_deletion; } + float GetPosPacketSpeed() { return pos_packet_speed; } + bool ControlFlagsChanged(); + void SetPlayerControlFlag(int8 param, int8 param_value, bool is_active); + void SendControlFlagUpdates(Client* client); + + /// Casts all the passive spells for the player, only call after zoning is complete. + void ApplyPassiveSpells(); + + /// Removes all passive spell effects from the player and clears the passive list + void RemoveAllPassives(); + + /// Gets the current recipie ID + int32 GetCurrentRecipe() { return current_recipe; } + + /// Sets the current recipie ID + /// Id of the new recipe + void SetCurrentRecipe(int32 val) { current_recipe = val; } + + /// Reset the pet window info + void ResetPetInfo(); + + void ProcessCombat(); + + /* Character history stuff */ + + /// Adds a new history event to the player + /// The history type + /// The history sub type + /// The first history value + /// The second history value + void UpdatePlayerHistory(int8 type, int8 subtype, int32 value, int32 value2 = 0); + + /// Checks to see if the player has discovered the location + /// The ID of the location to check + /// True if the player has discovered the location + bool DiscoveredLocation(int32 locationID); + + /// Load the players history from the database + /// The history type + /// The history sub type + /// The history data + void LoadPlayerHistory(int8 type, int8 subtype, HistoryData* hd); + + /// Save the player's history to the database + void SaveHistory(); + + + /* New functions for spell locking and unlocking*/ + /// Lock all Spells, Combat arts, and Abilities (not trade skill spells) + void LockAllSpells(); + + /// Unlocks all Spells, Combat arts, and Abilities (not trade skill spells) + void UnlockAllSpells(bool modify_recast = false, Spell* exception = 0); + + /// Locks the given spell as well as all spells with a shared timer + void LockSpell(Spell* spell, int16 recast); + + /// Unlocks the given spell as well as all spells with shared timers + void UnlockSpell(Spell* spell); + + /// Locks all ts spells and unlocks all normal spells + void LockTSSpells(); + + /// Unlocks all ts spells and locks all normal spells + void UnlockTSSpells(); + + /// Queue the given spell + void QueueSpell(Spell* spell); + + /// Unqueue the given spell + void UnQueueSpell(Spell* spell); + + ///Get all the spells the player has with the given id + vector GetSpellBookSpellsByTimer(Spell* spell, int32 timerID); + + PacketStruct* GetQuestJournalPacket(Quest* quest, int16 version, int32 crc, bool updated = true); + + void SetSpawnInfoStruct(PacketStruct* packet) { safe_delete(spawn_info_struct); spawn_info_struct = packet; } + void SetSpawnVisStruct(PacketStruct* packet) { safe_delete(spawn_vis_struct); spawn_vis_struct = packet; } + void SetSpawnPosStruct(PacketStruct* packet) { safe_delete(spawn_pos_struct); spawn_pos_struct = packet; } + void SetSpawnHeaderStruct(PacketStruct* packet) { safe_delete(spawn_header_struct); spawn_header_struct = packet; } + void SetSpawnFooterStruct(PacketStruct* packet) { safe_delete(spawn_footer_struct); spawn_footer_struct = packet; } + void SetSignFooterStruct(PacketStruct* packet) { safe_delete(sign_footer_struct); sign_footer_struct = packet; } + void SetWidgetFooterStruct(PacketStruct* packet) { safe_delete(widget_footer_struct); widget_footer_struct = packet; } + + PacketStruct* GetSpawnInfoStruct() { return spawn_info_struct; } + PacketStruct* GetSpawnVisStruct() { return spawn_vis_struct; } + PacketStruct* GetSpawnPosStruct() { return spawn_pos_struct; } + PacketStruct* GetSpawnHeaderStruct() { return spawn_header_struct; } + PacketStruct* GetSpawnFooterStruct() { return spawn_footer_struct; } + PacketStruct* GetSignFooterStruct() { return sign_footer_struct; } + PacketStruct* GetWidgetFooterStruct() { return widget_footer_struct; } + + Mutex info_mutex; + Mutex pos_mutex; + Mutex vis_mutex; + Mutex index_mutex; + Mutex spawn_mutex; + mutable std::shared_mutex spawn_aggro_range_mutex; + + void SetTempMount(int32 id) { tmp_mount_model = id; } + int32 GetTempMount() { return tmp_mount_model; } + + void SetTempMountColor(EQ2_Color* color) { tmp_mount_color = *color; } + EQ2_Color GetTempMountColor() { return tmp_mount_color; } + + void SetTempMountSaddleColor(EQ2_Color* color) { tmp_mount_saddle_color = *color; } + EQ2_Color GetTempMountSaddleColor() { return tmp_mount_saddle_color; } + + + void LoadLUAHistory(int32 event_id, LUAHistory* history); + void SaveLUAHistory(); + void UpdateLUAHistory(int32 event_id, int32 value, int32 value2); + LUAHistory* GetLUAHistory(int32 event_id); + + bool HasGMVision() { return gm_vision; } + void SetGMVision(bool val) { gm_vision = val; } + + void StopCombat(int8 type=0) { + switch(type) + { + case 2: + SetRangeAttack(false); + InCombat(false, true); + break; + default: + InCombat(false); + InCombat(false, true); + SetRangeAttack(false); + break; + } + } + + NPC* InstantiateSpiritShard(float origX, float origY, float origZ, float origHeading, int32 origGridID, ZoneServer* origZone); + + void DismissAllPets(); + + void SaveSpellEffects(); + + void SetSaveSpellEffects(bool val) { stop_save_spell_effects = val; } + AppearanceData SavedApp; + CharFeatures SavedFeatures; + bool custNPC; + Entity* custNPCTarget; + // bot index, spawn id + map SpawnedBots; + bool StopSaveSpellEffects() { return stop_save_spell_effects; } + + void MentorTarget(); + void SetMentorStats(int32 effective_level, int32 target_char_id = 0, bool update_stats = true); + + bool ResetMentorship() { + bool mentorship_status = reset_mentorship; + if(mentorship_status) + { + SetMentorStats(GetLevel()); + } + reset_mentorship = false; + return mentorship_status; + } + + void EnableResetMentorship() { + reset_mentorship = true; + } + + bool SerializeItemPackets(EquipmentItemList* equipList, vector* packets, Item* item, int16 version, Item* to_item = 0); + + void AddGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, int16 visual_tag); + int16 MatchGMVisualFilter(int32 filter_type, int32 filter_value, char* filter_search_str, bool in_vismutex_lock = false); + void ClearGMVisualFilters(); + int GetPVPAlignment(); + + int32 GetCurrentLanguage() { return current_language_id; } + void SetCurrentLanguage(int32 language_id) { current_language_id = language_id; } + + void SetActiveReward(bool val) { active_reward = val; } + bool IsActiveReward() { return active_reward; } + + + bool IsSpawnInRangeList(int32 spawn_id); + void SetSpawnInRangeList(int32 spawn_id, bool in_range); + void ProcessSpawnRangeUpdates(); + void CalculatePlayerHPPower(int16 new_level = 0); + bool IsAllowedCombatEquip(int8 slot = 255, bool send_message = false); + + void SetActiveFoodUniqueID(int32 unique_id, bool update_db = true); + void SetActiveDrinkUniqueID(int32 unique_id, bool update_db = true); + + int32 GetActiveFoodUniqueID() { return active_food_unique_id; } + int32 GetActiveDrinkUniqueID() { return active_drink_unique_id; } + + Mutex MPlayerQuests; + float pos_packet_speed; + + map > >* SortedTraitList; + map >* ClassTraining; + map >* RaceTraits; + map >* InnateRaceTraits; + map >* FocusEffects; + mutable std::shared_mutex trait_mutex; + std::atomic need_trait_update; + + void InitXPTable(); + map m_levelXPReq; + + mutable std::shared_mutex spell_packet_update_mutex; +private: + bool reset_mentorship; + bool range_attack; + int16 last_movement_activity; + bool returning_from_ld; + PlayerGroup* group; + + float test_x; + float test_y; + float test_z; + int32 test_time; + map > pending_loot_items; + Mutex MSpellsBook; + Mutex MRecipeBook; + map current_quest_flagged; + PlayerFaction factions; + map completed_quests; + bool charsheet_changed; + map spawn_vis_packet_list; + map spawn_info_packet_list; + map spawn_pos_packet_list; + map spawn_packet_sent; + map spawn_state_list; + uchar* movement_packet; + uchar* old_movement_packet; + uchar* spell_orig_packet; + uchar* spell_xor_packet; + int16 spell_count; + //float speed; + int16 target_id; + Spawn* combat_target; + int32 char_id; + bool quickbar_updated; + bool resurrecting; + PlayerInfo* info; + vector spells; + vector quickbar_items; + map statistics; + void RemovePlayerStatistics(); + map friend_list; + map ignore_list; + bool pending_deletion; + PlayerControlFlags control_flags; + + map target_invis_history; + + // JA: POI Discoveries + map > players_poi_list; + + // Jabantiz: Passive spell list, just stores spell id's + vector passive_spells; + + /// Adds a new passive spell to the list + /// Spell id to add + /// Tier of spell to add + void AddPassiveSpell(int32 id, int8 tier); + + /// Removes a passive spell from the list + /// Spell id to remove + /// Tier of spell to remove + /// Remove the spell from this players passive list, default true + void RemovePassive(int32 id, int8 tier, bool remove_from_list = true); + + CharacterInstances character_instances; + string away_message; + string biography; + MutexMap mail_list; + bool is_tracking; + Guild* guild; + PlayerCollectionList collection_list; + Collection * pending_collection_reward; + vector pending_item_rewards; + map> pending_selectable_item_rewards; + PlayerTitlesList player_titles_list; + PlayerRecipeList recipe_list; + PlayerLanguagesList player_languages_list; + PlayerRecipeBookList recipebook_list; + PlayerAchievementList achievement_list; + PlayerAchievementUpdateList achievement_update_list; + // Need to keep track of the recipe the player is crafting as not all crafting packets have this info + int32 current_recipe; + + void HandleHistoryNone(int8 subtype, int32 value, int32 value2); + void HandleHistoryDeath(int8 subtype, int32 value, int32 value2); + void HandleHistoryDiscovery(int8 subtype, int32 value, int32 value2); + void HandleHistoryXP(int8 subtype, int32 value, int32 value2); + + /// + void ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); + void AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); + void RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0); + void SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast); +// void InitXPTable(); +// map m_levelXPReq; + + //The following variables are for serializing spawn packets + PacketStruct* spawn_pos_struct; + PacketStruct* spawn_info_struct; + PacketStruct* spawn_vis_struct; + PacketStruct* spawn_header_struct; + PacketStruct* spawn_footer_struct; + PacketStruct* sign_footer_struct; + PacketStruct* widget_footer_struct; + uchar* spawn_tmp_vis_xor_packet; + uchar* spawn_tmp_pos_xor_packet; + uchar* spawn_tmp_info_xor_packet; + int32 vis_xor_size; + int32 pos_xor_size; + int32 info_xor_size; + + // Character history, map > > + map > > m_characterHistory; + + map m_charLuaHistory; + Mutex mLUAHistory; + + int32 tmp_mount_model; + EQ2_Color tmp_mount_color; + EQ2_Color tmp_mount_saddle_color; + + bool gm_vision; + bool stop_save_spell_effects; + + map player_spawn_id_map; + map player_spawn_reverse_id_map; + map player_aggro_range_spawns; + + bool all_spells_locked; + Timer lift_cooldown; + + vector gm_visual_filters; + + int32 current_language_id; + + bool active_reward; + + Quest* GetAnyQuest(int32 quest_id); + Quest* GetCompletedQuest(int32 quest_id); + + std::atomic active_food_unique_id; + std::atomic active_drink_unique_id; +}; +#pragma pack() +#endif diff --git a/source/WorldServer/PlayerGroups.cpp b/source/WorldServer/PlayerGroups.cpp new file mode 100644 index 0000000..483b007 --- /dev/null +++ b/source/WorldServer/PlayerGroups.cpp @@ -0,0 +1,1000 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#include "PlayerGroups.h" +#include "../common/Log.h" +#include "World.h" +#include "Spells.h" +#include "LuaInterface.h" +#include "Bots/Bot.h" +#include "SpellProcess.h" +#include "Rules/Rules.h" + +extern ZoneList zone_list; +extern RuleManager rule_manager; + +/******************************************************** PlayerGroup ********************************************************/ + +PlayerGroup::PlayerGroup(int32 id) { + m_id = id; + MGroupMembers.SetName("MGroupMembers"); + SetDefaultGroupOptions(); +} + +PlayerGroup::~PlayerGroup() { + Disband(); +} + +bool PlayerGroup::AddMember(Entity* member) { + // Check to make sure the entity we are adding is valid + if (!member) { + LogWrite(GROUP__ERROR, 0, "Group", "New member is null"); + return false; + } + + // Make sure entity we are adding isn't already in a group by checking if it has a GroupMemberInfo pointer + if (member->GetGroupMemberInfo()) { + LogWrite(GROUP__ERROR, 0, "Group", "New member (%s) already has a group", member->GetName()); + return false; + } + + // Create a new GroupMemberInfo and assign it to the new member + GroupMemberInfo* gmi = new GroupMemberInfo; + gmi->group_id = m_id; + gmi->member = member; + gmi->leader = false; + if (member->IsPlayer()) + gmi->client = ((Player*)member)->GetClient(); + else + gmi->client = 0; + gmi->mentor_target_char_id = 0; + + member->SetGroupMemberInfo(gmi); + member->group_id = gmi->group_id; + MGroupMembers.writelock(); + m_members.push_back(gmi); + member->UpdateGroupMemberInfo(true, true); + MGroupMembers.releasewritelock(); + + SendGroupUpdate(); + return true; +} + +bool PlayerGroup::RemoveMember(Entity* member) { + GroupMemberInfo* gmi = member->GetGroupMemberInfo(); + if (!gmi) { + return false; + } + + bool ret = false; + + MGroupMembers.writelock(); + member->SetGroupMemberInfo(0); + + deque::iterator erase_itr = m_members.end(); + deque::iterator itr; + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + if (gmi == *itr) + erase_itr = itr; + + if(member->IsPlayer() && (*itr)->mentor_target_char_id == ((Player*)member)->GetCharacterID() && (*itr)->client) + { + (*itr)->mentor_target_char_id = 0; + (*itr)->client->GetPlayer()->EnableResetMentorship(); + } + + if ((*itr)->client) + (*itr)->client->GetPlayer()->SetCharSheetChanged(true); + } + if (erase_itr != m_members.end()) { + ret = true; + m_members.erase(erase_itr); + } + MGroupMembers.releasewritelock(); + + member->SetGroupMemberInfo(nullptr); + safe_delete(gmi); + if (member->IsBot()) + ((Bot*)member)->Camp(); + + return ret; +} + +void PlayerGroup::Disband() { + deque::iterator itr; + MGroupMembers.writelock(); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + if ((*itr)->member) { + (*itr)->member->SetGroupMemberInfo(0); + if ((*itr)->member->IsBot()) + ((Bot*)(*itr)->member)->Camp(); + } + if((*itr)->mentor_target_char_id && (*itr)->client) + { + (*itr)->mentor_target_char_id = 0; + (*itr)->client->GetPlayer()->EnableResetMentorship(); + } + + if ((*itr)->client) + (*itr)->client->GetPlayer()->SetCharSheetChanged(true); + + safe_delete(*itr); + } + + m_members.clear(); + MGroupMembers.releasewritelock(); +} + +void PlayerGroup::SendGroupUpdate(Client* exclude) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client && gmi->client != exclude && !gmi->client->IsZoning()) + gmi->client->GetPlayer()->SetCharSheetChanged(true); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void PlayerGroup::SimpleGroupMessage(const char* message) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for(itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if(info->client) + info->client->SimpleMessage(CHANNEL_GROUP_CHAT, message); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void PlayerGroup::SendGroupMessage(int8 type, const char* message, ...) { + va_list argptr; + char buffer[4096]; + buffer[0] = 0; + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if (info->client) + info->client->SimpleMessage(type, buffer); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +void PlayerGroup::GroupChatMessage(Spawn* from, int32 language, const char* message) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for(itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if(info && info->client && info->client->GetCurrentZone()) + info->client->GetCurrentZone()->HandleChatMessage(info->client, from, 0, CHANNEL_GROUP_SAY, message, 0, 0, true, language); + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); +} + +bool PlayerGroup::MakeLeader(Entity* new_leader) { + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if (info->leader) { + info->leader = false; + break; + } + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + new_leader->GetGroupMemberInfo()->leader = true; + SendGroupUpdate(); + + return true; +} + +bool PlayerGroup::ShareQuestWithGroup(Client* quest_sharer, Quest* quest) { + if(!quest || !quest_sharer) + return false; + + bool canShare = quest->CanShareQuestCriteria(quest_sharer); + + if(!canShare) { + return false; + } + + deque::iterator itr; + MGroupMembers.readlock(__FUNCTION__, __LINE__); + for(itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* info = *itr; + if(info && info->client && info->client->GetCurrentZone()) { + if( quest_sharer != info->client && info->client->GetPlayer()->HasAnyQuest(quest->GetQuestID()) == 0 ) { + info->client->AddPendingQuest(new Quest(quest)); + info->client->Message(CHANNEL_COLOR_YELLOW, "%s has shared the quest %s with you.", quest_sharer->GetPlayer()->GetName(), quest->GetName()); + } + } + } + MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + + + +/******************************************************** PlayerGroupManager ********************************************************/ + +PlayerGroupManager::PlayerGroupManager() { + m_nextGroupID = 1; + + MPendingInvites.SetName("PlayerGroupManager::m_pendingInvites"); +} + +PlayerGroupManager::~PlayerGroupManager() { + MPendingInvites.writelock(__FUNCTION__, __LINE__); + m_pendingInvites.clear(); + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + + std::unique_lock lock(MGroups); + map::iterator itr; + for (itr = m_groups.begin(); itr != m_groups.end(); itr++) + safe_delete(itr->second); + + m_groups.clear(); +} + +bool PlayerGroupManager::AddGroupMember(int32 group_id, Entity* member) { + std::shared_lock lock(MGroups); + bool ret = false; + + if (m_groups.count(group_id) > 0) { + PlayerGroup* group = m_groups[group_id]; + ret = group->AddMember(member); + } + + return ret; +} + +bool PlayerGroupManager::RemoveGroupMember(int32 group_id, Entity* member) { + bool ret = false; + bool remove = false; + Client* client = 0; + if(member->GetGroupMemberInfo()->mentor_target_char_id) + { + if(member->IsPlayer()) + { + Player* tmpPlayer = (Player*)member; + member->GetGroupMemberInfo()->mentor_target_char_id = 0; + tmpPlayer->EnableResetMentorship(); + } + } + + GroupLock(__FUNCTION__, __LINE__); + + if (m_groups.count(group_id) > 0) { + PlayerGroup* group = m_groups[group_id]; + + if (member->IsPlayer()) + client = member->GetGroupMemberInfo()->client; + + ret = group->RemoveMember(member); + + // If only 1 person left in the group set a flag to remove the group + if (group->Size() == 1) + remove = true; + } + + ReleaseGroupLock(__FUNCTION__, __LINE__); + + if (client) + RemoveGroupBuffs(group_id, client); + + // Call RemoveGroup outside the locks as it uses the same locks + if (remove) + RemoveGroup(group_id); + + return ret; +} + +void PlayerGroupManager::NewGroup(Entity* leader) { + std::unique_lock lock(MGroups); + + // Highly doubt this will ever be needed but putting it in any way, basically bump the id and ensure + // no active group is currently using this id, if we hit the max for an int32 then reset the id to 1 + while (m_groups.count(m_nextGroupID) > 0) { + // If m_nextGroupID is at its max then reset it to 1, else increment it + if (m_nextGroupID == 4294967295) + m_nextGroupID = 1; + else + m_nextGroupID++; + } + + // Create a new group with the valid ID we got from above + PlayerGroup* new_group = new PlayerGroup(m_nextGroupID); + + GroupOptions goptions; + goptions.loot_method = leader->GetInfoStruct()->get_group_loot_method(); + goptions.loot_items_rarity = leader->GetInfoStruct()->get_group_loot_items_rarity(); + goptions.auto_split = leader->GetInfoStruct()->get_group_auto_split(); + goptions.default_yell = leader->GetInfoStruct()->get_group_default_yell(); + goptions.group_autolock = leader->GetInfoStruct()->get_group_autolock(); + goptions.group_lock_method = leader->GetInfoStruct()->get_group_lock_method(); + goptions.solo_autolock = leader->GetInfoStruct()->get_group_solo_autolock(); + goptions.auto_loot_method = leader->GetInfoStruct()->get_group_auto_loot_method(); + new_group->SetDefaultGroupOptions(&goptions); + + // Add the new group to the list (need to do this first, AddMember needs ref to the PlayerGroup ptr -> UpdateGroupMemberInfo) + m_groups[m_nextGroupID] = new_group; + + // Add the leader to the group + new_group->AddMember(leader); + + leader->GetGroupMemberInfo()->leader = true; +} + +void PlayerGroupManager::RemoveGroup(int32 group_id) { + std::unique_lock lock(MGroups); + + // Check to see if the id is in the list + if (m_groups.count(group_id) > 0) { + // Get a pointer to the group + PlayerGroup* group = m_groups[group_id]; + // Erase the group from the list + m_groups.erase(group_id); + // Delete the group + safe_delete(group); + } +} + +int8 PlayerGroupManager::Invite(Player* leader, Entity* member) { + int8 ret = 255; // Should be changed, if it is not then we have an unknown error + + // Lock the pending invite list so we can work with it + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (!member || (member->IsBot() && ((Bot*)member)->IsImmediateCamp())) + ret = 6; // failure, not a valid target + else if (member->IsNPC() && (!member->IsBot() /*|| !member->IsMec()*/)) + ret = 6; + else if (leader == member) + ret = 5; // failure, can't invite yourself + else if (member->GetGroupMemberInfo()) + ret = 1; // failure, member already in a group + // Check to see if the target of the invite already has a pending invite + else if (m_pendingInvites.count(member->GetName()) > 0) + ret = 2; // Target already has an invite + // Check to see if the player that invited is already in a group + else if (leader->GetGroupMemberInfo()) { + // Read lock the group list so we can get the size of the inviters group + GroupLock(__FUNCTION__, __LINE__); + int32 group_size = m_groups[leader->GetGroupMemberInfo()->group_id]->Size(); + ReleaseGroupLock(__FUNCTION__, __LINE__); + + // Check to see if the group is full + if (m_groups[leader->GetGroupMemberInfo()->group_id]->Size() >= 6) + ret = 3; // Group full + // Group isn't full so add the member to the pending invite list + else { + m_pendingInvites[member->GetName()] = leader->GetName(); + ret = 0; // Success + } + } + // Inviter is not in a group + else { + // Check to see if the inviter has a pending invite himself + if (m_pendingInvites.count(leader->GetName()) > 0) + ret = 4; // inviter already has a pending group invite + // No pending invites for the inviter add both the inviter and the target of the invite to the list + else { + m_pendingInvites[leader->GetName()] = leader->GetName(); + m_pendingInvites[member->GetName()] = leader->GetName(); + ret = 0; // success + } + } + // Release the lock on pending invites + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + + /* testing purposes only */ + if (ret == 0 && member->IsNPC()) + AcceptInvite(member); + + return ret; +} + +int8 PlayerGroupManager::AcceptInvite(Entity* member) { + int8 ret = 3; // default to unknown error + + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (m_pendingInvites.count(member->GetName()) > 0) { + string leader = m_pendingInvites[member->GetName()]; + Client* client_leader = zone_list.GetClientByCharName(leader); + + if (client_leader) { + if (m_pendingInvites.count(leader) > 0) { + NewGroup(client_leader->GetPlayer()); + m_pendingInvites.erase(leader); + } + + // Remove from invite list and add to the group + m_pendingInvites.erase(member->GetName()); + + GroupMessage(client_leader->GetPlayer()->GetGroupMemberInfo()->group_id, "%s has joined the group.", member->GetName()); + AddGroupMember(client_leader->GetPlayer()->GetGroupMemberInfo()->group_id, member); + ret = 0; // success + } + else { + // Was unable to find the leader, remove from the invite list + m_pendingInvites.erase(member->GetName()); + ret = 2; // failure, can't find leader + } + } + else + ret = 1; // failure, no pending invite + + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +void PlayerGroupManager::DeclineInvite(Entity* member) { + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (m_pendingInvites.count(member->GetName()) > 0) { + string leader = m_pendingInvites[member->GetName()]; + m_pendingInvites.erase(member->GetName()); + if (m_pendingInvites.count(leader) > 0) + m_pendingInvites.erase(leader); + } + + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); +} + +bool PlayerGroupManager::IsGroupIDValid(int32 group_id) { + std::shared_lock lock(MGroups); + bool ret = false; + ret = m_groups.count(group_id) > 0; + return ret; +} + +void PlayerGroupManager::SendGroupUpdate(int32 group_id, Client* exclude) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->SendGroupUpdate(exclude); + } +} + +PlayerGroup* PlayerGroupManager::GetGroup(int32 group_id) { + if (m_groups.count(group_id) > 0) + return m_groups[group_id]; + + return 0; +} + +void PlayerGroupManager::ClearPendingInvite(Entity* member) { + MPendingInvites.writelock(__FUNCTION__, __LINE__); + + if (m_pendingInvites.count(member->GetName()) > 0) + m_pendingInvites.erase(member->GetName()); + + MPendingInvites.releasewritelock(__FUNCTION__, __LINE__); +} + +void PlayerGroupManager::RemoveGroupBuffs(int32 group_id, Client* client) { + SpellEffects* se = 0; + Spell* spell = 0; + LuaSpell* luaspell = 0; + EQ2Packet* packet = 0; + Entity* pet = 0; + Player* player = 0; + Entity* charmed_pet = 0; + PlayerGroup* group = 0; + + MGroups.lock_shared(); + if (m_groups.count(group_id) > 0) + group = m_groups[group_id]; + + if (group && client) { + /* first remove all spell effects this group member has on them from other group members */ + player = client->GetPlayer(); + bool recoup_lock = true; + for (int i = 0; i < NUM_SPELL_EFFECTS; i++) { + if(recoup_lock) { + player->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); + recoup_lock = false; + se = player->GetSpellEffects(); + } + if (se && se[i].spell_id != 0xFFFFFFFF) { + //If the client is the caster, don't remove the spell + if (se[i].caster == player) + continue; + + luaspell = se[i].spell; + spell = luaspell->spell; + /* is this a friendly group spell? */ + if (spell && spell->GetSpellData()->group_spell && spell->GetSpellData()->friendly_spell) { + + player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + recoup_lock = true; + // we have to remove our spell effect mutex lock since RemoveSpellEffect needs a write lock to remove it + //Remove all group buffs not cast by this player + player->RemoveSpellEffect(luaspell); + player->RemoveSpellBonus(luaspell); + player->RemoveSkillBonus(spell->GetSpellID()); + + //Also remove group buffs from pets + pet = 0; + charmed_pet = 0; + if (player->HasPet()){ + pet = player->GetPet(); + pet = player->GetCharmedPet(); + } + if (pet){ + pet->RemoveSpellEffect(luaspell); + pet->RemoveSpellBonus(luaspell); + } + if (charmed_pet){ + charmed_pet->RemoveSpellEffect(luaspell); + charmed_pet->RemoveSpellBonus(luaspell); + } + } + } + } + if(!recoup_lock) { // we previously set a readlock that we now release + player->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + } + packet = client->GetPlayer()->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + MGroups.unlock_shared(); +} + +int32 PlayerGroupManager::GetGroupSize(int32 group_id) { + std::shared_lock lock(MGroups); + int32 ret = 0; + + if (m_groups.count(group_id) > 0) + ret = m_groups[group_id]->Size(); + + return ret; +} + +void PlayerGroupManager::SendGroupQuests(int32 group_id, Client* client) { + std::shared_lock lock(MGroups); + GroupMemberInfo* info = 0; + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + deque::iterator itr; + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + if (info->client) { + LogWrite(PLAYER__DEBUG, 0, "Player", "Send Quest Journal..."); + info->client->SendQuestJournal(false, client); + client->SendQuestJournal(false, info->client); + } + } + m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } +} + +bool PlayerGroupManager::HasGroupCompletedQuest(int32 group_id, int32 quest_id) { + std::shared_lock lock(MGroups); + bool questComplete = true; + GroupMemberInfo* info = 0; + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + deque::iterator itr; + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + if (info->client) { + bool isComplete = info->client->GetPlayer()->HasQuestBeenCompleted(quest_id); + if(!isComplete) + { + questComplete = isComplete; + break; + } + } + } + m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + return questComplete; +} + +void PlayerGroupManager::SimpleGroupMessage(int32 group_id, const char* message) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) + m_groups[group_id]->SimpleGroupMessage(message); +} + +void PlayerGroupManager::SendGroupMessage(int32 group_id, int8 type, const char* message, ...) { + std::shared_lock lock(MGroups); + + va_list argptr; + char buffer[4096]; + buffer[0] = 0; + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + + if (m_groups.count(group_id) > 0) + m_groups[group_id]->SendGroupMessage(type, buffer); +} + +void PlayerGroupManager::GroupMessage(int32 group_id, const char* message, ...) { + va_list argptr; + char buffer[4096]; + buffer[0] = 0; + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + SimpleGroupMessage(group_id, buffer); +} + +void PlayerGroupManager::GroupChatMessage(int32 group_id, Spawn* from, int32 language, const char* message) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) + m_groups[group_id]->GroupChatMessage(from, language, message); +} + +bool PlayerGroupManager::MakeLeader(int32 group_id, Entity* new_leader) { + std::shared_lock lock(MGroups); + + if (m_groups.count(group_id) > 0) + return m_groups[group_id]->MakeLeader(new_leader); + + return false; +} + +void PlayerGroupManager::UpdateGroupBuffs() { + map::iterator itr; + deque::iterator member_itr; + deque::iterator target_itr; + map::iterator itr_skills; + MaintainedEffects* me = nullptr; + LuaSpell* luaspell = nullptr; + Spell* spell = nullptr; + Entity* group_member = nullptr; + SkillBonus* sb = nullptr; + EQ2Packet* packet = nullptr; + int32 i = 0; + PlayerGroup* group = nullptr; + Player* caster = nullptr; + vector new_target_list; + vector char_list; + Client* client = nullptr; + bool has_effect = false; + vector* sb_list = nullptr; + BonusValues* bv = nullptr; + Entity* pet = nullptr; + Entity* charmed_pet = nullptr; + + + + for (itr = m_groups.begin(); itr != m_groups.end(); itr++) { + group = itr->second; + + /* loop through the group members and see if any of them have any maintained spells that are group buffs and friendly. + if so, update the list of targets and apply/remove effects as needed */ + vector players; + + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + for (member_itr = group->GetMembers()->begin(); member_itr != group->GetMembers()->end(); member_itr++) { + if ((*member_itr)->client) + caster = (*member_itr)->client->GetPlayer(); + else caster = 0; + + if (!caster) + continue; + + if (!caster->GetMaintainedSpellBySlot(0)) + continue; + + players.push_back(caster); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + vector::iterator vitr; + + for (vitr = players.begin(); vitr != players.end(); vitr++) { + caster = *vitr; + caster->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); + // go through the player's maintained spells + me = caster->GetMaintainedSpells(); + for (i = 0; i < NUM_MAINTAINED_EFFECTS; i++) { + if (me[i].spell_id == 0xFFFFFFFF) + continue; + luaspell = me[i].spell; + + if (!luaspell) + continue; + + if (!luaspell->caster) + { + LogWrite(PLAYER__ERROR, 0, "Player", "Bad luaspell, caster is NULL, spellid: %u", me[i].spell_id); + continue; + } + + spell = luaspell->spell; + + if (spell && spell->GetSpellData()->group_spell && spell->GetSpellData()->friendly_spell && + (spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE || spell->GetSpellData()->target_type == SPELL_TARGET_RAID_AE)) { + + luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + luaspell->char_id_targets.clear(); + + for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) { + group_member = (*target_itr)->member; + + if (!group_member) + continue; + + if (group_member == caster) + continue; + + client = (*target_itr)->client; + + has_effect = false; + + if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) { + has_effect = true; + } + if(!has_effect && (std::find(luaspell->removed_targets.begin(), + luaspell->removed_targets.end(), group_member->GetID()) != luaspell->removed_targets.end())) { + continue; + } + // Check if player is within range of the caster + if (!rule_manager.GetGlobalRule(R_Spells, EnableCrossZoneGroupBuffs)->GetInt8() && + (group_member->GetZone() != caster->GetZone() || caster->GetDistance(group_member) > spell->GetSpellData()->radius)) { + if (has_effect) { + group_member->RemoveSpellEffect(luaspell); + group_member->RemoveSpellBonus(luaspell); + group_member->RemoveSkillBonus(spell->GetSpellID()); + if (client) { + packet = ((Player*)group_member)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + //Also remove group buffs from pet + if (group_member->HasPet()) { + pet = group_member->GetPet(); + charmed_pet = group_member->GetCharmedPet(); + if (pet) { + pet->RemoveSpellEffect(luaspell); + pet->RemoveSpellBonus(luaspell); + } + if (charmed_pet) { + charmed_pet->RemoveSpellEffect(luaspell); + charmed_pet->RemoveSpellBonus(luaspell); + } + } + } + continue; + } + + if(group_member->GetZone() != caster->GetZone()) + { + SpellProcess::AddSelfAndPetToCharTargets(luaspell, group_member); + } + else + { + //this group member is a target of the spell + new_target_list.push_back(group_member->GetID()); + } + + if (has_effect) + continue; + + pet = 0; + charmed_pet = 0; + + if (group_member->HasPet()) { + pet = group_member->GetPet(); + charmed_pet = group_member->GetCharmedPet(); + } + + group_member->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0); + if (pet) + pet->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0); + if (charmed_pet) + charmed_pet->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0); + + if (pet) + new_target_list.push_back(pet->GetID()); + if (charmed_pet) + new_target_list.push_back(charmed_pet->GetID()); + + + // look for a spell bonus on caster's spell + sb_list = caster->GetAllSpellBonuses(luaspell); + for (int32 x = 0; x < sb_list->size(); x++) { + bv = sb_list->at(x); + group_member->AddSpellBonus(luaspell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req); + if (pet) + pet->AddSpellBonus(luaspell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req); + if (charmed_pet) + charmed_pet->AddSpellBonus(luaspell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req); + } + + sb_list->clear(); + safe_delete(sb_list); + + // look for a skill bonus on the caster's spell + sb = caster->GetSkillBonus(me[i].spell_id); + if (sb) { + for (itr_skills = sb->skills.begin(); itr_skills != sb->skills.end(); itr_skills++) + group_member->AddSkillBonus(sb->spell_id, (*itr_skills).second->skill_id, (*itr_skills).second->value); + } + + if (client) { + packet = ((Player*)group_member)->GetSkills()->GetSkillPacket(client->GetVersion()); + if (packet) + client->QueuePacket(packet); + } + } + + luaspell->targets.swap(new_target_list); + SpellProcess::AddSelfAndPet(luaspell, caster); + luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + new_target_list.clear(); + } + } + caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__); + } + } +} + +bool PlayerGroupManager::IsInGroup(int32 group_id, Entity* member) { + std::shared_lock lock(MGroups); + bool ret = false; + + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + if (member == members->at(i)->member) { + ret = true; + break; + } + } + m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + return ret; +} + +Entity* PlayerGroupManager::IsPlayerInGroup(int32 group_id, int32 character_id) { + std::shared_lock lock(MGroups); + + Entity* ret = nullptr; + + if (m_groups.count(group_id) > 0) { + m_groups[group_id]->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = m_groups[group_id]->GetMembers(); + for (int8 i = 0; i < members->size(); i++) { + if (members->at(i)->member && members->at(i)->member->IsPlayer() && character_id == ((Player*)members->at(i)->member)->GetCharacterID()) { + ret = members->at(i)->member; + break; + } + } + m_groups[group_id]->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + return ret; +} + +void PlayerGroup::RemoveClientReference(Client* remove) { + deque::iterator itr; + MGroupMembers.writelock(); + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client && gmi->client == remove) + { + gmi->client = 0; + gmi->member = 0; + break; + } + } + MGroupMembers.releasewritelock(); +} + +void PlayerGroup::UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked) { + Player* player = (Player*)ent; + + if (!player || !player->GetGroupMemberInfo()) + return; + + if(!groupMembersLocked) + MGroupMembers.writelock(); + + GroupMemberInfo* group_member_info = player->GetGroupMemberInfo(); + player->GetGroupMemberInfo()->class_id = player->GetAdventureClass(); + group_member_info->hp_max = player->GetTotalHP(); + group_member_info->hp_current = player->GetHP(); + group_member_info->level_max = player->GetLevel(); + group_member_info->level_current = player->GetLevel(); + group_member_info->name = string(player->GetName()); + group_member_info->power_current = player->GetPower(); + group_member_info->power_max = player->GetTotalPower(); + group_member_info->race_id = player->GetRace(); + if (player->GetZone()) + group_member_info->zone = player->GetZone()->GetZoneDescription(); + else + group_member_info->zone = "Unknown"; + + if(!groupMembersLocked) + MGroupMembers.releasewritelock(); +} + +Entity* PlayerGroup::GetGroupMemberByPosition(Entity* seeker, int32 mapped_position) { + Entity* ret = nullptr; + + deque::iterator itr; + + MGroupMembers.readlock(); + + int32 count = 1; + for (itr = m_members.begin(); itr != m_members.end(); itr++) { + if ((*itr)->member == seeker) { + continue; + } + count++; + if(count >= mapped_position) { + ret = (Entity*)(*itr)->member; + break; + } + } + + MGroupMembers.releasereadlock(); + + return ret; +} + +void PlayerGroup::SetDefaultGroupOptions(GroupOptions* options) { + MGroupMembers.writelock(); + if (options != nullptr) { + group_options.loot_method = options->loot_method; + group_options.loot_items_rarity = options->loot_items_rarity; + group_options.auto_split = options->auto_split; + group_options.default_yell = options->default_yell; + group_options.group_lock_method = options->group_lock_method; + group_options.group_autolock = options->group_autolock; + group_options.solo_autolock = options->solo_autolock; + group_options.auto_loot_method = options->auto_loot_method; + } + else { + group_options.loot_method = 1; + group_options.loot_items_rarity = 0; + group_options.auto_split = 1; + group_options.default_yell = 1; + group_options.group_lock_method = 0; + group_options.group_autolock = 0; + group_options.solo_autolock = 0; + group_options.auto_loot_method = 0; + group_options.last_looted_index = 0; + } + + MGroupMembers.releasewritelock(); +} diff --git a/source/WorldServer/PlayerGroups.h b/source/WorldServer/PlayerGroups.h new file mode 100644 index 0000000..e7b9b27 --- /dev/null +++ b/source/WorldServer/PlayerGroups.h @@ -0,0 +1,221 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#ifndef __PLAYERGROUPS_H__ +#define __PLAYERGROUPS_H__ + +#include +#include +#include +#include +#include "Spells.h" + +#include "../common/types.h" +#include "Entity.h" + +using namespace std; + +// GroupOptions isn't used yet +struct GroupOptions{ + int8 loot_method; + int8 loot_items_rarity; + int8 auto_split; + int8 default_yell; + int8 group_lock_method; + int8 group_autolock; + int8 solo_autolock; + int8 auto_loot_method; + int8 last_looted_index; +}; + +/// All the generic info for the group window, plus a client pointer for players +struct GroupMemberInfo { + int32 group_id; + string name; + string zone; + sint32 hp_current; + sint32 hp_max; + sint32 power_current; + sint32 power_max; + int16 level_current; + int16 level_max; + int8 race_id; + int8 class_id; + bool leader; + Client* client; + Entity* member; + int32 mentor_target_char_id; +}; + +/// Represents a players group in game +class PlayerGroup { +public: + PlayerGroup(int32 id); + ~PlayerGroup(); + + /// Adds a new member to the players group + /// Entity to add to the group, can be a Player or NPC + /// True if the member was added + bool AddMember(Entity* member); + + /// Removes a member from the players group + /// Entity to remove from the player group + /// True if the member was removed + bool RemoveMember(Entity* member); + + /// Removes all members from the group and destroys the group + void Disband(); + + /// Sends updates to all the clients in the group + /// Client to exclude from the update + void SendGroupUpdate(Client* exclude = 0); + + /// Gets the total number of members in the group + /// int32, number of members in the group + int32 Size() { return m_members.size(); } + + /// Gets a pointer to the list of members + /// deque pointer + deque* GetMembers() { return &m_members; } + + + void SimpleGroupMessage(const char* message); + void SendGroupMessage(int8 type, const char* message, ...); + void GroupChatMessage(Spawn* from, int32 language, const char* message); + bool MakeLeader(Entity* new_leader); + + bool ShareQuestWithGroup(Client* quest_sharer, Quest* quest); + + void RemoveClientReference(Client* remove); + void UpdateGroupMemberInfo(Entity* ent, bool groupMembersLocked=false); + Entity* GetGroupMemberByPosition(Entity* seeker, int32 mapped_position); + + void SetDefaultGroupOptions(GroupOptions* options=nullptr); + + GroupOptions* GetGroupOptions() { return &group_options; } + int8 GetLastLooterIndex() { return group_options.last_looted_index; } + void SetNextLooterIndex(int8 new_index) { group_options.last_looted_index = new_index; } + Mutex MGroupMembers; // Mutex for the group members +private: + GroupOptions group_options; + int32 m_id; // ID of this group + deque m_members; // List of members in this group + +}; + +/// Responsible for managing all the player groups in the world +class PlayerGroupManager { +public: + PlayerGroupManager(); + ~PlayerGroupManager(); + + /// Adds a member to a group + /// ID of the group to add a member to + /// Entity* to add to the group + /// True if the member was added to the group + bool AddGroupMember(int32 group_id, Entity* member); + + /// Removes a member from a group + /// ID of the group to remove a member from + /// Entity* to remove from the group + /// True if the member was removed from the group + bool RemoveGroupMember(int32 group_id, Entity* member); + + /// Creates a new group with the provided Entity* as the leader + /// The Entity* that will be the leader of the group + void NewGroup(Entity* leader); + + /// Removes the group from the group manager + /// ID of the group to remove + void RemoveGroup(int32 group_id); + + /// Handles a player inviting another player or NPC to a group + /// Player that sent the invite + /// Player or NPC that is the target of the invite + /// Error code if invite was unsuccessful, 0 if successful + int8 Invite(Player* leader, Entity* member); + + /// Handles accepting of a group invite + /// Entity* that is accepting the invite + /// Error code if accepting the invite failed, 0 if successful + int8 AcceptInvite(Entity* member); + + /// Handles declining of a group invite + /// Entity* that is declining the invite + void DeclineInvite(Entity* member); + + /// Checks to see if there is a group with the given id in the group manager + /// ID to check for + /// True if a group with the given ID is found + bool IsGroupIDValid(int32 group_id); + + /// Send updates to all the clients in the group + /// ID of the group to send updates to + /// Client* to exclude from the update, usually the one that triggers the update + void SendGroupUpdate(int32 group_id, Client* exclude = 0); + + + PlayerGroup* GetGroup(int32 group_id); + + /// Read locks the group list, no changes to the list should be made when using this + /// Name of the function called from, used for better debugging in the event of a deadlock + /// Line number that this was called from, used for better debugging in the event of a deadlock + void GroupHardLock(const char* function = 0, int32 line = 0U) { MGroups.lock(); } + void GroupLock(const char* function = 0, int32 line = 0U) { MGroups.lock_shared(); } + + /// Releases the readlock acquired from GroupLock() + /// Name of the function called from, used for better debugging in the event of a deadlock + /// Line number that this was called from, used for better debugging in the event of a deadlock + void ReleaseGroupHardLock(const char* function = 0, int32 line = 0U) { MGroups.unlock(); } + void ReleaseGroupLock(const char* function = 0, int32 line = 0U) { MGroups.unlock_shared(); } + + void ClearPendingInvite(Entity* member); + + void RemoveGroupBuffs(int32 group_id, Client* client); + + int32 GetGroupSize(int32 group_id); + + void SendGroupQuests(int32 group_id, Client* client); + bool HasGroupCompletedQuest(int32 group_id, int32 quest_id); + + void SimpleGroupMessage(int32 group_id, const char* message); + void SendGroupMessage(int32 group_id, int8 type, const char* message, ...); + void GroupMessage(int32 group_id, const char* message, ...); + void GroupChatMessage(int32 group_id, Spawn* from, int32 language, const char* message); + bool MakeLeader(int32 group_id, Entity* new_leader); + void UpdateGroupBuffs(); + + bool IsInGroup(int32 group_id, Entity* member); + Entity* IsPlayerInGroup(int32 group_id, int32 char_id); + // TODO: Any function below this comment + bool IsSpawnInGroup(int32 group_id, string name); // used in follow + Player* GetGroupLeader(int32 group_id); + +private: + int32 m_nextGroupID; // Used to generate a new unique id for new groups + + map m_groups; // int32 is the group id, PlayerGroup* is a pointer to the actual group + map m_pendingInvites; // First string is the person invited to the group, second string is the leader of the group + + mutable std::shared_mutex MGroups; // Mutex for the group map (m_groups) + Mutex MPendingInvites; // Mutex for the pending invites map (m_pendingInvites) +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Quests.cpp b/source/WorldServer/Quests.cpp new file mode 100644 index 0000000..81fd2ed --- /dev/null +++ b/source/WorldServer/Quests.cpp @@ -0,0 +1,1867 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Quests.h" +#include "../common/ConfigReader.h" +#include "Player.h" +#include "LuaInterface.h" +#include "Spells.h" +#include "RaceTypes/RaceTypes.h" +#include "../common/Log.h" + +#ifdef WIN32 + #include +#else + #include +#endif + +extern LuaInterface* lua_interface; +extern ConfigReader configReader; +extern MasterFactionList master_faction_list; +extern MasterRaceTypeList race_types_list; + +QuestStep::QuestStep(int32 in_id, int8 in_type, string in_description, vector* in_ids, int32 in_quantity, const char* in_task_group, vector* in_locations, float in_max_variation, float in_percentage, int32 in_usableitemid){ + type = in_type; + description = in_description; + ids = 0; + locations = 0; + if(in_task_group) + task_group = string(in_task_group); + if(type != QUEST_STEP_TYPE_LOCATION) { + if (in_ids){ + ids = new std::map(); + for(int32 i=0;isize();i++) + ids->insert(make_pair(in_ids->at(i), true)); + } + } + else { // location step + if (in_locations) { + locations = new vector; + for(int32 i=0; i < in_locations->size(); i++) + locations->push_back(in_locations->at(i)); + } + } + max_variation = in_max_variation; + quantity = in_quantity; + step_progress = 0; + icon = 11; + id = in_id; + updated = false; + percentage = in_percentage; + usableitemid = in_usableitemid; +} + +QuestStep::QuestStep(QuestStep* old_step){ + type = old_step->type; + description = old_step->description; + task_group = old_step->task_group; + quantity = old_step->quantity; + max_variation = old_step->max_variation; + step_progress = 0; + ids = 0; + locations = 0; + if(type != QUEST_STEP_TYPE_LOCATION) { + if (old_step->ids){ + ids = new std::map(); + std::map::iterator itr; + for(itr = old_step->ids->begin();itr != old_step->ids->end();itr++) + ids->insert(make_pair(itr->first, itr->second)); + } + } + else { // location step + if (old_step->locations) { + locations = new vector; + for(int32 i=0; i < old_step->locations->size(); i++) + locations->push_back(old_step->locations->at(i)); + } + } + icon = old_step->icon; + id = old_step->id; + updated = false; + percentage = old_step->percentage; + usableitemid = old_step->usableitemid; +} + +QuestStep::~QuestStep(){ + safe_delete(ids); + safe_delete(locations); +} + +bool QuestStep::WasUpdated(){ + return updated; +} + +void QuestStep::WasUpdated(bool val){ + updated = val; +} + +float QuestStep::GetPercentage(){ + return percentage; +} + +int32 QuestStep::GetStepID(){ + return id; +} +int32 QuestStep::GetItemID() { + return usableitemid; +} +void QuestStep::SetComplete(){ + step_progress = quantity; + updated = true; +} + +bool QuestStep::Complete(){ + return step_progress >= quantity; +} + +int8 QuestStep::GetStepType(){ + return type; +} + +bool QuestStep::CheckStepReferencedID(int32 id){ + bool ret = false; + if(ids){ + std::map::iterator itr; + itr = ids->find(id); + if(itr != ids->end()) + ret = true; + } + return ret; +} + +bool QuestStep::CheckStepKillRaceReqUpdate(Spawn* spawn){ + bool ret = false; + if(ids){ + std::map::iterator itr; + for(itr = ids->begin();itr != ids->end();itr++){ + int32 curid = itr->first; + if(curid == spawn->GetRace() || + curid == race_types_list.GetRaceType(spawn->GetModelType()) || + curid == race_types_list.GetRaceBaseType(spawn->GetModelType())){ + ret = true; + break; + } + } + } + return ret; +} + +int16 QuestStep::GetIcon(){ + return icon; +} + +void QuestStep::SetIcon(int16 in_icon){ + icon = in_icon; +} + +const char* QuestStep::GetUpdateName(){ + if(update_name.length() > 0) + return update_name.c_str(); + return 0; +} + +void QuestStep::SetUpdateName(const char* name){ + update_name = string(name); +} + +const char* QuestStep::GetUpdateTargetName(){ + if(update_target_name.length() > 0) + return update_target_name.c_str(); + return 0; +} + +void QuestStep::SetUpdateTargetName(const char* name){ + update_target_name = string(name); +} + +bool QuestStep::CheckStepLocationUpdate(float char_x, float char_y, float char_z, int32 zone_id){ + bool ret = false; + if (locations) { + for (int32 i=0; i < locations->size(); i++) { + float total_diff = 0; + Location loc = locations->at(i); + if(loc.zone_id > 0 && loc.zone_id != zone_id) + continue; + + float diff = loc.x - char_x; //Check X + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + + diff = loc.z - char_z; //Check Z (we check Z first because it is far more likely to be a much greater variation than y) + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + if(total_diff <= max_variation) { //Check Total + + diff = loc.y - char_y; //Check Y + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + if(total_diff <= max_variation) { //Check Total + ret = true; + break; + } + } + } + } + } + } + } + return ret; +} + +void QuestStep::SetStepProgress(int32 val){ + step_progress = val; +} + +int32 QuestStep::AddStepProgress(int32 val){ + updated = true; + if (val > (quantity - step_progress)){ + step_progress += (quantity - step_progress); + return (quantity - step_progress); + } + else + step_progress += val; + return val; +} + +int32 QuestStep::GetStepProgress() { + return step_progress; +} + +void QuestStep::ResetTaskGroup(){ + task_group = ""; +} + +const char* QuestStep::GetTaskGroup(){ + if(task_group.length() > 0) + return task_group.c_str(); + return 0; +} + +const char* QuestStep::GetDescription(){ + if(description.length() > 0) + return description.c_str(); + return 0; +} + +void QuestStep::SetDescription(string desc){ + description = desc; +} + +int16 QuestStep::GetQuestCurrentQuantity(){ + return (int16)step_progress; +} + +int16 QuestStep::GetQuestNeededQuantity(){ + return (int16)quantity; +} + +Quest::Quest(int32 in_id){ + id = in_id; + reward_status = 0; + quest_giver = 0; + deleted = false; + turned_in = false; + update_needed = true; + player = 0; + return_id = 0; + task_group_num = 1; + prereq_level = 1; + prereq_tslevel = 0; + prereq_max_level = 0; + prereq_max_tslevel = 0; + reward_coins = 0; + reward_coins_max = 0; + completed_flag = false; + has_sent_last_update = false; + enc_level = 0; + reward_exp = 0; + reward_tsexp = 0; + m_featherColor = 0; + m_repeatable = false; + yellow_name = false; + m_hidden = false; + generated_coin = 0; + m_questFlags = 0; + m_timestamp = 0; + m_completeCount = 0; + MQuestSteps.SetName("Quest::MQuestSteps"); + MCompletedActions.SetName("Quest::MCompletedActions"); + MProgressActions.SetName("Quest::MProgressActions"); + MFailedActions.SetName("Quest::failed_Actions"); + quest_state_temporary = false; + tmp_reward_status = 0; + tmp_reward_coins = 0; + completed_description = string(""); + quest_temporary_description = string(""); + quest_shareable_flag = 0; + can_delete_quest = false; +} + +Quest::Quest(Quest* old_quest){ + task_group_num = 1; + name = old_quest->name; + type = old_quest->type; + zone = old_quest->zone; + level = old_quest->level; + enc_level = old_quest->enc_level; + description = old_quest->description; + prereq_level = old_quest->prereq_level; + prereq_tslevel = old_quest->prereq_tslevel; + prereq_max_level = old_quest->prereq_max_level; + prereq_max_tslevel = old_quest->prereq_max_tslevel; + prereq_quests = old_quest->prereq_quests; + prereq_classes = old_quest->prereq_classes; + prereq_tradeskillclass = old_quest->prereq_tradeskillclass; + prereq_races = old_quest->prereq_races; + prereq_factions = old_quest->prereq_factions; + reward_coins = old_quest->reward_coins; + reward_coins_max = old_quest->reward_coins_max; + reward_factions = old_quest->reward_factions; + reward_status = old_quest->reward_status; + reward_comment = old_quest->reward_comment; + reward_exp = old_quest->reward_exp; + reward_tsexp = old_quest->reward_tsexp; + generated_coin = old_quest->generated_coin; + completed_flag = old_quest->completed_flag; + has_sent_last_update = old_quest->has_sent_last_update; + yellow_name = old_quest->yellow_name; + m_questFlags = old_quest->m_questFlags; + id = old_quest->id; + + vector quest_steps; + for(int32 i=0;iquest_steps.size();i++) + AddQuestStep(new QuestStep(old_quest->quest_steps[i])); + for(int32 i=0;iprereq_items.size();i++) + AddPrereqItem(new Item(old_quest->prereq_items[i])); + for(int32 i=0;ireward_items.size();i++) + AddRewardItem(new Item(old_quest->reward_items[i])); + for(int32 i=0;iselectable_reward_items.size();i++) + AddSelectableRewardItem(new Item(old_quest->selectable_reward_items[i])); + complete_actions = old_quest->complete_actions; + progress_actions = old_quest->progress_actions; + failed_actions = old_quest->failed_actions; + time_t raw; + struct tm *newtime; + time(&raw); + newtime = localtime(&raw); + day = newtime->tm_mday; + month = newtime->tm_mon + 1; + year = newtime->tm_year - 100; + visible = 1; + step_updates.clear(); + step_failures.clear(); + completed_description = old_quest->completed_description; + deleted = old_quest->deleted; + update_needed = old_quest->update_needed; + turned_in = old_quest->turned_in; + quest_giver = old_quest->quest_giver; + player = old_quest->player; + return_id = old_quest->return_id; + m_featherColor = old_quest->m_featherColor; + m_repeatable = old_quest->IsRepeatable(); + m_hidden = false; + m_timestamp = 0; + m_completeCount = old_quest->GetCompleteCount(); + MQuestSteps.SetName("Quest::MQuestSteps"); + MProgressActions.SetName("Quest::MProgressActions"); + MCompletedActions.SetName("Quest::MCompletedActions"); + quest_state_temporary = false; + tmp_reward_status = 0; + tmp_reward_coins = 0; + quest_temporary_description = string(""); + quest_shareable_flag = old_quest->GetQuestShareableFlag(); + can_delete_quest = old_quest->CanDeleteQuest(); +} + +Quest::~Quest(){ + + MQuestSteps.lock(); + for(int32 i=0;iGetStepID() == step_id){ + ret = quest_steps[i]; + break; + } + } + MQuestSteps.unlock(); + return ret; +} + +vector* Quest::GetQuestSteps(){ + return &quest_steps; +} + +vector* Quest::GetQuestUpdates(){ + return &step_updates; +} + +vector* Quest::GetQuestFailures(){ + return &step_failures; +} + +bool Quest::CheckQuestReferencedSpawns(Spawn* spawn){ + QuestStep* step = 0; + bool ret = false; + int32 spawnDBID = spawn->GetDatabaseID(); + + MQuestSteps.lock(); + for(int32 i=0;iComplete()) + continue; + switch(step->GetStepType()) + { + case QUEST_STEP_TYPE_KILL: + case QUEST_STEP_TYPE_NORMAL: { + if(step->CheckStepReferencedID(spawnDBID)) + ret = true; + + break; + } + case QUEST_STEP_TYPE_KILL_RACE_REQ: { + if(step->CheckStepKillRaceReqUpdate(spawn)) + ret = true; + + break; + } + } + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::CheckQuestKillUpdate(Spawn* spawn, bool update){ + QuestStep* step = 0; + bool ret = false; + int32 id = spawn->GetDatabaseID(); + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepType() == QUEST_STEP_TYPE_KILL && !step->Complete() && step->CheckStepReferencedID(id)) || + (step->GetStepType() == QUEST_STEP_TYPE_KILL_RACE_REQ && !step->Complete() && step->CheckStepKillRaceReqUpdate(spawn))) + { + if (update == true) { + bool passed = true; + if (step->GetPercentage() < 100) + passed = (step->GetPercentage() > MakeRandomFloat(0, 100)); + if (passed) { + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(1); + if (lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + step->SetUpdateName(spawn->GetName()); + ret = true; + } + else + step_failures.push_back(step); + } + else { + ret = true; + } + } + } + MQuestSteps.unlock(); + if(ret && update) + SetSaveNeeded(true); + return ret; +} + +int16 Quest::GetTaskGroupStep(){ + int16 ret = task_group_order.size(); + map::iterator itr; + MQuestSteps.lock(); + for(itr = task_group_order.begin(); itr != task_group_order.end(); itr++){ + if(task_group.count(itr->second) > 0){ + vector tmp_steps = task_group[itr->second]; + bool complete = true; + for(int32 i=0;iComplete() == false) + complete = false; + } + if(!complete){ + if(itr->first < ret) + ret = itr->first; + } + } + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::SetStepComplete(int32 step){ + QuestStep* quest_step = 0; + bool ret = false; + MQuestSteps.lock(); + for(int32 i=0;iGetStepID() == step){ + quest_step->SetComplete(); + step_updates.push_back(quest_step); + ret = true; + break; + } + } + MQuestSteps.unlock(); + if (ret) { + SetSaveNeeded(true); + } + + return ret; +} + +bool Quest::AddStepProgress(int32 step_id, int32 progress) { + QuestStep* quest_step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for (int32 i = 0; i < quest_steps.size(); i++) { + quest_step = quest_steps[i]; + if (quest_step && quest_step->GetStepID() == step_id) { + bool passed = true; + if(quest_step->GetPercentage() < 100 && quest_step->GetPercentage() != 0) + passed = (quest_step->GetPercentage() > MakeRandomFloat(0, 100)); + if(passed) { + //Call the progress action function with the total amount of progress actually granted + prog_added = quest_step->AddStepProgress(progress); + if(lua_interface && progress_actions[step_id].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step_id].c_str(), player, prog_added); + step_updates.push_back(quest_step); + ret = true; + break; + } + else { + step_failures.push_back(quest_step); + break; + } + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +int32 Quest::GetStepProgress(int32 step_id) { + QuestStep* quest_step = 0; + int32 ret = 0; + + MQuestSteps.lock(); + for (int32 i = 0; i < quest_steps.size(); i++) { + quest_step = quest_steps[i]; + if (quest_step && quest_step->GetStepID() == step_id) { + ret = quest_step->GetStepProgress(); + break; + } + } + MQuestSteps.unlock(); + + return ret; +} + +bool Quest::GetQuestStepCompleted(int32 step_id){ + bool ret = false; + QuestStep* quest_step = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepID() == step_id && quest_step->Complete()){ + ret = true; + break; + } + } + MQuestSteps.unlock(); + return ret; +} + +int16 Quest::GetQuestStep(){ + int16 ret = 0; + QuestStep* step = 0; + MQuestSteps.lock(); + for(int32 i=0;iComplete()){ + ret = step->GetStepID(); + break; + } + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::QuestStepIsActive(int16 quest_step_id) { + bool ret = false; + QuestStep* step = 0; + MQuestSteps.lock(); + for (int32 i = 0; i < quest_steps.size(); i++) { + step = quest_steps[i]; + if (step->GetStepID() == quest_step_id && !step->Complete()) { + ret = true; + break; + } + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::CheckQuestChatUpdate(int32 id, bool update){ + QuestStep* step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepType() == QUEST_STEP_TYPE_CHAT && !step->Complete() && step->CheckStepReferencedID(id)){ + if(update){ + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(1); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + } + ret = true; + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +bool Quest::CheckQuestItemUpdate(int32 id, int8 quantity){ + QuestStep* step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepType() == QUEST_STEP_TYPE_OBTAIN_ITEM && !step->Complete() && step->CheckStepReferencedID(id)){ + bool passed = true; + if(step->GetPercentage() < 100) + passed = (step->GetPercentage() > MakeRandomFloat(0, 100)); + if(passed){ + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(quantity); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + ret = true; + } + else + step_failures.push_back(step); + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +bool Quest::CheckQuestRefIDUpdate(int32 id, int32 quantity){ + QuestStep* step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iComplete()) + continue; + + switch(step->GetStepType()) { + case QUEST_STEP_TYPE_HARVEST: + case QUEST_STEP_TYPE_CRAFT: { + map* id_list = step->GetUpdateIDs(); + map::iterator itr; + for(itr = id_list->begin();itr != id_list->end(); itr++){ + int32 update_id = itr->first; + if(update_id == id){ + bool passed = true; + if(step->GetPercentage() < 100) + passed = (step->GetPercentage() > MakeRandomFloat(0, 100)); + if(passed){ + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(quantity); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + } + else + step_failures.push_back(step); + ret = true; + } + } + break; + } + } + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +bool Quest::CheckQuestLocationUpdate(float char_x, float char_y, float char_z, int32 zone_id){ + QuestStep* step = 0; + bool ret = false; + int32 prog_added = 0; + MQuestSteps.lock(); + for(int32 i=0;iGetStepType() == QUEST_STEP_TYPE_LOCATION && !step->Complete() && step->CheckStepLocationUpdate(char_x, char_y, char_z, zone_id)){ + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(1); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + ret = true; + } + } + MQuestSteps.unlock(); + if(ret) + SetSaveNeeded(true); + return ret; +} + +bool Quest::CheckQuestSpellUpdate(Spell* spell){ + QuestStep* step = 0; + bool ret = false; + int32 id = spell->GetSpellID(); + int32 prog_added = 0; + MQuestSteps.lock(); + for (int32 i = 0; i < quest_steps.size(); i++) { + step = quest_steps[i]; + if(step && step->GetStepType() == QUEST_STEP_TYPE_SPELL && !step->Complete() && step->CheckStepReferencedID(id)){ + bool passed = true; + if(step->GetPercentage() < 100) + passed = (step->GetPercentage() > MakeRandomFloat(0, 100)); + if(passed) { + //Call the progress action function with the total amount of progress actually granted + prog_added = step->AddStepProgress(1); + if(lua_interface && progress_actions[step->GetStepID()].length() > 0 && prog_added > 0) + lua_interface->CallQuestFunction(this, progress_actions[step->GetStepID()].c_str(), player, prog_added); + step_updates.push_back(step); + //step->SetUpdateName(spawn->GetName()); + ret = true; + } + else + step_failures.push_back(step); + } + } + MQuestSteps.unlock(); + if (ret) + SetSaveNeeded(true); + return ret; +} + +void Quest::SetQuestID(int32 in_id){ + id = in_id; +} + +EQ2Packet* Quest::OfferQuest(int16 version, Player* player){ + PacketStruct* packet = configReader.getStruct("WS_OfferQuest", version); + if(packet){ + packet->setDataByName("reward", "Quest Reward!"); + if(packet->GetVersion() <= 373) { + std::string quotedName = std::string("\"" + name + "\""); + packet->setDataByName("title", quotedName.c_str()); + } + else { + packet->setDataByName("title", name.c_str()); + } + packet->setDataByName("description", description.c_str()); + if(type == "Tradeskill") + packet->setDataByName("quest_difficulty", player->GetTSArrowColor(level)); + else + packet->setDataByName("quest_difficulty", player->GetArrowColor(level)); + packet->setDataByName("encounter_level", enc_level); + packet->setDataByName("unknown0", 255); + if (version >= 1188) + packet->setDataByName("unknown4b", 1); + else + packet->setDataByName("unknown", 5); + packet->setDataByName("level", level); + if(reward_coins > 0){ + packet->setDataByName("min_coin", reward_coins); + if (reward_coins_max) + packet->setDataByName("max_coin", reward_coins_max); + } + packet->setDataByName("status_points", reward_status); + if(reward_comment.length() > 0) + packet->setDataByName("text", reward_comment.c_str()); + if(reward_items.size() > 0){ + player->GetClient()->PopulateQuestRewardItems(&reward_items, packet); + } + if(selectable_reward_items.size() > 0){ + player->GetClient()->PopulateQuestRewardItems(&selectable_reward_items, packet, std::string("num_select_rewards"), + std::string("select_reward_id"), std::string("select_item")); + } + map* reward_factions = GetRewardFactions(); + if (reward_factions && reward_factions->size() > 0) { + packet->setArrayLengthByName("num_factions", reward_factions->size()); + map::iterator itr; + int16 index = 0; + for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) { + int32 faction_id = itr->first; + sint32 amount = itr->second; + const char* faction_name = master_faction_list.GetFactionNameByID(faction_id); + if (faction_name) { + packet->setArrayDataByName("faction_name", const_cast(faction_name), index); + packet->setArrayDataByName("amount", amount, index); + } + index++; + } + } + char accept[35] = {0}; + char decline[35] = {0}; + sprintf(accept, "q_accept_pending_quest %u", id); + sprintf(decline, "q_deny_pending_quest %u", id); + packet->setDataByName("accept_command", accept); + packet->setDataByName("decline_command", decline); + EQ2Packet* outapp = packet->serialize(); +#if EQDEBUG >= 9 + DumpPacket(outapp); +#endif + safe_delete(packet); + return outapp; + } + return 0; +} + +int32 Quest::GetQuestID(){ + return id; +} + +int8 Quest::GetQuestLevel(){ + return level; +} + +int8 Quest::GetVisible(){ + return visible; +} + +EQ2Packet* Quest::QuestJournalReply(int16 version, int32 player_crc, Player* player, QuestStep* updateStep, int8 update_count, bool old_completed_quest, bool quest_failure, bool display_quest_helper) { + PacketStruct* packet = configReader.getStruct("WS_QuestJournalReply", version); + if (packet) { + packet->setDataByName("quest_id", id); + packet->setDataByName("player_crc", player_crc); + packet->setDataByName("name", name.c_str()); + packet->setDataByName("description", description.c_str()); + packet->setDataByName("zone", zone.c_str()); + packet->setDataByName("type", type.c_str()); + packet->setDataByName("complete_header", "To complete this quest, I must do the following tasks:"); + packet->setDataByName("day", day); + packet->setDataByName("month", month); + packet->setDataByName("year", year); + packet->setDataByName("level", level); + packet->setDataByName("visible", 1); + /* To get the quest timer to work you need to set unknown, index 4 to 1 and the time stamp + to the current time + the time in seconds you want to show in the journal*/ + if (m_timestamp > 0) { + packet->setDataByName("unknown", 1, 4); + packet->setDataByName("time_stamp", m_timestamp); + } + + int8 difficulty = 0; + if (type == "Tradeskill") + difficulty = player->GetTSArrowColor(level); + else + difficulty = player->GetArrowColor(level); + packet->setDataByName("difficulty", difficulty); + if (enc_level > 4) + packet->setDataByName("encounter_level", enc_level); + else + packet->setDataByName("encounter_level", 4); + packet->setDataByName("unknown2b", 4); + int16 task_groups_completed = 0; + int16 total_task_groups = 0; + vector primary_order; + vector secondary_order; + packet->setDataByName("unknown8", 1, 1); + packet->setSubstructArrayDataByName("reward_data", "unknown8", 1, 1); + packet->setDataByName("unknown8b", 255); + + if (version >= 1096) { + packet->setDataByName("deletable", (int8)CanDeleteQuest()); + packet->setDataByName("shareable", (int8)CanShareQuestCriteria(player->GetClient(),false)); + } + else { + packet->setDataByName("unknown3", 1, 5); // this supposed to be CanDeleteQuest? + packet->setDataByName("unknown3", 1, 6); // this supposed to be CanShareQuestCriteria? + } + + int8 map_data_count = 0; + + packet->setDataByName("bullets", 1); + if (old_completed_quest) { + if (version >= 1096 || version == 546 || version == 561) { + packet->setDataByName("complete", 1); + packet->setDataByName("complete2", 1); + packet->setDataByName("complete3", 1); + } + else { + packet->setDataByName("unknown3", 1); + packet->setDataByName("unknown3", 1, 1); + packet->setDataByName("unknown3", 1, 2); + packet->setDataByName("unknown3", 1, 5); + packet->setDataByName("unknown3", 1, 6); + } + } + // must always send for newer clients like AoM or else crash! + else if (GetCompleted() && ((version >= 1096) || ((version == 561 || version == 546) && HasSentLastUpdate()))) { //need to send last quest update before erasing all progress of the quest + packet->setDataByName("complete", 1); + packet->setDataByName("complete2", 1); + packet->setDataByName("complete3", 1); + packet->setDataByName("num_task_groups", 1); + packet->setArrayDataByName("task_group", completed_description.c_str()); + packet->setArrayDataByName("unknown4", 0xFFFFFFFF); + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("update_task_number", 1); + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_text", completed_description.c_str()); + if (updateStep) + packet->setDataByName("onscreen_update_icon", updateStep->GetIcon()); + else + packet->setDataByName("onscreen_update_icon", 0); + // Is Heritage or db entry for classic eq quest turn in sound + packet->setDataByName("classic_eq_sound", 0); + // No clue what this does, set it to mach live packets + packet->setDataByName("unknown12b", 1, 0); + } + else { + map task_group_names; + map::iterator order_itr; + vector* steps = 0; + MQuestSteps.lock(); + for (order_itr = task_group_order.begin(); order_itr != task_group_order.end(); order_itr++) { + //The following is kind of crazy, but necessary to order the quests with completed ones first. This is to make the packet like live's packet + //for(itr = task_group.begin(); itr != task_group.end(); itr++){ + bool complete = true; + if (task_group.count(order_itr->second) > 0) { + steps = &(task_group[order_itr->second]); + for (int32 i = 0; i < steps->size(); i++) { + if (steps->at(i)->Complete() == false) + complete = false; + } + if (complete) { + for (int32 i = 0; i < steps->size(); i++) { + if (i == 0) + task_group_names[steps->at(i)] = order_itr->second; + primary_order.push_back(steps->at(i)); + } + task_groups_completed++; + total_task_groups++; + } + else { + for (int32 i = 0; i < steps->size(); i++) { + if (i == 0) + task_group_names[steps->at(i)] = order_itr->second; + secondary_order.push_back(steps->at(i)); + } + total_task_groups++; + } + } + } + packet->setDataByName("task_groups_completed", task_groups_completed); + packet->setArrayLengthByName("num_task_groups", total_task_groups); + if (IsTracked() /*display_quest_helper*/ && task_groups_completed < total_task_groups) + packet->setDataByName("display_quest_helper", 1); + int16 index = 0; + QuestStep* step = 0; + for (int32 i = 0; i < primary_order.size(); i++) { + if (primary_order[i]->GetTaskGroup()) { + if (task_group_names.count(primary_order[i]) > 0) + packet->setArrayDataByName("task_group", primary_order[i]->GetTaskGroup(), index); + else + continue; + packet->setSubArrayLengthByName("num_tasks", task_group[primary_order[i]->GetTaskGroup()].size(), index); + packet->setSubArrayLengthByName("num_updates", task_group[primary_order[i]->GetTaskGroup()].size(), index); + map_data_count += task_group[primary_order[i]->GetTaskGroup()].size(); + if (task_group[primary_order[i]->GetTaskGroup()].size() > 0) { + packet->setDataByName("bullets", 1); + } + for (int32 x = 0; x < task_group[primary_order[i]->GetTaskGroup()].size(); x++) { + step = task_group[primary_order[i]->GetTaskGroup()].at(x); + if (!step) + continue; + if (step->GetDescription()) + packet->setSubArrayDataByName("task", step->GetDescription(), index, x); + packet->setSubArrayDataByName("task_completed", 1, index, x); + packet->setSubArrayDataByName("index", x, index, x); + packet->setSubArrayDataByName("update_currentval", step->GetQuestCurrentQuantity(), index, x); + if(step->GetQuestCurrentQuantity() > 0) + packet->setDataByName("journal_updated", 1); + packet->setSubArrayDataByName("update_maxval", step->GetQuestNeededQuantity(), index, x); + if (step->GetUpdateTargetName()) + packet->setSubArrayDataByName("update_target_name", step->GetUpdateTargetName(), index, x); + packet->setSubArrayDataByName("icon", step->GetIcon(), index, x); + if (updateStep && step == updateStep) { + packet->setDataByName("update", 1); + // packet->setDataByName("unknown5d", 1); + if (!quest_failure) + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_count", update_count); + packet->setDataByName("onscreen_update_icon", step->GetIcon()); + if (step->GetUpdateName()) + packet->setDataByName("onscreen_update_text", step->GetUpdateName()); + else if (step->GetDescription()) + packet->setDataByName("onscreen_update_text", step->GetDescription()); + packet->setDataByName("update_task_number", x); + packet->setDataByName("update_taskgroup_number", index); + } + packet->setArrayDataByName("unknown4", 0xFFFFFFFF, x); + } + index++; + } + else { + if (task_group_names.count(primary_order[i]) > 0) { + step = primary_order[i]; + if (updateStep && step == updateStep) { + packet->setDataByName("update", 1); + // packet->setDataByName("unknown5d", 1); + if (!quest_failure) + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_count", update_count); + packet->setDataByName("onscreen_update_icon", step->GetIcon()); + if (step->GetUpdateName()) + packet->setDataByName("onscreen_update_text", step->GetUpdateName()); + else if (step->GetDescription()) + packet->setDataByName("onscreen_update_text", step->GetDescription()); + packet->setDataByName("update_task_number", i); + packet->setDataByName("update_taskgroup_number", index); + } + packet->setArrayDataByName("task_group", primary_order[i]->GetDescription(), index); + index++; + } + } + } + for (int32 i = 0; i < secondary_order.size(); i++) { + if (secondary_order[i]->GetTaskGroup()) { + if (task_group_names.count(secondary_order[i]) > 0) + packet->setArrayDataByName("task_group", secondary_order[i]->GetTaskGroup(), index); + else + continue; + packet->setSubArrayLengthByName("num_tasks", task_group[secondary_order[i]->GetTaskGroup()].size(), index); + packet->setSubArrayLengthByName("num_updates", task_group[secondary_order[i]->GetTaskGroup()].size(), index); + map_data_count += task_group[secondary_order[i]->GetTaskGroup()].size(); + if (task_group[secondary_order[i]->GetTaskGroup()].size() > 0) + packet->setDataByName("bullets", 1); + for (int32 x = 0; x < task_group[secondary_order[i]->GetTaskGroup()].size(); x++) { + step = task_group[secondary_order[i]->GetTaskGroup()].at(x); + if (!step) + continue; + if (step->GetDescription()) + packet->setSubArrayDataByName("task", step->GetDescription(), index, x); + if (step->Complete()) + packet->setSubArrayDataByName("task_completed", 1, index, x); + else + packet->setSubArrayDataByName("task_completed", 0, index, x); + packet->setSubArrayDataByName("index", x, index, x); + packet->setSubArrayDataByName("update_currentval", step->GetQuestCurrentQuantity(), index, x); + if (step->GetQuestCurrentQuantity() > 0) + packet->setDataByName("journal_updated", 1); + packet->setSubArrayDataByName("update_maxval", step->GetQuestNeededQuantity(), index, x); + packet->setSubArrayDataByName("icon", step->GetIcon(), index, x); + if (step->GetUpdateTargetName()) + packet->setSubArrayDataByName("update_target_name", step->GetUpdateTargetName(), index, x); + if (updateStep && step == updateStep) { + packet->setDataByName("update", 1); + if (!quest_failure) + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_count", update_count); + packet->setDataByName("onscreen_update_icon", step->GetIcon()); + if (step->GetUpdateName()) + packet->setDataByName("onscreen_update_text", step->GetUpdateName()); + else if (step->GetDescription()) + packet->setDataByName("onscreen_update_text", step->GetDescription()); + if (quest_failure) + packet->setDataByName("onscreen_update_text2", "failed"); + packet->setDataByName("update_task_number", x); + packet->setDataByName("update_taskgroup_number", index); + } + packet->setArrayDataByName("unknown4", 0xFFFFFFFF, x); + } + index++; + } + else { + if (task_group_names.count(secondary_order[i]) > 0) { + step = secondary_order[i]; + if (updateStep && step == updateStep) { + packet->setDataByName("update", 1); + if (!quest_failure) + packet->setDataByName("onscreen_update", 1); + packet->setDataByName("onscreen_update_count", update_count); + packet->setDataByName("onscreen_update_icon", step->GetIcon()); + if (step->GetUpdateName()) + packet->setDataByName("onscreen_update_text", step->GetUpdateName()); + else if (step->GetDescription()) + packet->setDataByName("onscreen_update_text", step->GetDescription()); + packet->setDataByName("update_task_number", i); + packet->setDataByName("update_taskgroup_number", index); + } + if (task_group_names.size() == 1) { + packet->setSubArrayLengthByName("num_tasks", 1, index); + packet->setSubArrayDataByName("task", secondary_order[i]->GetDescription(), index); + packet->setDataByName("bullets", 0); + } + else + packet->setArrayDataByName("task_group", secondary_order[i]->GetDescription(), index); + index++; + } + } + } + + for (int16 i = 0; i < total_task_groups; i++) + packet->setArrayDataByName("unknown4", 0xFFFFFFFF, i); + + if (step && step->GetItemID() > 0) { + packet->setArrayLengthByName("usable_item_count", 1); + Item* item = player->GetPlayerItemList()->GetItemFromID(step->GetItemID(), 1); + if (item) { + packet->setArrayDataByName("item_id", item->details.item_id, 0); + packet->setArrayDataByName("item_unique_id", item->details.unique_id, 0); + packet->setArrayDataByName("item_icon", item->GetIcon(version), 0); + packet->setArrayDataByName("unknown2", 0xFFFFFFFF, 0);//item->details.item_id, 0); + + } + } + if (GetCompleted()) { //mark the last update as being sent, next time we send the quest reply, it will only be a brief portion + SetSentLastUpdate(true); + } + } + MQuestSteps.unlock(); + + + string reward_str = ""; + if (version >= 1096 || version == 546 || version == 561) + reward_str = "reward_data_"; + string tmp = reward_str + "reward"; + packet->setDataByName(tmp.c_str(), "Quest Reward!"); + if (reward_coins > 0) { + tmp = reward_str + "min_coin"; + packet->setDataByName(tmp.c_str(), reward_coins); + tmp = reward_str + "max_coin"; + packet->setDataByName(tmp.c_str(), reward_coins_max); + } + tmp = reward_str + "status_points"; + packet->setDataByName(tmp.c_str(), reward_status); + if (reward_comment.length() > 0) { + tmp = reward_str + "text"; + packet->setDataByName(tmp.c_str(), reward_comment.c_str()); + } + if (reward_items.size() > 0) { + tmp = reward_str + "num_rewards"; + packet->setArrayLengthByName(tmp.c_str(), reward_items.size()); + Item* item = 0; + for (int32 i = 0; i < reward_items.size(); i++) { + item = reward_items[i]; + packet->setArrayDataByName("reward_id", item->details.item_id, i); + if (version < 860) + packet->setItemArrayDataByName("item", item, player, i, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemArrayDataByName("item", item, player, i); + else + packet->setItemArrayDataByName("item", item, player, i, 0, 2); + } + } + if (selectable_reward_items.size() > 0) { + tmp = reward_str + "num_select_rewards"; + packet->setArrayLengthByName(tmp.c_str(), selectable_reward_items.size()); + Item* item = 0; + for (int32 i = 0; i < selectable_reward_items.size(); i++) { + item = selectable_reward_items[i]; + packet->setArrayDataByName("select_reward_id", item->details.item_id, i); + if (version < 860) + packet->setItemArrayDataByName("select_item", item, player, i, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemArrayDataByName("select_item", item, player, i); + else + packet->setItemArrayDataByName("select_item", item, player, i, 0, 2); + } + } + map* reward_factions = GetRewardFactions(); + if (reward_factions && reward_factions->size() > 0) { + tmp = reward_str + "num_factions"; + packet->setArrayLengthByName(tmp.c_str(), reward_factions->size()); + map::iterator itr; + int16 index = 0; + for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) { + int32 faction_id = itr->first; + sint32 amount = itr->second; + const char* faction_name = master_faction_list.GetFactionNameByID(faction_id); + if (faction_name) { + packet->setArrayDataByName("faction_name", const_cast(faction_name), index); + packet->setArrayDataByName("amount", amount, index); + } + index++; + } + } + + //packet->setArrayLengthByName("map_data_array_size", map_data_count); + + EQ2Packet* outapp = packet->serialize(); + //packet->PrintPacket(); + //DumpPacket(outapp); + safe_delete(packet); + return outapp; + } + + return 0; +} + +bool Quest::AddQuestStep(QuestStep* step){ + bool ret = true; + MQuestSteps.lock(); + if(quest_step_map.count(step->GetStepID()) == 0){ + quest_steps.push_back(step); + quest_step_reverse_map[step] = step->GetStepID(); + quest_step_map[step->GetStepID()] = step; + if(step->GetTaskGroup()){ + string tmp_task_group = string(step->GetTaskGroup()); + if(task_group.count(tmp_task_group) == 0){ + task_group_order[task_group_num] = tmp_task_group; + task_group_num++; + } + task_group[tmp_task_group].push_back(step); + } + else{ + string tmp_task_group = string(step->GetDescription()); + if(task_group.count(tmp_task_group) == 0){ + task_group_order[task_group_num] = tmp_task_group; + task_group_num++; + } + task_group[tmp_task_group].push_back(step); + } + } + else{ + LogWrite(QUEST__ERROR, 0, "Quest", "Quest Warning in '%s': step %u used multiple times!", GetName(), step->GetStepID()); + ret = false; + } + MQuestSteps.unlock(); + return ret; +} + +bool Quest::RemoveQuestStep(int32 step, Client* client) { + bool ret = true; + MQuestSteps.writelock(__FUNCTION__, __LINE__); + if (quest_step_map.count(step) > 0) { + QuestStep* quest_step = quest_step_map[step]; + if (quest_step) { + client->QueuePacket(QuestJournalReply(client->GetVersion(), client->GetNameCRC(), client->GetPlayer(), quest_step, 0, 0, true)); + int32 step2 = step - 1; + QuestStep* reset_step = quest_step_map[step2]; + reset_step->SetStepProgress(0); + MCompletedActions.lock(); + complete_actions.erase(step); + MCompletedActions.unlock(); + string tmp_task_group = string(quest_step->GetTaskGroup()); + if(task_group.count(tmp_task_group) > 0) { + task_group.erase(tmp_task_group); + int task_num = 0; + map::iterator itr; + for (itr = task_group_order.begin(); itr != task_group_order.end(); itr++) { + if (itr->second == tmp_task_group) { + task_num = itr->first; + break; + } + } + if (task_num > 0) { + task_group_order.erase(task_num); + task_group_num--; + } + } + + // Remove the step from the various maps before we delete it + quest_step_map.erase(step); + quest_step_reverse_map.erase(quest_step); + vector::iterator itr; + for (itr = quest_steps.begin(); itr != quest_steps.end(); itr++) { + if ((*itr) == quest_step) { + quest_steps.erase(itr); + break; + } + } + safe_delete(quest_step); + } + else { + LogWrite(QUEST__ERROR, 0, "Quest", "Unable to get a valid step (%u) for quest %s", step, GetName()); + ret = false; + } + } + else { + LogWrite(QUEST__ERROR, 0, "Quest", "Unable to remove step (%u) for quest %s", step, GetName()); + ret = false; + } + MQuestSteps.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +QuestStep* Quest::AddQuestStep(int32 id, int8 in_type, string in_description, vector* in_ids, int32 in_quantity, const char* in_task_group, vector* in_locations, float in_max_variation, float in_percentage,int32 in_usableitemid){ + QuestStep* step = new QuestStep(id, in_type, in_description, in_ids, in_quantity, in_task_group, in_locations, in_max_variation, in_percentage, in_usableitemid); + if(!AddQuestStep(step)){ + safe_delete(step); + return 0; + } + return step; +} + +void Quest::AddPrereqClass(int8 class_id){ + prereq_classes.push_back(class_id); +} + +void Quest::AddPrereqTradeskillClass(int8 class_id) { + prereq_tradeskillclass.push_back(class_id); +} + +void Quest::AddPrereqModelType(int16 model_type){ + prereq_model_types.push_back(model_type); +} + +void Quest::AddPrereqRace(int8 race){ + prereq_races.push_back(race); +} + +void Quest::SetPrereqLevel(int8 lvl){ + prereq_level = lvl; +} + +void Quest::SetPrereqTSLevel(int8 lvl) { + prereq_tslevel = lvl; +} + +void Quest::AddPrereqQuest(int32 quest_id){ + prereq_quests.push_back(quest_id); +} + +void Quest::AddPrereqFaction(int32 faction_id, sint32 min, sint32 max){ + QuestFactionPrereq faction; + faction.faction_id = faction_id; + faction.min = min; + faction.max = max; + prereq_factions.push_back(faction); +} + +void Quest::AddPrereqItem(Item* item){ + prereq_items.push_back(item); +} + +void Quest::AddRewardItemVec(vector* items, Item* item, bool combine_items) { + if(!items || !item) + return; + + bool stacked = false; + + if(combine_items) { + for(int32 i=0;isize();i++) { + Item* cur_item = (Item*)items->at(i); + if(cur_item->stack_count > 1) { + if(cur_item->details.item_id == item->details.item_id && cur_item->details.count+1 < cur_item->stack_count) { + if(!cur_item->details.count) // sometimes the count is 0, so we want it to be 2 now + cur_item->details.count = 2; + else + cur_item->details.count += 1; + stacked = true; + break; + } + } + } + } + + if(!stacked) { + items->push_back(item); + } +} + +void Quest::AddSelectableRewardItem(Item* item){ + AddRewardItemVec(&selectable_reward_items, item, false); +} + +void Quest::AddRewardItem(Item* item){ + AddRewardItemVec(&reward_items, item); +} + +void Quest::AddTmpRewardItem(Item* item){ + AddRewardItemVec(&tmp_reward_items, item); +} + +void Quest::GetTmpRewardItemsByID(std::vector* items) { + if(!items) + return; + for(int32 i=0;ipush_back(tmp_reward_items[i]); +} + +void Quest::AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat){ + reward_coins = copper + (silver*100) + (gold*10000) + ((int64)plat*1000000); +} + +void Quest::AddRewardCoinsMax(int64 coins){ + reward_coins_max = coins; +} + +void Quest::AddRewardFaction(int32 faction_id, sint32 amount){ + reward_factions[faction_id] = amount; +} + +void Quest::SetRewardStatus(int32 amount){ + reward_status = amount; +} + +void Quest::SetRewardComment(string comment){ + reward_comment = comment; +} + +void Quest::SetRewardXP(int32 xp){ + reward_exp = xp; +} + +vector* Quest::GetPrereqClasses(){ + return &prereq_classes; +} + +vector* Quest::GetPrereqTradeskillClasses(){ + return &prereq_tradeskillclass; +} + +vector* Quest::GetPrereqFactions(){ + return &prereq_factions; +} + +vector* Quest::GetPrereqRaces(){ + return &prereq_races; +} + +vector* Quest::GetPrereqModelTypes(){ + return &prereq_model_types; +} + +int8 Quest::GetPrereqLevel(){ + return prereq_level; +} + +int8 Quest::GetPrereqTSLevel() { + return prereq_tslevel; +} + +vector* Quest::GetPrereqQuests(){ + return &prereq_quests; +} + +vector* Quest::GetPrereqItems(){ + return &prereq_items; +} + +vector* Quest::GetRewardItems(){ + return &reward_items; +} + +vector* Quest::GetTmpRewardItems(){ + return &tmp_reward_items; +} + +vector* Quest::GetSelectableRewardItems(){ + return &selectable_reward_items; +} + +map* Quest::GetRewardFactions() { + return &reward_factions; +} + +void Quest::SetTaskGroupDescription(int32 step, string desc, bool display_bullets){ + MQuestSteps.lock(); + if(step <= task_group_num && task_group_order.count(step) > 0){ + string old_desc = task_group_order[step]; + if(display_bullets) + task_group[desc] = task_group[old_desc]; + else{ + if(task_group[old_desc].size() > 0){ + task_group[desc].push_back(task_group[old_desc].at(0)); + task_group[desc].at(0)->ResetTaskGroup(); + task_group[desc].at(0)->SetDescription(desc); + } + } + task_group.erase(old_desc); + task_group_order[step] = desc; + } + MQuestSteps.unlock(); +} + +void Quest::SetStepDescription(int32 step, string desc){ + MQuestSteps.lock(); + for(int32 i=0;iGetStepID() == step){ + quest_steps[i]->SetDescription(desc); + break; + } + } + MQuestSteps.unlock(); +} + +void Quest::SetDescription(string desc){ + description = desc; +} + +void Quest::SetCompletedDescription(string desc){ + completed_description = desc; +} + +const char* Quest::GetCompletedDescription(){ + return completed_description.c_str(); +} + +void Quest::AddCompleteAction(int32 step, string action){ + MCompletedActions.lock(); + complete_actions[step] = action; + MCompletedActions.unlock(); +} + +void Quest::AddProgressAction(int32 step, string action){ + MProgressActions.lock(); + progress_actions[step] = action; + MProgressActions.unlock(); +} + +void Quest::AddFailedAction(int32 step, string action) { + MFailedActions.writelock(__FUNCTION__, __LINE__); + failed_actions[step] = action; + MFailedActions.releasewritelock(__FUNCTION__, __LINE__); +} + +void Quest::SetCompleteAction(string action){ + complete_action = action; +} + +const char* Quest::GetCompleteAction(){ + if(complete_action.length() > 0) + return complete_action.c_str(); + return 0; +} + +const char* Quest::GetCompleteAction(int32 step){ + const char* ret = 0; + MCompletedActions.lock(); + if(complete_actions.count(step) > 0) + ret = complete_actions[step].c_str(); + MCompletedActions.unlock(); + return ret; +} + +void Quest::SetQuestReturnNPC(int32 id){ + return_id = id; +} + +int32 Quest::GetQuestReturnNPC(){ + return return_id; +} + +void Quest::SetQuestGiver(int32 id){ + quest_giver = id; + if(return_id == 0) + return_id = id; +} + +bool Quest::GetCompleted(){ + bool ret = true; + for(int32 i=0;iComplete() == false){ + ret = false; + break; + } + } + return ret; +} + +bool Quest::CheckCategoryYellow(){ + bool ret = false; + string category = GetType(); + + //Check for these category names, return true to set category as yellow in the quest journal + if (category == "Signature" || category == "Heritage" || category == "Hallmark" || category == "Deity" || category == "Miscellaneaous" || category == "Language" || category == "Lore and Legend" || category == "World Event" || category == "Tradeskill") + ret = true; + return ret; +} + +Player* Quest::GetPlayer(){ + return player; +} + +void Quest::SetPlayer(Player* in_player){ + player = in_player; +} + +void Quest::SetGeneratedCoin(int64 coin){ + generated_coin = coin; +} + +int32 Quest::GetQuestGiver(){ + return quest_giver; +} + +int64 Quest::GetCoinsReward(){ + return reward_coins; +} + +int64 Quest::GetCoinsRewardMax(){ + return reward_coins_max; +} + +int64 Quest::GetGeneratedCoin(){ + return generated_coin; +} + +int32 Quest::GetExpReward(){ + return reward_exp; +} + +void Quest::GiveQuestReward(Player* player){ + if(reward_coins > 0) + player->AddCoins(reward_coins); + + if(!GetQuestTemporaryState()) + reward_items.clear(); +} + +bool Quest::GetDeleted(){ + return deleted; +} + +void Quest::SetDeleted(bool val){ + deleted = val; +} + +bool Quest::GetUpdateRequired(){ + return update_needed; +} + +void Quest::SetUpdateRequired(bool val){ + update_needed = val; +} + +void Quest::SetTurnedIn(bool val){ + turned_in = val; + visible = 0; +} + +bool Quest::GetTurnedIn(){ + return turned_in; +} + +void Quest::SetName(string in_name) { + name = in_name; +} + +void Quest::SetType(string in_type) { + type = in_type; +} + +void Quest::SetLevel(int8 in_level) { + level = in_level; +} + +void Quest::SetCompletedFlag(bool val) { + completed_flag = val; + SetUpdateRequired(true); + if(player && player->GetClient()) + player->GetClient()->SendQuestJournalUpdate(this, true); +} + +void Quest::SetStepTimer(int32 duration) { + m_timestamp = duration == 0 ? 0 : Timer::GetUnixTimeStamp() + duration; +} + +void Quest::StepFailed(int32 step) { + if(lua_interface && failed_actions.count(step) > 0 && failed_actions[step].length() > 0) + lua_interface->CallQuestFunction(this, failed_actions[step].c_str(), player); +} + +MasterQuestList::MasterQuestList(){ + MQuests.SetName("MasterQuestList::MQuests"); +} + +void MasterQuestList::Reload(){ + MQuests.lock(); + quests.clear(); //deletes taken care of in LuaInterface + if(lua_interface) + lua_interface->DestroyQuests(true); + MQuests.unlock(); +} + +void MasterQuestList::LockQuests(){ + MQuests.lock(); +} + +void MasterQuestList::UnlockQuests(){ + MQuests.unlock(); +} + +void Quest::SetQuestTemporaryState(bool tempState, std::string customDescription) +{ + if(!tempState) + { + tmp_reward_coins = 0; + tmp_reward_status = 0; + + for(int32 i=0;iGetPlayer()->GetQuest(GetQuestID()); // gets active quest if available + if(((GetQuestShareableFlag() & QUEST_SHAREABLE_COMPLETED) == 0) && quest_sharer->GetPlayer()->HasQuestBeenCompleted(GetQuestID())) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest after it is already completed."); + return false; + } + else if((GetQuestShareableFlag() == QUEST_SHAREABLE_COMPLETED) && !quest_sharer->GetPlayer()->HasQuestBeenCompleted(GetQuestID())) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest until it is completed."); + return false; + } + else if(((GetQuestShareableFlag() & QUEST_SHAREABLE_DURING) == 0) && clientQuest && clientQuest->GetQuestStep() > 1) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest after already completing a step."); + return false; + } + else if(((GetQuestShareableFlag() & QUEST_SHAREABLE_ACTIVE) == 0) && clientQuest) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest while it is active."); + return false; + } + else if(!GetQuestShareableFlag()) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You cannot share this quest."); + return false; + } + else if(((GetQuestShareableFlag() & QUEST_SHAREABLE_COMPLETED) == 0) && clientQuest == nullptr) { + if(display_client_msg) + quest_sharer->SimpleMessage(CHANNEL_COLOR_RED, "You do not have this quest."); + return false; + } + + return true; +} \ No newline at end of file diff --git a/source/WorldServer/Quests.h b/source/WorldServer/Quests.h new file mode 100644 index 0000000..a70942f --- /dev/null +++ b/source/WorldServer/Quests.h @@ -0,0 +1,447 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef QUESTS_H +#define QUESTS_H + +#include +#include +#include "Items/Items.h" + +#define QUEST_STEP_TYPE_KILL 1 +#define QUEST_STEP_TYPE_CHAT 2 +#define QUEST_STEP_TYPE_OBTAIN_ITEM 3 +#define QUEST_STEP_TYPE_LOCATION 4 +#define QUEST_STEP_TYPE_SPELL 5 +#define QUEST_STEP_TYPE_NORMAL 6 +#define QUEST_STEP_TYPE_CRAFT 7 +#define QUEST_STEP_TYPE_HARVEST 8 +#define QUEST_STEP_TYPE_KILL_RACE_REQ 9 // kill using race type requirement instead of npc db id + +#define QUEST_DISPLAY_STATUS_HIDDEN 0 +#define QUEST_DISPLAY_STATUS_NO_CHECK 1 +#define QUEST_DISPLAY_STATUS_YELLOW 2 +#define QUEST_DISPLAY_STATUS_COMPLETED 4 +#define QUEST_DISPLAY_STATUS_REPEATABLE 8 +#define QUEST_DISPLAY_STATUS_CAN_SHARE 16 +#define QUEST_DISPLAY_STATUS_COMPLETE_FLAG 32 +#define QUEST_DISPLAY_STATUS_SHOW 64 +#define QUEST_DISPLAY_STATUS_CHECK 128 + + +#define QUEST_SHAREABLE_NONE 0 +#define QUEST_SHAREABLE_ACTIVE 1 +#define QUEST_SHAREABLE_DURING 2 +#define QUEST_SHAREABLE_COMPLETED 4 + +struct QuestFactionPrereq{ + int32 faction_id; + sint32 min; + sint32 max; +}; + +struct Location { + int32 id; + float x; + float y; + float z; + int32 zone_id; +}; + +class QuestStep{ +public: + QuestStep(int32 in_id, int8 in_type, string in_description, vector* in_ids, int32 in_quantity, const char* in_task_group, vector* in_locations, float in_max_variation, float in_percentage, int32 in_usableitemid); + QuestStep(QuestStep* old_step); + ~QuestStep(); + bool CheckStepKillRaceReqUpdate(Spawn* spawn); + bool CheckStepReferencedID(int32 id); + bool CheckStepLocationUpdate(float char_x, float char_y, float char_z, int32 zone_id); + int32 AddStepProgress(int32 val); + void SetStepProgress(int32 val); + int32 GetStepProgress(); + int8 GetStepType(); + bool Complete(); + void SetComplete(); + void ResetTaskGroup(); + const char* GetTaskGroup(); + const char* GetDescription(); + void SetDescription(string desc); + int16 GetQuestCurrentQuantity(); + int16 GetQuestNeededQuantity(); + map* GetUpdateIDs() { return ids; } + int16 GetIcon(); + void SetIcon(int16 in_icon); + const char* GetUpdateTargetName(); + void SetUpdateTargetName(const char* name); + const char* GetUpdateName(); + void SetUpdateName(const char* name); + int32 GetStepID(); + int32 GetItemID(); + bool WasUpdated(); + void WasUpdated(bool val); + float GetPercentage(); + + void SetTaskGroup(string val) { task_group = val; } + +private: + bool updated; + int32 id; + string update_name; + string update_target_name; + int16 icon; + int8 type; + string description; + std::map* ids; + int32 quantity; + string task_group; + vector* locations; + float max_variation; + float percentage; + int32 usableitemid; + int32 step_progress; +}; +class Player; +class Spell; + +class Quest{ +public: + Quest(int32 in_id); + Quest(Quest* old_quest); + ~Quest(); + EQ2Packet* OfferQuest(int16 version, Player* player); + EQ2Packet* QuestJournalReply(int16 version, int32 player_crc, Player* player, QuestStep* updateStep = 0, int8 update_count = 1, bool old_completed_quest=false, bool quest_failure = false, bool display_quest_helper = true); + + void RegisterQuest(string in_name, string in_type, string in_zone, int8 in_level, string in_desc); + + void SetPrereqLevel(int8 lvl); + void SetPrereqTSLevel(int8 lvl); + void SetPrereqMaxLevel(int8 lvl) {prereq_max_level = lvl;} + void SetPrereqMaxTSLevel(int8 lvl) {prereq_max_tslevel = lvl;} + void AddPrereqClass(int8 class_id); + void AddPrereqTradeskillClass(int8 class_id); + void AddPrereqModelType(int16 model_type); + void AddPrereqRace(int8 race); + void AddPrereqQuest(int32 quest_id); + void AddPrereqFaction(int32 faction_id, sint32 min, sint32 max = 0); + void AddPrereqItem(Item* item); + + void AddRewardItem(Item* item); + void AddTmpRewardItem(Item* item); + void GetTmpRewardItemsByID(std::vector* items); + void AddRewardItemVec(vector* items, Item* item, bool combine_items = true); + void AddSelectableRewardItem(Item* item); + void AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat); + void AddRewardCoinsMax(int64 coins); + void AddRewardFaction(int32 faction_id, sint32 amount); + void SetRewardStatus(int32 amount); + void SetRewardComment(string comment); + void SetRewardXP(int32 xp); + void SetRewardTSXP(int32 xp) { reward_tsexp = xp; } + + bool AddQuestStep(QuestStep* step); + QuestStep* AddQuestStep(int32 id, int8 in_type, string in_description, vector* in_ids, int32 in_quantity, const char* in_task_group = 0, vector* in_locations = 0, float in_max_variation = 0, float in_percentage = 0,int32 in_usableitemid = 0); + bool SetStepComplete(int32 step); + bool AddStepProgress(int32 step_id, int32 progress); + int16 GetQuestStep(); + int32 GetStepProgress(int32 step_id); + int16 GetTaskGroupStep(); + bool QuestStepIsActive(int16 quest_step_id); + bool CheckQuestReferencedSpawns(Spawn* spawn); + bool CheckQuestKillUpdate(Spawn* spawn, bool update = true); + bool CheckQuestChatUpdate(int32 id, bool update = true); + bool CheckQuestItemUpdate(int32 id, int8 quantity = 1); + bool CheckQuestLocationUpdate(float char_x, float char_y, float char_z, int32 zone_id); + bool CheckQuestSpellUpdate(Spell* spell); + bool CheckQuestRefIDUpdate(int32 id, int32 quantity = 1); + + int8 GetQuestLevel(); + int8 GetVisible(); + int32 GetQuestID(); + void SetQuestID(int32 in_id); + int8 GetPrereqLevel(); + int8 GetPrereqTSLevel(); + int8 GetPrereqMaxLevel() {return prereq_max_level;} + int8 GetPrereqMaxTSLevel() {return prereq_max_tslevel;} + vector* GetPrereqFactions(); + vector* GetPrereqRaces(); + vector* GetPrereqModelTypes(); + vector* GetPrereqClasses(); + vector* GetPrereqTradeskillClasses(); + vector* GetPrereqQuests(); + vector* GetPrereqItems(); + vector* GetRewardItems(); + vector* GetTmpRewardItems(); + vector* GetSelectableRewardItems(); + map* GetRewardFactions(); + void GiveQuestReward(Player* player); + void AddCompleteAction(int32 step, string action); + void AddProgressAction(int32 step, string action); + void SetName(string in_name); + void SetType(string in_type); + void SetLevel(int8 in_level); + void SetEncounterLevel(int8 level) {enc_level = level;} + void SetDescription(string desc); + void SetStepDescription(int32 step, string desc); + void SetTaskGroupDescription(int32 step, string desc, bool display_bullets); + + void SetStatusTmpReward(int32 status) { tmp_reward_status = status; } + int64 GetStatusTmpReward() { return tmp_reward_status; } + + void SetCoinTmpReward(int64 coins) { tmp_reward_coins = coins; } + int64 GetCoinTmpReward() { return tmp_reward_coins; } + int64 GetCoinsReward(); + int64 GetCoinsRewardMax(); + int64 GetGeneratedCoin(); + void SetGeneratedCoin(int64 coin); + int8 GetLevel(); + int8 GetEncounterLevel() { return enc_level; } + const char* GetName(); + const char* GetDescription(); + const char* GetType(); + void SetZone(string in_zone); + const char* GetZone(); + int8 GetDay(); + int8 GetMonth(); + int8 GetYear(); + int32 GetStatusPoints(); + void SetDay(int8 value); + void SetMonth(int8 value); + void SetYear(int8 value); + vector* GetQuestUpdates(); + vector* GetQuestFailures(); + vector* GetQuestSteps(); + bool GetQuestStepCompleted(int32 step_id); + QuestStep* GetQuestStep(int32 step_id); + void SetCompleteAction(string action); + const char* GetCompleteAction(); + const char* GetCompleteAction(int32 step); + void SetQuestGiver(int32 id); + int32 GetQuestGiver(); + void SetQuestReturnNPC(int32 id); + int32 GetQuestReturnNPC(); + Player* GetPlayer(); + void SetPlayer(Player* in_player); + bool GetCompleted(); + bool HasSentLastUpdate() { return has_sent_last_update; } + void SetSentLastUpdate(bool val) { has_sent_last_update = val; } + void SetCompletedDescription(string desc); + const char* GetCompletedDescription(); + int32 GetExpReward(); + int32 GetTSExpReward() { return reward_tsexp; } + bool GetDeleted(); + void SetDeleted(bool val); + bool GetUpdateRequired(); + void SetUpdateRequired(bool val); + void SetTurnedIn(bool val); + bool GetTurnedIn(); + bool GetSaveNeeded(){ return needs_save; } + void SetSaveNeeded(bool val){ needs_save = val; } + + void SetFeatherColor(int8 val) { m_featherColor = val; } + int8 GetFeatherColor() { return m_featherColor; } + + void SetRepeatable(bool val) { m_repeatable = val; } + bool IsRepeatable() { return m_repeatable; } + + void SetTracked(bool val) { m_tracked = val; } + bool GetTracked() { return m_tracked; } + bool IsTracked() { return m_tracked && !m_hidden; } + void SetCompletedFlag(bool val); + bool GetCompletedFlag() {return completed_flag;} + bool GetYellowName() {return yellow_name;} + void SetYellowName(bool val) {yellow_name = val;} + bool CheckCategoryYellow(); + + ///Sets the custom flags for use in lua + ///Value to set the flags to + void SetQuestFlags(int32 flags) { m_questFlags = flags; SetSaveNeeded(true); } + + ///Gets the custom lua flags + ///The current flags (int32) + int32 GetQuestFlags() { return m_questFlags; } + + ///Checks to see if the quest is hidden + ///True if the quest is hidden + bool IsHidden() { return m_hidden; } + + ///Sets the quest hidden flag + ///Value to set the hidden flag to + void SetHidden(bool val) { m_hidden = val; SetSaveNeeded(true); } + + ///Gets the step timer + ///Unix timestamp (int32) + int32 GetStepTimer() { return m_timestamp; } + + ///Sets the step timer + ///How long to set the timer for, in seconds + void SetStepTimer(int32 duration); + + ///Sets the step that the timer is for + ///Step to set the timer for + void SetTimerStep(int32 step) { m_timerStep = step; } + + ///Gets the step that the timer is for + int32 GetTimerStep() { return m_timerStep; } + + ///Adds a lua call back function for when the step fails + ///The step to add the call back for + ///The lua callback function + void AddFailedAction(int32 step, string action); + + ///Fail the given step + ///The step to fail + void StepFailed(int32 step); + + ///Removes the given step from the quest + ///The step id to remove + ///The client who has this quest + ///True if able to remove the quest + bool RemoveQuestStep(int32 step, Client* client); + + int16 GetCompleteCount() { return m_completeCount; } + void SetCompleteCount(int16 val) { m_completeCount = val; } + void IncrementCompleteCount() { m_completeCount += 1; } + + void SetQuestTemporaryState(bool tempState, std::string customDescription = string("")); + bool GetQuestTemporaryState() { return quest_state_temporary; } + std::string GetQuestTemporaryDescription() { return quest_temporary_description; } + + void SetQuestShareableFlag(int32 flag) { quest_shareable_flag = flag; } + void SetCanDeleteQuest(bool newval) { can_delete_quest = newval; } + + int32 GetQuestShareableFlag() { return quest_shareable_flag; } + bool CanDeleteQuest() { return can_delete_quest; } + + bool CanShareQuestCriteria(Client* quest_sharer, bool display_client_msg = true); + Mutex MQuestSteps; +protected: + bool needs_save; + Mutex MCompletedActions; + Mutex MProgressActions; + Mutex MFailedActions; + bool turned_in; + bool update_needed; + bool deleted; + bool has_sent_last_update; + + string completed_description; + + map complete_actions; + map progress_actions; + map failed_actions; + int8 day; //only here to make our lives easier + int8 month; + int8 year; + int8 visible; + + int32 id; + string name; + string type; + string zone; + int8 level; + int8 enc_level; + string description; + string complete_action; + + Player* player; + vector prereq_factions; + int8 prereq_level; + int8 prereq_tslevel; + int8 prereq_max_level; + int8 prereq_max_tslevel; + vector prereq_model_types; + vector prereq_races; + vector prereq_classes; + vector prereq_tradeskillclass; + vector prereq_quests; + vector prereq_items; + vector reward_items; + vector selectable_reward_items; + vector tmp_reward_items; + int64 reward_coins; + int64 reward_coins_max; + int32 tmp_reward_status; + int64 tmp_reward_coins; + int64 generated_coin; + map reward_factions; + int32 reward_status; + string reward_comment; + int32 reward_exp; + int32 reward_tsexp; + vector step_updates; + vector step_failures; + map quest_step_map; + map quest_step_reverse_map; + vector quest_steps; + map task_group_order; + int16 task_group_num; + map > task_group; + int32 quest_giver; + int32 return_id; + int8 m_featherColor; + bool m_repeatable; + bool m_tracked; + bool completed_flag; + bool yellow_name; + int32 m_questFlags; + bool m_hidden; + + int32 m_timestamp; // timer for a quest step + int32 m_timerStep; // used for the fail action when timer expires + + int16 m_completeCount; + + bool quest_state_temporary; + std::string quest_temporary_description; + int32 quest_shareable_flag; + bool can_delete_quest; +}; + +class MasterQuestList{ +public: + MasterQuestList(); + Quest* GetQuest(int32 id, bool copyQuest = true){ + if(quests.count(id) > 0) + { + if(copyQuest) + return new Quest(quests[id]); + else + return quests[id]; + } + else + return 0; + } + + map* GetQuests(){ + return &quests; + } + + void AddQuest(int32 id, Quest* quest){ + quests[id] = quest; + } + void Reload(); + void LockQuests(); + void UnlockQuests(); + +private: + Mutex MQuests; + map quests; +}; + +#endif diff --git a/source/WorldServer/RaceTypes/RaceTypes.cpp b/source/WorldServer/RaceTypes/RaceTypes.cpp new file mode 100644 index 0000000..0a2734c --- /dev/null +++ b/source/WorldServer/RaceTypes/RaceTypes.cpp @@ -0,0 +1,120 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "RaceTypes.h" +#include + +MasterRaceTypeList::MasterRaceTypeList() { + +} + +MasterRaceTypeList::~MasterRaceTypeList() { + +} + +bool MasterRaceTypeList::AddRaceType(int16 model_id, int16 race_type_id, const char* category, const char* subcategory, const char* modelname, bool allow_override) { + if (m_raceList.count(model_id) == 0 || allow_override) { + RaceTypeStructure rts; + m_raceList[model_id].race_type_id = race_type_id; + if(category != NULL) { + strncpy(m_raceList[model_id].category, category, 64); + } else { + strcpy(m_raceList[model_id].category,""); + } + + if(subcategory != NULL) { + strncpy(m_raceList[model_id].subcategory, subcategory, 64); + } else { + strcpy(m_raceList[model_id].subcategory,""); + } + + if(modelname != NULL) { + strncpy(m_raceList[model_id].modelname, modelname, 64); + } else { + strcpy(m_raceList[model_id].modelname,""); + } + + return true; + } + + return false; +} + +int16 MasterRaceTypeList::GetRaceType(int16 model_id) { + int16 ret = 0; + + if (m_raceList.count(model_id) > 0) + ret = m_raceList[model_id].race_type_id; + + return ret; +} + +char* MasterRaceTypeList::GetRaceTypeCategory(int16 model_id) { + if(m_raceList.count(model_id) > 0 && strlen(m_raceList[model_id].category) > 0) + return m_raceList[model_id].category; + + return ""; +} + +char* MasterRaceTypeList::GetRaceTypeSubCategory(int16 model_id) { + if(m_raceList.count(model_id) > 0 && strlen(m_raceList[model_id].subcategory) > 0) + return m_raceList[model_id].subcategory; + + return ""; +} + + +char* MasterRaceTypeList::GetRaceTypeModelName(int16 model_id) { + if(m_raceList.count(model_id) > 0 && strlen(m_raceList[model_id].modelname) > 0) + return m_raceList[model_id].modelname; + + return ""; +} + +int16 MasterRaceTypeList::GetRaceBaseType(int16 model_id) { + int16 ret = 0; + + if (m_raceList.count(model_id) == 0) + return ret; + + int16 race = m_raceList[model_id].race_type_id; + if (race >= DRAGONKIND && race < FAY) + ret = DRAGONKIND; + else if (race >= FAY && race < MAGICAL) + ret = FAY; + else if (race >= MAGICAL && race < MECHANIMAGICAL) + ret = MAGICAL; + else if (race >= MECHANIMAGICAL && race < NATURAL) + ret = MECHANIMAGICAL; + else if (race >= NATURAL && race < PLANAR) + ret = NATURAL; + else if (race >= PLANAR && race < PLANT) + ret = PLANAR; + else if (race >= PLANT && race < SENTIENT) + ret = PLANT; + else if (race >= SENTIENT && race < UNDEAD) + ret = SENTIENT; + else if (race >= UNDEAD && race < WERE) + ret = UNDEAD; + else if (race >= WERE) + ret = WERE; + + return ret; +} \ No newline at end of file diff --git a/source/WorldServer/RaceTypes/RaceTypes.h b/source/WorldServer/RaceTypes/RaceTypes.h new file mode 100644 index 0000000..899fbab --- /dev/null +++ b/source/WorldServer/RaceTypes/RaceTypes.h @@ -0,0 +1,321 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifndef __RACETYPES_H__ +#define __RACETYPES_H__ + +#include "../../common/types.h" +#include + +#define DRAGONKIND 101 +#define DRAGON 102 +#define DRAKE 103 +#define DRAKOTA 104 +#define DROAG 105 +#define FAEDRAKE 106 +//FLYINGSNAKE Defined in natural as well, think is a better fit there then here +#define SOKOKAR 107 +#define WURM 108 +#define WYRM 109 +#define WYVERN 110 + +#define FAY 111 +#define ARASAI_NPC 112 +#define BIXIE 113 +#define BROWNIE 114 +#define DRYAD 115 +#define FAE_NPC 116 +#define FAIRY 117 +#define SIREN 118 +#define SPIRIT 119 +#define SPRITE 120 +#define TREANT 121 //L&L 8 +#define WISP 122 + +#define MAGICAL 123 +#define AMORPH 124 +#define CONSTRUCT 125 +#define ANIMATION 126 +#define BONEGOLEM 127 +#define BOVOCH 128 +#define CARRIONGOLEM 129 +#define CLAYGOLEM 130 +#define CUBE 131 +#define DERVISH 132 +#define DEVOURER 133 +#define GARGOYLE 134 +#define GOLEM 135 +#define GOO 136 +#define HARPY 137 +#define IMP 138 +#define LIVINGSTATUE 139 +#define MANNEQUIN 140 +#define MIMIC 141 +#define MOPPET 142 +#define NAGA 143 +#define NAYAD 144 +#define OOZE 145 +#define RUMBLER 146 +#define RUSTMONSTER 147 +#define SATYR 148 +#define SCARECROW 149 +#define SPHEROID 150 +#define TENTACLETERROR 151 +#define TOME 152 +#define UNICORN 153 +#define WOODELEMENTAL 154 + +#define MECHANIMAGICAL 155 +#define CLOCKWORK 156 +#define IRONGUARDIAN 157 + +#define NATURAL 158 +#define ANIMAL 159 +#define AQUATIC 160 +#define AVIAN 161 +#define CANINE 162 +#define EQUINE 163 +#define FELINE 164 +#define INSECT 165 +#define PRIMATE 166 +#define REPTILE 167 +#define ANEMONE 168 +#define APOPHELI 169 +#define ARMADILLO 170 +#define BADGER 171 +#define BARRACUDA 172 +#define BASILISK 173 +#define BAT 174 +#define BEAR 175 +#define BEAVER 176 +#define BEETLE 177 +#define BOVINE 178 +#define BRONTOTHERIUM 179 +#define BRUTE 180 +#define CAMEL 181 +#define CAT 182 +#define CENTIPEDE 183 +#define CERBERUS 184 +#define CHIMERA 185 +#define CHOKIDAI 186 +#define COBRA 187 +#define COCKATRICE 188 +#define CRAB 189 +#define CROCODILE 190 +#define DEER 191 +#define DRAGONFLY 192 +#define DUCK 193 +#define EEL 194 +#define ELEPHANT 195 +#define FLYINGSNAKE 196 +#define FROG 197 +#define GOAT 198 +#define GORILLA 199 +#define GRIFFIN 200 +#define HAWK 201 +#define HIVEQUEEN 202 +#define HORSE 203 +#define HYENA 204 +#define KHOALRAT 205 +#define KYBUR 206 +#define LEECH 207 +#define LEOPARD 208 +#define LION 209 +#define LIZARD 210 +#define MAMMOTH 211 +#define MANTARAY 212 +#define MOLERAT 213 +#define MONKEY 214 +#define MYTHICAL 215 +#define OCTOPUS 216 +#define OWLBEAR 217 +#define PIG 218 +#define PIRANHA 219 +#define RAPTOR 220 +#define RAT 221 +#define RHINOCEROS 222 +#define ROCKCRAWLER 223 +#define SABERTOOTH 224 +#define SCORPION 225 +#define SEATURTLE 226 +#define SHARK 227 +#define SHEEP 228 +#define SLUG 229 +#define SNAKE 230 +#define SPIDER 231 +#define STIRGE 232 +#define SWORDFISH 233 +#define TIGER 234 +#define TURTLE 235 +#define VERMIN 236 +#define VULRICH 237 +#define WOLF 238 +#define YETI 239 + +#define PLANAR 240 +#define ABOMINATION 241 +#define AIRELEMENTAL 242 +#define AMYGDALAN 243 +#define AVATAR 244 +#define CYCLOPS 245 +#define DEMON 246 +#define DJINN 247 +#define EARTHELEMENTAL 248 +#define EFREETI 249 +#define ELEMENTAL 250 +#define ETHEREAL 251 +#define ETHERPINE 252 +#define EVILEYE 253 +#define FIREELEMENTAL 254 +#define GAZER 255 +#define GEHEIN 256 +#define GEONID 257 +#define GIANT 258 //L&L 5 +#define SALAMANDER 259 +#define SHADOWEDMAN 260 +#define SPHINX 261 +#define SPORE 262 +#define SUCCUBUS 263 +#define VALKYRIE 264 +#define VOIDBEAST 265 +#define WATERELEMENTAL 266 +#define WRAITH 267 + +#define PLANT 268 +#define CARNIVOROUSPLANT 269 +#define CATOPLEBAS 270 +#define MANTRAP 271 +#define ROOTABOMINATION 272 +#define ROOTHORROR 273 +#define SUCCULENT 274 + +#define SENTIENT 275 +#define ASHLOK 276 +#define AVIAK 277 +#define BARBARIAN_NPC 278 +#define BIRDMAN 279 +#define BOARFIEND 280 +#define BUGBEAR 281 +#define BURYNAI 282 +#define CENTAUR 283 ////L&L 4 +#define COLDAIN 284 +#define DAL 285 +#define DARKELF_NPC 286 +#define DIZOK 287 +#define DRACHNID 288 +#define DRAFLING 289 +#define DROLVARG 290 +#define DWARF_NPC 291 +#define ERUDITE_NPC 292 +#define ETTIN 293 +#define FREEBLOOD_NPC 294 +#define FROGLOK_NPC 295 +#define FROSTFELLELF 296 +#define FUNGUSMAN 297 +#define GNOLL 298 //L&L 1 +#define GNOME_NPC 299 +#define GOBLIN 300 //L&L 3 +#define GRUENGACH 301 +#define HALFELF_NPC 302 // Not on the list from wikia but all other races were here so added them +#define HALFLING_NPC 303 +#define HIGHELF_NPC 304 // Not on the list from wikia but all other races were here so added them +#define HOLGRESH 305 +#define HOOLUK 306 +#define HUAMEIN 307 +#define HUMAN_NPC 308 +#define HUMANOID 309 +#define IKSAR_NPC 310 +#define KERIGDAL 311 +#define KERRAN_NPC 312 +#define KOBOLD 313 +#define LIZARDMAN 314 +#define MINOTAUR 315 +#define OGRE_NPC 316 +#define ORC 317 //L&L 2 +#define OTHMIR 318 +#define RATONGA_NPC 319 +#define RAVASECT 320 +#define RENDADAL 321 +#define ROEKILLIK 322 +#define SARNAK_NPC 323 +#define SKORPIKIS 324 +#define SPIROC 325 +#define TROGLODYTE 326 +#define TROLL_NPC 327 +#define ULTHORK 328 +#define VULTAK 329 +#define WOODELF_NPC 330 +#define WRAITHGUARD 331 +#define YHALEI 332 + +#define UNDEAD 333 +#define GHOST 334 +#define GHOUL 335 +#define GUNTHAK 336 +#define HORROR 337 +#define MUMMY 338 +#define SHINREEORCS 339 +#define SKELETON 340 //L&L 6 +#define SPECTRE 341 +#define VAMPIRE_NPC 342 +#define ZOMBIE 343 //L&L 7 + +#define WERE 344 +#define AHROUNWEREWOLVES 345 +#define LYKULAKWEREWOLVES 346 +#define WEREWOLF 347 + +struct RaceTypeStructure { + int16 race_type_id; + char category[64]; + char subcategory[64]; + char modelname[250]; +}; + +class MasterRaceTypeList { +public: + MasterRaceTypeList(); + ~MasterRaceTypeList(); + + /// Add a race type define to the list + /// The id of the model + /// The id of the race type + /// The category of the race type + /// The subcategory of the race type + /// The model name of the model id + bool AddRaceType(int16 model_id, int16 race_type_id, const char* category, const char* subcategory, const char* modelname, bool allow_override = false); + + /// Gets the race type for the given model + /// The model id to get the race type for + int16 GetRaceType(int16 model_id); + char* GetRaceTypeCategory(int16 model_id); + char* GetRaceTypeSubCategory(int16 model_id); + char* GetRaceTypeModelName(int16 model_id); + + /// Gets the base race type for the given model + /// The model id to get the base race type for + int16 GetRaceBaseType(int16 model_id); + +private: + // model id, race type id + map m_raceList; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/RaceTypes/RaceTypesDB.cpp b/source/WorldServer/RaceTypes/RaceTypesDB.cpp new file mode 100644 index 0000000..9b4008a --- /dev/null +++ b/source/WorldServer/RaceTypes/RaceTypesDB.cpp @@ -0,0 +1,43 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "../WorldDatabase.h" +#include "../../common/Log.h" +#include "RaceTypes.h" + +extern MasterRaceTypeList race_types_list; + +void WorldDatabase::LoadRaceTypes() { + DatabaseResult result; + + if(database_new.Select(&result, "SELECT `model_type`, `race_id`, `category`, `subcategory`, `model_name` FROM `race_types`")) { + int32 count = 0; + + while (result.Next()) { + int16 race_id = result.GetInt16Str("race_id"); + if (race_id > 0) { + race_types_list.AddRaceType(result.GetInt16Str("model_type"), race_id, result.GetStringStr("category"), result.GetStringStr("subcategory"), result.GetStringStr("model_name")); + count++; + } + } + + LogWrite(WORLD__INFO, 0, "World", "- Loaded %u Race Types", count); + } +} \ No newline at end of file diff --git a/source/WorldServer/Recipes/Recipe.cpp b/source/WorldServer/Recipes/Recipe.cpp new file mode 100644 index 0000000..b005209 --- /dev/null +++ b/source/WorldServer/Recipes/Recipe.cpp @@ -0,0 +1,778 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include +#include "../../common/debug.h" +#include "../../common/Log.h" +#include "../../common/database.h" +#include "Recipe.h" +#include "../../common/ConfigReader.h" +#include "../Items/Items.h" +#include "../World.h" + +extern ConfigReader configReader; +extern MasterItemList master_item_list; +extern World world; + + +Recipe::Recipe() { + id = 0; + book_id = 0; + memset(name, 0, sizeof(name)); + memset(book_name, 0, sizeof(book_name)); + memset(book, 0, sizeof(book)); + memset(device, 0, sizeof(device)); + level = 0; + tier = 0; + icon = 0; + skill = 0; + technique = 0; + knowledge = 0; + classes = 0; + unknown2 = 0; + unknown3 = 0; + unknown4 = 0; +} + +Recipe::~Recipe() { + map::iterator itr; + for (itr = products.begin(); itr != products.end(); itr++) + safe_delete(itr->second); +} + +Recipe::Recipe(Recipe *in){ + assert(in); + id = in->GetID(); + soe_id = in->GetSoeID(); + book_id = in->GetBookID(); + strncpy(name, in->GetName(), sizeof(name)); + strncpy(description, in->GetDescription(), sizeof(description)); + strncpy(book_name, in->GetBookName(), sizeof(book_name)); + strncpy(book, in->GetBook(), sizeof(book)); + strncpy(device, in->GetDevice(), sizeof(device)); + + level = in->GetLevel(); + tier = in->GetTier(); + icon = in->GetIcon(); + skill = in->GetSkill(); + technique = in->GetTechnique(); + knowledge = in->GetKnowledge(); + device_sub_type = in->GetDevice_Sub_Type(); + classes = in->GetClasses(); + unknown1 = in->GetUnknown1(); + unknown2 = in->GetUnknown2(); + unknown3 = in->GetUnknown3(); + unknown4 = in->GetUnknown4(); + + product_item_id = in->GetProductID(); + strncpy(product_name, in->product_name, sizeof(product_name)); + product_qty = in->GetProductQuantity(); + + strncpy(primary_build_comp_title, in->primary_build_comp_title, sizeof(primary_build_comp_title)); + strncpy(build1_comp_title, in->build1_comp_title, sizeof(build1_comp_title)); + strncpy(build2_comp_title, in->build2_comp_title, sizeof(build2_comp_title)); + strncpy(build3_comp_title, in->build3_comp_title, sizeof(build3_comp_title)); + strncpy(build4_comp_title, in->build4_comp_title, sizeof(build4_comp_title)); + + strncpy(fuel_comp_title, in->fuel_comp_title, sizeof(fuel_comp_title)); + build1_comp_qty = in->GetBuild1ComponentQuantity(); + build2_comp_qty = in->GetBuild2ComponentQuantity(); + build3_comp_qty = in->GetBuild3ComponentQuantity(); + build4_comp_qty = in->GetBuild4ComponentQuantity(); + fuel_comp_qty = in->GetFuelComponentQuantity(); + primary_comp_qty = in->GetPrimaryComponentQuantity(); + highestStage = in->GetHighestStage(); + + std::map::iterator itr; + for (itr = in->products.begin(); itr != in->products.end(); itr++) { + RecipeProducts* rp = new RecipeProducts; + rp->product_id = itr->second->product_id; + rp->byproduct_id = itr->second->byproduct_id; + rp->product_qty = itr->second->product_qty; + rp->byproduct_qty = itr->second->byproduct_qty; + products.insert(make_pair(itr->first, rp)); + } + + std::map>::iterator itr2; + for (itr2 = in->components.begin(); itr2 != in->components.end(); itr2++) { + std::vector recipe_component; + std::copy(itr2->second.begin(), itr2->second.end(), + std::back_inserter(recipe_component)); + components.insert(make_pair(itr2->first, recipe_component)); + } +} + +MasterRecipeList::MasterRecipeList() { + m_recipes.SetName("MasterRecipeList::recipes"); +} + +MasterRecipeList::~MasterRecipeList() { + ClearRecipes(); +} + +bool MasterRecipeList::AddRecipe(Recipe *recipe) { + bool ret = false; + int32 id; + + assert(recipe); + + id = recipe->GetID(); + m_recipes.writelock(__FUNCTION__, __LINE__); + if (recipes.count(id) == 0) { + recipes[id] = recipe; + recipes_crc[recipe->GetSoeID()] = recipe; + ret = true; + } + m_recipes.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +Recipe* MasterRecipeList::GetRecipe(int32 recipe_id) { + Recipe *ret = 0; + + m_recipes.readlock(__FUNCTION__, __LINE__); + if (recipes.count(recipe_id) > 0) + ret = recipes[recipe_id]; + m_recipes.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Recipe* MasterRecipeList::GetRecipeByCRC(int32 recipe_crc) { + Recipe *ret = 0; + + m_recipes.readlock(__FUNCTION__, __LINE__); + if (recipes_crc.count(recipe_crc) > 0) + ret = recipes_crc[recipe_crc]; + m_recipes.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Recipe* MasterRecipeList::GetRecipeByName(const char* name) { + Recipe* ret = 0; + map::iterator itr; + + m_recipes.readlock(__FUNCTION__, __LINE__); + for (itr = recipes.begin(); itr != recipes.end(); itr++) { + if (::ToLower(string(name)) == ::ToLower(string(itr->second->GetName()))) { + ret = itr->second; + break; + } + } + m_recipes.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void MasterRecipeList::ClearRecipes() { + map::iterator itr; + + m_recipes.writelock(__FUNCTION__, __LINE__); + for (itr = recipes.begin(); itr != recipes.end(); itr++) + safe_delete(itr->second); + recipes.clear(); + recipes_crc.clear(); + m_recipes.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 MasterRecipeList::Size() { + int32 ret; + + m_recipes.readlock(__FUNCTION__, __LINE__); + ret = (int32)recipes.size(); + m_recipes.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +vector MasterRecipeList::GetRecipes(const char* book_name) { + vector ret; + map::iterator itr; + + m_recipes.writelock(__FUNCTION__, __LINE__); + for (itr = recipes.begin(); itr != recipes.end(); itr++) { + if (::ToLower(string(book_name)) == ::ToLower(string(itr->second->GetBook()))) + ret.push_back(itr->second); + } + m_recipes.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerRecipeList::PlayerRecipeList(){ +} + +PlayerRecipeList::~PlayerRecipeList(){ + ClearRecipes(); +} + +bool PlayerRecipeList::AddRecipe(Recipe *recipe){ + std::unique_lock lock(player_recipe_mutex); + assert(recipe); + + if(recipes.count(recipe->GetID()) == 0){ + recipes[recipe->GetID()] = recipe; + return true; + } + return false; +} + +Recipe * PlayerRecipeList::GetRecipe(int32 recipe_id){ + std::shared_lock lock(player_recipe_mutex); + if (recipes.count(recipe_id) > 0) + return recipes[recipe_id]; + return 0; +} + +void PlayerRecipeList::ClearRecipes(){ + std::unique_lock lock(player_recipe_mutex); + map::iterator itr; + + for (itr = recipes.begin(); itr != recipes.end(); itr++) + safe_delete(itr->second); + recipes.clear(); +} + +bool PlayerRecipeList::RemoveRecipe(int32 recipe_id) { + std::unique_lock lock(player_recipe_mutex); + bool ret = false; + if (recipes.count(recipe_id) > 0) { + recipes.erase(recipe_id); + ret = true; + } + return ret; +} + + +int32 PlayerRecipeList::Size() { + std::unique_lock lock(player_recipe_mutex); + return recipes.size(); +} + +MasterRecipeBookList::MasterRecipeBookList(){ + m_recipeBooks.SetName("MasterRecipeBookList::recipeBooks"); +} + +MasterRecipeBookList::~MasterRecipeBookList(){ + ClearRecipeBooks(); +} + +bool MasterRecipeBookList::AddRecipeBook(Recipe *recipe){ + bool ret = false; + int32 id = 0; + + assert(recipe); + + id = recipe->GetBookID(); + m_recipeBooks.writelock(__FUNCTION__, __LINE__); + if(recipeBooks.count(id) == 0){ + recipeBooks[id] = recipe; + ret = true; + } + m_recipeBooks.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +Recipe * MasterRecipeBookList::GetRecipeBooks(int32 recipebook_id){ + Recipe *ret = 0; + + m_recipeBooks.readlock(__FUNCTION__, __LINE__); + if (recipeBooks.count(recipebook_id) > 0) + ret = recipeBooks[recipebook_id]; + m_recipeBooks.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void MasterRecipeBookList::ClearRecipeBooks(){ + map::iterator itr; + + m_recipeBooks.writelock(__FUNCTION__, __LINE__); + for (itr = recipeBooks.begin(); itr != recipeBooks.end(); itr++) + safe_delete(itr->second); + recipeBooks.clear(); + m_recipeBooks.releasewritelock(__FUNCTION__, __LINE__); +} + +int32 MasterRecipeBookList::Size(){ + int32 ret = 0; + + m_recipeBooks.readlock(__FUNCTION__, __LINE__); + ret = (int32)recipeBooks.size(); + m_recipeBooks.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +EQ2Packet* MasterRecipeList::GetRecipePacket(int32 recipe_id, Client* client, bool display, int8 packet_type){ + Recipe *recipe = GetRecipe(recipe_id); + if(recipe){ + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Recipe ID: %u Recipe Name: %s", recipe->GetID(), recipe->GetName()); + return recipe->SerializeRecipe(client, recipe, display, packet_type); + } + return 0; +} + +PlayerRecipeBookList::PlayerRecipeBookList(){ +} + +PlayerRecipeBookList::~PlayerRecipeBookList(){ + ClearRecipeBooks(); +} + +bool PlayerRecipeBookList::AddRecipeBook(Recipe *recipe){ + assert(recipe); + + if(recipeBooks.count(recipe->GetBookID()) == 0){ + recipeBooks[recipe->GetBookID()] = recipe; + return true; + } + return false; +} + +Recipe * PlayerRecipeBookList::GetRecipeBook(int32 recipebook_id){ + if(recipeBooks.count(recipebook_id) > 0) + return recipeBooks[recipebook_id]; + return 0; +} + +bool PlayerRecipeBookList::HasRecipeBook(int32 book_id) { + if (recipeBooks.count(book_id) > 0) + return true; + return false; +} + +void PlayerRecipeBookList::ClearRecipeBooks(){ + map::iterator itr; + + for(itr = recipeBooks.begin(); itr != recipeBooks.end(); itr++) + safe_delete(itr->second); + recipeBooks.clear(); +} + +EQ2Packet * Recipe::SerializeRecipe(Client *client, Recipe *recipe, bool display, int8 packet_type, int8 subpacket_type, const char *struct_name){ + int16 version = 1; + Item* item = 0; + RecipeProducts* rp = 0; + vector::iterator itr; + vector comp_list; + + int8 i = 0; + int32 firstID = 0; + int32 primary_comp_id = 0; + if(client) + version = client->GetVersion(); + if(!struct_name) + struct_name = "WS_ExamineRecipeInfo"; + PacketStruct *packet = configReader.getStruct(struct_name, version); + if(display) + packet->setSubstructDataByName("info_header", "show_name", 1); + else + packet->setSubstructDataByName("info_header", "show_popup", 1); + + if(client->GetVersion() <= 561) { + packet->setSubstructDataByName("info_header", "packettype", 0x02); + } + else if(packet_type > 0) + packet->setSubstructDataByName("info_header", "packettype", GetItemPacketType(packet->GetVersion())); + else + if(version == 1096) + packet->setSubstructDataByName("info_header", "packettype",0x35FE); + if (version == 1208) + packet->setSubstructDataByName("info_header", "packettype", 0x45FE); + if(version >= 57048) + packet->setSubstructDataByName("info_header", "packettype",0x48FE); + if(subpacket_type == 0) + subpacket_type = 0x02; + packet->setSubstructDataByName("info_header", "packetsubtype", subpacket_type); + + packet->setSubstructDataByName("recipe_info", "id", recipe->GetID()); + packet->setSubstructDataByName("recipe_info", "unknown", 3); + packet->setSubstructDataByName("recipe_info", "level", recipe->GetLevel()); + packet->setSubstructDataByName("recipe_info", "technique", recipe->GetTechnique()); + packet->setSubstructDataByName("recipe_info", "skill_level", 50); //50 + packet->setSubstructDataByName("recipe_info", "knowledge", recipe->GetKnowledge()); + packet->setSubstructDataByName("recipe_info", "device", recipe->GetDevice()); + packet->setSubstructDataByName("recipe_info", "icon", recipe->GetIcon()); + packet->setSubstructDataByName("recipe_info", "unknown3", 1); + packet->setSubstructDataByName("recipe_info", "adventure_id", 0xFF); + packet->setSubstructDataByName("recipe_info", "tradeskill_id", client ? client->GetPlayer()->GetTradeskillClass() : 0); + packet->setSubstructDataByName("recipe_info", "unknown4a", 100); + packet->setSubstructDataByName("recipe_info", "unknown4aa", 1); + packet->setSubstructDataByName("recipe_info", "unknown5a", 20);//level *10 + packet->setSubstructDataByName("recipe_info", "product_classes", recipe->GetClasses()); + int32 HS = 0; + if (client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID()) == NULL) + HS = 0; + else + HS = client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->highestStage; + + + packet->setSubstructDataByName("recipe_info", "show_previous", HS);// recipe->highestStage); + + + rp = recipe->products[1]; + if (rp->product_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->product_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "previous1_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "previous1_name", "previous1_name"); + packet->setSubstructDataByName("recipe_info", "previous1_qty", recipe->products[1]->product_qty); + packet->setSubstructDataByName("recipe_info", "previous1_item_id", recipe->products[1]->product_id); + packet->setSubstructDataByName("recipe_info", "previous1_item_crc", -853046774); + packet->setSubstructDataByName("recipe_info", "firstbar_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "firstbar_name", "firstbar_name"); + packet->setSubstructDataByName("recipe_info", "firstbar_qty", recipe->products[1]->product_qty); + packet->setSubstructDataByName("recipe_info", "firstbar_item_id", recipe->products[2]->product_id); + packet->setSubstructDataByName("recipe_info", "firstbar_item_crc", -853046774); + } + } + rp = recipe->products[2]; + if (rp->product_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->product_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "previous2_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "previous2_name", "previous2_name"); + packet->setSubstructDataByName("recipe_info", "previous2_qty", recipe->products[2]->product_qty); + packet->setSubstructDataByName("recipe_info", "previous2_item_id", recipe->products[2]->product_id); + packet->setSubstructDataByName("recipe_info", "previous2_item_crc", -853046774); + packet->setSubstructDataByName("recipe_info", "secondbar_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "secondbar_name", "secondbar_name"); + packet->setSubstructDataByName("recipe_info", "secondbar_qty", recipe->products[2]->product_qty); + packet->setSubstructDataByName("recipe_info", "secondbar_item_id", recipe->products[2]->product_id); + packet->setSubstructDataByName("recipe_info", "secondbar_item_crc", -853046774); + } + } + rp = recipe->products[3]; + if (rp->product_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->product_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "previous3_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "previous3_name", "previous3_name"); + packet->setSubstructDataByName("recipe_info", "previous3_qty", recipe->products[3]->product_qty); + packet->setSubstructDataByName("recipe_info", "previous3_item_id", recipe->products[3]->product_id); + packet->setSubstructDataByName("recipe_info", "previous3_item_crc", -853046774); + packet->setSubstructDataByName("recipe_info", "thirdbar_icon", item->GetIcon(client->GetVersion())); + packet->setSubstructDataByName("recipe_info", "thirdbar_name", "thirdbar_name"); + packet->setSubstructDataByName("recipe_info", "thirdbar_qty", recipe->products[3]->product_qty); + packet->setSubstructDataByName("recipe_info", "thirdbar_item_id", recipe->products[3]->product_id); + packet->setSubstructDataByName("recipe_info", "thirdbar_item_crc", -2065846136); + } + } + + + + + //item = master_item_list.GetItemByName(recipe->GetName());// TODO: MJ we should be getting item by item number in case of multiple items with same name + item = master_item_list.GetItem(recipe->GetProductID()); + if(item) { + packet->setSubstructDataByName("recipe_info", "product_icon", item->GetIcon(client->GetVersion())); //item->details.icon); + packet->setSubstructDataByName("recipe_info", "product_name", item->name.c_str()); //item->name); + packet->setSubstructDataByName("recipe_info", "product_qty", 1); + packet->setSubstructDataByName("recipe_info", "product_item_id", item->details.item_id); //item->details.item_id); + packet->setSubstructDataByName("recipe_info", "product_item_crc", 0); //item->details.item_crc); + } + + rp = recipe->products[0]; + if (rp->byproduct_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->byproduct_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "byproduct_icon", item->GetIcon(client->GetVersion()));//11 + packet->setSubstructDataByName("recipe_info", "byproduct_id", item->details.item_id); + } + + } + rp = recipe->products[1]; + if (rp->product_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->product_id); + if (item) { + packet->setSubstructDataByName("recipe_info", "byproduct_icon", item->GetIcon(client->GetVersion()));//11 + packet->setSubstructDataByName("recipe_info", "byproduct_id", item->details.item_id); + } + + } + + item = 0; + + // Check to see if we have a primary component (slot = 0) + vector itemss; + if (recipe->components.count(0) > 0) { + if(client->GetVersion() <= 561) { + packet->setSubstructDataByName("recipe_info", "primary_count", 1); + } + + int16 have_qty = 0; + vector rc = recipe->components[0]; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + item = master_item_list.GetItem(*itr); + packet->setSubstructDataByName("recipe_info", "primary_comp", recipe->primary_build_comp_title); + packet->setSubstructDataByName("recipe_info", "primary_qty", recipe->GetPrimaryComponentQuantity()); + item = 0; + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) { + int16 needed_qty = recipe->GetPrimaryComponentQuantity(); + for (int8 i = 0; i < itemss.size(); i++) { + have_qty += itemss[i]->details.count; + } + } + } + packet->setSubstructDataByName("recipe_info", "primary_qty_avail", have_qty); + } + + + + int8 total_build_components = recipe->GetTotalBuildComponents(); + + int8 index = 0; + int8 count = 0; + if (total_build_components > 0) { + packet->setSubstructArrayLengthByName("recipe_info", "num_comps", total_build_components); + for (index = 0; index < 4; index++) { + if (recipe->components.count(index + 1) == 0) + continue; + + count++; + vector rc = recipe->components[index + 1]; + int16 have_qty = 0; + string comp_title; + int8 comp_qty; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + if (index == 0) { + comp_title = recipe->build1_comp_title; + comp_qty = recipe->build1_comp_qty; + } + else if (index == 1) { + comp_title = recipe->build2_comp_title; + comp_qty = recipe->build2_comp_qty; + } + else if (index == 2) { + comp_title = recipe->build3_comp_title; + comp_qty = recipe->build3_comp_qty; + } + else if (index == 3) { + comp_title = recipe->build4_comp_title; + comp_qty = recipe->build4_comp_qty; + } + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + for (int8 j = 0; j < itemss.size(); j++) { + have_qty += itemss[j]->details.count; + } + } + packet->setArrayDataByName("build_comp", comp_title.c_str(), index); + packet->setArrayDataByName("build_comp_qty", comp_qty, index); + packet->setArrayDataByName("build_comp_qty_avail", have_qty, index); + } + + } + + if(client->GetVersion() <= 561) { + packet->setSubstructDataByName("recipe_info", "fuel_count", 1); + packet->setSubstructDataByName("recipe_info", "fuel_comp", recipe->fuel_comp_title); + packet->setSubstructDataByName("recipe_info", "fuel_comp_qty", recipe->fuel_comp_qty); + } + // Check to see if we have a fuel component (slot = 5) + else if (recipe->components.count(5) > 0) { + vector rc = recipe->components[5]; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + item = master_item_list.GetItem(*itr); + packet->setSubstructDataByName("recipe_info", "fuel_comp", recipe->fuel_comp_title); + packet->setSubstructDataByName("recipe_info", "fuel_comp_qty", recipe->fuel_comp_qty); + item = 0; + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) { + int16 have_qty = 0; + for (int8 i = 0; i < itemss.size(); i++) { + have_qty += itemss[i]->details.count; + } + packet->setSubstructDataByName("recipe_info", "fuel_comp_qty_avail", have_qty); + break; + } + } + } + packet->setSubstructDataByName("recipe_info", "build_comp_qty_avail_flag", 1); + packet->setSubstructDataByName("recipe_info", "unknown6", 4, 0); + packet->setSubstructDataByName("recipe_info", "min_product", 1); + packet->setSubstructDataByName("recipe_info", "max_product", 1); + packet->setSubstructDataByName("recipe_info", "available_flag", 4); + packet->setSubstructDataByName("recipe_info", "not_commissionable", 1); + packet->setSubstructDataByName("recipe_info", "recipe_name", recipe->GetName()); + packet->setSubstructDataByName("recipe_info", "recipe_description", recipe->GetDescription()); + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + EQ2Packet* app = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + safe_delete(packet); + safe_delete(data); + //DumpPacket(app); + return app; +} + +void Recipe::AddBuildComp(int32 itemID, int8 slot, bool preffered) { + if (preffered) + components[slot].insert(components[slot].begin(), itemID); + else + components[slot].push_back(itemID); +} + +int8 Recipe::GetTotalBuildComponents() { + int8 total_build_components = 0; + for(int i=1;i<=4;i++) { + if (components.count(i) > 0) + total_build_components++; + } + return total_build_components; +} + +bool Recipe::ProvidedAllRequiredComponents(Client* client, vector* player_components, vector>* player_component_pair_qty) { + vector::iterator itr; + std::vector> player_comp_itr; + + // Check to see if we have a fuel component (slot = 5) + bool matched = false; + bool hasfuel = false; + if (components.count(5) > 0) { + vector rc = components[5]; + for (itr = rc.begin(); itr != rc.end(); itr++) { + hasfuel = true; + LogWrite(TRADESKILL__INFO, 5, "Recipes", "Recipe ID: %u Recipe Name: %s, item %s (%u), fuel quantity required %u", GetID(), GetName(), fuel_comp_title, (*itr), fuel_comp_qty); + if(PlayerHasComponentByItemID(client, player_components, player_component_pair_qty, (*itr), fuel_comp_qty)) { + matched = true; + break; + } + } + } + if(hasfuel && !matched) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Recipe ID: %u Recipe Name: %s, item %s (%u), lacking fuel quantity required %u", GetID(), GetName(), fuel_comp_title, (*itr), fuel_comp_qty); + return false; + } + + for (int8 index = 0; index < GetTotalBuildComponents(); index++) { + if (components.count(index + 1) == 0) + continue; + + vector rc = components[index + 1]; + string comp_title; + int8 comp_qty; + matched = false; + for (itr = rc.begin(); itr != rc.end(); itr++) { + if (index == 0) { + comp_title = build1_comp_title; + comp_qty = build1_comp_qty; + } + else if (index == 1) { + comp_title = build2_comp_title; + comp_qty = build2_comp_qty; + } + else if (index == 2) { + comp_title = build3_comp_title; + comp_qty = build3_comp_qty; + } + else if (index == 3) { + comp_title = build4_comp_title; + comp_qty = build4_comp_qty; + } + LogWrite(TRADESKILL__INFO, 5, "Recipes", "Recipe ID: %u Recipe Name: %s, item %s (%u), index %u quantity required %u", GetID(), GetName(), comp_title.c_str(), (*itr), index, comp_qty); + if(PlayerHasComponentByItemID(client, player_components, player_component_pair_qty, (*itr), comp_qty)) { + matched = true; + break; + } + } + if(!matched) { + return false; + } + } + return true; +} + +bool Recipe::PlayerHasComponentByItemID(Client* client, vector* player_components, vector>* player_component_pair_qty, int32 item_id, int8 required_qty) { + vector::iterator itr; + int16 have_qty = 0; + for(itr = player_components->begin(); itr != player_components->end(); itr++) { + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "PlayerHasComponentByItemID %u to match %u, qty %u, qtyreq: %u", (*itr)->details.item_id, item_id, (*itr)->details.count, required_qty); + if((*itr) && (*itr)->details.item_id == item_id && (*itr)->details.count >= required_qty) { + return true; + } + } + + vector itemss = client->GetPlayer()->item_list.GetAllItemsFromID(item_id); + if (itemss.size() > 0) { + for (int8 i = 0; i < itemss.size(); i++) { + have_qty += itemss[i]->details.count; + } + } + + int16 track_req_qty = required_qty; + if(have_qty >= required_qty) { + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "PlayerHasComponentByItemID OVERRIDE! Inventory has item id %u, more than required for quantity %u, have %u", item_id, required_qty, have_qty); + have_qty = 0; + for (int8 i = 0; i < itemss.size(); i++) { + have_qty += itemss[i]->details.count; + int8 cur_qty = itemss[i]->details.count; + if(cur_qty > track_req_qty) + cur_qty = track_req_qty; + + track_req_qty -= cur_qty; + itemss[i]->details.item_locked = true; + player_component_pair_qty->push_back({itemss[i]->details.unique_id, cur_qty}); + player_components->push_back(itemss[i]); + if(have_qty >= required_qty) + break; + } + return true; + } + + return false; +} + +int8 Recipe::GetItemRequiredQuantity(int32 item_id) { + vector::iterator itr; + int8 comp_qty = 0, qty = 0; + for (int8 index = 0; index < GetTotalBuildComponents(); index++) { + if (components.count(index + 1) == 0) + continue; + + vector rc = components[index + 1]; + string comp_title; + int8 comp_qty; + bool matched = false; + for (itr = rc.begin(); itr != rc.end(); itr++) { + if (index == 0) { + comp_qty = build1_comp_qty; + } + else if (index == 1) { + comp_qty = build2_comp_qty; + } + else if (index == 2) { + comp_qty = build3_comp_qty; + } + else if (index == 3) { + comp_qty = build4_comp_qty; + } + if((*itr) == item_id) + qty += comp_qty; + } + } + return qty; +} \ No newline at end of file diff --git a/source/WorldServer/Recipes/Recipe.h b/source/WorldServer/Recipes/Recipe.h new file mode 100644 index 0000000..596e3fc --- /dev/null +++ b/source/WorldServer/Recipes/Recipe.h @@ -0,0 +1,272 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef RECIPE_H_ +#define RECIPE_H_ + +#include "../../common/types.h" +#include "../../common/Mutex.h" +#include "../classes.h" + +#include +#include + +class Item; +using namespace std; + +struct RecipeComp + +{ + int32 RecipeComp; + +}; +struct RecipeProducts { + int32 product_id; + int32 byproduct_id; + int8 product_qty; + int8 byproduct_qty; +}; + +class Recipe { +public: + Recipe(); + Recipe(Recipe *in); + virtual ~Recipe(); + + EQ2Packet *SerializeRecipe(Client *client, Recipe *recipe, bool display, int8 packet_type = 0, int8 sub_packet_type = 0, const char *struct_name = 0); + void SetID(int32 id) {this->id = id;} + void SetSoeID(int32 soe_id) { this->soe_id = soe_id; } + void SetBookID(int32 book_id) {this->book_id = book_id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + void SetDescription(const char* description) { strncpy(this->description, description, sizeof(this->description)); } + void SetBookName(const char *book_name) {strncpy(this->book_name, book_name, sizeof(this->book_name));} + void SetBook(const char *book) {strncpy(this->book, book, sizeof(this->book));} + void SetDevice(const char *device) {strncpy(this->device, device, sizeof(this->device));} + void SetLevel(int8 level) {this->level = level;} + void SetTier(int8 tier) {this->tier = tier;} + void SetIcon(int16 icon) {this->icon = icon;} + void SetSkill(int32 skill) {this->skill = skill;} + void SetTechnique(int32 technique) {this->technique = technique;} + void SetKnowledge(int32 knowledge) {this->knowledge = knowledge;} + void SetClasses(int32 classes) {this->classes = classes;} + void SetDevice_Sub_Type(int8 device_sub_type) {this->device_sub_type = device_sub_type;} + void SetUnknown1(int8 unknown1) {this->unknown1 = unknown1;} + void SetUnknown2(int32 unknown2) {this->unknown2 = unknown2;} + void SetUnknown3(int32 unknown3) {this->unknown3 = unknown3;} + void SetUnknown4(int32 unknown4) {this->unknown4 = unknown4;} + void SetProductID(int32 itemID) { product_item_id = itemID; } + void SetProductQuantity(int8 qty) { product_qty = qty; } + void SetProductName(const char* productName) { strncpy(product_name, productName, sizeof(product_name)); } + void SetBuild1ComponentTitle(const char* title) { strncpy(build1_comp_title, title, sizeof(build1_comp_title)); } + void SetBuild2ComponentTitle(const char* title) { strncpy(build2_comp_title, title, sizeof(build2_comp_title)); } + void SetBuild3ComponentTitle(const char* title) { strncpy(build3_comp_title, title, sizeof(build3_comp_title)); } + void SetBuild4ComponentTitle(const char* title) { strncpy(build4_comp_title, title, sizeof(build4_comp_title)); } + void SetFuelComponentTitle(const char* title) { strncpy(fuel_comp_title, title, sizeof(fuel_comp_title)); } + void SetPrimaryComponentTitle(const char* title) { strncpy(primary_build_comp_title, title, sizeof(primary_build_comp_title)); } + void SetBuild1ComponentQuantity(int8 qty) { build1_comp_qty = qty; } + void SetBuild2ComponentQuantity(int8 qty) { build2_comp_qty = qty; } + void SetBuild3ComponentQuantity(int8 qty) { build3_comp_qty = qty; } + void SetBuild4ComponentQuantity(int8 qty) { build4_comp_qty = qty; } + void SetFuelComponentQuantity(int8 qty) { fuel_comp_qty = qty; } + void SetPrimaryComponentQuantity(int8 qty) { primary_comp_qty = qty; } + + int32 GetID() {return id;} + int32 GetSoeID() { return soe_id; } + int32 GetBookID() {return book_id;} + const char * GetName() {return name;} + const char* GetDescription() { return description; } + const char * GetBookName() {return book_name;} + const char * GetBook() {return book;} + const char * GetDevice() {return device;} + int8 GetLevel() {return level;} + int8 GetTier() {return tier;} + int16 GetIcon() {return icon;} + int32 GetSkill() {return skill;} + int32 GetTechnique() {return technique;} + int32 GetKnowledge() {return knowledge;} + int32 GetClasses() {return classes;} + //class_id = classes.GetTSBaseClass(spawn->GetTradeskillClass()) bit-match on class ids 1-13 + //secondary_class_id = classes.GetSecondaryTSBaseClass(spawn->GetTradeskillClass()) bit-match on class ids 1-13 + //tertiary_class_id = spawn->GetTradeskillClass() (direct match) + bool CanUseRecipeByClass(Item* item, int8 class_id) { + /* any can use bit combination of 1+2 + adornments = 1 + artisan = 2 + */ + return item->generic_info.tradeskill_classes < 4 || (1 << class_id) & item->generic_info.tradeskill_classes; + } + int8 GetDevice_Sub_Type() {return device_sub_type;} + int8 GetUnknown1() {return unknown1;} + int32 GetUnknown2() {return unknown2;} + int32 GetUnknown3() {return unknown3;} + int32 GetUnknown4() {return unknown4;} + + int32 GetProductID() { return product_item_id; } + const char* GetProductTitle() { return product_name; } + int8 GetProductQuantity() { return product_qty; } + const char* GetPrimaryBuildComponentTitle() { return primary_build_comp_title; } + const char* GetBuild1ComponentTitle() { return build1_comp_title; } + const char* GetBuild2ComponentTitle() { return build2_comp_title; } + const char* GetBuild3ComponentTitle() { return build3_comp_title; } + const char* GetBuild4ComponentTitle() { return build4_comp_title; } + const char* GetFuelComponentTitle() { return fuel_comp_title; } + int16 GetBuild1ComponentQuantity() { return build1_comp_qty; } + int16 GetBuild2ComponentQuantity() { return build2_comp_qty; } + int16 GetBuild3ComponentQuantity() { return build3_comp_qty; } + int16 GetBuild4ComponentQuantity() { return build4_comp_qty; } + int16 GetFuelComponentQuantity() { return fuel_comp_qty; } + int16 GetPrimaryComponentQuantity() { return primary_comp_qty; } + + ///Add a build component to this recipe + ///Item id of the component + ///Slot id for this component + void AddBuildComp(int32 itemID, int8 slot, bool preferred = 0); + + // int8 = slot, vector = itemid + map > components; + + // int8 = stage, RecipeProducts = products/byproducts for this stage + map products; + + int8 GetHighestStage() { return highestStage; } + void SetHighestStage(int8 val) { highestStage = val; } + + int8 GetTotalBuildComponents(); + bool ProvidedAllRequiredComponents(Client* client, vector* player_components, vector>* player_component_pair_qty); + bool PlayerHasComponentByItemID(Client* client, vector* player_components, vector>* player_component_pair_qty, int32 item_id, int8 required_qty); + int8 GetItemRequiredQuantity(int32 item_id); +private: + int32 id; + int32 soe_id; + int32 book_id; + char name[256]; + char description[256]; + char book_name[256]; + char book[256]; + char device[30]; + int8 level; + int8 tier; + int16 icon; + int32 skill; + int32 technique; + int32 knowledge; + int8 device_sub_type; + int32 classes; + int8 unknown1; + int32 unknown2; + int32 unknown3; + int32 unknown4; + + int32 product_item_id; + char product_name[256]; + int8 product_qty; + char primary_build_comp_title[256]; + char build1_comp_title[256]; + char build2_comp_title[256]; + char build3_comp_title[256]; + char build4_comp_title[256]; + char fuel_comp_title[256]; + int16 build1_comp_qty; + int16 build2_comp_qty; + int16 build3_comp_qty; + int16 build4_comp_qty; + int16 fuel_comp_qty; + int16 primary_comp_qty; + int8 highestStage; + +}; + +class MasterRecipeList { +public: + MasterRecipeList(); + virtual ~MasterRecipeList(); + + bool AddRecipe(Recipe *recipe); + Recipe* GetRecipe(int32 recipe_id); + Recipe* GetRecipeByCRC(int32 recipe_crc); + void ClearRecipes(); + int32 Size(); + EQ2Packet* GetRecipePacket(int32 recipe_id, Client *client = 0, bool display = false, int8 packet_type = 0); + + /// Gets all the recipes for the given book name + /// Book name to get recipes for + /// A vector of all the recipes for the given book + vector GetRecipes(const char* book_name); + + /// Gets a recipe with the given name + /// The name of the recipe to get + /// Recipe* whos name matches the given name + Recipe* GetRecipeByName(const char* name); + +private: + Mutex m_recipes; + map recipes; + map recipes_crc; +}; + +class MasterRecipeBookList { +public: + MasterRecipeBookList(); + virtual ~MasterRecipeBookList(); + + bool AddRecipeBook(Recipe *recipe); + Recipe * GetRecipeBooks(int32 recipe_id); + void ClearRecipeBooks(); + int32 Size(); + +private: + Mutex m_recipeBooks; + map recipeBooks; +}; + +class PlayerRecipeList { +public: + PlayerRecipeList(); + virtual ~PlayerRecipeList(); + + bool AddRecipe(Recipe *recipe); + Recipe * GetRecipe(int32 recipe_id); + void ClearRecipes(); + bool RemoveRecipe(int32 recipe_id); + int32 Size(); + + map * GetRecipes() {return &recipes;} + +private: + map recipes; + mutable std::shared_mutex player_recipe_mutex; +}; + +class PlayerRecipeBookList { +public: + PlayerRecipeBookList(); + virtual ~PlayerRecipeBookList(); + + bool AddRecipeBook(Recipe *recipe); + bool HasRecipeBook(int32 book_id); + Recipe * GetRecipeBook(int32 recipe_id); + void ClearRecipeBooks(); + int32 Size(); + + map * GetRecipeBooks() {return &recipeBooks;} + +private: + map recipeBooks; +}; +#endif diff --git a/source/WorldServer/Recipes/RecipeDB.cpp b/source/WorldServer/Recipes/RecipeDB.cpp new file mode 100644 index 0000000..785b34f --- /dev/null +++ b/source/WorldServer/Recipes/RecipeDB.cpp @@ -0,0 +1,296 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Recipe.h" + +extern MasterRecipeList master_recipe_list; +extern MasterRecipeBookList master_recipebook_list; +extern MasterItemList master_item_list; + +void WorldDatabase::LoadRecipes() { + DatabaseResult res; + + bool status = database_new.Select(&res, + "SELECT r.`id`,r.`soe_id`,r.`level`,r.`icon`,r.`skill_level`,r.`technique`,r.`knowledge`,r.`name`,r.`description`,i.`name` as `book`,r.`bench`,ipc.`adventure_classes`, " + "r.`stage4_id`, r.`name`, r.`stage4_qty`, pcl.`name` as primary_comp_title,r.primary_comp_qty, fcl.name as `fuel_comp_title`, r.fuel_comp_qty, " + "bc.`name` AS build_comp_title, bc.qty AS build_comp_qty, bc2.`name` AS build2_comp_title, bc2.qty AS build2_comp_qty, " + "bc3.`name` AS build3_comp_title, bc3.qty AS build3_comp_qty, bc4.`name` AS build4_comp_title, bc4.qty AS build4_comp_qty,\n" + "r.stage0_id, r.stage1_id, r.stage2_id, r.stage3_id, r.stage4_id, r.stage0_qty, r.stage1_qty, r.stage2_qty, r.stage3_qty, r.stage4_qty,\n" + "r.stage0_byp_id, r.stage1_byp_id, r.stage2_byp_id, r.stage3_byp_id, r.stage4_byp_id, r.stage0_byp_qty, r.stage1_byp_qty, r.stage2_byp_qty, r.stage3_byp_qty, r.stage4_byp_qty\n" + "FROM `recipe` r\n" + "LEFT JOIN ((SELECT recipe_id, soe_recipe_crc FROM item_details_recipe_items GROUP BY soe_recipe_crc) as idri) ON idri.soe_recipe_crc = r.soe_id\n" + "LEFT JOIN items i ON idri.recipe_id = i.id\n" + "INNER JOIN items ipc ON r.stage4_id = ipc.id\n" + "INNER JOIN recipe_comp_list pcl ON r.primary_comp_list = pcl.id\n" + "INNER JOIN recipe_comp_list fcl ON r.fuel_comp_list = fcl.id\n" + "LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.`index`, rcl.`name`, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE `index` = 0) AS bc ON bc.recipe_id = r.id\n" + "LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.`index`, rcl.`name`, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE `index` = 1) AS bc2 ON bc2.recipe_id = r.id\n" + "LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.`index`, rcl.`name`, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE `index` = 2) AS bc3 ON bc3.recipe_id = r.id\n" + "LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.`index`, rcl.`name`, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE `index` = 3) AS bc4 ON bc4.recipe_id = r.id\n" + "WHERE r.bHaveAllProducts"); + + if (!status) + return; + + while (res.Next()) { + int32 i = 0; + Recipe* recipe = new Recipe(); + recipe->SetID(res.GetInt32(i++)); + recipe->SetSoeID(res.GetInt32(i++)); + recipe->SetLevel(res.GetInt32(i++)); + recipe->SetTier(recipe->GetLevel() / 10 + 1); + recipe->SetIcon(res.GetInt32(i++)); + recipe->SetSkill(res.GetInt32(i++)); + recipe->SetTechnique(res.GetInt32(i++)); + recipe->SetKnowledge(res.GetInt32(i++)); + recipe->SetName(res.GetString(i++)); + recipe->SetDescription(res.GetString(i++)); + recipe->SetBook(res.GetString(i++)); + + //Convert the device string + string device = res.GetString(i++); + int32 deviceID = 0; + int8 deviceSubType = 0; + + recipe->SetDevice(GetDeviceName(device).c_str()); + recipe->SetUnknown2(deviceID); + recipe->SetDevice_Sub_Type(deviceSubType); + recipe->SetClasses(res.GetInt64(i++)); + recipe->SetUnknown3(0); + recipe->SetUnknown4(0); + + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Loading recipe: %s (%u)", recipe->GetName(), recipe->GetID()); + + recipe->SetProductID(res.GetInt32(i++)); + recipe->SetProductName(res.GetString(i++)); + recipe->SetProductQuantity(res.GetInt8(i++)); + recipe->SetPrimaryComponentTitle(res.GetString(i++)); + recipe->SetPrimaryComponentQuantity(res.GetInt16(i++)); + recipe->SetFuelComponentTitle(res.GetString(i++)); + recipe->SetFuelComponentQuantity(res.GetInt16(i++)); + + recipe->SetBuild1ComponentTitle(res.GetString(i++)); + recipe->SetBuild1ComponentQuantity(res.GetInt16(i++)); + recipe->SetBuild2ComponentTitle(res.GetString(i++)); + recipe->SetBuild2ComponentQuantity(res.GetInt16(i++)); + recipe->SetBuild3ComponentTitle(res.GetString(i++)); + recipe->SetBuild3ComponentQuantity(res.GetInt16(i++)); + recipe->SetBuild4ComponentTitle(res.GetString(i++)); + recipe->SetBuild4ComponentQuantity(res.GetInt16(i++)); + + LogWrite(TRADESKILL__DEBUG, 7, "Recipes", "Loading recipe: %s (%u)", recipe->GetName(), recipe->GetID()); + + if (!master_recipe_list.AddRecipe(recipe)) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding recipe '%s' - duplicate ID: %u", recipe->GetName(), recipe->GetID()); + delete recipe; + continue; + } + + //Products/By-Products + for (int8 stage = 0; stage < 5; stage++) { + RecipeProducts* rp = new RecipeProducts; + rp->product_id = res.GetInt32(i); + rp->product_qty = res.GetInt8(i + 5); + rp->byproduct_id = res.GetInt32(i + 10); + rp->byproduct_qty = res.GetInt8(i + 15); + recipe->products[stage] = rp; + i++; + } + //Advance i past all the product info + //i += 15; + } + LoadRecipeComponents(); + + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "\tLoaded %u recipes", master_recipe_list.Size()); +} + +void WorldDatabase::LoadRecipeBooks(){ + Recipe *recipe; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + res = query.RunQuery2(Q_SELECT, "SELECT id, name, tradeskill_default_level FROM items WHERE item_type='Recipe'"); + if (res){ + while ((row = mysql_fetch_row(res))){ + recipe = new Recipe(); + recipe->SetBookID(atoul(row[0])); + recipe->SetBookName(row[1]); + recipe->SetLevel(atoi(row[2])); + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Loading Recipe Books: %s (%u)", recipe->GetBookName(), recipe->GetBookID()); + + if (!master_recipebook_list.AddRecipeBook(recipe)){ + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding Recipe Book '%s' - duplicate ID: %u", recipe->GetBookName(), recipe->GetBookID()); + safe_delete(recipe); + continue; + } + } + } + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "\tLoaded %u Recipe Books ", master_recipebook_list.Size()); +} + +void WorldDatabase::LoadPlayerRecipes(Player *player){ + Recipe *recipe; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int16 total = 0; + + assert(player); + + res = query.RunQuery2(Q_SELECT, "SELECT recipe_id, highest_stage FROM character_recipes WHERE char_id = %u", player->GetCharacterID()); + if (res) { + while ((row = mysql_fetch_row(res))){ + int32 recipe_id = atoul(row[0]); + Recipe* master_recipe = master_recipe_list.GetRecipe(recipe_id); + if(!master_recipe) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding recipe %u to player '%s' - duplicate ID", atoul(row[0]), player->GetName()); + continue; + } + recipe = new Recipe(master_recipe); + recipe->SetHighestStage(atoi(row[1])); + + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Loading recipe: %s (%u) for player: %s (%u)", recipe->GetName(), recipe->GetID(), player->GetName(), player->GetCharacterID()); + + if (!player->GetRecipeList()->AddRecipe(recipe)){ + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding recipe %u to player '%s' - duplicate ID", recipe->GetID(), player->GetName()); + safe_delete(recipe); + continue; + } + total++; + } + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "Loaded %u recipes for player: %s (%u)", total, player->GetName(), player->GetCharacterID()); + } +} + +int32 WorldDatabase::LoadPlayerRecipeBooks(int32 char_id, Player *player) { + assert(player); + + LogWrite(TRADESKILL__DEBUG, 0, "Recipes", "Loading Character Recipe Books for player '%s' ...", player->GetName()); + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 count = 0; + int32 old_id = 0; + int32 new_id = 0; + Recipe* recipe; + + res = query.RunQuery2(Q_SELECT, "SELECT recipebook_id FROM character_recipe_books WHERE char_id = %u", char_id); + if (res && mysql_num_rows(res) > 0) { + while (res && (row = mysql_fetch_row(res))){ + count++; + new_id = atoul(row[0]); + if(new_id == old_id) + continue; + + Item* item = master_item_list.GetItem(new_id); + if (!item) + continue; + + recipe = new Recipe(); + recipe->SetBookID(new_id); + recipe->SetBookName(item->name.c_str()); + + LogWrite(TRADESKILL__DEBUG, 5, "Recipes", "Loading Recipe Books: %s (%u)", recipe->GetBookName(), recipe->GetBookID()); + + if (!player->GetRecipeBookList()->AddRecipeBook(recipe)) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error adding player Recipe Book '%s' - duplicate ID: %u", recipe->GetBookName(), recipe->GetBookID()); + safe_delete(recipe); + continue; + } + old_id = new_id; + } + } + return count; +} + +void WorldDatabase::SavePlayerRecipeBook(Player* player, int32 recipebook_id){ + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_INSERT, "INSERT INTO character_recipe_books (char_id, recipebook_id) VALUES(%u, %u)", player->GetCharacterID(), recipebook_id); + //if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + //LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error in SavePlayerRecipeBook query '%s' : %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::SavePlayerRecipe(Player* player, int32 recipe_id) { + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_INSERT, "INSERT INTO character_recipes (char_id, recipe_id) VALUES (%u, %u)", player->GetCharacterID(), recipe_id); + //if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + //LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error in SavePlayerRecipeBook query '%s' : %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::LoadRecipeComponents() { + DatabaseResult res; + bool status = database_new.Select(&res, + "SELECT r.id, pc.item_id AS primary_comp, fc.item_id AS fuel_comp, sc.item_id as secondary_comp, rsc.`index` + 1 AS slot\n" + "FROM recipe r\n" + "INNER JOIN (select comp_list, item_id FROM recipe_comp_list_item ) as pc ON r.primary_comp_list = pc.comp_list\n" + "INNER JOIN (select comp_list, item_id FROM recipe_comp_list_item ) as fc ON r.fuel_comp_list = fc.comp_list\n" + "LEFT JOIN recipe_secondary_comp rsc ON rsc.recipe_id = r.id\n" + "LEFT JOIN (select comp_list, item_id FROM recipe_comp_list_item) as sc ON rsc.comp_list = sc.comp_list\n" + "WHERE r.bHaveAllProducts\n" + "ORDER BY r.id, rsc.`index` ASC"); + + if (!status) { + return; + } + + Recipe* recipe = 0; + int32 id = 0; + while (res.Next()) { + int32 tmp = res.GetInt32(0); + if (id != tmp) { + id = tmp; + recipe = master_recipe_list.GetRecipe(id); + + if (!recipe) { + continue; + } + + } + if (recipe && !res.IsNull(3)) { + if (find(recipe->components[0].begin(), recipe->components[0].end(), res.GetInt32(1)) == recipe->components[0].end()) + recipe->AddBuildComp(res.GetInt32(1), 0); + if (find(recipe->components[5].begin(), recipe->components[5].end(), res.GetInt32(2)) == recipe->components[5].end()) + recipe->AddBuildComp(res.GetInt32(2), 5); + if (find(recipe->components[res.GetInt8(4)].begin(), recipe->components[res.GetInt8(4)].end(), res.GetInt32(3)) == recipe->components[res.GetInt8(4)].end()) + recipe->AddBuildComp(res.GetInt32(3), res.GetInt8(4)); + } + //else + //LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading `recipe_build_comps`, Recipe ID: %u", id); + } +} + +void WorldDatabase::UpdatePlayerRecipe(Player* player, int32 recipe_id, int8 highest_stage) { + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_UPDATE, "UPDATE `character_recipes` SET `highest_stage` = %i WHERE `char_id` = %u AND `recipe_id` = %u", highest_stage, player->GetCharacterID(), recipe_id); +} + +/* +ALTER TABLE `character_recipes` + ADD COLUMN `highest_stage` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' AFTER `recipe_id`; + +*/ \ No newline at end of file diff --git a/source/WorldServer/Rules/Rules.cpp b/source/WorldServer/Rules/Rules.cpp new file mode 100644 index 0000000..066a3c2 --- /dev/null +++ b/source/WorldServer/Rules/Rules.cpp @@ -0,0 +1,532 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include +#include "../../common/debug.h" +#include "../../common/Log.h" +#include "../../common/database.h" +#include "Rules.h" + +extern RuleManager rule_manager; + +Rule::Rule() { + category = 0; + type = 0; + strncpy(value, "", sizeof(value)); + strncpy(combined, "NONE", sizeof(combined)); +} + +Rule::Rule(int32 category, int32 type, const char *value, const char *combined) { + this->category = category; + this->type = type; + strncpy(this->value, value, sizeof(this->value)); + strncpy(this->combined, combined, sizeof(this->combined)); +} + +Rule::Rule (Rule *rule_in) { + category = rule_in->GetCategory(); + type = rule_in->GetType(); + strncpy(value, rule_in->GetValue(), sizeof(value)); + strncpy(combined, rule_in->GetCombined(), sizeof(combined)); +} + +Rule::~Rule() { +} + +RuleSet::RuleSet() { + id = 0; + memset(name, 0, sizeof(name)); + m_rules.SetName("RuleSet::rules"); +} + +RuleSet::RuleSet(RuleSet *in_rule_set) { + assert(in_rule_set); + + map > * in_rules = in_rule_set->GetRules(); + map >::iterator itr; + map::iterator itr2; + Rule * rule; + + m_rules.SetName("RuleSet::rules"); + id = in_rule_set->GetID(); + strncpy(name, in_rule_set->GetName(), sizeof(name)); + for (itr = in_rules->begin(); itr != in_rules->end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + rule = itr2->second; + rules[rule->GetCategory()][rule->GetType()] = new Rule(rule); + } + } +} + +RuleSet::~RuleSet() { + ClearRules(); +} + +void RuleSet::CopyRulesInto(RuleSet *in_rule_set) { + assert(in_rule_set); + + map > * in_rules = in_rule_set->GetRules(); + map >::iterator itr; + map::iterator itr2; + Rule * rule; + + ClearRules(); + m_rules.writelock(__FUNCTION__, __LINE__); + for (itr = in_rules->begin(); itr != in_rules->end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + rule = itr2->second; + rules[rule->GetCategory()][rule->GetType()] = new Rule(rule); + } + } + m_rules.releasewritelock(__FUNCTION__, __LINE__); +} + +void RuleSet::AddRule(Rule *rule) { + int32 category, type; + + assert(rule); + + category = rule->GetCategory(); + type = rule->GetType(); + m_rules.writelock(__FUNCTION__, __LINE__); + if (rules[category].count(type) == 0) + rules[category][type] = rule; + else + rules[category][type]->SetValue(rule->GetValue()); + m_rules.releasewritelock(__FUNCTION__, __LINE__); +} + +Rule * RuleSet::GetRule(int32 category, int32 type) { + Rule *ret = 0; + + m_rules.readlock(__FUNCTION__, __LINE__); + if (rules[category].count(type) > 0) + ret = rules[category][type]; + m_rules.releasereadlock(__FUNCTION__, __LINE__); + + if (!ret) + ret = rule_manager.GetBlankRule(); + + LogWrite(RULESYS__DEBUG, 5, "Rules", "Rule: %s, Value: %s", ret->GetCombined(), ret->GetValue()); + return ret; +} + +Rule * RuleSet::GetRule(const char *category, const char *type) { + map >::iterator itr; + map::iterator itr2; + char combined[256]; + Rule *ret = 0; + + snprintf(combined, sizeof(combined), "%s:%s", category, type); + // Zero terminate ([max - 1] = 0) to prevent a warning/error + combined[255] = 0; + + m_rules.readlock(__FUNCTION__, __LINE__); + for (itr = rules.begin(); itr != rules.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + if (!strcmp(itr2->second->GetCombined(), combined)) { + ret = itr2->second; + break; + } + } + } + m_rules.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void RuleSet::ClearRules() { + map >::iterator itr; + map::iterator itr2; + + m_rules.writelock(__FUNCTION__, __LINE__); + for (itr = rules.begin(); itr != rules.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) + safe_delete(itr2->second); + } + rules.clear(); + m_rules.releasewritelock(__FUNCTION__, __LINE__); +} + +RuleManager::RuleManager() { + m_rule_sets.SetName("RuleManager::rule_sets"); + m_global_rule_set.SetName("RuleManager::global_rule_set"); + m_zone_rule_sets.SetName("RuleManager::zone_rule_sets"); + + Init(); +} + +RuleManager::~RuleManager() { + Flush(); +} + +void RuleManager::Init() +{ +#define RULE_INIT(category, type, value) rules[category][type] = new Rule(category, type, value, #category ":" #type) + + /* CLIENT */ + RULE_INIT(R_Client, ShowWelcomeScreen, "0"); + RULE_INIT(R_Client, GroupSpellsTimer, "1000"); + RULE_INIT(R_Client, QuestQueueTimer, "50"); // in milliseconds + + /* FACTION */ + RULE_INIT(R_Faction, AllowFactionBasedCombat, "1"); + + /* GUILD */ + RULE_INIT(R_Guild, MaxLevel, "50"); + RULE_INIT(R_Guild, MaxPlayers, "-1"); + + /* PLAYER */ + RULE_INIT(R_Player, MaxLevel, "50"); + RULE_INIT(R_Player, MaxLevelOverrideStatus, "100"); + RULE_INIT(R_Player, VitalityAmount, ".5"); + RULE_INIT(R_Player, VitalityFrequency, "3600"); + RULE_INIT(R_Player, XPMultiplier, "1.0"); + RULE_INIT(R_Player, TSXPMultiplier, "1.0"); + RULE_INIT(R_Player, MaxAA, "320"); + RULE_INIT(R_Player, MaxClassAA, "100"); + RULE_INIT(R_Player, MaxSubclassAA, "100"); + RULE_INIT(R_Player, MaxShadowsAA, "70"); + RULE_INIT(R_Player, MaxHeroicAA, "50"); + RULE_INIT(R_Player, MaxTradeskillAA, "40"); + RULE_INIT(R_Player, MaxPrestigeAA, "25"); + RULE_INIT(R_Player, MaxTradeskillPrestigeAA, "25"); + RULE_INIT(R_Player, MinLastNameLevel, "20"); + RULE_INIT(R_Player, MaxLastNameLength, "20"); + RULE_INIT(R_Player, MinLastNameLength, "4"); + RULE_INIT(R_Player, DisableHouseAlignmentRequirement, "1"); + RULE_INIT(R_Player, MentorItemDecayRate, ".05"); // 5% per level lost when mentoring + RULE_INIT(R_Player, TemporaryItemLogoutTime, "1800.0"); // time in seconds (double) for temporary item to decay after being logged out for a period of time, 30 min is the default + RULE_INIT(R_Player, HeirloomItemShareExpiration, "172800.0"); // 2 days ('48 hours') in seconds + RULE_INIT(R_Player, SwimmingSkillMinSpeed, "20"); + RULE_INIT(R_Player, SwimmingSkillMaxSpeed, "200"); + RULE_INIT(R_Player, SwimmingSkillMinBreathLength, "30"); + RULE_INIT(R_Player, SwimmingSkillMaxBreathLength, "1000"); + RULE_INIT(R_Player, AutoSkillUpBaseSkills, "0"); // when set to 1 we auto skill to max value on levelling up for armor,shield,class,weapon skills + RULE_INIT(R_Player, MaxWeightStrengthMultiplier, "2.0"); // multiplier for strength to add to max weight, eg 25 str * 2.0 = 50 max weight + base weight + RULE_INIT(R_Player, BaseWeight, "50"); // base weight per class, added to max weight with the strength multiplier + RULE_INIT(R_Player, WeightPercentImpact, "0.01"); // overweight in stone speed impact (.01 = 1% per 1 stone) + RULE_INIT(R_Player, WeightPercentCap, "0.95"); // cap total impact for being overweight (.95 = 95%) + RULE_INIT(R_Player, CoinWeightPerStone, "40.0"); // coin weight per stone, 40.0 = 40 coins per 1 stone (per DoF client hover over) + RULE_INIT(R_Player, WeightInflictsSpeed, "1"); // whether weight will inflict speed, 1 = on, 0 = off + RULE_INIT(R_Player, LevelMasterySkillMultiplier, "5"); // multiplier for adventure level / recommended level when applying mastery damage to determine if they are in mastery range + RULE_INIT(R_Player, TraitTieringSelection, "1"); // when set to true limited to single trait per group, otherwise you can freely select from any group + RULE_INIT(R_Player, ClassicTraitLevelTable, "1"); // uses built in table based on Prima Guide, see Traits.cpp for more, otherwise uses the levels below + RULE_INIT(R_Player, TraitFocusSelectLevel, "9"); // x levels to receive new trait of focus, eg level/rule, level 10, rule value 5, 10/5 = 2 focus traits available at level 10 + RULE_INIT(R_Player, TraitTrainingSelectLevel, "10"); // x levels to receive new trait of focus + RULE_INIT(R_Player, TraitRaceSelectLevel, "10"); // x levels to receive new trait of focus + RULE_INIT(R_Player, TraitCharacterSelectLevel, "10"); // x levels to receive new trait of focus + RULE_INIT(R_Player, StartHPBase, "40"); + RULE_INIT(R_Player, StartPowerBase, "45"); + RULE_INIT(R_Player, StartHPLevelMod, "2.0"); + RULE_INIT(R_Player, StartPowerLevelMod, "2.1"); + RULE_INIT(R_Player, AllowPlayerEquipCombat, "1"); + RULE_INIT(R_Player, MaxTargetCommandDistance, "50.0"); // max distance allowed for /target command when target name is not in group + RULE_INIT(R_Player, HarvestSkillUpMultiplier, "1.5"); /* multiplier for node to take the "min skill" max and use a multiplier to offset the max skill allowed to skill up on node. + ** Eg. 50 min skill on node, 50*1.5=75, no one with higher than 75 skill gets a skill up + */ + /* PVP */ + RULE_INIT(R_PVP, AllowPVP, "0"); + RULE_INIT(R_PVP, LevelRange, "4"); + RULE_INIT(R_PVP, InvisPlayerDiscoveryRange, "20"); // value > 0 sets radius inner to see, = 0 means always seen, -1 = never seen + RULE_INIT(R_PVP, PVPMitigationModByLevel, "25"); // gives a bonus to mitigation for PVP combat to offset the percentage level * mod (default 25) + + /* COMBAT */ + RULE_INIT(R_Combat, MaxCombatRange, "4.0"); + RULE_INIT(R_Combat, DeathExperienceDebt, "50.00"); // divide by 100, 50/100 = .5% debt per pve death + RULE_INIT(R_Combat, PVPDeathExperienceDebt, "25.00"); // divide by 100, 25/100 = .25% debt per pvp death + RULE_INIT(R_Combat, GroupExperienceDebt, "0"); // set to 1 means we will share debt between the group + RULE_INIT(R_Combat, ExperienceToDebt, "50.00"); // percentage of xp earned to debt vs obtained xp 50/100 = 50% to debt + RULE_INIT(R_Combat, ExperienceDebtRecoveryPercent, "5.00"); // recovery percentage per period of time, 5/100 = 5% recovered (so if .5% debt, .5*.05 = .025, .5-.025=.475% debt left) + RULE_INIT(R_Combat, ExperienceDebtRecoveryPeriod, "600"); // every 10 minutes (x*60 seconds) recover ExperienceDebtRecoveryPercent + RULE_INIT(R_Combat, EnableSpiritShards, "1"); + RULE_INIT(R_Combat, SpiritShardSpawnScript, "SpawnScripts/Generic/SpiritShard.lua"); + RULE_INIT(R_Combat, ShardDebtRecoveryPercent, "25.00"); // recovered percentage of debt upon obtainig shard, 25/100 means 25%. If there is .5 DeathExperienceDebt, .5*25% = .125, .5 - .125 = .375 + RULE_INIT(R_Combat, ShardRecoveryByRadius, "1"); // allow shards to auto pick up by radius, not requiring to click/right click the shard + RULE_INIT(R_Combat, ShardLifetime, "86400"); // default: 86400 seconds (one day) + RULE_INIT(R_Combat, EffectiveMitigationCapLevel, "80"); // level multiplier for max effective cap, level * 80 (default) + RULE_INIT(R_Combat, CalculatedMitigationCapLevel, "100"); // The cap to calculate your mitigation from is [level*100]. + RULE_INIT(R_Combat, MitigationLevelEffectivenessMax, "1.5"); // ratio victim level / attacker level for max effectiveness, when victim is higher level cap can reach 1.5 + RULE_INIT(R_Combat, MitigationLevelEffectivenessMin, ".5"); // ratio victim level / attacker level for min effectiveness + RULE_INIT(R_Combat, MaxMitigationAllowed, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVE + RULE_INIT(R_Combat, MaxMitigationAllowedPVP, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVP + RULE_INIT(R_Combat, StrengthNPC, "10"); // divider for strength NPC only str/x = additional dmg to low/high dmg + RULE_INIT(R_Combat, StrengthOther, "25"); // divider for strength other than NPC str/x = additional dmg to low/high dmg + RULE_INIT(R_Combat, MaxSkillBonusByLevel, "1.5"); // Level * 1.5 = max bonus skill allowed + RULE_INIT(R_Combat, LockedEncounterNoAttack, "1"); // when set to 1, players/group members not part of the encounter cannot attack until /yell + + /* SPAWN */ + RULE_INIT(R_Spawn, SpeedMultiplier, "300"); // note: this value was 1280 until 6/1/2009, then was 600 til Sep 2009, when it became 300...? + RULE_INIT(R_Spawn, ClassicRegen, "0"); + RULE_INIT(R_Spawn, HailMovementPause, "5000"); // time in milliseconds the spawn is paused on hail + RULE_INIT(R_Spawn, HailDistance, "5"); // max distance to hail a spawn/npc + RULE_INIT(R_Spawn, UseHardCodeWaterModelType, "1"); // uses alternate method of setting water type by model type (hardcoded) versus relying on just DB + RULE_INIT(R_Spawn, UseHardCodeFlyingModelType, "1"); // uses alternate method of setting flying type by model type (hardcoded) versus relying on just DB + + /* TIMER */ + + /* UI */ + RULE_INIT(R_UI, MaxWhoResults, "20"); + RULE_INIT(R_UI, MaxWhoOverrideStatus, "200"); + + /* WORLD */ + RULE_INIT(R_World, DefaultStartingZoneID, "1"); + RULE_INIT(R_World, EnablePOIDiscovery, "0"); + RULE_INIT(R_World, GamblingTokenItemID, "2"); + RULE_INIT(R_World, GuildAutoJoin, "0"); + RULE_INIT(R_World, GuildAutoJoinID, "1"); + RULE_INIT(R_World, GuildAutoJoinDefaultRankID, "7"); + RULE_INIT(R_World, MaxPlayers, "-1"); + RULE_INIT(R_World, MaxPlayersOverrideStatus, "100"); + RULE_INIT(R_World, ServerLocked, "0"); + RULE_INIT(R_World, ServerLockedOverrideStatus, "10"); + RULE_INIT(R_World, SyncZonesWithLogin, "1"); + RULE_INIT(R_World, SyncEquipWithLogin, "1"); + RULE_INIT(R_World, UseBannedIPsTable, "0"); + RULE_INIT(R_World, LinkDeadTimer, "120000"); // default: 2 minutes + RULE_INIT(R_World, RemoveDisconnectedClientsTimer, "30000"); // default: 30 seconds + RULE_INIT(R_World, PlayerCampTimer, "20"); // default: 20 seconds + RULE_INIT(R_World, GMCampTimer, "1"); // default: 1 second + RULE_INIT(R_World, AutoAdminPlayers, "0"); // default: No + RULE_INIT(R_World, AutoAdminGMs, "0"); // default: No + RULE_INIT(R_World, AutoAdminStatusValue, "10"); // default: 10 (CSR) + RULE_INIT(R_World, DuskTime, "20:00"); // default: 8pm + RULE_INIT(R_World, DawnTime, "8:00"); // default: 8am + RULE_INIT(R_World, ThreadedLoad, "0"); // default: no threaded loading + RULE_INIT(R_World, TradeskillSuccessChance, "87.0"); // default: 87% chance of success while crafting + RULE_INIT(R_World, TradeskillCritSuccessChance, "2.0"); // default: 2% chance of critical success while crafting + RULE_INIT(R_World, TradeskillFailChance, "10.0"); // default: 10% chance of failure while crafting + RULE_INIT(R_World, TradeskillCritFailChance, "1.0"); // default: 1% chance of critical failure while crafting + RULE_INIT(R_World, TradeskillEventChance, "15.0"); // default: 15% chance of a tradeskill event while crafting + RULE_INIT(R_World, EditorURL, "www.eq2emulator.net"); // default: www.eq2emulator.net + RULE_INIT(R_World, EditorIncludeID, "0"); // default: 0 (0 = disabled, 1 = enabled) + RULE_INIT(R_World, EditorOfficialServer, "0"); // default: 0 (0 = disabled, 1 = enabled) + RULE_INIT(R_World, SavePaperdollImage, "1"); // default: true + RULE_INIT(R_World, SaveHeadshotImage, "1"); // default: true + RULE_INIT(R_World, SendPaperdollImagesToLogin, "1"); // default: true + RULE_INIT(R_World, TreasureChestDisabled, "0"); // default: false + RULE_INIT(R_World, StartingZoneLanguages, "0"); // default: 0 (0 = Live Like, 1 = Starting City Based) + RULE_INIT(R_World, StartingZoneRuleFlag, "0"); // default: 0 - match any options available, just based on version/other fields (will not force qc/outpost) + // 1 - force split zones on alignment/deity despite client selection (queens colony/overlord outpost) + // 4 - send to 'new' starting zones, won't support old clients + // 8 - (isle of refuge) + RULE_INIT(R_World, EnforceRacialAlignment, "1"); + RULE_INIT(R_World, MemoryCacheZoneMaps, "0"); // 0 disables caching the zone maps in memory, too many individual/unique zones entered may cause a lot of memory build up + RULE_INIT(R_World, AutoLockEncounter, "0"); // When set to 0 we require player to attack to lock the encounter, otherwise if 1 then npc can auto lock encounter + RULE_INIT(R_World, DisplayItemTiers, "1"); // Display item tiers when set to 1, otherwise do not + RULE_INIT(R_World, LoreAndLegendAccept, "0"); // default: 0 - L&L quests accepted only through starter books. 1 - L&L quests can be started by examining bodyparts. + + //INSERT INTO `ruleset_details`(`id`, `ruleset_id`, `rule_category`, `rule_type`, `rule_value`, `description`) VALUES (NULL, '1', 'R_World', '', '', '') + + /* ZONE */ + RULE_INIT(R_Zone, MaxPlayers, "100"); + RULE_INIT(R_Zone, MinZoneLevelOverrideStatus, "1"); + RULE_INIT(R_Zone, MinZoneAccessOverrideStatus, "100"); + RULE_INIT(R_Zone, WeatherEnabled, "1"); // default: 1 (0 = disabled, 1 = enabled) + RULE_INIT(R_Zone, WeatherType, "0"); // default: 1 (0 = normal, 1 = dynamic, 2 = random, 3 = chaotic) + RULE_INIT(R_Zone, MinWeatherSeverity, "0.0"); // default: 0.0 or no weather + RULE_INIT(R_Zone, MaxWeatherSeverity, "1.0"); // default: 1.0 or hard rain (range 0.0 - 1.0, rain starts at 0.75) + RULE_INIT(R_Zone, WeatherChangeFrequency, "300"); // default: 5 minutes + RULE_INIT(R_Zone, WeatherChangePerInterval, "0.02"); // default: 0.02 (slight changes) + RULE_INIT(R_Zone, WeatherChangeChance, "20"); // default: 20% (in whole percents) + RULE_INIT(R_Zone, WeatherDynamicMaxOffset, "0.08"); // default: 0.08 - dynamic weather changes can only change this max amount + RULE_INIT(R_Zone, SpawnUpdateTimer, "50"); // default: 50ms - how often to check for spawn update sends + RULE_INIT(R_Zone, CheckAttackNPC, "2000"); // default: 2 seconds, how often to for NPCs to attack eachother + RULE_INIT(R_Zone, CheckAttackPlayer, "2000"); // default: 2 seconds, how often to check for NPCs to attack players + RULE_INIT(R_Zone, HOTime, "10.0"); // default: 10 seconds, time to complete the HO wheel before it expires + + /* ZONE TIMERS */ + RULE_INIT(R_Zone, RegenTimer, "6000"); + RULE_INIT(R_Zone, ClientSaveTimer, "60000"); + RULE_INIT(R_Zone, ShutdownDelayTimer, "120000"); + RULE_INIT(R_Zone, WeatherTimer, "60000"); // default: 1 minute + RULE_INIT(R_Zone, SpawnDeleteTimer, "30000"); // default: 30 seconds, how long a spawn pointer is held onto after being removed from the world before deleting it + RULE_INIT(R_Zone, UseMapUnderworldCoords, "1"); // use maps lowest Y coordinate to establish underworld markers + RULE_INIT(R_Zone, MapUnderworldCoordOffset, "-200.0"); // adds (or in the case of negative value subtracts) so that the underworld marker is lower when map is using its lowest Y coordinate + + RULE_INIT(R_Loot, LootRadius, "5.0"); + RULE_INIT(R_Loot, AutoDisarmChest, "1"); + RULE_INIT(R_Loot, ChestTriggerRadiusGroup, "10.0"); // radius at which chest will trigger against group members + RULE_INIT(R_Loot, ChestUnlockedTimeDrop, "1200"); // time in seconds, 20 minutes by default, triggers only if AllowChestUnlockByDropTime is 1 + RULE_INIT(R_Loot, AllowChestUnlockByDropTime, "1"); // when set to 1 we will start a countdown timer to allow anyone to loot once ChestUnlockedTimeDrop elapsed + RULE_INIT(R_Loot, ChestUnlockedTimeTrap, "600"); // time in seconds, 10 minutes by default + RULE_INIT(R_Loot, AllowChestUnlockByTrapTime, "1"); // when set to 1 we will allow unlocking the chest to all players after the trap is triggered (or chest is open) and period ChestUnlockedTimeTrap elapsed + RULE_INIT(R_Loot, SkipLootGrayMob, "1"); + RULE_INIT(R_Loot, LootDistributionTime, "120"); // time in seconds that we allow the group to determine their loot decision (lotto/need/greed/decline). + + RULE_INIT(R_Spells, NoInterruptBaseChance, "50"); + RULE_INIT(R_Spells, EnableFizzleSpells, "1"); // enables/disables the 'fizzling' of spells based on can_fizzle in the spells table. This also enables increasing specialized skills for classes based on spells/abilities. + RULE_INIT(R_Spells, DefaultFizzleChance, "10.0"); // default percentage x / 100, eg 10% is 10.0 + RULE_INIT(R_Spells, FizzleMaxSkill, "1.2"); // 1.0 is 100%, 1.2 is 120%, so you get 120% your max skill against a spell, no fizzle + RULE_INIT(R_Spells, FizzleDefaultSkill, ".2"); // offset against MaxSkill to average out to 100%, default of .2f so we don't go over the threshold if no skill + RULE_INIT(R_Spells, EnableCrossZoneGroupBuffs, "0"); // enables/disables allowing cross zone group buffs + RULE_INIT(R_Spells, EnableCrossZoneTargetBuffs, "0"); // enables/disables allowing cross zone target buffs + RULE_INIT(R_Spells, PlayerSpellSaveStateWaitInterval, "100"); // time in milliseconds we wait before performing a save when the spell save trigger is activated, allows additional actions to take place until the cap is hit + RULE_INIT(R_Spells, PlayerSpellSaveStateCap, "1000"); // sets a maximum wait time before we queue a spell state save to the DB, given a lot can go on in a short period with players especially in combat, maybe good to have this at a higher interval. + RULE_INIT(R_Spells, RequirePreviousTierScribe, "0"); // requires step up apprentice -> apprentice (handcrafted?) -> journeyman (handcrafted?) -> adept -> expert -> master + RULE_INIT(R_Spells, CureSpellID, "110003"); // Base Cure spell that was used after they removed cure types + RULE_INIT(R_Spells, CureCurseSpellID, "110004"); // Curse Spell ID in the spells database + RULE_INIT(R_Spells, CureNoxiousSpellID, "110005"); // Noxious/Poison Spell ID in the spells database + RULE_INIT(R_Spells, CureMagicSpellID, "210006"); // Magic/Elemental Spell ID in the spells database + RULE_INIT(R_Spells, CureTraumaSpellID, "0"); // Trauma/Mental Spell ID in the spells database + RULE_INIT(R_Spells, CureArcaneSpellID, "0"); // Arcane/Heat Spell ID in the spells database + RULE_INIT(R_Spells, MinistrationSkillID, "366253016"); // ministration skill id used to map power reduction rule MinistrationPowerReductionMax + RULE_INIT(R_Spells, MinistrationPowerReductionMax, "15.0"); // max percentage of power reduction for spells with ministration mastery skill (default is 15.0 for 15%) + RULE_INIT(R_Spells, MinistrationPowerReductionSkill, "25"); // divides by integer value to establish how much skill req for higher power reduction + RULE_INIT(R_Spells, MasterSkillReduceSpellResist, "25"); // divides by integer value to establish how much skill bonus for reducing spell resistance on target + + RULE_INIT(R_Expansion, GlobalExpansionFlag, "0"); + RULE_INIT(R_Expansion, GlobalHolidayFlag, "0"); + + RULE_INIT(R_World, DatabaseVersion, "0"); + + //devn00b + RULE_INIT(R_Discord, DiscordEnabled, "0"); //Enable/Disable built in discord bot. + RULE_INIT(R_Discord, DiscordWebhookURL, "None"); //Webhook url used for server -> discord messages. + RULE_INIT(R_Discord, DiscordBotToken, "None"); //Bot token used to connect to discord and provides discord -> server messages. + RULE_INIT(R_Discord, DiscordChannel, "Discord"); // in-game channel used for server -> discord messages. + RULE_INIT(R_Discord, DiscordListenChan, "0"); // Discord ChannelID used for discord->server messages. +#undef RULE_INIT +} + +void RuleManager::Flush(bool reinit) +{ + map >::iterator itr; + map::iterator itr2; + + for (itr = rules.begin(); itr != rules.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) + safe_delete(itr2->second); + } + + rules.clear(); + + ClearRuleSets(); + ClearZoneRuleSets(); + + if (reinit) + Init(); +} + +void RuleManager::LoadCodedDefaultsIntoRuleSet(RuleSet *rule_set) { + map >::iterator itr; + map::iterator itr2; + + assert(rule_set); + + for (itr = rules.begin(); itr != rules.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) + rule_set->AddRule(new Rule(itr2->second)); + } +} + +bool RuleManager::AddRuleSet(RuleSet *rule_set) { + bool ret = false; + int32 id; + + assert(rule_set); + + id = rule_set->GetID(); + m_rule_sets.writelock(__FUNCTION__, __LINE__); + if (rule_sets.count(id) == 0) { + rule_sets[id] = rule_set; + ret = true; + } + m_rule_sets.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +int32 RuleManager::GetNumRuleSets() { + int32 ret; + + m_rule_sets.readlock(__FUNCTION__, __LINE__); + ret = rule_sets.size(); + m_rule_sets.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void RuleManager::ClearRuleSets() { + map::iterator itr; + + m_rule_sets.writelock(__FUNCTION__, __LINE__); + for (itr = rule_sets.begin(); itr != rule_sets.end(); itr++) + safe_delete(itr->second); + rule_sets.clear(); + m_rule_sets.releasewritelock(__FUNCTION__, __LINE__); +} + +bool RuleManager::SetGlobalRuleSet(int32 rule_set_id) { + if (rule_sets.count(rule_set_id) == 0) + return false; + + global_rule_set.CopyRulesInto(rule_sets[rule_set_id]); + return true; +} + +Rule * RuleManager::GetGlobalRule(int32 category, int32 type) { + return global_rule_set.GetRule(category, type); +} + +Rule * RuleManager::GetGlobalRule(const char* category, const char* type) { + return global_rule_set.GetRule(category, type); +} + +bool RuleManager::SetZoneRuleSet(int32 zone_id, int32 rule_set_id) { + bool ret = true; + RuleSet *rule_set; + + m_rule_sets.readlock(__FUNCTION__, __LINE__); + if (rule_sets.count(rule_set_id) == 0) + ret = false; + + rule_set = rule_sets[rule_set_id]; + if (ret) { + m_zone_rule_sets.writelock(__FUNCTION__, __LINE__); + zone_rule_sets[zone_id] = rule_set; + m_zone_rule_sets.releasewritelock(__FUNCTION__, __LINE__); + } + m_rule_sets.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Rule * RuleManager::GetZoneRule(int32 zone_id, int32 category, int32 type) { + Rule *ret = 0; + + /* we never want to return null so MAKE SURE the rule exists. if this assertion fails then the server admin must fix the problem */ + assert(rules.count(category) > 0); + assert(rules[category].count(type) > 0); + + /* first try to get the zone rule */ + m_zone_rule_sets.readlock(__FUNCTION__, __LINE__); + if (zone_rule_sets.count(zone_id) > 0) + ret = zone_rule_sets[zone_id]->GetRule(category, type); + m_zone_rule_sets.releasereadlock(__FUNCTION__, __LINE__); + + return ret ? ret : rules[category][type]; +} + +void RuleManager::ClearZoneRuleSets() { + m_zone_rule_sets.writelock(__FUNCTION__, __LINE__); + zone_rule_sets.clear(); + m_zone_rule_sets.releasewritelock(__FUNCTION__, __LINE__); +} \ No newline at end of file diff --git a/source/WorldServer/Rules/Rules.h b/source/WorldServer/Rules/Rules.h new file mode 100644 index 0000000..6fc4542 --- /dev/null +++ b/source/WorldServer/Rules/Rules.h @@ -0,0 +1,363 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef RULES_H_ +#define RULES_H_ + +#include +#include +#include "../../common/Mutex.h" +#include "../../common/types.h" + +using namespace std; + +enum RuleCategory { + R_Client, + R_Faction, + R_Guild, + R_Player, + R_PVP, + R_Combat, + R_Spawn, + R_UI, + R_World, + R_Zone, + R_Loot, + R_Spells, + R_Expansion, + R_Discord +}; + +enum RuleType { + /* CLIENT */ + ShowWelcomeScreen, + + /* FACTION */ + AllowFactionBasedCombat, + + /* GUILD */ + /* PLAYER */ + MaxLevel, + MaxLevelOverrideStatus, + MaxPlayers, + MaxPlayersOverrideStatus, + VitalityAmount, + VitalityFrequency, + MaxAA, + MaxClassAA, + MaxSubclassAA, + MaxShadowsAA, + MaxHeroicAA, + MaxTradeskillAA, + MaxPrestigeAA, + MaxTradeskillPrestigeAA, + MaxDragonAA, + MinLastNameLevel, + MaxLastNameLength, + MinLastNameLength, + DisableHouseAlignmentRequirement, + MentorItemDecayRate, + TemporaryItemLogoutTime, + HeirloomItemShareExpiration, + SwimmingSkillMinSpeed, + SwimmingSkillMaxSpeed, + SwimmingSkillMinBreathLength, + SwimmingSkillMaxBreathLength, + AutoSkillUpBaseSkills, + MaxWeightStrengthMultiplier, + BaseWeight, + WeightPercentImpact, + WeightPercentCap, + CoinWeightPerStone, + WeightInflictsSpeed, + LevelMasterySkillMultiplier, + TraitTieringSelection, + ClassicTraitLevelTable, + TraitFocusSelectLevel, + TraitTrainingSelectLevel, + TraitRaceSelectLevel, + TraitCharacterSelectLevel, + StartHPBase, + StartPowerBase, + StartHPLevelMod, + StartPowerLevelMod, + AllowPlayerEquipCombat, + MaxTargetCommandDistance, + HarvestSkillUpMultiplier, + + /* PVP */ + AllowPVP, + LevelRange, + InvisPlayerDiscoveryRange, + PVPMitigationModByLevel, + + /* COMBAT */ + MaxCombatRange, + DeathExperienceDebt, + GroupExperienceDebt, + PVPDeathExperienceDebt, + ExperienceToDebt, + ExperienceDebtRecoveryPercent, + ExperienceDebtRecoveryPeriod, + EnableSpiritShards, + SpiritShardSpawnScript, + ShardDebtRecoveryPercent, + ShardRecoveryByRadius, + ShardLifetime, + EffectiveMitigationCapLevel, + CalculatedMitigationCapLevel, + MitigationLevelEffectivenessMax, + MitigationLevelEffectivenessMin, + MaxMitigationAllowed, + MaxMitigationAllowedPVP, + StrengthNPC, + StrengthOther, + MaxSkillBonusByLevel, + LockedEncounterNoAttack, + + /* SPAWN */ + SpeedMultiplier, + ClassicRegen, + HailMovementPause, + HailDistance, + UseHardCodeWaterModelType, + UseHardCodeFlyingModelType, + //SpeedRatio, + + /* UI */ + MaxWhoResults, + MaxWhoOverrideStatus, + + /* WORLD */ + DefaultStartingZoneID, + EnablePOIDiscovery, + GamblingTokenItemID, + GuildAutoJoin, + GuildAutoJoinID, + GuildAutoJoinDefaultRankID, + ServerLocked, + ServerLockedOverrideStatus, + SyncZonesWithLogin, + SyncEquipWithLogin, + UseBannedIPsTable, + LinkDeadTimer, + RemoveDisconnectedClientsTimer, + PlayerCampTimer, + GMCampTimer, + AutoAdminPlayers, + AutoAdminGMs, + AutoAdminStatusValue, + DuskTime, + DawnTime, + ThreadedLoad, + TradeskillSuccessChance, + TradeskillCritSuccessChance, + TradeskillFailChance, + TradeskillCritFailChance, + TradeskillEventChance, + EditorURL, + EditorIncludeID, + EditorOfficialServer, + GroupSpellsTimer, + QuestQueueTimer, + SavePaperdollImage, + SaveHeadshotImage, + SendPaperdollImagesToLogin, + TreasureChestDisabled, + StartingZoneLanguages, + StartingZoneRuleFlag, + EnforceRacialAlignment, + MemoryCacheZoneMaps, + AutoLockEncounter, + DisplayItemTiers, + LoreAndLegendAccept, + + /* ZONE */ + MinZoneLevelOverrideStatus, + MinZoneAccessOverrideStatus, + XPMultiplier, + TSXPMultiplier, + WeatherEnabled, + WeatherType, + MinWeatherSeverity, + MaxWeatherSeverity, + WeatherChangeFrequency, + WeatherChangePerInterval, + WeatherDynamicMaxOffset, + WeatherChangeChance, + SpawnUpdateTimer, + CheckAttackPlayer, + CheckAttackNPC, + HOTime, + UseMapUnderworldCoords, + MapUnderworldCoordOffset, + + /* LOOT */ + LootRadius, + AutoDisarmChest, // if enabled disarm only works if you right click and disarm, clicking and opening chest won't attempt auto disarm + ChestTriggerRadiusGroup, + ChestUnlockedTimeDrop, + AllowChestUnlockByDropTime, + ChestUnlockedTimeTrap, + AllowChestUnlockByTrapTime, + + /* SPELLS */ + NoInterruptBaseChance, + EnableFizzleSpells, + DefaultFizzleChance, + FizzleMaxSkill, + FizzleDefaultSkill, + EnableCrossZoneGroupBuffs, + EnableCrossZoneTargetBuffs, + PlayerSpellSaveStateWaitInterval, + PlayerSpellSaveStateCap, + RequirePreviousTierScribe, + CureSpellID, + CureCurseSpellID, + CureNoxiousSpellID, + CureMagicSpellID, + CureTraumaSpellID, + CureArcaneSpellID, + MinistrationSkillID, + MinistrationPowerReductionMax, + MinistrationPowerReductionSkill, + MasterSkillReduceSpellResist, + + /* ZONE TIMERS */ + RegenTimer, + ClientSaveTimer, + ShutdownDelayTimer, + WeatherTimer, + SpawnDeleteTimer, + + GlobalExpansionFlag, + GlobalHolidayFlag, + + DatabaseVersion, + + SkipLootGrayMob, + LootDistributionTime, + DiscordEnabled, + DiscordWebhookURL, + DiscordBotToken, + DiscordChannel, + DiscordListenChan +}; + +class Rule { +public: + Rule(); + Rule(int32 category, int32 type, const char *value, const char *combined); + Rule (Rule *rule_in); + virtual ~Rule(); + + void SetValue(const char *value) {strncpy(this->value, value, sizeof(this->value));} + + int32 GetCategory() {return category;} + int32 GetType() {return type;} + const char * GetValue() {return value;} + const char * GetCombined() {return combined;} + + int8 GetInt8() {return (int8)atoul(value);} + int16 GetInt16() {return (int16)atoul(value);} + int32 GetInt32() {return (int32)atoul(value);} + int64 GetInt64() {return (int64)atoi64(value);} + sint8 GetSInt8() {return (sint8)atoi(value);} + sint16 GetSInt16() {return (sint16)atoi(value);} + sint32 GetSInt32() {return (sint32)atoi(value);} + sint64 GetSInt64() {return (sint64)atoi64(value);} + bool GetBool() {return atoul(value) > 0 ? true : false;} + float GetFloat() {return atof(value);} + char GetChar() {return value[0];} + const char * GetString() {return value;} + +private: + int32 category; + int32 type; + char value[1024]; + char combined[2048]; +}; + +class RuleSet { +public: + RuleSet(); + RuleSet(RuleSet *in_rule_set); + virtual ~RuleSet(); + + void CopyRulesInto(RuleSet *in_rule_set); + + void SetID(int32 id) {this->id = id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + + int32 GetID() {return id;} + const char *GetName() {return name;} + + void AddRule(Rule *rule); + Rule * GetRule(int32 category, int32 type); + Rule * GetRule(const char *category, const char *type); + void ClearRules(); + + map > * GetRules() {return &rules;} + +private: + int32 id; + char name[64]; + Mutex m_rules; + map > rules; +}; + +class RuleManager { +public: + RuleManager(); + virtual ~RuleManager(); + + void Init(); + void Flush(bool reinit=false); + + void LoadCodedDefaultsIntoRuleSet(RuleSet *rule_set); + + bool AddRuleSet(RuleSet *rule_set); + int32 GetNumRuleSets(); + void ClearRuleSets(); + + Rule * GetBlankRule() {return &blank_rule;} + + bool SetGlobalRuleSet(int32 rule_set_id); + Rule * GetGlobalRule(int32 category, int32 type); + Rule * GetGlobalRule(const char* category, const char* type); + + bool SetZoneRuleSet(int32 zone_id, int32 rule_set_id); + Rule * GetZoneRule(int32 zone_id, int32 category, int32 type); + void ClearZoneRuleSets(); + + RuleSet * GetGlobalRuleSet() {return &global_rule_set;} + map > * GetRules() {return &rules;} + +private: + Mutex m_rule_sets; + Mutex m_global_rule_set; + Mutex m_zone_rule_sets; + Rule blank_rule; /* READ ONLY */ + map > rules; /* all of the rules loaded with their defaults (FROM THE CODE). map> */ + map rule_sets; /* all of the possible rule sets from the database. map */ + RuleSet global_rule_set; /* the global rule set, first fill it the defaults from the code, then over ride from the database */ + map zone_rule_sets; /* references to a zone's rule set. map */ +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Rules/RulesDB.cpp b/source/WorldServer/Rules/RulesDB.cpp new file mode 100644 index 0000000..c5c57f7 --- /dev/null +++ b/source/WorldServer/Rules/RulesDB.cpp @@ -0,0 +1,109 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" + +extern RuleManager rule_manager; + +void WorldDatabase::LoadGlobalRuleSet() { + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + int32 rule_set_id = 0; + + res = query.RunQuery2(Q_SELECT, "SELECT `variable_value`\n" + "FROM `variables`\n" + "WHERE `variable_name`='default_ruleset_id'"); + if (res && (row = mysql_fetch_row(res))) + { + rule_set_id = atoul(row[0]); + LogWrite(RULESYS__DEBUG, 5, "Rules", "\t\tLoading Global Ruleset id %i", rule_set_id); + } + + if (rule_set_id > 0 && !rule_manager.SetGlobalRuleSet(rule_set_id)) + LogWrite(RULESYS__ERROR, 0, "Rules", "Error loading global rule set. A rule set with ID %u does not exist.", rule_set_id); + else if(rule_set_id == 0) + LogWrite(RULESYS__ERROR, 0, "Rules", "Variables table is missing default_ruleset_id variable name, this means the global rules will be code-default, database entries not used. Use query such as \"insert into variables set variable_name='default_ruleset_id',variable_value='1',comment='Default ruleset';\" to resolve."); + +} + +void WorldDatabase::LoadRuleSets(bool reload) { + RuleSet *rule_set; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + if (reload) + rule_manager.Flush(true); + + /* first load the coded defaults in */ + rule_manager.LoadCodedDefaultsIntoRuleSet(rule_manager.GetGlobalRuleSet()); + + res = query.RunQuery2(Q_SELECT, "SELECT `ruleset_id`,`ruleset_name`\n" + "FROM `rulesets`\n" + "WHERE `ruleset_active`>0"); + if (res) { + while ((row = mysql_fetch_row(res))) { + rule_set = new RuleSet(); + rule_set->SetID(atoul(row[0])); + rule_set->SetName(row[1]); + if (rule_manager.AddRuleSet(rule_set)) + { + LogWrite(RULESYS__DEBUG, 5, "Rules", "\t\tLoading rule set '%s' (%u)", rule_set->GetName(), rule_set->GetID()); + LoadRuleSetDetails(rule_set); + } + else { + LogWrite(RULESYS__ERROR, 0, "Rules", "Unable to add rule set '%s'. A ruleset with ID %u already exists.", rule_set->GetName(), rule_set->GetID()); + safe_delete(rule_set); + } + } + } + + LogWrite(RULESYS__DEBUG, 3, "Rules", "--Loaded %u Rule Sets", rule_manager.GetNumRuleSets()); + + LoadGlobalRuleSet(); +} + +void WorldDatabase::LoadRuleSetDetails(RuleSet *rule_set) { + Rule *rule; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + assert(rule_set); + + rule_set->CopyRulesInto(rule_manager.GetGlobalRuleSet()); + res = query.RunQuery2(Q_SELECT, "SELECT `rule_category`,`rule_type`,`rule_value`\n" + "FROM `ruleset_details`\n" + "WHERE `ruleset_id`=%u", + rule_set->GetID()); + if (res) { + while ((row = mysql_fetch_row(res))) { + if (!(rule = rule_set->GetRule(row[0], row[1]))) { + LogWrite(RULESYS__WARNING, 0, "Rules", "Unknown rule with category '%s' and type '%s'", row[0], row[1]); + continue; + } + LogWrite(RULESYS__DEBUG, 5, "Rules", "---Setting rule category '%s', type '%s' to value: %s", row[0], row[1], row[2]); + rule->SetValue(row[2]); + } + } +} diff --git a/source/WorldServer/Sign.cpp b/source/WorldServer/Sign.cpp new file mode 100644 index 0000000..66b7ec1 --- /dev/null +++ b/source/WorldServer/Sign.cpp @@ -0,0 +1,319 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ +#include "Sign.h" + +#include "../common/ConfigReader.h" +#include "WorldDatabase.h" +#include "World.h" +#include "../common/Log.h" + +extern World world; +extern ConfigReader configReader; +extern WorldDatabase database; +extern ZoneList zone_list; +extern MasterSpellList master_spell_list; + +Sign::Sign(){ + widget_id = 0; + widget_x = 0; + widget_y = 0; + widget_z = 0; + appearance.pos.state = 1; + appearance.difficulty = 0; + spawn_type = 2; + appearance.activity_status = 64; + sign_type = 0; + zone_x = 0; + zone_y = 0; + zone_z = 0; + zone_heading = 0; + sign_distance = 0; + include_location = false; + include_heading = false; + zone_id = 0; + language = 0; +} + +Sign::~Sign(){ + +} + +int32 Sign::GetWidgetID(){ + return widget_id; +} + +EQ2Packet* Sign::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +void Sign::SetWidgetID(int32 val){ + widget_id = val; +} + +void Sign::SetWidgetX(float val){ + widget_x = val; +} + +float Sign::GetWidgetX(){ + return widget_x; +} + +void Sign::SetWidgetY(float val){ + widget_y = val; +} + +float Sign::GetWidgetY(){ + return widget_y; +} + +void Sign::SetWidgetZ(float val){ + widget_z = val; +} + +float Sign::GetWidgetZ(){ + return widget_z; +} + +void Sign::SetSignIcon(int8 val){ + appearance.icon = val; +} + +void Sign::SetIncludeLocation(bool val){ + include_location = val; +} +bool Sign::GetIncludeLocation(){ + return include_location; +} +void Sign::SetIncludeHeading(bool val){ + include_heading = val; +} +bool Sign::GetIncludeHeading(){ + return include_heading; +} + +Sign* Sign::Copy(){ + Sign* new_spawn = new Sign(); + if(GetSizeOffset() > 0){ + int8 offset = GetSizeOffset()+1; + sint32 tmp_size = size + (rand()%offset - rand()%offset); + if(tmp_size < 0) + tmp_size = 1; + else if(tmp_size >= 0xFFFF) + tmp_size = 0xFFFF; + new_spawn->size = (int16)tmp_size; + } + else + new_spawn->size = size; + new_spawn->SetCollector(IsCollector()); + new_spawn->SetMerchantID(merchant_id); + new_spawn->SetMerchantType(merchant_type); + new_spawn->SetMerchantLevelRange(GetMerchantMinLevel(), GetMerchantMaxLevel()); + new_spawn->SetPrimaryCommands(&primary_command_list); + new_spawn->primary_command_list_id = primary_command_list_id; + new_spawn->SetSecondaryCommands(&secondary_command_list); + new_spawn->secondary_command_list_id = secondary_command_list_id; + new_spawn->database_id = database_id; + memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData)); + new_spawn->SetWidgetID(widget_id); + new_spawn->SetWidgetX(widget_x); + new_spawn->SetWidgetY(widget_y); + new_spawn->SetWidgetZ(widget_z); + new_spawn->SetSignType(sign_type); + new_spawn->SetSignZoneX(zone_x); + new_spawn->SetSignZoneY(zone_y); + new_spawn->SetSignZoneZ(zone_z); + new_spawn->SetSignZoneHeading(zone_heading); + new_spawn->SetSignZoneID(GetSignZoneID()); + new_spawn->SetSignTitle(GetSignTitle()); + new_spawn->SetSignDescription(GetSignDescription()); + new_spawn->SetSignDistance(sign_distance); + new_spawn->SetIncludeHeading(include_heading); + new_spawn->SetIncludeLocation(include_location); + new_spawn->SetTransporterID(GetTransporterID()); + new_spawn->SetSoundsDisabled(IsSoundsDisabled()); + new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); + new_spawn->SetLootTier(GetLootTier()); + new_spawn->SetLootDropType(GetLootDropType()); + new_spawn->SetLanguage(GetLanguage()); + return new_spawn; +} + +int32 Sign::GetSignZoneID(){ + return zone_id; +} + +void Sign::SetSignZoneID(int32 val){ + zone_id = val; +} + +const char* Sign::GetSignTitle(){ + if(title.length() > 0) + return title.c_str(); + else + return 0; +} + +void Sign::SetSignTitle(const char* val){ + if(val) + title = string(val); +} + +const char* Sign::GetSignDescription(){ + if(description.length() > 0) + return description.c_str(); + else + return 0; +} + +void Sign::SetSignDescription(const char* val){ + if(val) + description = string(val); +} + +int8 Sign::GetSignType(){ + return sign_type; +} + +void Sign::SetSignType(int8 val){ + sign_type = val; +} + +float Sign::GetSignZoneX(){ + return zone_x; +} +void Sign::SetSignZoneX(float val){ + zone_x = val; +} +float Sign::GetSignZoneY(){ + return zone_y; +} +void Sign::SetSignZoneY(float val){ + zone_y = val; +} +float Sign::GetSignZoneZ(){ + return zone_z; +} +void Sign::SetSignZoneZ(float val){ + zone_z = val; +} +float Sign::GetSignZoneHeading(){ + return zone_heading; +} +void Sign::SetSignZoneHeading(float val){ + zone_heading = val; +} +float Sign::GetSignDistance(){ + return sign_distance; +} +void Sign::SetSignDistance(float val){ + sign_distance = val; +} +void Sign::HandleUse(Client* client, string command) +{ + vector destinations; + + //The following check disables the use of doors and other widgets if the player does not meet the quest requirements + //If this is from a script ignore this check (client will be null) + if (client) { + bool meets_quest_reqs = MeetsSpawnAccessRequirements(client->GetPlayer()); + if (!meets_quest_reqs && (GetQuestsRequiredOverride() & 2) == 0) + return; + else if (meets_quest_reqs && appearance.show_command_icon != 1) + return; + } + + if( GetTransporterID() > 0 ) + GetZone()->GetTransporters(&destinations, client, GetTransporterID()); + + if( destinations.size() ) + { + client->SetTemporaryTransportID(0); + client->ProcessTeleport(this, &destinations, GetTransporterID()); + } + else if( sign_type == SIGN_TYPE_ZONE && GetSignZoneID() > 0 ) + { + if( GetSignDistance() == 0 || client->GetPlayer()->GetDistance(this) <= GetSignDistance() ) + { + string name = database.GetZoneName(GetSignZoneID()); + if( name.length() >0 ) + { + if( !client->CheckZoneAccess(name.c_str()) ) + return; + + // determine if the coordinates should be set (returns false if they should) + // clearer, if the sign has x,y,z,heading coordinates, use them otherwise I assume we use the zones safe coords(?) + bool zone_coords_invalid = ( zone_x == 0 && zone_y == 0 && zone_z == 0 && zone_heading == 0 ); + + // I really hate double-negatives. Seriously? + if ( !zone_coords_invalid ) + { + LogWrite(SIGN__DEBUG, 0, "Sign", "Sign has valid zone-to coordinates (%.2f, %.2f, %.2f, %.2f)", zone_x, zone_y, zone_z, zone_heading); + client->GetPlayer()->SetX(zone_x); + client->GetPlayer()->SetY(zone_y); + client->GetPlayer()->SetZ(zone_z); + client->GetPlayer()->SetHeading(zone_heading); + } + else // alert client we couldnt set the coordinates + { + LogWrite(SIGN__WARNING, 0, "Sign", "Sign has no zone-to coordinates set, using zones safe coords."); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid zone in coords, taking you to a safe point."); + } + + // Test if where we're going is an Instanced zone + if ( !client->TryZoneInstance(GetSignZoneID(), zone_coords_invalid) ) + { + LogWrite(SIGN__DEBUG, 0, "Sign", "Sending client to instance of zone_id: %u", GetSignZoneID()); + client->Zone(name.c_str(), zone_coords_invalid); + } + } + else + { + LogWrite(SIGN__WARNING, 0, "Sign", "Unable to find zone with ID: %u", GetSignZoneID()); + client->Message(CHANNEL_COLOR_YELLOW, "Unable to find zone with ID: %u", GetSignZoneID()); + } + } + else + { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are too far away!"); + } + } + + else if (client && command.length() > 0) + { + EntityCommand* entity_command = FindEntityCommand(command); + + + //devn00b: Add support for marking objects + if (entity_command && strcmp(entity_command->command.c_str(), "mark") == 0) { + LogWrite(SIGN__DEBUG, 0, "Sign", "ActivateMarkReqested Sign - Command: '%s' (Should read mark)", entity_command->command.c_str()); + int32 char_id = client->GetCharacterID(); + database.SaveSignMark(char_id, GetWidgetID(), database.GetCharacterName(char_id), client); + return; + } + + if (entity_command) + { + LogWrite(SIGN__DEBUG, 0, "Sign", "ActivateQuestRequired Sign - Command: '%s'", entity_command->command.c_str()); + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + } + + } + +} diff --git a/source/WorldServer/Sign.h b/source/WorldServer/Sign.h new file mode 100644 index 0000000..d202bf5 --- /dev/null +++ b/source/WorldServer/Sign.h @@ -0,0 +1,91 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#ifndef __EQ2_SIGN__ +#define __EQ2_SIGN__ +#include "Spawn.h" + +#define SIGN_TYPE_GENERIC 0 +#define SIGN_TYPE_ZONE 1 + +using namespace std; + +class Sign : public Spawn{ +public: + Sign(); + virtual ~Sign(); + bool IsSign(){ return true; } + int32 GetWidgetID(); + void SetWidgetID(int32 val); + void SetWidgetX(float val); + float GetWidgetX(); + void SetWidgetY(float val); + float GetWidgetY(); + void SetWidgetZ(float val); + float GetWidgetZ(); + void SetSignIcon(int8 val); + Sign* Copy(); + EQ2Packet* serialize(Player *player, int16 version); + void HandleUse(Client* client, string command); + int8 GetSignType(); + void SetSignType(int8 val); + float GetSignZoneX(); + void SetSignZoneX(float val); + float GetSignZoneY(); + void SetSignZoneY(float val); + float GetSignZoneZ(); + void SetSignZoneZ(float val); + float GetSignZoneHeading(); + void SetSignZoneHeading(float val); + float GetSignDistance(); + void SetSignDistance(float val); + int32 GetSignZoneID(); + void SetSignZoneID(int32 val); + const char* GetSignTitle(); + void SetSignTitle(const char* val); + const char* GetSignDescription(); + void SetSignDescription(const char* val); + void SetIncludeLocation(bool val); + bool GetIncludeLocation(); + void SetIncludeHeading(bool val); + bool GetIncludeHeading(); + void SetLanguage(int8 in_language) { language = in_language; } + int8 GetLanguage() { return language; } + +private: + string description; + string title; + int8 sign_type; + float widget_x; + float widget_y; + float widget_z; + int32 widget_id; + float zone_x; + float zone_y; + float zone_z; + float zone_heading; + int32 zone_id; + float sign_distance; + bool include_location; + bool include_heading; + int8 language; +}; + +#endif diff --git a/source/WorldServer/Skills.cpp b/source/WorldServer/Skills.cpp new file mode 100644 index 0000000..7bffcf9 --- /dev/null +++ b/source/WorldServer/Skills.cpp @@ -0,0 +1,591 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Skills.h" +#include "Spawn.h" +#include "LuaInterface.h" +#include "../common/Log.h" + +extern ConfigReader configReader; +extern LuaInterface* lua_interface; + +MasterSkillList::MasterSkillList(){ +} + +MasterSkillList::~MasterSkillList(){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + safe_delete(itr->second); + } + map::iterator itr2; + for(itr2 = populate_packets.begin(); itr2 != populate_packets.end(); itr2++){ + safe_delete(itr2->second); + } +} + +Skill::Skill(){ + skill_id = 0; + current_val = 0; + previous_val = 0; + max_val = 0; + skill_type = 0; + display = 0; + save_needed = false; + active_skill = true; +} + +Skill::Skill(Skill* skill){ + skill_id = skill->skill_id; + current_val = skill->current_val; + previous_val = skill->current_val; + max_val = skill->max_val; + skill_type = skill->skill_type; + display = skill->display; + short_name = skill->short_name; + name = skill->name; + description = skill->description; + save_needed = false; + active_skill = true; +} + +map* MasterSkillList::GetAllSkills(){ + return &skills; +} + +Skill* MasterSkillList::GetSkill(int32 skill_id){ + if(skills.count(skill_id) > 0) + return skills[skill_id]; + else + return 0; +} + +Skill* MasterSkillList::GetSkillByName(const char* skill_name) { + Skill* skill = 0; + map::iterator itr; + for (itr = skills.begin(); itr != skills.end(); itr++) { + Skill* current_skill = itr->second; + if (::ToLower(string(current_skill->name.data.c_str())) == ::ToLower(string(skill_name))) { + skill = current_skill; + break; + } + } + return skill; +} + +int16 MasterSkillList::GetSkillCount(){ + return skills.size(); +} + +void MasterSkillList::AddSkill(Skill* skill){ + if(skill) + skills[skill->skill_id] = skill; +} + +EQ2Packet* MasterSkillList::GetPopulateSkillsPacket(int16 version){ + EQ2Packet* ret = 0; + int16 packet_version = configReader.GetStructVersion("WS_SkillMap", version); + if(populate_packets.count(packet_version) > 0) + ret = populate_packets[packet_version]; + else{ + PacketStruct* packet = configReader.getStruct("WS_SkillMap", packet_version); + int32 count = skills.size(); + Skill* skill = 0; + int32 i = 0; + packet->setArrayLengthByName("skill_count", count); + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++, i++){ + skill = itr->second; + packet->setArrayDataByName("skill_id", skill->skill_id, i); + packet->setArrayDataByName("short_name", &skill->short_name, i); + packet->setArrayDataByName("name", &skill->name, i); + packet->setArrayDataByName("description", &skill->description, i); + } + populate_packets[packet_version] = packet->serialize(); + safe_delete(packet); + ret = populate_packets[packet_version]; + } + if(ret) + return ret->Copy(); //need to return a copy as the packet is deleted after the client confirms it and we want to keep the packet to prevent constant generation of the same data + else + return ret; +} + +PlayerSkillList::PlayerSkillList(){ + xor_packet = 0; + orig_packet = 0; + packet_count = 0; + has_updates = false; + MSkillUpdates.SetName("PlayerSkillList::MSkillUpdates"); +} + +PlayerSkillList::~PlayerSkillList(){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + safe_delete(itr->second); + } + MutexMap::iterator sb_itr = skill_bonus_list.begin(); + while (sb_itr.Next()) + RemoveSkillBonus(sb_itr.first); + safe_delete_array(xor_packet); + safe_delete_array(orig_packet); +} + +void PlayerSkillList::AddSkill(Skill* new_skill){ + std::unique_lock lock(MPlayerSkills); + Skill* tmpSkill = nullptr; + if(skills.count(new_skill->skill_id)) { + tmpSkill = skills[new_skill->skill_id]; + } + skills[new_skill->skill_id] = new_skill; + if(tmpSkill) { + lua_interface->SetLuaUserDataStale(tmpSkill); + safe_delete(tmpSkill); + } + name_skill_map.clear(); +} + +void PlayerSkillList::RemoveSkill(Skill* skill) { + std::unique_lock lock(MPlayerSkills); + if (skill) { + lua_interface->SetLuaUserDataStale(skill); + skill->active_skill = false; + name_skill_map.clear(); + } +} + +map* PlayerSkillList::GetAllSkills(){ + return &skills; +} + +void PlayerSkillList::SetSkillValuesByType(int8 type, int16 value, bool send_update) { + map::iterator itr; + for (itr = skills.begin(); itr != skills.end(); itr++) { + if (itr->second && itr->second->skill_type == type) + SetSkill(itr->second, value, send_update); + } +} + +void PlayerSkillList::SetSkillCapsByType(int8 type, int16 value){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + if(itr->second && itr->second->skill_type == type) + SetSkillCap(itr->second, value); + } +} + +void PlayerSkillList::IncreaseSkillCapsByType(int8 type, int16 value){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + if(itr->second && itr->second->skill_type == type) + IncreaseSkillCap(itr->second, value); + } +} + +void PlayerSkillList::IncreaseAllSkillCaps(int16 value){ + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + IncreaseSkillCap(itr->second, value); + } +} + +bool PlayerSkillList::HasSkill(int32 skill_id){ + std::shared_lock lock(MPlayerSkills); + return (skills.count(skill_id) > 0 && skills[skill_id]->active_skill); +} + +Skill* PlayerSkillList::GetSkill(int32 skill_id){ + std::shared_lock lock(MPlayerSkills); + if(skills.count(skill_id) > 0 && skills[skill_id]->active_skill) + return skills[skill_id]; + else + return 0; +} + +void PlayerSkillList::IncreaseSkill(Skill* skill, int16 amount){ + if(skill){ + skill->previous_val = skill->current_val; + skill->current_val += amount; + if(skill->current_val > skill->max_val) + skill->max_val = skill->current_val; + AddSkillUpdateNeeded(skill); + skill->save_needed = true; + } +} + +void PlayerSkillList::IncreaseSkill(int32 skill_id, int16 amount){ + IncreaseSkill(GetSkill(skill_id), amount); +} + +void PlayerSkillList::DecreaseSkill(Skill* skill, int16 amount){ + if(skill){ + skill->previous_val = skill->current_val; + if((skill->current_val - amount) < 0) + skill->current_val = 0; + else + skill->current_val -= amount; + skill->save_needed = true; + AddSkillUpdateNeeded(skill); + } +} + +void PlayerSkillList::DecreaseSkill(int32 skill_id, int16 amount){ + DecreaseSkill(GetSkill(skill_id), amount); +} + +void PlayerSkillList::SetSkill(Skill* skill, int16 value, bool send_update){ + if(skill){ + skill->previous_val = skill->current_val; + skill->current_val = value; + if(skill->current_val > skill->max_val) + skill->max_val = skill->current_val; + skill->save_needed = true; + if(send_update) + AddSkillUpdateNeeded(skill); + } +} + +void PlayerSkillList::SetSkill(int32 skill_id, int16 value, bool send_update){ + SetSkill(GetSkill(skill_id), value, send_update); +} + +void PlayerSkillList::IncreaseSkillCap(Skill* skill, int16 amount){ + if(skill){ + skill->max_val += amount; + skill->save_needed = true; + } +} + +void PlayerSkillList::IncreaseSkillCap(int32 skill_id, int16 amount){ + IncreaseSkillCap(GetSkill(skill_id), amount); +} + +void PlayerSkillList::DecreaseSkillCap(Skill* skill, int16 amount){ + if(skill){ + if((skill->max_val - amount) < 0) + skill->max_val = 0; + else + skill->max_val -= amount; + if(skill->current_val > skill->max_val){ + skill->previous_val = skill->current_val; + skill->current_val = skill->max_val; + } + AddSkillUpdateNeeded(skill); + skill->save_needed = true; + } +} + +void PlayerSkillList::DecreaseSkillCap(int32 skill_id, int16 amount){ + DecreaseSkillCap(GetSkill(skill_id), amount); +} + +void PlayerSkillList::SetSkillCap(Skill* skill, int16 value){ + if(skill){ + skill->max_val = value; + if(skill->current_val > skill->max_val){ + skill->previous_val = skill->current_val; + skill->current_val = skill->max_val; + } + AddSkillUpdateNeeded(skill); + skill->save_needed = true; + } +} + +void PlayerSkillList::SetSkillCap(int32 skill_id, int16 value){ + SetSkillCap(GetSkill(skill_id), value); +} + +int16 PlayerSkillList::CalculateSkillValue(int32 skill_id, int16 current_val){ + if (current_val > 5) { + int16 new_val = current_val; + MutexMap::iterator itr = skill_bonus_list.begin(); + while (itr.Next()) { + SkillBonus* sb = itr.second; + map::iterator sbv_itr; + for (sbv_itr = sb->skills.begin(); sbv_itr != sb->skills.end(); sbv_itr++) { + SkillBonusValue* sbv = sbv_itr->second; + if (sbv->skill_id == skill_id) + new_val += (int16)sbv->value; + } + } + return new_val; + } + + return current_val; +} + +int16 PlayerSkillList::CalculateSkillMaxValue(int32 skill_id, int16 max_val) { + int16 new_val = max_val; + MutexMap::iterator itr = skill_bonus_list.begin(); + while (itr.Next()) { + SkillBonus* sb = itr->second; + map::iterator sbv_itr; + for (sbv_itr = sb->skills.begin(); sbv_itr != sb->skills.end(); sbv_itr++) { + SkillBonusValue* sbv = sbv_itr->second; + if (sbv->skill_id == skill_id) + new_val += (int16)sbv->value; + } + } + return new_val; +} + +EQ2Packet* PlayerSkillList::GetSkillPacket(int16 version){ + std::unique_lock lock(MPlayerSkills); + PacketStruct* packet = configReader.getStruct("WS_UpdateSkillBook", version); + if(packet){ + int16 skill_count = 0; + map::iterator itr; + for (itr = skills.begin(); itr != skills.end(); itr++) { + if (itr->second && itr->second->active_skill) + skill_count++; + } + int16 size = 0; + if (version > 561) { + size = 21 * skill_count + 8; + } + else if (version < 373) { + size = 12 * skill_count + 6; + } + else if (version <= 373) { + size = 15 * skill_count + 6; + } + else if (version <= 561) { + size = 21 * skill_count + 7; + } + + if (skill_count > packet_count) { + uchar* tmp = 0; + if (orig_packet) { + tmp = new uchar[size]; + memset(tmp, 0, size); + memcpy(tmp, orig_packet, orig_packet_size); + safe_delete_array(orig_packet); + safe_delete_array(xor_packet); + orig_packet = tmp; + } + else { + orig_packet = new uchar[size]; + memset(orig_packet, 0, size); + } + xor_packet = new uchar[size]; + memset(xor_packet, 0, size); + } + packet_count = skill_count; + orig_packet_size = size; + packet->setArrayLengthByName("skill_count", skill_count); + Skill* skill = 0; + int32 i=0; + for(itr = skills.begin(); itr != skills.end(); itr++){ + skill = itr->second; + if(skill && skill->active_skill){ + int16 skill_max_with_bonuses = CalculateSkillMaxValue(skill->skill_id, skill->max_val); + int16 skill_with_bonuses = int(CalculateSkillValue(skill->skill_id, skill->current_val)); + packet->setArrayDataByName("skill_id", skill->skill_id, i); + if (version <= 561 && skill->skill_type >= SKILL_TYPE_GENERAL) { //covert it to DOF types + packet->setArrayDataByName("type", skill->skill_type-2, i); + } + else if(version >= 60085 && skill->skill_type >= 12) { + packet->setArrayDataByName("type", skill->skill_type-1, i); + } + else { + packet->setArrayDataByName("type", skill->skill_type, i); + } + + int16 current_val = skill->current_val; + + if(skill->skill_type == SKILL_TYPE_LANGUAGE) { // 13 is language in the DB?? 14 is the skill type though + packet->setArrayDataByName("language_unknown", skill->skill_id, i); + packet->setArrayDataByName("display_maxval", 1, i); + packet->setArrayDataByName("max_val", 1, i); + } + else { + packet->setArrayDataByName("max_val", skill->max_val, i); + packet->setArrayDataByName("display_minval", skill->display, i); + packet->setArrayDataByName("display_maxval", skill->display, i); + packet->setArrayDataByName("skill_delta", 0, i);// skill_with_bonuses- skill->current_val + packet->setArrayDataByName("skill_delta2", skill_max_with_bonuses - skill->max_val, i);// skill_max_with_bonuses - skill->max_val, i); + } + + packet->setArrayDataByName("current_val", current_val, i); + packet->setArrayDataByName("base_val", current_val, i); + i++; + } + } + int8 offset = 1; + if (version <= 373) + offset = 0; + EQ2Packet* ret = packet->serializeCountPacket(version, offset, orig_packet, xor_packet); + //packet->PrintPacket(); + //DumpPacket(orig_packet, orig_packet_size); + //DumpPacket(ret); + safe_delete(packet); + return ret; + } + return 0; +} + +bool PlayerSkillList::CheckSkillIncrease(Skill* skill){ + if(!skill || skill->current_val >= skill->max_val) + return false; + // Assuming that skills will be used more at higher levels, increase chances are: + // skill val of 1 ~ 20% chance, value of 100 ~ 10%, value of 400 ~ 4% + int8 percent = (int8)(((float)((float)100/(float)(50 + skill->current_val)))*10); + if(rand()%100 < percent){ // skill increase + IncreaseSkill(skill, 1); + return true; + } + else + return false; +} + +Skill* PlayerSkillList::GetSkillByName(const char* name){ + std::shared_lock lock(MPlayerSkills); + if(name_skill_map.size() == 0){ + map::iterator itr; + Skill* skill = 0; + for(itr = skills.begin(); itr != skills.end(); itr++){ + skill = itr->second; + name_skill_map[skill->name.data] = skill; + } + } + if(name_skill_map.count(name) > 0) + return name_skill_map[name]; + else + return 0; +} + +vector* PlayerSkillList::GetSaveNeededSkills(){ + std::shared_lock lock(MPlayerSkills); + vector* ret = new vector; + map::iterator itr; + for(itr = skills.begin(); itr != skills.end(); itr++){ + if(itr->second->save_needed){ + ret->push_back(itr->second); + itr->second->save_needed = false; + } + } + return ret; +} + +void PlayerSkillList::AddSkillUpdateNeeded(Skill* skill){ + MSkillUpdates.writelock(__FUNCTION__, __LINE__); + skill_updates.push_back(skill); + has_updates = true; + MSkillUpdates.releasewritelock(__FUNCTION__, __LINE__); +} + +vector* PlayerSkillList::GetSkillUpdates(){ + vector* ret = 0; + vector::iterator itr; + MSkillUpdates.writelock(__FUNCTION__, __LINE__); + if(skill_updates.size() > 0){ + ret = new vector(); + ret->insert(ret->begin(), skill_updates.begin(), skill_updates.end()); + skill_updates.clear(); + } + has_updates = false; + MSkillUpdates.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +bool PlayerSkillList::HasSkillUpdates(){ + return has_updates; +} + +void PlayerSkillList::AddSkillBonus(int32 spell_id, int32 skill_id, float value) { + if (value != 0) { + SkillBonus* sb; + if (skill_bonus_list.count(spell_id) == 0) { + sb = new SkillBonus; + sb->spell_id = spell_id; + skill_bonus_list.Put(spell_id, sb); + } + else + sb = skill_bonus_list.Get(spell_id); + + if (sb->skills[skill_id] == nullptr) { + SkillBonusValue* sbv = new SkillBonusValue; + sbv->skill_id = skill_id; + sbv->value = value; + sb->skills[skill_id] = sbv; + } + } +} + +void PlayerSkillList::ResetPackets() { + std::unique_lock lock(MPlayerSkills); + safe_delete_array(orig_packet); + safe_delete_array(xor_packet); + orig_packet_size = 0; + orig_packet = 0; + xor_packet = 0; + packet_count = 0; +} + +SkillBonus* PlayerSkillList::GetSkillBonus(int32 spell_id) { + SkillBonus *ret = 0; + + if (skill_bonus_list.count(spell_id) > 0) + ret = skill_bonus_list.Get(spell_id); + + return ret; +} + +void PlayerSkillList::RemoveSkillBonus(int32 spell_id) { + if (skill_bonus_list.count(spell_id) > 0) { + SkillBonus* sb = skill_bonus_list.Get(spell_id); + skill_bonus_list.erase(spell_id); + map::iterator itr; + for (itr = sb->skills.begin(); itr != sb->skills.end(); itr++) + safe_delete(itr->second); + safe_delete(sb); + } +} + +int Skill::CheckDisarmSkill(int16 targetLevel, int8 chest_difficulty) +{ + if (chest_difficulty < 2) // no triggers on this chest type + return 1; + + if (targetLevel < 1) + targetLevel = 1; + + int chest_diff_result = targetLevel * chest_difficulty; + float base_difficulty = 15.0f; + float fail_threshold = 10.0f; + + + float chance = ((100.0f - base_difficulty) * ((float)current_val / (float)chest_diff_result)); + + if (chance > (100.0f - base_difficulty)) + { + chance = 100.0f - base_difficulty; + } + + float d100 = (float)MakeRandomFloat(0, 100); + + if (d100 <= chance) + return 1; + else + { + if (d100 > (chance + fail_threshold)) + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/source/WorldServer/Skills.h b/source/WorldServer/Skills.h new file mode 100644 index 0000000..7d59f1f --- /dev/null +++ b/source/WorldServer/Skills.h @@ -0,0 +1,180 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_SKILLS_H__ +#define __EQ2_SKILLS_H__ + +#include +#include +#include + +#include "../common/ConfigReader.h" +#include "../common/types.h" +#include "MutexMap.h" + +#define SKILL_TYPE_WEAPONRY 1 +#define SKILL_TYPE_SPELLCASTING 2 +#define SKILL_TYPE_AVOIDANCE 3 +#define SKILL_TYPE_ARMOR 4 +#define SKILL_TYPE_SHIELD 5 +#define SKILL_TYPE_HARVESTING 6 +#define SKILL_TYPE_ARTISAN 7 +#define SKILL_TYPE_CRAFTSMAN 8 +#define SKILL_TYPE_OUTFITTER 9 +#define SKILL_TYPE_SCHOLAR 10 +#define SKILL_TYPE_GENERAL 13 +#define SKILL_TYPE_LANGUAGE 14 +#define SKILL_TYPE_CLASS 15 +#define SKILL_TYPE_COMBAT 16 +#define SKILL_TYPE_WEAPON 17 +#define SKILL_TYPE_TSKNOWLEDGE 18 + +#define SKILL_TYPE_GENERAL_DOF 11 +#define SKILL_TYPE_LANGUAGE_DOF 12 +#define SKILL_TYPE_CLASS_DOF 13 +#define SKILL_TYPE_COMBAT_DOF 14 +#define SKILL_TYPE_WEAPON_DOF 15 +#define SKILL_TYPE_TSKNOWLEDGE_DOF 16 + +#define SKILL_ID_SCULPTING 1039865549 +#define SKILL_ID_ARTISTRY 3881305672 +#define SKILL_ID_FLETCHING 3076004370 +#define SKILL_ID_METALWORKING 4032608519 +#define SKILL_ID_METALSHAPING 3108933728 +#define SKILL_ID_TAILORING 2082133324 +#define SKILL_ID_CHEMISTRY 2557647574 +#define SKILL_ID_ARTIFICING 3330500131 +#define SKILL_ID_SCRIBING 773137566 + +//the following update the current_value to the max_value as soon as the max_value is updated +#define SKILL_ID_DUALWIELD 1852383242 +#define SKILL_ID_FISTS 3177806075 +#define SKILL_ID_DESTROYING 3429135390 +#define SKILL_ID_MAGIC_AFFINITY 2072844078 + +#define SKILL_ID_GREATSWORD 2292577688 // aka 2h slashing +#define SKILL_ID_GREATSPEAR 2380184628 // aka 2h piercing +#define SKILL_ID_STAFF 3180399725 // aka 2h crushing + +/* Each SkillBonus is comprised of multiple possible skill bonus values. This is so one spell can modify + more than one skill */ +struct SkillBonusValue { + int32 skill_id; + float value; +}; + +struct SkillBonus { + int32 spell_id; + map skills; +}; + + +class Skill{ +public: + Skill(); + Skill(Skill* skill); + int32 skill_id; + int16 current_val; + int16 previous_val; + int16 max_val; + int32 skill_type; + int8 display; + EQ2_16BitString short_name; + EQ2_16BitString name; + EQ2_16BitString description; + bool save_needed; + bool active_skill; + int CheckDisarmSkill(int16 targetLevel, int8 chest_difficulty=0); +}; + +class MasterSkillList{ +public: + MasterSkillList(); + ~MasterSkillList(); + void AddSkill(Skill* skill); + int16 GetSkillCount(); + EQ2Packet* GetPopulateSkillsPacket(int16 version); + map* GetAllSkills(); + Skill* GetSkill(int32 skill_id); + Skill* GetSkillByName(const char* skill_name); + +private: + map skills; + map populate_packets; +}; + +class PlayerSkillList{ +public: + PlayerSkillList(); + ~PlayerSkillList(); + void RemoveSkill(Skill* skill); + void AddSkill(Skill* new_skill); + bool CheckSkillIncrease(Skill* skill); + Skill* GetSkillByName(const char* name); + bool HasSkill(int32 skill_id); + Skill* GetSkill(int32 skill_id); + + void IncreaseSkill(Skill* skill, int16 amount); + void IncreaseSkill(int32 skill_id, int16 amount); + void DecreaseSkill(Skill* skill, int16 amount); + void DecreaseSkill(int32 skill_id, int16 amount); + void SetSkill(Skill* skill, int16 value, bool send_update = true); + void SetSkill(int32 skill_id, int16 value, bool send_update = true); + + void IncreaseSkillCap(Skill* skill, int16 amount); + void IncreaseSkillCap(int32 skill_id, int16 amount); + void DecreaseSkillCap(Skill* skill, int16 amount); + void DecreaseSkillCap(int32 skill_id, int16 amount); + void SetSkillCap(Skill* skill, int16 value); + void SetSkillCap(int32 skill_id, int16 value); + void IncreaseAllSkillCaps(int16 value); + void IncreaseSkillCapsByType(int8 type, int16 value); + void SetSkillCapsByType(int8 type, int16 value); + void SetSkillValuesByType(int8 type, int16 value, bool send_update = true); + void AddSkillUpdateNeeded(Skill* skill); + + void AddSkillBonus(int32 spell_id, int32 skill_id, float value); + SkillBonus* GetSkillBonus(int32 spell_id); + void RemoveSkillBonus(int32 spell_id); + + int16 CalculateSkillValue(int32 skill_id, int16 current_val); + int16 CalculateSkillMaxValue(int32 skill_id, int16 max_val); + EQ2Packet* GetSkillPacket(int16 version); + vector* GetSaveNeededSkills(); + vector* GetSkillUpdates(); + map* GetAllSkills(); + bool HasSkillUpdates(); + + void ResetPackets(); +private: + volatile bool has_updates; + mutable std::shared_mutex MPlayerSkills; + Mutex MSkillUpdates; + int16 packet_count; + uchar* orig_packet; + int16 orig_packet_size; + uchar* xor_packet; + map skills; + map name_skill_map; + vector skill_updates; + MutexMap skill_bonus_list; +}; + +#endif + diff --git a/source/WorldServer/Spawn.cpp b/source/WorldServer/Spawn.cpp new file mode 100644 index 0000000..4499b3e --- /dev/null +++ b/source/WorldServer/Spawn.cpp @@ -0,0 +1,5316 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Spawn.h" +#include +#include "../common/timer.h" +#include +#include +#include "Entity.h" +#include "Widget.h" +#include "Sign.h" +#include "../common/MiscFunctions.h" +#include "../common/Log.h" +#include "Rules/Rules.h" +#include "World.h" +#include "LuaInterface.h" +#include "Bots/Bot.h" +#include "Zone/raycast_mesh.h" +#include "RaceTypes/RaceTypes.h" +#include "VisualStates.h" + +extern ConfigReader configReader; +extern RuleManager rule_manager; +extern World world; +extern ZoneList zone_list; +extern MasterRaceTypeList race_types_list; +extern LuaInterface* lua_interface; +extern VisualStates visual_states; + +Spawn::Spawn(){ + loot_coins = 0; + trap_triggered = false; + trap_state = 0; + chest_drop_time = 0; + trap_opened_time = 0; + group_id = 0; + size_offset = 0; + merchant_id = 0; + merchant_type = 0; + merchant_min_level = 0; + merchant_max_level = 0; + memset(&appearance, 0, sizeof(AppearanceData)); + memset(&basic_info, 0, sizeof(BasicInfoStruct)); + appearance.pos.state = 0x4080; + appearance.difficulty = 6; + size = 32; + appearance.pos.collision_radius = 32; + id = Spawn::NextID(); + oversized_packet = 0xFF; + zone = 0; + spawn_location_id = 0; + spawn_entry_id = 0; + spawn_location_spawns_id = 0; + respawn = 0; + expire_time = 0; + expire_offset = 0; + x_offset = 0; + y_offset = 0; + z_offset = 0; + database_id = 0; + packet_num = 1; + changed = false; + vis_changed = false; + position_changed = false; + send_spawn_changes = true; + info_changed = false; + appearance.pos.Speed1 = 0; + last_attacker = 0; + faction_id = 0; + running_to = 0; + tmp_visual_state = -1; + tmp_action_state = -1; + transporter_id = 0; + invulnerable = false; + spawn_group_list = 0; + MSpawnGroup = 0; + movement_locations = 0; + target = 0; + primary_command_list_id = 0; + secondary_command_list_id = 0; + is_pet = false; + m_followTarget = 0; + following = false; + req_quests_continued_access = false; + req_quests_override = 0; + req_quests_private = false; + m_illusionModel = 0; + Cell_Info.CurrentCell = nullptr; + Cell_Info.CellListIndex = -1; + m_addedToWorldTimestamp = 0; + m_spawnAnim = 0; + m_spawnAnimLeeway = 0; + m_Update.SetName("Spawn::m_Update"); + m_requiredHistory.SetName("Spawn::m_requiredHistory"); + m_requiredQuests.SetName("Spawn::m_requiredQuests"); + last_heading_angle = 0.0; + last_grid_update = 0; + last_location_update = 0.0; + last_movement_update = Timer::GetCurrentTime2(); + forceMapCheck = false; + m_followDistance = 0; + MCommandMutex.SetName("Entity::MCommandMutex"); + has_spawn_proximities = false; + pickup_item_id = 0; + pickup_unique_item_id = 0; + disable_sounds = false; + has_quests_required = false; + has_history_required = false; + is_flying_creature = false; + is_water_creature = false; + region_map = nullptr; + current_map = nullptr; + RegionMutex.SetName("Spawn::RegionMutex"); + pause_timer.Disable(); + m_SpawnMutex.SetName("Spawn::SpawnMutex"); + appearance_equipment_list.SetAppearanceType(1); + is_transport_spawn = false; + rail_id = 0; + is_omitted_by_db_flag = false; + loot_tier = 0; + loot_drop_type = 0; + deleted_spawn = false; + is_collector = false; + trigger_widget_id = 0; + scared_by_strong_players = false; + is_alive = true; + SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE); + loot_method = GroupLootMethod::METHOD_FFA; + loot_rarity = 0; + loot_group_id = 0; + looter_spawn_id = 0; + is_loot_complete = false; + is_loot_dispensed = false; + reset_movement = false; + ResetKnockedBack(); +} + +Spawn::~Spawn(){ + is_running = false; + + vector::iterator itr; + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) + safe_delete(*itr); + loot_items.clear(); + + RemovePrimaryCommands(); + + for(int32 i=0;isize()){ + safe_delete(movement_locations->front()); + movement_locations->pop_front(); + } + safe_delete(movement_locations); + } + MMovementLocations.unlock(); + + MMovementLoop.lock(); + for (int32 i = 0; i < movement_loop.size(); i++) + safe_delete(movement_loop.at(i)); + movement_loop.clear(); + MMovementLoop.unlock(); + + m_requiredHistory.writelock(__FUNCTION__, __LINE__); + map::iterator lua_itr; + for (lua_itr = required_history.begin(); lua_itr != required_history.end(); lua_itr++) { + safe_delete(lua_itr->second); + } + required_history.clear(); + m_requiredHistory.releasewritelock(__FUNCTION__, __LINE__); + + m_requiredQuests.writelock(__FUNCTION__, __LINE__); + map* >::iterator rq_itr; + for (rq_itr = required_quests.begin(); rq_itr != required_quests.end(); rq_itr++){ + safe_delete(rq_itr->second); + } + required_quests.clear(); + m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__); + + // just in case to make sure data is destroyed + RemoveSpawnProximities(); + + Regions.clear(); +} + +void Spawn::RemovePrimaryCommands() +{ + for (int32 i = 0; i < primary_command_list.size(); i++) { + safe_delete(primary_command_list[i]); + } + primary_command_list.clear(); +} + +void Spawn::InitializeHeaderPacketData(Player* player, PacketStruct* header, int16 index) { + header->setDataByName("index", index); + + if (GetSpawnAnim() > 0 && Timer::GetCurrentTime2() < (GetAddedToWorldTimestamp() + GetSpawnAnimLeeway())) { + if (header->GetVersion() >= 57080) + header->setDataByName("spawn_anim", GetSpawnAnim()); + else + header->setDataByName("spawn_anim", (int16)GetSpawnAnim()); + } + else { + if (header->GetVersion() >= 57080) + header->setDataByName("spawn_anim", 0xFFFFFFFF); + else + header->setDataByName("spawn_anim", 0xFFFF); + } + + if (primary_command_list.size() > 0){ + if (primary_command_list.size() > 1) { + header->setArrayLengthByName("command_list", primary_command_list.size()); + for (int32 i = 0; i < primary_command_list.size(); i++) { + header->setArrayDataByName("command_list_name", primary_command_list[i]->name.c_str(), i); + header->setArrayDataByName("command_list_max_distance", primary_command_list[i]->distance, i); + header->setArrayDataByName("command_list_error", primary_command_list[i]->error_text.c_str(), i); + header->setArrayDataByName("command_list_command", primary_command_list[i]->command.c_str(), i); + } + } + if (header->GetVersion() <= 561) { + header->setMediumStringByName("default_command", primary_command_list[0]->name.c_str()); + } + else + header->setMediumStringByName("default_command", primary_command_list[0]->command.c_str()); + header->setDataByName("max_distance", primary_command_list[0]->distance); + } + if (spawn_group_list && MSpawnGroup){ + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + header->setArrayLengthByName("group_size", spawn_group_list->size()); + vector::iterator itr; + int i = 0; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++, i++){ + int32 idx = 0; + idx = player->GetIDWithPlayerSpawn((*itr)); + header->setArrayDataByName("group_spawn_id", idx, i); + } + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } + + header->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(this)); + header->setDataByName("crc", 1); + header->setDataByName("time_stamp", Timer::GetCurrentTime2()); +} + +void Spawn::InitializeVisPacketData(Player* player, PacketStruct* vis_packet) { + int16 version = vis_packet->GetVersion(); + +//why? + /*if (IsPlayer()) { + appearance.pos.grid_id = 0xFFFFFFFF; + }*/ + + int8 tag_icon = 0; + + int32 tmp_id = 0; + if(faction_id && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_FACTION, faction_id, "", true)) > 0); + else if(IsGroundSpawn() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_GROUNDSPAWN, 1, "", true)) > 0); + else if((this->GetSpawnGroupID() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, 1, "", true)) > 0) || + (!this->GetSpawnGroupID() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_SPAWNGROUP, 0, "", true)) > 0)); + else if((this->GetRace() && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, GetRace(), "", true)) > 0)); + else if(((tmp_id = race_types_list.GetRaceType(GetModelType()) > 0) && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, tmp_id, "", true)) > 0)); + else if(((tmp_id = race_types_list.GetRaceBaseType(GetModelType()) > 0) && (tag_icon = player->MatchGMVisualFilter(GMTagFilterType::GMFILTERTYPE_RACE, tmp_id, "", true)) > 0)); + else if(IsEntity() && (tag_icon = ((Entity*)this)->GetInfoStruct()->get_tag1()) > 0); + + vis_packet->setDataByName("tag1", tag_icon); + + if (IsPlayer()) + vis_packet->setDataByName("player", 1); + if (version <= 561) { + vis_packet->setDataByName("targetable", appearance.targetable); + vis_packet->setDataByName("show_name", appearance.display_name); + vis_packet->setDataByName("attackable", appearance.attackable); + if(appearance.attackable == 1) + vis_packet->setDataByName("attackable_icon", 1); + if (IsPlayer()) { + if (((Player*)this)->IsGroupMember(player)) + vis_packet->setDataByName("group_member", 1); + } + + } + if (appearance.targetable == 1 || appearance.show_level == 1 || appearance.display_name == 1) { + if (!IsGroundSpawn()) { + int8 arrow_color = ARROW_COLOR_WHITE; + sint8 npc_con = player->GetFactions()->GetCon(faction_id); + + if (IsPlayer() && !((Player*)this)->CanSeeInvis(player)) + npc_con = 0; + else if (!IsPlayer() && IsEntity() && !((Entity*)this)->CanSeeInvis(player)) + npc_con = 0; + + if (appearance.attackable == 1) + arrow_color = player->GetArrowColor(GetLevel()); + if (version <= 373) { + if (GetMerchantID() > 0) + arrow_color += 7; + else { + if (primary_command_list.size() > 0) { + int16 len = strlen(primary_command_list[0]->command.c_str()); + if(len >= 4 && strncmp(primary_command_list[0]->command.c_str(), "bank", 4) == 0) + arrow_color += 14; + else if (len >= 4 && strncmp(primary_command_list[0]->command.c_str(), "hail", 4) == 0) + arrow_color += 21; + else if (len >= 6 && strncmp(primary_command_list[0]->command.c_str(), "attack", 6) == 0) { + if (arrow_color > 5) + arrow_color = 34; + else + arrow_color += 29; + } + } + } + } + if(IsNPC() && (((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_BROKEN || + (((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_OVERMATCHED) || + ((Entity*)this)->GetLockedNoLoot() == ENCOUNTER_STATE_LOCKED && !((NPC*)this)->Brain()->IsEntityInEncounter(player->GetID()))) { + vis_packet->setDataByName("arrow_color", ARROW_COLOR_GRAY); + } + else { + vis_packet->setDataByName("arrow_color", arrow_color); + } + if (appearance.attackable == 0 || IsPlayer() || IsBot() || (IsEntity() && ((Entity*)this)->GetOwner() && + (((Entity*)this)->GetOwner()->IsPlayer() || ((Entity*)this)->GetOwner()->IsBot()))) { + vis_packet->setDataByName("locked_no_loot", 1); + } + else { + vis_packet->setDataByName("locked_no_loot", appearance.locked_no_loot); + } + if (player->GetArrowColor(GetLevel()) == ARROW_COLOR_GRAY) + if (npc_con == -4) + npc_con = -3; + vis_packet->setDataByName("npc_con", npc_con); + if (appearance.attackable == 1 && IsNPC() && (player->GetFactions()->GetCon(faction_id) <= -4 || ((NPC*)this)->Brain()->GetHate(player) > 1)) { + vis_packet->setDataByName("npc_hate", ((NPC*)this)->Brain()->GetHatePercentage(player)); + vis_packet->setDataByName("show_difficulty_arrows", 1); + } + int8 quest_flag = player->CheckQuestFlag(this); + if (version < 1188 && quest_flag >= 16) + quest_flag = 1; + vis_packet->setDataByName("quest_flag", quest_flag); + if (player->HasQuestUpdateRequirement(this)) { + vis_packet->setDataByName("name_quest_icon", 1); + } + } + } + + int8 vis_flags = 0; + if (MeetsSpawnAccessRequirements(player)) { + if (appearance.attackable == 1) + vis_flags += 64; //attackable icon + if (appearance.show_level == 1) + vis_flags += 32; + if (appearance.display_name == 1) + vis_flags += 16; + if (IsPlayer() || appearance.targetable == 1) + vis_flags += 4; + if (appearance.show_command_icon == 1) + vis_flags += 2; + if (this == player) { + //if (version <= 283) { + // vis_flags = 1; + //} + //else + vis_flags += 1; + } + } + else if (req_quests_override > 0) + { + //Check to see if there's an override value set + vis_flags = req_quests_override & 0xFF; + } + + if (player->HasGMVision()) + { + if ((vis_flags & 16) == 0 && appearance.display_name == 0) + vis_flags += 16; + if ((vis_flags & 4) == 0) + vis_flags += 4; + } + + if (version <= 546 && (vis_flags > 1 || appearance.display_hand_icon > 0)) //interactable + vis_flags = 1; + vis_packet->setDataByName("vis_flags", vis_flags); + + + if (MeetsSpawnAccessRequirements(player)) { + vis_packet->setDataByName("hand_flag", appearance.display_hand_icon); + } + else { + if ((req_quests_override & 256) > 0) + vis_packet->setDataByName("hand_flag", 1); + } + if ((version == 546 || version == 561) && GetMerchantID() > 0) { + vis_packet->setDataByName("guild", ""); + } +} + +void Spawn::InitializeFooterPacketData(Player* player, PacketStruct* footer) { + if (IsWidget()){ + Widget* widget = (Widget*)this; + if (widget->GetMultiFloorLift()) { + footer->setDataByName("widget_x", widget->GetX()); + footer->setDataByName("widget_y", widget->GetY()); + footer->setDataByName("widget_z", widget->GetZ()); + } + else { + footer->setDataByName("widget_x", widget->GetWidgetX()); + footer->setDataByName("widget_y", widget->GetWidgetY()); + footer->setDataByName("widget_z", widget->GetWidgetZ()); + } + footer->setDataByName("widget_id", widget->GetWidgetID()); + } + else if (IsSign()){ + Sign* sign = (Sign*)this; + footer->setDataByName("widget_id", sign->GetWidgetID()); + footer->setDataByName("widget_x", sign->GetWidgetX()); + footer->setDataByName("widget_y", sign->GetWidgetY()); + footer->setDataByName("widget_z", sign->GetWidgetZ()); + + int8 showSignText = 1; + if((HasQuestsRequired() || HasHistoryRequired()) && !MeetsSpawnAccessRequirements(player)) { + showSignText = 0; + } + + if (sign->GetSignTitle()) + footer->setMediumStringByName("title", sign->GetSignTitle()); + if (sign->GetSignDescription()) + footer->setMediumStringByName("description", sign->GetSignDescription()); + footer->setDataByName("sign_distance", sign->GetSignDistance()); + footer->setDataByName("show", showSignText); + // in live we see that the language is set when the player does not have it, otherwise its left as 00's. + if(!player->HasLanguage(sign->GetLanguage())) { + footer->setDataByName("language", sign->GetLanguage()); + } + } + + if ( IsPlayer()) + footer->setDataByName("is_player", 1); + + + if (strlen(appearance.name) < 1) + strncpy(appearance.name,to_string(GetID()).c_str(),128); + + footer->setMediumStringByName("name", appearance.name); + footer->setMediumStringByName("guild", appearance.sub_title); + footer->setMediumStringByName("prefix", appearance.prefix_title); + footer->setMediumStringByName("suffix", appearance.suffix_title); + footer->setMediumStringByName("last_name", appearance.last_name); + if (appearance.attackable == 0 && GetLevel() > 0) + footer->setDataByName("spawn_type", 1); + else if (appearance.attackable == 0) + footer->setDataByName("spawn_type", 6); + else + footer->setDataByName("spawn_type", 3); +} + +EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, int32 value, int16 offset2, int16 offset3, int16 offset4, int32 value2) { + // If spawn is NPC AND is pet && owner is a player && owner is the player passed to this function && player's char sheet pet id is 0 + m_Update.writelock(__FUNCTION__, __LINE__); + + int16 index = 0; + if ((index = player->GetIndexForSpawn(this)) > 0) { + player->SetSpawnMapIndex(this, index); + } + else { + index = player->SetSpawnMapAndIndex(this); + } + + // Jabantiz - [Bug] Client Crash on Revive + if (player->GetIDWithPlayerSpawn(this) == 0) { + player->SetSpawnMap(this); + } + + PacketStruct* header = player->GetSpawnHeaderStruct(); + header->ResetData(); + InitializeHeaderPacketData(player, header, index); + + PacketStruct* footer = 0; + if (IsWidget()) + footer = player->GetWidgetFooterStruct(); + else if (IsSign()) + footer = player->GetSignFooterStruct(); + else if (version > 561) + footer = player->GetSpawnFooterStruct(); + if (footer) { + footer->ResetData(); + InitializeFooterPacketData(player, footer); + } + PacketStruct* vis_struct = player->GetSpawnVisStruct(); + PacketStruct* info_struct = player->GetSpawnInfoStruct(); + PacketStruct* pos_struct = player->GetSpawnPosStruct(); + + player->info_mutex.writelock(__FUNCTION__, __LINE__); + player->vis_mutex.writelock(__FUNCTION__, __LINE__); + player->pos_mutex.writelock(__FUNCTION__, __LINE__); + + info_struct->ResetData(); + InitializeInfoPacketData(player, info_struct); + + vis_struct->ResetData(); + InitializeVisPacketData(player, vis_struct); + + pos_struct->ResetData(); + InitializePosPacketData(player, pos_struct); + if (version <= 283) { + if (offset == 777) { + info_struct->setDataByName("name", "This is a really long name\n"); + info_struct->setDataByName("last_name", "This is a really long LAST name\n"); + info_struct->setDataByName("name_suffix", "This is a really long SUFFIX\n"); + info_struct->setDataByName("name_prefix", "This is a really long PREFIX\n"); + info_struct->setDataByName("unknown", "This is a really long UNKNOWN\n"); + info_struct->setDataByName("second_suffix", "This is a really long 2nd SUFFIX\n"); + } + //info_struct->setDataByName("unknown2", 3, 0); // level + //info_struct->setDataByName("unknown2", 1, 1); //unknown, two down arrows + //info_struct->setDataByName("unknown2", 1, 2); //unknown + //info_struct->setDataByName("unknown2", 1, 3); //unknown + //info_struct->setDataByName("unknown2", 1, 4); //solo fight + //info_struct->setDataByName("unknown2", 1, 5); //unknown + //info_struct->setDataByName("unknown2", 1, 6); //unknown + //info_struct->setDataByName("unknown2", 1, 7); //merchant + //info_struct->setDataByName("unknown2", 1, 8); //unknown + //info_struct->setDataByName("unknown2", 1, 9); //unknown + //info_struct->setDataByName("unknown2", 1, 10); + //112: 00 00 00 01 02 03 04 05 - 06 07 08 09 0A 00 00 00 | ................ merchant, x4 + //112: 00 00 00 01 02 03 04 05 - 00 00 00 00 00 00 00 00 | ................ x4, epic, indifferent + //info_struct->setDataByName("body_size", 42); + //for(int i=0;i<8;i++) + // info_struct->setDataByName("persistent_spell_visuals", 329, i); + //info_struct->setDataByName("persistent_spell_levels", 20); + } + + string* vis_data = vis_struct->serializeString(); + string* pos_data = pos_struct->serializeString(); + string* info_data = info_struct->serializeString(); + + int16 part2_size = pos_data->length() + vis_data->length() + info_data->length(); + uchar* part2 = new uchar[part2_size]; + + player->AddSpawnPosPacketForXOR(id, (uchar*)pos_data->c_str(), pos_data->length()); + player->AddSpawnVisPacketForXOR(id, (uchar*)vis_data->c_str(), vis_data->length()); + player->AddSpawnInfoPacketForXOR(id, (uchar*)info_data->c_str(), info_data->length()); + + int32 vislength = vis_data->length(); + int32 poslength = pos_data->length(); + int32 infolength = info_data->length(); + + uchar* ptr = part2; + memcpy(ptr, pos_data->c_str(), pos_data->length()); + ptr += pos_data->length(); + memcpy(ptr, vis_data->c_str(), vis_data->length()); + ptr += vis_data->length(); + memcpy(ptr, info_data->c_str(), info_data->length()); + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + string* part1 = header->serializeString(); + string* part3 = 0; + if (footer) + part3 = footer->serializeString(); + + //uchar blah7[] = {0x01,0x01,0x00,0x00,0x01,0x01,0x00,0x01,0x01,0x00 }; + //uchar blah7[] = { 0x03,0x01,0x00,0x01,0x01,0x01,0x00,0x01,0x01,0x00 }; base + //uchar blah7[] = { 0x03,0x00,0x00,0x01,0x01,0x01,0x00,0x01,0x01,0x00 }; //no change + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x01,0x01,0x00,0x01,0x01,0x00 }; //blue instead of green + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x01,0x00 }; //no change + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00 }; //not selectable + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x01,0x00 }; //no change + //uchar blah7[] = { 0x03,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; //no longer have the two down arrows + //uchar blah7[] = { 0x01,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; //arrow color green instead of white/gray + //memcpy(part2 + 77, blah7, sizeof(blah7)); + + + + + + //DumpPacket(part2, 885); + if (offset > 0) { + if (offset4 > 0 && offset4 >= offset3) { + uchar* ptr2 = (uchar*)part2; + ptr2 += offset3; + while (offset4 >= offset3) { + int8 jumpsize = 1; + if (value2 > 0xFFFF) { + memcpy(ptr2, (uchar*)&value2, 4); + jumpsize = 4; + } + else if (value2 > 0xFF) { + memcpy(ptr2, (uchar*)&value2, 2); + jumpsize = 2; + } + else { + memcpy(ptr2, (uchar*)&value2, 1); + jumpsize = 1; + } + ptr2 += jumpsize; + offset4 -= jumpsize; + } + } + if (offset2 > 0 && offset2 >= offset) { + uchar* ptr2 = (uchar*)part2; + ptr2 += offset; + while (offset2 >= offset) { + int8 jumpsize = 1; + if (value > 0xFFFF) { + memcpy(ptr2, (uchar*)&value, 4); + jumpsize = 4; + } + else if (value > 0xFF) { + memcpy(ptr2, (uchar*)&value, 2); + jumpsize = 2; + } + else + memcpy(ptr2, (uchar*)&value, 1); + ptr2 += jumpsize; + offset2 -= jumpsize; + } + } + else { + uchar* ptr2 = (uchar*)part2; + ptr2 += offset; + if (value > 0xFFFF) + memcpy(ptr2, (uchar*)&value, 4); + else if (value > 0xFF) + memcpy(ptr2, (uchar*)&value, 2); + else + memcpy(ptr2, (uchar*)&value, 1); + } + cout << "setting offset: " << offset << " to: " << value << endl; + } + //if (offset > 0) + // DumpPacket(part2, part2_size); + + uchar tmp[4000]; + bool reverse = (version > 373); + part2_size = Pack(tmp, part2, part2_size, 4000, version, reverse); + int32 total_size = part1->length() + part2_size + 3; + if (part3) + total_size += part3->length(); + int32 final_packet_size = total_size + 1; + + if (version > 373) + final_packet_size += 3; + else { + if (final_packet_size >= 255) { + final_packet_size += 2; + } + } + uchar* final_packet = new uchar[final_packet_size]; + ptr = final_packet; + if (version <= 373) { + if ((final_packet_size - total_size) > 1) { + memcpy(ptr, &oversized_packet, sizeof(oversized_packet)); + ptr += sizeof(oversized_packet); + memcpy(ptr, &total_size, 2); + ptr += 2; + } + else { + memcpy(ptr, &total_size, 1); + ptr += 1; + } + } + else { + memcpy(ptr, &total_size, sizeof(total_size)); + ptr += sizeof(total_size); + } + + memcpy(ptr, &oversized_packet, sizeof(oversized_packet)); + ptr += sizeof(oversized_packet); + + int16 opcode = 0; + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) { + if(IsWidget()) + opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateWidgetCmd); + else if(IsSign()) + opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateSignWidgetCmd); + else + opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateGhostCmd); + } + memcpy(ptr, &opcode, sizeof(opcode)); + ptr += sizeof(opcode); + + memcpy(ptr, part1->c_str(), part1->length()); + ptr += part1->length(); + + + memcpy(ptr, tmp, part2_size); + ptr += part2_size; + + if (part3) + memcpy(ptr, part3->c_str(), part3->length()); + delete[] part2; + + //printf("SpawnPacket %s (id: %u, index: %u) to %s: p1: %i, p2: %i, p3: %i, ts: %i. poslength: %u, infolength: %u, vislength: %u\n", GetName(), GetID(), index, player->GetName(), part1->length(), part2_size, (part3 != nullptr) ? part3->length() : -1, total_size, poslength, infolength, vislength); + EQ2Packet* ret = new EQ2Packet(OP_ClientCmdMsg, final_packet, final_packet_size); + delete[] final_packet; + + m_Update.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +uchar* Spawn::spawn_info_changes(Player* player, int16 version, int16* info_packet_size){ + int16 index = player->GetIndexForSpawn(this); + + PacketStruct* packet = player->GetSpawnInfoStruct(); + + player->info_mutex.writelock(__FUNCTION__, __LINE__); + packet->ResetData(); + InitializeInfoPacketData(player, packet); + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* xor_info_packet = player->GetTempInfoPacketForXOR(); + if (!xor_info_packet || size != player->GetTempInfoXorSize()) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateInfoPacket: %i, %i", size, player->GetTempInfoXorSize()); + safe_delete(xor_info_packet); + xor_info_packet = player->SetTempInfoPacketForXOR(size); + } + + uchar* orig_packet = player->GetSpawnInfoPacketForXOR(id); + if(orig_packet){ + memcpy(xor_info_packet, (uchar*)data->c_str(), size); + Encode(xor_info_packet, orig_packet, size); + } + + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_info_packet[i]) { + changed = true; + break; + } + } + + if (!changed) { + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + uchar* tmp = new uchar[size + 1000]; + size = Pack(tmp, xor_info_packet, size, size+1000, version); + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + size-=sizeof(int32); + size+=CheckOverLoadSize(index); + *info_packet_size = size + CheckOverLoadSize(size); + + uchar* tmp2 = new uchar[*info_packet_size]; + uchar* ptr = tmp2; + ptr += DoOverLoad(size, ptr); + ptr += DoOverLoad(index, ptr); + memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32)); + delete[] tmp; + return tmp2; +} + +uchar* Spawn::spawn_vis_changes(Player* player, int16 version, int16* vis_packet_size){ + PacketStruct* vis_struct = player->GetSpawnVisStruct(); + int16 index = player->GetIndexForSpawn(this); + + player->vis_mutex.writelock(__FUNCTION__, __LINE__); + uchar* orig_packet = player->GetSpawnVisPacketForXOR(id); + vis_struct->ResetData(); + InitializeVisPacketData(player, vis_struct); + string* data = vis_struct->serializeString(); + int32 size = data->length(); + uchar* xor_vis_packet = player->GetTempVisPacketForXOR(); + if (!xor_vis_packet || size != player->GetTempVisXorSize()) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateVisPacket: %i, %i", size, player->GetTempVisXorSize()); + safe_delete(xor_vis_packet); + xor_vis_packet = player->SetTempVisPacketForXOR(size); + } + if(orig_packet){ + memcpy(xor_vis_packet, (uchar*)data->c_str(), size); + Encode(xor_vis_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_vis_packet[i]) { + changed = true; + break; + } + } + + if (!changed) { + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + uchar* tmp = new uchar[size + 1000]; + size = Pack(tmp, xor_vis_packet, size, size+1000, version); + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + size-=sizeof(int32); + size+=CheckOverLoadSize(index); + *vis_packet_size = size + CheckOverLoadSize(size); + uchar* tmp2 = new uchar[*vis_packet_size]; + uchar* ptr = tmp2; + ptr += DoOverLoad(size, ptr); + ptr += DoOverLoad(index, ptr); + memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32)); + delete[] tmp; + return tmp2; +} + +uchar* Spawn::spawn_pos_changes(Player* player, int16 version, int16* pos_packet_size, bool override_) { + int16 index = player->GetIndexForSpawn(this); + + PacketStruct* packet = player->GetSpawnPosStruct(); + + player->pos_mutex.writelock(__FUNCTION__, __LINE__); + uchar* orig_packet = player->GetSpawnPosPacketForXOR(id); + packet->ResetData(); + InitializePosPacketData(player, packet, true); + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* xor_pos_packet = player->GetTempPosPacketForXOR(); + if (!xor_pos_packet || size != player->GetTempPosXorSize()) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiatePosPacket: %i, %i", size, player->GetTempPosXorSize()); + safe_delete(xor_pos_packet); + xor_pos_packet = player->SetTempPosPacketForXOR(size); + } + if(orig_packet){ + memcpy(xor_pos_packet, (uchar*)data->c_str(), size); + Encode(xor_pos_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_pos_packet[i]) { + changed = true; + break; + } + } + + if (!changed && !override_) { + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + int16 newSize = size + 1000; + uchar* tmp = new uchar[newSize]; + size = Pack(tmp, xor_pos_packet, size, newSize, version); + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + // Needed for CoE+ clients + if (version >= 1188) + size += 1; + + if(IsPlayer() && version > 561) + size += 4; + size-=sizeof(int32); + size+=CheckOverLoadSize(index); + + *pos_packet_size = size + CheckOverLoadSize(size); + uchar* tmp2 = new uchar[*pos_packet_size]; + uchar* ptr = tmp2; + ptr += DoOverLoad(size, ptr); + ptr += DoOverLoad(index, ptr); + + // extra byte in coe+ clients, 0 for NPC's 1 for Players + int8 x = 0; + if (IsPlayer() && version > 561) { + if (version >= 1188) { + // set x to 1 and add it to the packet + x = 1; + memcpy(ptr, &x, sizeof(int8)); + ptr += sizeof(int8); + } + int32 now = Timer::GetCurrentTime2(); + memcpy(ptr, &now, sizeof(int32)); + ptr += sizeof(int32); + } + else if (version >= 1188) { + // add x to packet + memcpy(ptr, &x, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, tmp+sizeof(int32), orig_size - sizeof(int32)); + delete[] tmp; + return tmp2; +} + +EQ2Packet* Spawn::player_position_update_packet(Player* player, int16 version, bool override_){ + if(!player || player->IsPlayer() == false){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called player_position_update_packet without player!"); + return 0; + } + else if(IsPlayer() == false){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called player_position_update_packet from spawn!"); + return 0; + } + + static const int8 info_size = 1; + static const int8 vis_size = 1; + int16 pos_packet_size = 0; + m_Update.writelock(__FUNCTION__, __LINE__); + uchar* pos_changes = spawn_pos_changes(player, version, &pos_packet_size, override_); + if (pos_changes == NULL) + { + m_Update.releasewritelock(__FUNCTION__, __LINE__); + return NULL; + } + + int32 size = info_size + pos_packet_size + vis_size + 8; + if (version >= 374) + size += 3; + else if (version <= 373 && size >= 255) {//1 byte to 3 for overloaded val + size += 2; + } + static const int8 oversized = 255; + + + int16 opcode_val = 0; + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) { + opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); + } + uchar* tmp = new uchar[size]; + memset(tmp, 0, size); + uchar* ptr = tmp; + if (version >= 374) { + size -= 4; + memcpy(ptr, &size, sizeof(int32)); + size += 4; + ptr += sizeof(int32); + } + else { + if (size >= 255) { + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + size -= 3; + memcpy(ptr, &size, sizeof(int16)); + size += 3; + ptr += 3; + } + else { + size -= 1; + memcpy(ptr, &size, sizeof(int8)); + ptr += sizeof(int8); + size += 1; + ptr += 1; + } + } + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + memcpy(ptr, &opcode_val, sizeof(int16)); + ptr += sizeof(int16); + if (version <= 373) { + int32 timestamp = Timer::GetCurrentTime2(); + memcpy(ptr, ×tamp, sizeof(int32)); + } + ptr += sizeof(int32); + ptr += info_size; + memcpy(ptr, pos_changes, pos_packet_size); + delete[] pos_changes; + m_Update.releasewritelock(__FUNCTION__, __LINE__); + EQ2Packet* ret_packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size); +// DumpPacket(ret_packet); + delete[] tmp; + return ret_packet; +} + +EQ2Packet* Spawn::spawn_update_packet(Player* player, int16 version, bool override_changes, bool override_vis_changes){ + if(!player || player->IsPlayer() == false){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error: Called spawn_update_packet without player!"); + return 0; + } + else if((IsPlayer() && info_changed == false && vis_changed == false) || (info_changed == false && vis_changed == false && position_changed == false)){ + if(!override_changes && !override_vis_changes) + return 0; + } + + uchar* info_changes = 0; + uchar* pos_changes = 0; + uchar* vis_changes = 0; + static const int8 oversized = 255; + int16 opcode_val = 0; + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) { + opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); + } + + //We need to lock these variables up to make this thread safe + m_Update.writelock(__FUNCTION__, __LINE__); + //These variables are set in the spawn_info_changes, pos and vis changes functions + int16 info_packet_size = 1; + int16 pos_packet_size = 1; + int16 vis_packet_size = 1; + + if (info_changed || override_changes) + info_changes = spawn_info_changes(player, version, &info_packet_size); + if ((position_changed || override_changes) && IsPlayer() == false) + pos_changes = spawn_pos_changes(player, version, &pos_packet_size); + if (vis_changed || override_changes || override_vis_changes) + vis_changes = spawn_vis_changes(player, version, &vis_packet_size); + + int32 size = info_packet_size + pos_packet_size + vis_packet_size + 8; + if (version >= 374) + size += 3; + else if (version <= 373 && size >= 255) {//1 byte to 3 for overloaded val + size += 2; + } + uchar* tmp = new uchar[size]; + memset(tmp, 0, size); + uchar* ptr = tmp; + if (version >= 374) { + size -= 4; + memcpy(ptr, &size, sizeof(int32)); + size += 4; + ptr += sizeof(int32); + } + else { + if (size >= 255) { + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + size -= 3; + memcpy(ptr, &size, sizeof(int16)); + size += 3; + ptr += 3; + } + else { + size -= 1; + memcpy(ptr, &size, sizeof(int8)); + ptr += sizeof(int8); + size += 1; + } + } + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + memcpy(ptr, &opcode_val, sizeof(int16)); + ptr += sizeof(int16); + if (IsPlayer() == false || version <= 546) { //this isnt sent for player updates, it is sent on position update + //int32 time = Timer::GetCurrentTime2(); + packet_num = Timer::GetCurrentTime2(); + memcpy(ptr, &packet_num, sizeof(int32)); + } + ptr += sizeof(int32); + if(info_changes) + memcpy(ptr, info_changes, info_packet_size); + + ptr += info_packet_size; + + if(pos_changes) + memcpy(ptr, pos_changes, pos_packet_size); + + ptr += pos_packet_size; + + if(vis_changes) + memcpy(ptr, vis_changes, vis_packet_size); + + EQ2Packet* ret_packet = 0; + if(info_packet_size + pos_packet_size + vis_packet_size > 0) + ret_packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size); + delete[] tmp; + safe_delete_array(info_changes); + safe_delete_array(vis_changes); + safe_delete_array(pos_changes); + m_Update.releasewritelock(__FUNCTION__, __LINE__); + + return ret_packet; +} + +uchar* Spawn::spawn_info_changes_ex(Player* player, int16 version, int16* info_packet_size) { + int16 index = player->GetIndexForSpawn(this); + + PacketStruct* packet = player->GetSpawnInfoStruct(); + + player->info_mutex.writelock(__FUNCTION__, __LINE__); + + packet->ResetData(); + InitializeInfoPacketData(player, packet); + + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* xor_info_packet = player->GetTempInfoPacketForXOR(); + + if (!xor_info_packet || size != player->GetTempInfoXorSize()) { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateInfoExPacket: %i, %i", size, player->GetTempInfoXorSize()); + safe_delete(xor_info_packet); + xor_info_packet = player->SetTempInfoPacketForXOR(size); + } + + uchar* orig_packet = player->GetSpawnInfoPacketForXOR(id); + + if (orig_packet) { + //if (!IsPlayer() && this->EngagedInCombat()) + //packet->PrintPacket(); + memcpy(xor_info_packet, (uchar*)data->c_str(), size); + Encode(xor_info_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_info_packet[i]) { + changed = true; + break; + } + } + + if (!changed) { + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + uchar* tmp = new uchar[size + 1000]; + size = Pack(tmp, xor_info_packet, size, size+1000, version); + player->info_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + + size -= sizeof(int32); + size += CheckOverLoadSize(index); + *info_packet_size = size; + + uchar* tmp2 = new uchar[size]; + uchar* ptr = tmp2; + + ptr += DoOverLoad(index, ptr); + + memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32)); + + delete[] tmp; + + return move(tmp2); +} + +uchar* Spawn::spawn_vis_changes_ex(Player* player, int16 version, int16* vis_packet_size) { + PacketStruct* vis_struct = player->GetSpawnVisStruct(); + int16 index = player->GetIndexForSpawn(this); + + player->vis_mutex.writelock(__FUNCTION__, __LINE__); + + uchar* orig_packet = player->GetSpawnVisPacketForXOR(id); + + vis_struct->ResetData(); + InitializeVisPacketData(player, vis_struct); + + string* data = vis_struct->serializeString(); + int32 size = data->length(); + uchar* xor_vis_packet = player->GetTempVisPacketForXOR(); + + if (!xor_vis_packet || size != player->GetTempVisXorSize()) { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiateVisExPacket: %i, %i", size, player->GetTempVisXorSize()); + safe_delete(xor_vis_packet); + xor_vis_packet = player->SetTempVisPacketForXOR(size); + } + + if (orig_packet) { + //if (!IsPlayer() && this->EngagedInCombat()) + // vis_struct->PrintPacket(); + memcpy(xor_vis_packet, (uchar*)data->c_str(), size); + Encode(xor_vis_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_vis_packet[i]) { + changed = true; + break; + } + } + + if (!changed) { + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + uchar* tmp = new uchar[size + 1000]; + size = Pack(tmp, xor_vis_packet, size, size+1000, version); + + player->vis_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + + size -= sizeof(int32); + size += CheckOverLoadSize(index); + *vis_packet_size = size; + + uchar* tmp2 = new uchar[size]; + uchar* ptr = tmp2; + + ptr += DoOverLoad(index, ptr); + + memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32)); + + delete[] tmp; + + return move(tmp2); +} + +uchar* Spawn::spawn_pos_changes_ex(Player* player, int16 version, int16* pos_packet_size) { + int16 index = player->GetIndexForSpawn(this); + + PacketStruct* packet = player->GetSpawnPosStruct(); + + player->pos_mutex.writelock(__FUNCTION__, __LINE__); + + uchar* orig_packet = player->GetSpawnPosPacketForXOR(id); + + packet->ResetData(); + InitializePosPacketData(player, packet); + + string* data = packet->serializeString(); + int32 size = data->length(); + uchar* xor_pos_packet = player->GetTempPosPacketForXOR(); + + if (!xor_pos_packet || size != player->GetTempPosXorSize()) { + LogWrite(ZONE__DEBUG, 0, "Zone", "InstantiatePosExPacket: %i, %i", size, player->GetTempPosXorSize()); + safe_delete(xor_pos_packet); + xor_pos_packet = player->SetTempPosPacketForXOR(size); + } + + if (orig_packet) { + //if (!IsPlayer() && this->EngagedInCombat()) + // packet->PrintPacket(); + memcpy(xor_pos_packet, (uchar*)data->c_str(), size); + Encode(xor_pos_packet, orig_packet, size); + } + + bool changed = false; + for (int i = 0; i < size; ++i) { + if (xor_pos_packet[i]) { + changed = true; + break; + } + } + + if (!changed && version > 561) { + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + return nullptr; + } + + int16 newSize = size + 1000; + uchar* tmp = new uchar[newSize]; + size = Pack(tmp, xor_pos_packet, size, newSize, version); + player->pos_mutex.releasewritelock(__FUNCTION__, __LINE__); + + int32 orig_size = size; + + if (version >= 1188) { + size += 1; + } + + if (IsPlayer() && version > 561) { + size += 4; + } + + size -= sizeof(int32); + size += CheckOverLoadSize(index); + *pos_packet_size = size; + + uchar* tmp2 = new uchar[size]; + uchar* ptr = tmp2; + + ptr += DoOverLoad(index, ptr); + + // extra byte in coe+ clients, 0 for NPC's 1 for Players + int8 x = 0; + + if (version > 561) { + if (IsPlayer()) { + if (version >= 1188) { + x = 1; + memcpy(ptr, &x, sizeof(int8)); + ptr += sizeof(int8); + } + int32 now = Timer::GetCurrentTime2(); + memcpy(ptr, &now, sizeof(int32)); + ptr += sizeof(int32); + } + else if (version >= 1188) { + memcpy(ptr, &x, sizeof(int8)); + ptr += sizeof(int8); + } + } + + memcpy(ptr, tmp + sizeof(int32), orig_size - sizeof(int32)); + + delete[] tmp; + + return move(tmp2); +} + + +EQ2Packet* Spawn::serialize(Player* player, int16 version){ + return 0; +} + +Spawn* Spawn::GetTarget(){ + Spawn* ret = 0; + + // only attempt to get a spawn if we had a target stored + if (target != 0) + { + ret = GetZone()->GetSpawnByID(target); + + if (!ret) + target = 0; + } + + return ret; +} + +void Spawn::SetTarget(Spawn* spawn){ + SetInfo(&target, spawn ? spawn->GetID() : 0); +} + +Spawn* Spawn::GetLastAttacker() { + Spawn* ret = 0; + ret = GetZone()->GetSpawnByID(last_attacker); + if (!ret) + last_attacker = 0; + return ret; +} + +void Spawn::SetLastAttacker(Spawn* spawn){ + last_attacker = spawn->GetID(); +} + +void Spawn::SetInvulnerable(bool val){ + invulnerable = val; +} + +bool Spawn::GetInvulnerable(){ + return invulnerable; +} + +bool Spawn::TakeDamage(int32 damage){ + if(invulnerable) + return false; + if (IsEntity()) { + if (((Entity*)this)->IsMezzed()) + ((Entity*)this)->RemoveAllMezSpells(); + + if (damage == 0) + return true; + } + + int32 hp = GetHP(); + if(damage >= hp) { + SetHP(0); + if (IsPlayer()) { + ((Player*)this)->InCombat(false); + ((Player*)this)->SetRangeAttack(false); + GetZone()->TriggerCharSheetTimer(); // force char sheet updates now + } + } + else { + SetHP(hp - damage); + // if player flag the char sheet as changed so the ui updates properly + if (IsPlayer()) + ((Player*)this)->SetCharSheetChanged(true); + } + return true; +} +ZoneServer* Spawn::GetZone(){ + return zone; +} + +void Spawn::SetZone(ZoneServer* in_zone, int32 version){ + zone = in_zone; + + if(in_zone) + { + region_map = world.GetRegionMap(std::string(in_zone->GetZoneFile()), version); + current_map = world.GetMap(std::string(in_zone->GetZoneFile()), version); + } + else + { + region_map = nullptr; + current_map = nullptr; + } +} + + +/*** HIT POINT ***/ +void Spawn::SetHP(sint32 new_val, bool setUpdateFlags){ + if(new_val == 0){ + ClearRunningLocations(); + CalculateRunningLocation(true); + + if(IsEntity()) { + is_alive = false; + } + } + if(IsNPC() && new_val > 0 && !is_alive) { + is_alive = true; + } + else if(IsPlayer() && new_val > 0 && !is_alive) { + LogWrite(SPAWN__ERROR, 0, "Spawn", "Cannot change player HP > 0 while dead (%s)! Player must revive.", GetName()); + return; + } + if(new_val > basic_info.max_hp) + SetInfo(&basic_info.max_hp, new_val, setUpdateFlags); + SetInfo(&basic_info.cur_hp, new_val, setUpdateFlags); + if(/*IsPlayer() &&*/ GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); + + if ( IsPlayer() && new_val == 0 ) // fixes on death not showing hp update for players + ((Player*)this)->SetCharSheetChanged(true); + + if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() && ((NPC*)this)->GetOwner()->IsPlayer()) { + Player* player = (Player*)((NPC*)this)->GetOwner(); + if (player->GetPet() && player->GetCharmedPet()) { + if (this == player->GetPet()) { + player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp); + player->SetCharSheetChanged(true); + } + } + else { + player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp); + player->SetCharSheetChanged(true); + } + } +} +void Spawn::SetTotalHP(sint32 new_val){ + if(basic_info.hp_base == 0) { + SetTotalHPBase(new_val); + SetTotalHPBaseInstance(new_val); + } + SetInfo(&basic_info.max_hp, new_val); + + if(GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); + + if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) { + Player* player = (Player*)((NPC*)this)->GetOwner(); + if (basic_info.max_hp && player->GetPet() && player->GetCharmedPet()) { + if (this == player->GetPet()) { + player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp); + player->SetCharSheetChanged(true); + } + } + else if(basic_info.max_hp) { + player->GetInfoStruct()->set_pet_health_pct((float)basic_info.cur_hp / (float)basic_info.max_hp); + player->SetCharSheetChanged(true); + } + } +} +void Spawn::SetTotalHPBase(sint32 new_val) +{ + SetInfo(&basic_info.hp_base, new_val); + + if(GetZone() && basic_info.cur_hp > 0 && basic_info.cur_hp < basic_info.max_hp) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); +} + +void Spawn::SetTotalHPBaseInstance(sint32 new_val) +{ + SetInfo(&basic_info.hp_base_instance, new_val); +} + +sint32 Spawn::GetHP() +{ + return basic_info.cur_hp; +} +sint32 Spawn::GetTotalHP() +{ + return basic_info.max_hp; +} +sint32 Spawn::GetTotalHPBase() +{ + return basic_info.hp_base; +} +sint32 Spawn::GetTotalHPBaseInstance() +{ + return basic_info.hp_base_instance; +} +sint32 Spawn::GetTotalPowerBaseInstance() +{ + return basic_info.power_base_instance; +} + + +/*** POWER ***/ +void Spawn::SetPower(sint32 power, bool setUpdateFlags){ + if(power > basic_info.max_power) + SetInfo(&basic_info.max_power, power, setUpdateFlags); + SetInfo(&basic_info.cur_power, power, setUpdateFlags); + if(/*IsPlayer() &&*/ GetZone() && basic_info.cur_power < basic_info.max_power) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); + + if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) { + Player* player = (Player*)((NPC*)this)->GetOwner(); + if (player->GetPet() && player->GetCharmedPet()) { + if (this == player->GetPet()) { + player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power); + player->SetCharSheetChanged(true); + } + } + else { + player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power); + player->SetCharSheetChanged(true); + } + } +} +void Spawn::SetTotalPower(sint32 new_val) +{ + if(basic_info.power_base == 0) { + SetTotalPowerBase(new_val); + SetTotalPowerBaseInstance(new_val); + } + SetInfo(&basic_info.max_power, new_val); + + if(GetZone() && basic_info.cur_power < basic_info.max_power) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); + + if (IsNPC() && ((NPC*)this)->IsPet() && ((NPC*)this)->GetOwner() != nullptr && ((NPC*)this)->GetOwner()->IsPlayer()) { + Player* player = (Player*)((NPC*)this)->GetOwner(); + if (player->GetPet() && player->GetCharmedPet()) { + if (this == player->GetPet()) { + player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power); + player->SetCharSheetChanged(true); + } + } + else { + player->GetInfoStruct()->set_pet_power_pct((float)basic_info.cur_power / (float)basic_info.max_power); + player->SetCharSheetChanged(true); + } + } +} +void Spawn::SetTotalPowerBase(sint32 new_val) +{ + SetInfo(&basic_info.power_base, new_val); + + if(GetZone() && basic_info.cur_power < basic_info.max_power) + GetZone()->AddDamagedSpawn(this); + + SendGroupUpdate(); +} + +void Spawn::SetTotalPowerBaseInstance(sint32 new_val) +{ + SetInfo(&basic_info.power_base_instance, new_val); +} + +sint32 Spawn::GetPower() +{ + return basic_info.cur_power; +} +sint32 Spawn::GetTotalPower(){ + return basic_info.max_power; +} +sint32 Spawn::GetTotalPowerBase() +{ + return basic_info.power_base; +} + + +/*** SAVAGERY ***/ +void Spawn::SetSavagery(sint32 savagery, bool setUpdateFlags) +{ + /* JA: extremely limited functionality until we better understand Savagery */ + if(savagery > basic_info.max_savagery) + SetInfo(&basic_info.max_savagery, savagery, setUpdateFlags); + + SetInfo(&basic_info.cur_savagery, savagery, setUpdateFlags); +} +void Spawn::SetTotalSavagery(sint32 new_val) +{ + /* JA: extremely limited functionality until we better understand Savagery */ + if(basic_info.savagery_base == 0) + SetTotalSavageryBase(new_val); + + SetInfo(&basic_info.max_savagery, new_val); +} +void Spawn::SetTotalSavageryBase(sint32 new_val){ + SetInfo(&basic_info.savagery_base, new_val); + + SendGroupUpdate(); +} +sint32 Spawn::GetTotalSavagery() +{ + return basic_info.max_savagery; +} +sint32 Spawn::GetSavagery() +{ + return basic_info.cur_savagery; +} + + +/*** DISSONANCE ***/ +void Spawn::SetDissonance(sint32 dissonance, bool setUpdateFlags) +{ + /* JA: extremely limited functionality until we better understand Dissonance */ + if(dissonance > basic_info.max_dissonance) + SetInfo(&basic_info.max_dissonance, dissonance, setUpdateFlags); + + SetInfo(&basic_info.cur_dissonance, dissonance, setUpdateFlags); +} +void Spawn::SetTotalDissonance(sint32 new_val) +{ + /* JA: extremely limited functionality until we better understand Dissonance */ + if(basic_info.dissonance_base == 0) + SetTotalDissonanceBase(new_val); + + SetInfo(&basic_info.max_dissonance, new_val); + +} +void Spawn::SetTotalDissonanceBase(sint32 new_val) +{ + SetInfo(&basic_info.dissonance_base, new_val); + + SendGroupUpdate(); +} +sint32 Spawn::GetTotalDissonance() +{ + return basic_info.max_dissonance; +} +sint32 Spawn::GetDissonance() +{ + return basic_info.cur_dissonance; +} + +/* --< Alternate Advancement Points >-- */ +void Spawn::SetAssignedAA(sint16 new_val) +{ + SetInfo(&basic_info.assigned_aa, new_val); +} + +void Spawn::SetUnassignedAA(sint16 new_val) +{ + SetInfo(&basic_info.unassigned_aa, new_val); +} + +void Spawn::SetTradeskillAA(sint16 new_val) +{ + SetInfo(&basic_info.tradeskill_aa, new_val); +} + +void Spawn::SetUnassignedTradeskillAA(sint16 new_val) +{ + SetInfo(&basic_info.unassigned_tradeskill_aa, new_val); +} + +void Spawn::SetPrestigeAA(sint16 new_val) +{ + SetInfo(&basic_info.prestige_aa, new_val); +} + +void Spawn::SetUnassignedPrestigeAA(sint16 new_val) +{ + SetInfo(&basic_info.unassigned_prestige_aa, new_val); +} + +void Spawn::SetTradeskillPrestigeAA(sint16 new_val) +{ + SetInfo(&basic_info.tradeskill_prestige_aa, new_val); +} + +void Spawn::SetUnassignedTradeskillPrestigeAA(sint16 new_val) +{ + SetInfo(&basic_info.unassigned_tradeskill_prestige_aa, new_val); +} + +void Spawn::SetAAXPRewards(int32 value) +{ + SetInfo(&basic_info.aaxp_rewards, value, false); +} + +sint16 Spawn::GetAssignedAA() +{ + return basic_info.assigned_aa; +} + +sint16 Spawn::GetUnassignedAA() +{ + return basic_info.unassigned_aa; +} + +sint16 Spawn::GetTradeskillAA() +{ + return basic_info.tradeskill_aa; +} + +sint16 Spawn::GetUnassignedTradeskillAA() +{ + return basic_info.unassigned_tradeskill_aa; +} + +sint16 Spawn::GetPrestigeAA() +{ + return basic_info.prestige_aa; +} + +sint16 Spawn::GetUnassignedPretigeAA() +{ + return basic_info.unassigned_prestige_aa; +} + +sint16 Spawn::GetTradeskillPrestigeAA() +{ + return basic_info.tradeskill_prestige_aa; +} + +sint16 Spawn::GetUnassignedTradeskillPrestigeAA() +{ + return basic_info.unassigned_tradeskill_prestige_aa; +} + +int32 Spawn::GetAAXPRewards() +{ + return basic_info.aaxp_rewards; +} + +float Spawn::GetDistance(float x1, float y1, float z1, float x2, float y2, float z2){ + x1 = x1 - x2; + y1 = y1 - y2; + z1 = z1 - z2; + return sqrt(x1*x1 + y1*y1 + z1*z1); +} + +float Spawn::GetDistance(float x, float y, float z, float radius, bool ignore_y) { + if (ignore_y) + return GetDistance(x, y, z, GetX(), y, GetZ()) - radius; + else + return GetDistance(x, y, z, GetX(), GetY(), GetZ()) - radius; +} + +float Spawn::GetDistance(float x, float y, float z, bool ignore_y) { + return GetDistance(x, y, z, 0.0f, ignore_y); +} +float Spawn::GetDistance(Spawn* spawn, bool ignore_y, bool includeRadius){ + float ret = 0; + + if (spawn) + { + float radius = 0.0f; + if (includeRadius) + radius = CalculateRadius(spawn); + ret = GetDistance(spawn->GetX(), spawn->GetY(), spawn->GetZ(), radius, ignore_y); + } + + // maybe distance against ourselves, in that case we want to nullify the radius check + if (ret < 0) + ret = 0.0f; + + return ret; +} + +float Spawn::GetDistance(Spawn* spawn, float x1, float y1, float z1, bool includeRadius) { + float ret = 0; + + if (spawn) + { + float radius = 0.0f; + if (includeRadius) + radius = CalculateRadius(spawn); + ret = GetDistance(x1, y1, z1, spawn->GetX(), spawn->GetY(), spawn->GetZ()) - radius; + } + + // maybe distance against ourselves, in that case we want to nullify the radius check + if (ret < 0) + ret = 0.0f; + + return ret; +} + +float Spawn::CalculateRadius(Spawn* target) +{ + float srcRadius = short_to_float(appearance.pos.collision_radius); + if (target) + { + float targRadius = short_to_float(target->appearance.pos.collision_radius); + return (targRadius / 32.0f) + (srcRadius / 32.0f); + } + else + return (srcRadius / 32.0f); +} + +int32 Spawn::GetRespawnTime(){ + return respawn; +} + +void Spawn::SetRespawnTime(int32 time){ + respawn = time; +} + +int32 Spawn::GetExpireOffsetTime(){ + return expire_offset; +} + +void Spawn::SetExpireOffsetTime(int32 time){ + expire_offset = time; +} + +int32 Spawn::GetSpawnLocationID(){ + return spawn_location_id; +} + +void Spawn::SetSpawnLocationID(int32 id){ + spawn_location_id = id; +} + +int32 Spawn::GetSpawnEntryID(){ + return spawn_entry_id; +} + +void Spawn::SetSpawnEntryID(int32 id){ + spawn_entry_id = id; +} + +int32 Spawn::GetSpawnLocationPlacementID(){ + return spawn_location_spawns_id; +} + +void Spawn::SetSpawnLocationPlacementID(int32 id){ + spawn_location_spawns_id = id; +} + +const char* Spawn::GetSpawnScript(){ + if(spawn_script.length() > 0) + return spawn_script.c_str(); + else + return 0; +} + +void Spawn::SetSpawnScript(string name){ + spawn_script = name; +} + +void Spawn::SetPrimaryCommand(const char* name, const char* command, float distance){ + EntityCommand* entity_command = CreateEntityCommand(name, distance, command, "", 0, 0); + if(primary_command_list.size() > 0 && primary_command_list[0]){ + safe_delete(primary_command_list[0]); + primary_command_list[0] = entity_command; + } + else + primary_command_list.push_back(entity_command); +} + +void Spawn::SetSecondaryCommands(vector* commands){ + if(commands && commands->size() > 0){ + vector::iterator itr; + if(secondary_command_list.size() > 0){ + for(itr = secondary_command_list.begin(); itr != secondary_command_list.end(); itr++){ + safe_delete(*itr); + } + secondary_command_list.clear(); + } + EntityCommand* command = 0; + for(itr = commands->begin(); itr != commands->end(); itr++){ + command = CreateEntityCommand(*itr); + secondary_command_list.push_back(command); + } + } +} + +void Spawn::SetPrimaryCommands(vector* commands){ + if(commands && commands->size() > 0){ + vector::iterator itr; + if(primary_command_list.size() > 0){ + for(itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++){ + safe_delete(*itr); + } + primary_command_list.clear(); + } + EntityCommand* command = 0; + for(itr = commands->begin(); itr != commands->end(); itr++){ + command = CreateEntityCommand(*itr); + primary_command_list.push_back(command); + } + } +} + +EntityCommand* Spawn::FindEntityCommand(string command, bool primaryOnly) { + EntityCommand* entity_command = 0; + if (primary_command_list.size() > 0) { + vector::iterator itr; + for (itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++) { + if ((*itr)->command.compare(command) == 0) { + entity_command = *itr; + break; + } + } + } + + if (primaryOnly) + return entity_command; + + if (!entity_command && secondary_command_list.size() > 0) { + vector::iterator itr; + for (itr = secondary_command_list.begin(); itr != secondary_command_list.end(); itr++) { + if ((*itr)->command == command) { + entity_command = *itr; + break; + } + } + } + return entity_command; +} + +void Spawn::SetSizeOffset(int8 offset){ + size_offset = offset; +} + +int8 Spawn::GetSizeOffset(){ + return size_offset; +} + +void Spawn::SetMerchantID(int32 val){ + merchant_id = val; +} + +int32 Spawn::GetMerchantID(){ + return merchant_id; +} + +void Spawn::SetMerchantType(int8 val) { + merchant_type = val; +} + +int8 Spawn::GetMerchantType() { + return merchant_type; +} + +void Spawn::SetMerchantLevelRange(int32 minLvl, int32 maxLvl) { + merchant_min_level = minLvl; + merchant_max_level = maxLvl; +} + +int32 Spawn::GetMerchantMinLevel() { + return merchant_min_level; +} + +int32 Spawn::GetMerchantMaxLevel() { + return merchant_max_level; +} + +bool Spawn::IsClientInMerchantLevelRange(Client* client, bool sendMessageIfDenied) +{ + if (!client) + return false; + + if (GetMerchantMinLevel() && client->GetPlayer()->GetLevel() < GetMerchantMinLevel()) + { + client->Message(CHANNEL_COLOR_RED, "You are unable to interact with this merchant due to a minimum level %u allowed.", GetMerchantMinLevel()); + return false; + } + else if (GetMerchantMaxLevel() && client->GetPlayer()->GetLevel() > GetMerchantMaxLevel()) + { + client->Message(CHANNEL_COLOR_RED, "You are unable to interact with this merchant due to a maximum level %u allowed.", GetMerchantMaxLevel()); + return false; + } + + return true; +} + +void Spawn::SetQuestsRequired(Spawn* new_spawn){ + m_requiredQuests.writelock(__FUNCTION__, __LINE__); + if(required_quests.size() > 0){ + map* >::iterator itr; + for(itr = required_quests.begin(); itr != required_quests.end(); itr++){ + vector* quest_steps = itr->second; + for (int32 i = 0; i < quest_steps->size(); i++) + new_spawn->SetQuestsRequired(itr->first, quest_steps->at(i)); + } + } + m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__); +} + +void Spawn::SetQuestsRequired(int32 quest_id, int16 quest_step){ + m_requiredQuests.writelock(__FUNCTION__, __LINE__); + if (required_quests.count(quest_id) == 0) + required_quests.insert(make_pair(quest_id, new vector)); + else{ + for (int32 i = 0; i < required_quests[quest_id]->size(); i++){ + if (required_quests[quest_id]->at(i) == quest_step){ + m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__); + return; + } + } + } + + has_quests_required = true; + required_quests[quest_id]->push_back(quest_step); + m_requiredQuests.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Spawn::HasQuestsRequired(){ + return has_quests_required; +} + +bool Spawn::HasHistoryRequired(){ + return has_history_required; +} + +void Spawn::SetRequiredHistory(int32 event_id, int32 value1, int32 value2){ + LUAHistory* set_value = new LUAHistory(); + set_value->Value = value1; + set_value->Value2 = value2; + set_value->SaveNeeded = false; + m_requiredHistory.writelock(__FUNCTION__, __LINE__); + if (required_history.count(event_id) == 0) + required_history.insert(make_pair(event_id, set_value)); + else + { + LUAHistory* tmp_value = required_history[event_id]; + required_history[event_id] = set_value; + safe_delete(tmp_value); + } + has_history_required = true; + m_requiredHistory.releasewritelock(__FUNCTION__, __LINE__); +} + +void Spawn::SetTransporterID(int32 id){ + transporter_id = id; +} + +int32 Spawn::GetTransporterID(){ + return transporter_id; +} + +void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool bSpawnUpdate) { + int16 version = packet->GetVersion(); + + int32 new_grid_id = 0; + int32 new_widget_id = 0; + float new_y = 0.0f; + float ground_diff = 0.0f; + if(player->GetMap() != nullptr && player->GetMap()->IsMapLoaded()) + { + m_GridMutex.writelock(__FUNCTION__, __LINE__); + std::map::iterator itr = established_grid_id.find(version); + if ( itr == established_grid_id.end() || itr->second.timestamp <= (Timer::GetCurrentTime2())) + { + if(itr != established_grid_id.end() && itr->second.x == GetX() && itr->second.z == GetZ() && !itr->second.npc_save) { + itr->second.timestamp = Timer::GetCurrentTime2()+100; + itr->second.npc_save = false; + new_grid_id = itr->second.grid_id; + new_widget_id = itr->second.widget_id; + new_y = itr->second.offset_y; + + if(player->GetMap() != GetMap()) { + ground_diff = sqrtf((GetY() - itr->second.zone_ground_y) * (GetY() - itr->second.zone_ground_y)); + } + } + else { + auto loc = glm::vec3(GetX(), GetZ(), GetY()); + new_y = player->FindBestZ(loc, nullptr, &new_grid_id, &new_widget_id); + float zone_ground_y = new_y; + if(player->GetMap() != GetMap()) { + zone_ground_y = FindBestZ(loc, nullptr, &new_grid_id, &new_widget_id); + } + TimedGridData data; + data.grid_id = new_grid_id; + data.widget_id = new_widget_id; + data.x = GetX(); + data.y = GetY(); + data.z = GetZ(); + data.offset_y = new_y; + data.zone_ground_y = zone_ground_y; + ground_diff = sqrtf((GetY() - zone_ground_y) * (GetY() - zone_ground_y)); + data.npc_save = false; + data.timestamp = Timer::GetCurrentTime2()+100; + established_grid_id.insert(make_pair(packet->GetVersion(), data)); + } + } + else { + new_grid_id = itr->second.grid_id; + new_widget_id = itr->second.widget_id; + new_y = itr->second.offset_y; + ground_diff = sqrtf((GetY() - itr->second.zone_ground_y) * (GetY() - itr->second.zone_ground_y)); + } + m_GridMutex.releasewritelock(__FUNCTION__, __LINE__); + } + + if(IsKnockedBack()) { + packet->setDataByName("pos_grid_id", 0); + } + else { + packet->setDataByName("pos_grid_id", new_grid_id != 0 ? new_grid_id : GetLocation()); + } + + bool include_heading = true; + if (IsWidget() && ((Widget*)this)->GetIncludeHeading() == false) + include_heading = false; + else if (IsSign() && ((Sign*)this)->GetIncludeHeading() == false) + include_heading = false; + + if (include_heading){ + packet->setDataByName("pos_heading1", appearance.pos.Dir1); + packet->setDataByName("pos_heading2", appearance.pos.Dir2); + } + + if (version <= 910) { + packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32); + packet->setDataByName("pos_size", size > 0 ? size : 32); + packet->setDataByName("pos_size_multiplier", 32); //32 is normal + } + else { + if (size == 0) + size = 32; + + packet->setDataByName("pos_collision_radius", appearance.pos.collision_radius > 0 ? appearance.pos.collision_radius : 32); + + packet->setDataByName("pos_size", 1.0f); + + if (!IsPlayer()) + packet->setDataByName("pos_size", size > 0 ? (((float)size) / 32.0f) : 1.0f); // float not an integer + else + packet->setDataByName("pos_size", 1.0f); + + // please do not remove! This makes it so NPCs for example do not resize large/small when you are in combat with them! + packet->setDataByName("pos_size_ratio", 1.0f); + } + packet->setDataByName("pos_state", appearance.pos.state); + + bool include_location = true; + if (IsWidget() && ((Widget*)this)->GetIncludeLocation() == false) + include_location = false; + else if (IsSign() && ((Sign*)this)->GetIncludeLocation() == false) + include_location = false; + + if (include_location){ + if (IsWidget() && ((Widget*)this)->GetMultiFloorLift()) { + Widget* widget = (Widget*)this; + float x = appearance.pos.X - widget->GetWidgetX(); + float y = appearance.pos.Y - widget->GetWidgetY(); + float z = appearance.pos.Z - widget->GetWidgetZ(); + + packet->setDataByName("pos_x", x); + packet->setDataByName("pos_y", y); + packet->setDataByName("pos_z", z); + } + else { + packet->setDataByName("pos_x", appearance.pos.X); + float result_y = appearance.pos.Y; + if(!IsWidget() && !IsSign() && !(IsFlyingCreature() || IsWaterCreature() || InWater())) { + result_y = new_y; + } + if(GetMap() != player->GetMap()) { + result_y = new_y; + if(IsFlyingCreature() || IsWaterCreature() || InWater()) { + result_y += ground_diff; + } + } + packet->setDataByName("pos_y", result_y); + packet->setDataByName("pos_z", appearance.pos.Z); + } + if (IsSign()) + packet->setDataByName("pos_unknown6", 3, 2); + } + + if (IsPlayer()) { + Skill* skill = ((Player*)this)->GetSkillByName("Swimming", false); + sint16 swim_modifier = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMinSpeed)->GetSInt16(); + if(skill) { + sint16 max_val = 450; + if(skill->max_val > 0) + max_val = skill->max_val; + sint16 max_swim_mod = rule_manager.GetGlobalRule(R_Player, SwimmingSkillMaxSpeed)->GetSInt16(); + float diff = (float)(skill->current_val + ((Player*)this)->GetStat(ITEM_STAT_SWIMMING)) / (float)max_val; + sint16 diff_mod = (sint16)(float)max_swim_mod * diff; + if(diff_mod > max_swim_mod) + swim_modifier = max_swim_mod; + else if(diff_mod > swim_modifier) + swim_modifier = diff_mod; + } + packet->setDataByName("pos_swim_speed_modifier", swim_modifier); + packet->setDataByName("pos_x_velocity", TransformFromFloat(GetSpeedX(), 5)); + packet->setDataByName("pos_y_velocity", TransformFromFloat(GetSpeedY(), 5)); + packet->setDataByName("pos_z_velocity", TransformFromFloat(GetSpeedZ(), 5)); + } + if (appearance.pos.X2 == 0 && appearance.pos.Y2 == 0 && appearance.pos.Z2 && (appearance.pos.X != 0 || appearance.pos.Y != 0 || appearance.pos.Z != 0)) { + appearance.pos.X2 = appearance.pos.X; + appearance.pos.Y2 = appearance.pos.Y; + appearance.pos.Z2 = appearance.pos.Z; + } + if (appearance.pos.X3 == 0 && appearance.pos.Y3 == 0 && appearance.pos.Z3 && (appearance.pos.X != 0 || appearance.pos.Y != 0 || appearance.pos.Z != 0)) { + appearance.pos.X3 = appearance.pos.X; + appearance.pos.Y3 = appearance.pos.Y; + appearance.pos.Z3 = appearance.pos.Z; + } + //Transform To/From Float bits (original client) + //pos_loc_offset[3]: 5 + //pos_x_velocity: 5 + //pos_y_velocity: 5 + //pos_z_velocity: 5 + //pos_heading1: 6 + //pos_heading2: 6 + //pos_speed: 8 + //pos_dest_loc_offset[3]: 5 + //pos_dest_loc_offset2[3]: 5 + //pos_heading_speed: 5 + //pos_move_type: 5 (speed_modifier) + //pos_swim_speed_modifier: 5 + //pos_side_speed: 8 + //pos_vert_speed: 8 + //pos_requested_pitch: 6 + //pos_requested_pitch_speed: 5 + //pos_pitch: 6 + //pos_collision_radius: 5 + //pos_size: 5 + //actor_stop_range: 5 + //this is for original box client, destinations used to be offsets + if(version <= 373 || version > 561 || !IsPlayer()) { + if (appearance.pos.X2 != 0 || appearance.pos.Y2 != 0 || appearance.pos.Z2 != 0) { + packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.X2 - appearance.pos.X, 5)); + packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.Y2 - appearance.pos.Y, 5), 1); + packet->setDataByName("pos_dest_loc_offset", TransformFromFloat(appearance.pos.Z2 - appearance.pos.Z, 5), 2); + } + if (appearance.pos.X3 != 0 || appearance.pos.Y3 != 0 || appearance.pos.Z3 != 0) { + packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.X3 - appearance.pos.X, 5)); + packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.Y3 - appearance.pos.Y, 5), 1); + packet->setDataByName("pos_dest_loc_offset2", TransformFromFloat(appearance.pos.Z3 - appearance.pos.Z, 5), 2); + } + } + + bool bSendSpeed = true; + + // fixes lifts dropping back to the floor in zones like northfreeport when set as TransportSpawn (Some zones do not support multifloor lifts) + if (IsWidget() && (((Widget*)this)->GetMultiFloorLift() || (IsTransportSpawn()))) { + Widget* widget = (Widget*)this; + + float x; + float y; + float z; + + bool setCoords = false; + if(!widget->GetMultiFloorLift() && version >= 561) { + packet->setDataByName("pos_next_x", appearance.pos.X2); + packet->setDataByName("pos_next_y", appearance.pos.Y2); + packet->setDataByName("pos_next_z", appearance.pos.Z2); + + packet->setDataByName("pos_x3", appearance.pos.X); + packet->setDataByName("pos_y3", appearance.pos.Y); + packet->setDataByName("pos_z3", appearance.pos.Z); + setCoords = true; + } + else if (IsRunning()){ + x = appearance.pos.X2 - widget->GetWidgetX(); + y = appearance.pos.Y2 - widget->GetWidgetY(); + z = appearance.pos.Z2 - widget->GetWidgetZ(); + } + else { + x = appearance.pos.X - widget->GetWidgetX(); + y = appearance.pos.Y - widget->GetWidgetY(); + z = appearance.pos.Z - widget->GetWidgetZ(); + } + + if(!setCoords) { + packet->setDataByName("pos_next_x", x); + packet->setDataByName("pos_next_y", y); + packet->setDataByName("pos_next_z", z); + + packet->setDataByName("pos_x3", x); + packet->setDataByName("pos_y3", y); + packet->setDataByName("pos_z3", z); + } + } + else if(IsFlyingCreature() || IsWaterCreature() || InWater()) { + packet->setDataByName("pos_next_x", appearance.pos.X2); + packet->setDataByName("pos_next_y", (GetMap() != player->GetMap()) ? (ground_diff + new_y) : appearance.pos.Y2); + packet->setDataByName("pos_next_z", appearance.pos.Z2); + + packet->setDataByName("pos_x3", appearance.pos.X); + packet->setDataByName("pos_y3", (GetMap() != player->GetMap()) ? (ground_diff + new_y) : appearance.pos.Y); + packet->setDataByName("pos_z3", appearance.pos.Z); + } + //If this is a spawn update or this spawn is currently moving we can send these values, otherwise set speed and next_xyz to 0 + //This fixes the bug where spawns with movement scripts face south when initially spawning if they are at their target location. + else if (bSpawnUpdate || memcmp(&appearance.pos.X, &appearance.pos.X2, sizeof(float) * 3) != 0) { + packet->setDataByName("pos_next_x", appearance.pos.X2); + packet->setDataByName("pos_next_y", appearance.pos.Y2); + packet->setDataByName("pos_next_z", appearance.pos.Z2); + packet->setDataByName("pos_x3", appearance.pos.X3); + packet->setDataByName("pos_y3", appearance.pos.Y3); + packet->setDataByName("pos_z3", appearance.pos.Z3); + } + else + { + bSendSpeed = false; + } + //packet->setDataByName("pos_unknown2", 4, 2); + + int16 speed_multiplier = rule_manager.GetGlobalRule(R_Spawn, SpeedMultiplier)->GetInt16(); // was 1280, 600 and now 300... investigating why + int8 movement_mode = 0; + if (IsPlayer()) { + Player* player = static_cast(this); + sint16 pos_packet_speed = player->GetPosPacketSpeed() * speed_multiplier; + sint16 side_speed = player->GetSideSpeed() * speed_multiplier; + packet->setDataByName("pos_speed", pos_packet_speed); + packet->setDataByName("pos_side_speed", side_speed); + } + else if (bSendSpeed && (!IsNPC() || Alive())) { + sint16 side_speed = GetSpeed() * speed_multiplier; + packet->setDataByName("pos_speed", side_speed); + if(side_speed != 0 && ((IsWidget() && ((Widget*)this)->GetMultiFloorLift()) || IsTransportSpawn())) { + movement_mode = 2; + } + } + else if(((IsWidget() && ((Widget*)this)->GetMultiFloorLift()) || (!IsNPC() && version <= 561 && !IsRunning()))) { + movement_mode = 2; + } + if(IsFlyingCreature() || IsWaterCreature() || InWater()) { + movement_mode = 2; + } + + if (IsNPC() || IsPlayer()) { + packet->setDataByName("pos_move_type", 25); + } + else if (IsWidget() || IsSign()) { + packet->setDataByName("pos_move_type", 11); + } + else if(IsGroundSpawn()) { + packet->setDataByName("pos_move_type", 16); + } + + if (!IsPlayer()) { // has to be 2 or NPC's warp around when moving + packet->setDataByName("pos_movement_mode", movement_mode); + } + + packet->setDataByName("face_actor_id", 0xFFFFFFFF); + + packet->setDataByName("pos_pitch1", appearance.pos.Pitch1); + packet->setDataByName("pos_pitch2", appearance.pos.Pitch2); + packet->setDataByName("pos_roll", appearance.pos.Roll); +} + +void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) { + int16 version = packet->GetVersion(); + + bool spawnHiddenFromClient = false; + + int8 classicFlags = 0; + // radius of 0 is always seen, -1 is never seen (unless items/spells override), larger than 0 is a defined radius to restrict visibility + sint32 radius = rule_manager.GetGlobalRule(R_PVP, InvisPlayerDiscoveryRange)->GetSInt32(); + if (radius != 0 && (Spawn*)spawn != this && this->IsPlayer() && !spawn->CanSeeInvis((Entity*)this)) + spawnHiddenFromClient = true; + + if (!spawnHiddenFromClient && (appearance.targetable == 1 || appearance.show_level == 1 || appearance.display_name == 1)) { + if (!IsObject() && !IsGroundSpawn() && !IsWidget() && !IsSign()) { + int8 percent = 0; + if (GetHP() > 0) + percent = (int8)(((float)GetHP() / GetTotalHP()) * 100); + + if (version >= 373) { + if (percent < 100) { + packet->setDataByName("hp_remaining", 100 ^ percent); + } + else + packet->setDataByName("hp_remaining", 0); + } + else { + if (percent > 100) + percent = 100; + packet->setDataByName("hp_remaining", percent); + } + if (GetTotalPower() > 0) { + percent = (int8)(((float)GetPower() / GetTotalPower()) * 100); + if (percent > 0) + packet->setDataByName("power_percent", percent); + else + packet->setDataByName("power_percent", 0); + } + } + } + + if (version <= 561) { + packet->setDataByName("name", appearance.name); + for (int8 i = 0; i < 8; i++) + packet->setDataByName("unknown1", 0xFF, i); + if (appearance.show_level == 0) + packet->setDataByName("hide_health", 1); + } + if (GetHP() <= 0 && IsEntity()) { + packet->setDataByName("corpse", 1); + if(HasLoot()) + packet->setDataByName("loot_icon", 1); + } + if (!IsPlayer()) + packet->setDataByName("npc", 1); + if (GetMerchantID() > 0) + packet->setDataByName("merchant", 1); + + packet->setDataByName("effective_level", IsEntity() && ((Entity*)this)->GetInfoStruct()->get_effective_level() != 0 ? (int8)((Entity*)this)->GetInfoStruct()->get_effective_level() : (int8)GetLevel()); + packet->setDataByName("level", (int8)GetLevel()); + packet->setDataByName("unknown4", (int8)GetLevel()); + packet->setDataByName("difficulty", GetDifficulty()); //6); + packet->setDataByName("unknown6", 1); + packet->setDataByName("heroic_flag", appearance.heroic_flag); + packet->setDataByName("class", appearance.adventure_class); + + int16 model_type = appearance.model_type; + if (GetIllusionModel() != 0) { + if (IsPlayer()) { + if (((Player*)this)->get_character_flag(CF_SHOW_ILLUSION)) { + model_type = GetIllusionModel(); + } + } + else + model_type = GetIllusionModel(); + } + + int16 sogaModelType = appearance.soga_model_type; + if (spawnHiddenFromClient) + { + model_type = 0; + sogaModelType = 0; + } + + if(version <= 373 && (model_type == 5864 || model_type == 5865 || model_type == 4015)) { + model_type = 4034; + } + else if(version <= 561 && model_type == 7039) { // goblin + + model_type = 145; + } + packet->setDataByName("model_type", model_type); + if (appearance.soga_model_type == 0) + packet->setDataByName("soga_model_type", model_type); + else + packet->setDataByName("soga_model_type", sogaModelType); + + int16 action_state = appearance.action_state; + if(IsEntity()) { + std::string actionState = ""; + if (GetTempActionState() >= 0) { + action_state = GetTempActionState(); + actionState = ((Entity*)this)->GetInfoStruct()->get_combat_action_state(); + } + else { + actionState = ((Entity*)this)->GetInfoStruct()->get_action_state(); + } + + Client* client = spawn->GetClient(); + if(IsEntity() && client) { + if(actionState.size() > 0) { + Emote* emote = visual_states.FindEmote(actionState, client->GetVersion()); + if(emote != NULL) + action_state = emote->GetVisualState(); + } + } + } + packet->setDataByName("action_state", action_state); + + bool scaredOfPlayer = false; + + if(IsCollector() && spawn->GetCollectionList()->HasCollectionsToHandIn()) + packet->setDataByName("visual_state", VISUAL_STATE_COLLECTION_TURN_IN); + else if(!IsRunning() && IsNPC() && IsScaredByStrongPlayers() && spawn->GetArrowColor(GetLevel()) == ARROW_COLOR_GRAY && + (GetDistance(spawn)) <= ((NPC*)this)->GetAggroRadius() && CheckLoS(spawn)) { + packet->setDataByName("visual_state", VISUAL_STATE_IDLE_AFRAID); + scaredOfPlayer = true; + } + else if (GetTempVisualState() >= 0) + packet->setDataByName("visual_state", GetTempVisualState()); + else + packet->setDataByName("visual_state", appearance.visual_state); + + if (IsNPC() && !IsPet() && !scaredOfPlayer) + { + if(((Entity*)this)->GetInfoStruct()->get_interaction_flag()) { + if(((Entity*)this)->GetInfoStruct()->get_interaction_flag() == 255) { + packet->setDataByName("interaction_flag", 0); + classicFlags += INFO_CLASSIC_FLAG_NOLOOK; + } + else { + packet->setDataByName("interaction_flag", ((Entity*)this)->GetInfoStruct()->get_interaction_flag()); //this makes NPCs head turn to look at you (12) + } + } + else { + packet->setDataByName("interaction_flag", 12); //turn head since no other value is set + } + } + packet->setDataByName("emote_state", appearance.emote_state); + packet->setDataByName("mood_state", appearance.mood_state); + packet->setDataByName("gender", appearance.gender); + packet->setDataByName("race", appearance.race); + if (IsEntity()) { + Entity* entity = ((Entity*)this); + packet->setDataByName("combat_voice", entity->GetCombatVoice()); + packet->setDataByName("emote_voice", entity->GetEmoteVoice()); + for (int i = 0; i < 25; i++) { + if (i == 2) { //don't send helm if hidden flag + if (IsPlayer()) { + if (((Player*)this)->get_character_flag(CF_HIDE_HELM)) { + packet->setDataByName("equipment_types", 0, i); + packet->setColorByName("equipment_colors", 0, i); + packet->setColorByName("equipment_highlights", 0, i); + continue; + } + } + if (IsBot()) { + if (!((Bot*)this)->ShowHelm) { + packet->setDataByName("equipment_types", 0, i); + packet->setColorByName("equipment_colors", 0, i); + packet->setColorByName("equipment_highlights", 0, i); + continue; + } + } + } + else if (i == 19) { //don't send cloak if hidden + if (IsPlayer()) { + if (!((Player*)this)->get_character_flag(CF_SHOW_CLOAK)) { + packet->setDataByName("equipment_types", 0, i); + packet->setColorByName("equipment_colors", 0, i); + packet->setColorByName("equipment_highlights", 0, i); + continue; + } + } + if (IsBot()) { + if (!((Bot*)this)->ShowCloak) { + packet->setDataByName("equipment_types", 0, i); + packet->setColorByName("equipment_colors", 0, i); + packet->setColorByName("equipment_highlights", 0, i); + continue; + } + } + } + entity->MEquipment.lock(); + packet->setDataByName("equipment_types", entity->equipment.equip_id[i], i); + packet->setColorByName("equipment_colors", entity->equipment.color[i], i); + packet->setColorByName("equipment_highlights", entity->equipment.highlight[i], i); + entity->MEquipment.unlock(); + + } + packet->setDataByName("mount_type", entity->GetMount()); + + // find the visual flags + int8 vis_flag = 0; + //Invis + crouch flag check + if (entity->IsStealthed()) { + vis_flag += (INFO_VIS_FLAG_INVIS + INFO_VIS_FLAG_CROUCH); + classicFlags += INFO_VIS_FLAG_INVIS + INFO_VIS_FLAG_CROUCH; + } + //Invis flag check + else if (entity->IsInvis()) { + vis_flag += INFO_VIS_FLAG_INVIS; + classicFlags += INFO_VIS_FLAG_INVIS; + } + + //Mount flag check + if (entity->GetMount() > 0) { + vis_flag += INFO_VIS_FLAG_MOUNTED; + } + + //Hide hood check + bool vis_hide_hood = false; + if (IsPlayer() && ((Player*)this)->get_character_flag(CF_HIDE_HOOD)) { + if(version > 561) { + vis_flag += INFO_VIS_FLAG_HIDE_HOOD; + } + vis_hide_hood = true; + } + else if(IsPlayer()) { + classicFlags += INFO_CLASSIC_FLAG_SHOW_HOOD; + } + + if(!vis_hide_hood && appearance.hide_hood && version > 561) { + vis_flag += INFO_VIS_FLAG_HIDE_HOOD; + } + + if(version <= 561) { + packet->setDataByName("flags", classicFlags); + } + packet->setDataByName("visual_flag", vis_flag); + packet->setColorByName("mount_saddle_color", entity->GetMountSaddleColor()); + packet->setColorByName("mount_color", entity->GetMountColor()); + packet->setDataByName("hair_type_id", entity->features.hair_type); + packet->setDataByName("chest_type_id", entity->features.chest_type); + packet->setDataByName("wing_type_id", entity->features.wing_type); + packet->setDataByName("legs_type_id", entity->features.legs_type); + packet->setDataByName("soga_hair_type_id", entity->features.soga_hair_type); + packet->setDataByName("facial_hair_type_id", entity->features.hair_face_type); + packet->setDataByName("soga_facial_hair_type_id", entity->features.soga_hair_face_type); + for (int i = 0; i < 3; i++) { + packet->setDataByName("eye_type", entity->features.eye_type[i], i); + packet->setDataByName("ear_type", entity->features.ear_type[i], i); + packet->setDataByName("eye_brow_type", entity->features.eye_brow_type[i], i); + packet->setDataByName("cheek_type", entity->features.cheek_type[i], i); + packet->setDataByName("lip_type", entity->features.lip_type[i], i); + packet->setDataByName("chin_type", entity->features.chin_type[i], i); + packet->setDataByName("nose_type", entity->features.nose_type[i], i); + packet->setDataByName("soga_eye_type", entity->features.soga_eye_type[i], i); + packet->setDataByName("soga_ear_type", entity->features.soga_ear_type[i], i); + packet->setDataByName("soga_eye_brow_type", entity->features.soga_eye_brow_type[i], i); + packet->setDataByName("soga_cheek_type", entity->features.soga_cheek_type[i], i); + packet->setDataByName("soga_lip_type", entity->features.soga_lip_type[i], i); + packet->setDataByName("soga_chin_type", entity->features.soga_chin_type[i], i); + packet->setDataByName("soga_nose_type", entity->features.soga_nose_type[i], i); + } + + packet->setColorByName("skin_color", entity->features.skin_color); + packet->setColorByName("model_color", entity->features.model_color); + packet->setColorByName("eye_color", entity->features.eye_color); + packet->setColorByName("hair_type_color", entity->features.hair_type_color); + packet->setColorByName("hair_type_highlight_color", entity->features.hair_type_highlight_color); + packet->setColorByName("hair_face_color", entity->features.hair_face_color); + packet->setColorByName("hair_face_highlight_color", entity->features.hair_face_highlight_color); + packet->setColorByName("hair_highlight", entity->features.hair_highlight_color); + packet->setColorByName("wing_color1", entity->features.wing_color1); + packet->setColorByName("wing_color2", entity->features.wing_color2); + packet->setColorByName("hair_color1", entity->features.hair_color1); + packet->setColorByName("hair_color2", entity->features.hair_color2); + packet->setColorByName("soga_skin_color", entity->features.soga_skin_color); + packet->setColorByName("soga_model_color", entity->features.soga_model_color); + packet->setColorByName("soga_eye_color", entity->features.soga_eye_color); + packet->setColorByName("soga_hair_color1", entity->features.soga_hair_color1); + packet->setColorByName("soga_hair_color2", entity->features.soga_hair_color2); + packet->setColorByName("soga_hair_type_color", entity->features.soga_hair_type_color); + packet->setColorByName("soga_hair_type_highlight_color", entity->features.soga_hair_type_highlight_color); + packet->setColorByName("soga_hair_face_color", entity->features.soga_hair_face_color); + packet->setColorByName("soga_hair_face_highlight_color", entity->features.soga_hair_face_highlight_color); + packet->setColorByName("soga_hair_highlight", entity->features.soga_hair_highlight_color); + + packet->setDataByName("body_size", entity->features.body_size); + packet->setDataByName("body_age", entity->features.body_age); + + packet->setDataByName("soga_body_size", entity->features.soga_body_size); + packet->setDataByName("soga_body_age", entity->features.soga_body_age); + } + else { + EQ2_Color empty; + empty.red = 255; + empty.blue = 255; + empty.green = 255; + packet->setColorByName("skin_color", empty); + packet->setColorByName("model_color", empty); + packet->setColorByName("eye_color", empty); + packet->setColorByName("soga_skin_color", empty); + packet->setColorByName("soga_model_color", empty); + packet->setColorByName("soga_eye_color", empty); + } + if (appearance.icon == 0) { + if (appearance.attackable == 1) + appearance.icon = 0; + else if (GetDifficulty() > 0) + appearance.icon = 4; + else + appearance.icon = 6; + } + + // If Coe+ clients modify the values before we send + // if not then just send the value we have. + int8 temp_icon = appearance.icon; + + //Check if we need to add the hand icon.. + if ((temp_icon & 6) != 6 && appearance.display_hand_icon) { + temp_icon |= 6; + } + + //Icon value 28 for boats, set this without modifying the value + if (version >= 1188 && temp_icon != 28) { + if ((temp_icon & 64) > 0) { + temp_icon -= 64; // remove the DoV value; + temp_icon += 128; // add the CoE value + } + if ((temp_icon & 32) > 0) { + temp_icon -= 32; // remove the DoV value; + temp_icon += 64; // add the CoE value + } + if ((temp_icon & 4) > 0) { + temp_icon -= 4; // remove DoV value + temp_icon += 8; // add the CoE icon + } + if ((temp_icon & 6) > 0) { + temp_icon -= 10; // remove DoV value + temp_icon += 12; // add the CoE icon + } + } + packet->setDataByName("icon", temp_icon);//appearance.icon); + + int32 temp_activity_status = 0; + + if (!Alive() && GetTotalHP() > 0 && !IsObject() && !IsGroundSpawn()) + temp_activity_status = 1; + + if(version >= 1188 && GetChestDropTime()) { + temp_activity_status = 0; + } + temp_activity_status += (IsNPC() || IsObject() || IsGroundSpawn()) ? 1 << 1 : 0; + if (version > 561) { + // Fix widget or sign having 'Play Legends of Norrath' or 'Tell' options in right click (client hard-coded entity commands) + if(IsWidget() || IsSign()) + temp_activity_status = 2; + + if (IsGroundSpawn() || GetShowHandIcon()) + temp_activity_status += ACTIVITY_STATUS_INTERACTABLE_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0) + temp_activity_status += ACTIVITY_STATUS_ROLEPLAYING_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0) + temp_activity_status += ACTIVITY_STATUS_ANONYMOUS_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0) + temp_activity_status += ACTIVITY_STATUS_LINKDEAD_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0) + temp_activity_status += ACTIVITY_STATUS_CAMPING_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0) + temp_activity_status += ACTIVITY_STATUS_LFG_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_LFW) > 0) + temp_activity_status += ACTIVITY_STATUS_LFW_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0) + temp_activity_status += ACTIVITY_STATUS_SOLID_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_GAINED) > 0) + temp_activity_status += ACTIVITY_STATUS_IMMUNITY_GAINED_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_REMAINING) > 0) + temp_activity_status += ACTIVITY_STATUS_IMMUNITY_REMAINING_1188; + + if ((appearance.activity_status & ACTIVITY_STATUS_AFK) > 0) + temp_activity_status += ACTIVITY_STATUS_AFK_1188; + + if (EngagedInCombat()) + temp_activity_status += ACTIVITY_STATUS_INCOMBAT_1188; + + // if this is either a boat or lift let the client be manipulated by the object + // doesn't work for DoF client version 546 + if (appearance.icon == 28 || appearance.icon == 12 || IsTransportSpawn()) + { + // there is some other flags that setting with a transport breaks their solidity/ability to properly transport + // thus we just consider the following flags for now as all necessary + temp_activity_status = ACTIVITY_STATUS_SOLID_1188; + temp_activity_status += ACTIVITY_STATUS_ISTRANSPORT_1188; + } + } + else if (version == 561) { + // Fix widget or sign having 'Play Legends of Norrath' or 'Tell' options in right click (client hard-coded entity commands) + if(IsWidget() || IsSign()) + temp_activity_status = 2; + + if (IsGroundSpawn() || GetShowHandIcon()) + temp_activity_status += ACTIVITY_STATUS_INTERACTABLE_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0) + temp_activity_status += ACTIVITY_STATUS_ROLEPLAYING_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0) + temp_activity_status += ACTIVITY_STATUS_ANONYMOUS_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0) + temp_activity_status += ACTIVITY_STATUS_LINKDEAD_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0) + temp_activity_status += ACTIVITY_STATUS_CAMPING_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0) + temp_activity_status += ACTIVITY_STATUS_LFG_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_LFW) > 0) + temp_activity_status += ACTIVITY_STATUS_LFW_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0) + temp_activity_status += ACTIVITY_STATUS_SOLID_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_GAINED) > 0) + temp_activity_status += ACTIVITY_STATUS_IMMUNITY_GAINED_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_IMMUNITY_REMAINING) > 0) + temp_activity_status += ACTIVITY_STATUS_IMMUNITY_REMAINING_561; + + if ((appearance.activity_status & ACTIVITY_STATUS_AFK) > 0) + temp_activity_status += ACTIVITY_STATUS_AFK_561; + + if (EngagedInCombat()) + temp_activity_status += ACTIVITY_STATUS_INCOMBAT_561; + + // if this is either a boat or lift let the client be manipulated by the object + // doesn't work for DoF client version 546 + if (appearance.icon == 28 || appearance.icon == 12 || IsTransportSpawn()) + { + // there is some other flags that setting with a transport breaks their solidity/ability to properly transport + // thus we just consider the following flags for now as all necessary + temp_activity_status = ACTIVITY_STATUS_SOLID_561; + temp_activity_status += ACTIVITY_STATUS_ISTRANSPORT_561; + } + } + else + { + temp_activity_status = appearance.activity_status; + if(IsNPC()) + temp_activity_status = 0xFF; + + // this only partially fixes lifts in classic 283 client if you move just as the lift starts to move + if (appearance.icon == 28 || appearance.icon == 12) + packet->setDataByName("is_transport", 1); + + if (MeetsSpawnAccessRequirements(spawn)) + packet->setDataByName("hand_icon", appearance.display_hand_icon); + else { + if ((req_quests_override & 256) > 0) + packet->setDataByName("hand_icon", 1); + } + + if (IsPlayer()) { + if (((Player*)this)->get_character_flag(CF_AFK)) + packet->setDataByName("afk", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_ROLEPLAYING) > 0) + packet->setDataByName("roleplaying", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_ANONYMOUS) > 0) + packet->setDataByName("anonymous", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_LINKDEAD) > 0) + packet->setDataByName("linkdead", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_CAMPING) > 0) + packet->setDataByName("camping", 1); + if ((appearance.activity_status & ACTIVITY_STATUS_LFG) > 0) + packet->setDataByName("lfg", 1); + } + + if (EngagedInCombat()) { + packet->setDataByName("auto_attack", 1); + } + + if ((appearance.activity_status & ACTIVITY_STATUS_SOLID) > 0) + packet->setDataByName("solid_object", 1); + } + + packet->setDataByName("activity_status", temp_activity_status); //appearance.activity_status); + // If player and player has a follow target + /* Jan 2021 Note!! Setting follow_target 0xFFFFFFFF has the result in causing strange behavior in swimming. Targetting a mob makes you focus down to its swim level, unable to swim above it. + ** in the same respect the player will drop like a rock to the bottom of the ocean (seems to be when self set to that flag?) + ** for now disabling this, if DoF needs it enabled for whatever reason then we need a version check added. + */ + if (IsPlayer()) { + if (((Player*)this)->GetFollowTarget()) + packet->setDataByName("follow_target", version <= 561 ? (((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget())) : ((((Player*)this)->GetIDWithPlayerSpawn(((Player*)this)->GetFollowTarget()) * -1) - 1)); + else if(version <= 561) { + packet->setDataByName("follow_target", 0xFFFFFFFF); + } + //else + // packet->setDataByName("follow_target", 0xFFFFFFFF); + } + //else if (!IsPet()) { + // packet->setDataByName("follow_target", 0xFFFFFFFF); + //} + + // i think this is used in DoF as a way to make a client say they are in combat with this target and cannot camp, it forces you to stand up if self spawn sends this data + if ((version > 561 || spawn != this) && GetTarget() && GetTarget()->GetTargetable()) + packet->setDataByName("target_id", ((spawn->GetIDWithPlayerSpawn(GetTarget()) * -1) - 1)); + else + packet->setDataByName("target_id", 0xFFFFFFFF); + + //Send spell effects for target window + if(IsEntity()){ + InfoStruct* info = ((Entity*)this)->GetInfoStruct(); + int8 i = 0; + int16 backdrop = 0; + int16 spell_icon = 0; + int32 spell_id = 0; + LuaSpell* spell = 0; + ((Entity*)this)->GetSpellEffectMutex()->readlock(__FUNCTION__, __LINE__); + while(i < 30){ + //Change value of spell id for this packet if spell exists + spell_id = info->spell_effects[i].spell_id; + if(spell_id > 0) + spell_id = 0xFFFFFFFF - spell_id; + else + spell_id = 0; + packet->setSubstructDataByName("spell_effects", "spell_id", spell_id, i); + + //Change value of spell icon for this packet if spell exists + spell_icon = info->spell_effects[i].icon; + if(spell_icon > 0){ + if(!(spell_icon == 0xFFFF)) + spell_icon = 0xFFFF - spell_icon; + } + else + spell_icon = 0; + packet->setSubstructDataByName("spell_effects", "spell_icon", spell_icon, i); + + //Change backdrop values to match values in this packet + backdrop = info->spell_effects[i].icon_backdrop; + switch(backdrop){ + case 312: + backdrop = 33080; + break; + case 313: + backdrop = 33081; + break; + case 314: + backdrop = 33082; + break; + case 315: + backdrop = 33083; + break; + case 316: + backdrop = 33084; + break; + case 317: + backdrop = 33085; + break; + case (318 || 319): + backdrop = 33086; + break; + default: + break; + } + + packet->setSubstructDataByName("spell_effects", "spell_icon_backdrop", backdrop, i); + spell = info->spell_effects[i].spell; + if (spell) + packet->setSubstructDataByName("spell_effects", "spell_triggercount", spell->num_triggers, i); + i++; + } + ((Entity*)this)->GetSpellEffectMutex()->releasereadlock(__FUNCTION__, __LINE__); + } +} + +void Spawn::MoveToLocation(Spawn* spawn, float distance, bool immediate, bool mapped){ + if(!spawn) + return; + SetRunningTo(spawn); + FaceTarget(spawn, false); + + if (!IsPlayer() && distance > 0.0f) + { + if ((IsFlyingCreature() || IsWaterCreature() || InWater()) && CheckLoS(spawn)) + { + if (immediate) + ClearRunningLocations(); + + AddRunningLocation(spawn->GetX(), spawn->GetY(), spawn->GetZ(), GetSpeed(), distance, true, true, "", true); + } + else if (/*!mapped && */GetZone()) + { + GetZone()->movementMgr->NavigateTo((Entity*)this, spawn->GetX(), spawn->GetY(), spawn->GetZ()); + last_grid_update = Timer::GetCurrentTime2(); + } + else + { + if (immediate) + ClearRunningLocations(); + + AddRunningLocation(spawn->GetX(), spawn->GetY(), spawn->GetZ(), GetSpeed(), distance, true, true, "", mapped); + } + } +} + +void Spawn::ProcessMovement(bool isSpawnListLocked){ + CheckProximities(); + + if(IsBot() && ((Bot*)this)->IsCamping()) { + ((Bot*)this)->Begin_Camp(); + } + if(IsPlayer()){ + //Check if player is riding a boat, if so update pos (boat's current location + XYZ offsets) + Player* player = ((Player*)this); + int32 boat_id = player->GetBoatSpawn(); + Spawn* boat = 0; + if(boat_id > 0) + boat = GetZone()->GetSpawnByID(boat_id, isSpawnListLocked); + + //TODO: MAYBE do this for real boats, not lifts... GetWidgetTypeNameByTypeID + /*if(boat){ + SetX(boat->GetX() + player->GetBoatX()); + SetY(boat->GetY() + player->GetBoatY()); + SetZ(boat->GetZ() + player->GetBoatZ()); + }*/ + return; + } + + if(IsKnockedBack()) { + if(CalculateSpawnProjectilePosition(GetX(), GetY(), GetZ())) + return; // being launched! + } + + if(reset_movement) { + ResetMovement(); + reset_movement = false; + } + + if (forceMapCheck && GetZone() != nullptr && GetMap() != nullptr && GetMap()->IsMapLoaded()) + { + FixZ(true); + forceMapCheck = false; + } + + if (GetHP() <= 0 && !IsWidget()) + return; + + if (EngagedInCombat()) + { + if(IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted())) { + SetAppearancePosition(GetX(),GetY(),GetZ()); + if ( IsEntity() ) + ((Entity*)this)->SetSpeed(0.0f); + + SetSpeed(0.0f); + position_changed = true; + changed = true; + GetZone()->AddChangedSpawn(this); + StopMovement(); + return; + } + int locations = 0; + + MMovementLocations.lock_shared(); + if (movement_locations) { + locations = movement_locations->size(); + } + MMovementLocations.unlock_shared(); + + if (locations < 1 && GetZone() && ((Entity*)this)->IsFeared()) + { + CalculateNewFearpoint(); + ValidateRunning(true, true); + } + } + + Spawn* followTarget = GetZone()->GetSpawnByID(m_followTarget, isSpawnListLocked); + if (!followTarget && m_followTarget > 0) + m_followTarget = 0; + if (following && !IsPauseMovementTimerActive() && followTarget && !((Entity*)this)->IsFeared()) { + + // Need to clear m_followTarget before the zoneserver deletes it + if (followTarget->GetHP() <= 0) { + followTarget = 0; + return; + } + + if (!IsEntity() || (!((Entity*)this)->IsCasting() && !((Entity*)this)->IsMezzedOrStunned() && !((Entity*)this)->IsRooted())) { + if (GetBaseSpeed() > 0) { + CalculateRunningLocation(); + } + else { + float speed = 4.0f; + if (IsEntity()) + speed = ((Entity*)this)->GetMaxSpeed(); + if (IsEntity()) + ((Entity*)this)->SetSpeed(speed); + + SetSpeed(speed); + } + MovementLocation tmpLoc; + MovementLocation* loc = 0; + MMovementLocations.lock_shared(); + if(movement_locations && movement_locations->size() > 0){ + loc = movement_locations->front(); + if(loc) { + tmpLoc.attackable = loc->attackable; + tmpLoc.gridid = loc->gridid; + tmpLoc.lua_function = string(loc->lua_function); + tmpLoc.mapped = loc->mapped; + tmpLoc.reset_hp_on_runback = loc->reset_hp_on_runback; + tmpLoc.speed = loc->speed; + tmpLoc.stage = loc->stage; + tmpLoc.x = loc->x; + tmpLoc.y = loc->y; + tmpLoc.z = loc->z; + loc = &tmpLoc; + } + } + MMovementLocations.unlock_shared(); + + float dist = GetDistance(followTarget, true); + if ((!EngagedInCombat() && m_followDistance > 0 && dist <= m_followDistance) || + (dist <= rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())) { + ClearRunningLocations(); + CalculateRunningLocation(true); + } + else if (loc) { + float distance = GetDistance(followTarget, loc->x, loc->y, loc->z); + if ( (!EngagedInCombat() && m_followDistance > 0 && distance > m_followDistance) || + ( EngagedInCombat() && distance > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())) { + MoveToLocation(followTarget, rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat(), true, loc->mapped); + CalculateRunningLocation(); + } + } + else { + MoveToLocation(followTarget, rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat(), false); + CalculateRunningLocation(); + } + } + } + + bool movementCase = false; + // Movement loop is only for scripted paths + if(!EngagedInCombat() && !IsPauseMovementTimerActive() && !NeedsToResumeMovement() && (!IsNPC() || !((NPC*)this)->m_runningBack)){ + MMovementLoop.writelock(); + if(movement_loop.size() > 0 && movement_index < movement_loop.size()) + { + movementCase = true; + // Get the target location + MovementData* data = movement_loop[movement_index]; + // need to resume our movement + if(resume_movement){ + MMovementLocations.lock(); + if (movement_locations){ + while (movement_locations->size()){ + safe_delete(movement_locations->front()); + movement_locations->pop_front(); + } + movement_locations->clear(); + } + MMovementLocations.unlock(); + + data = movement_loop[movement_index]; + + if(data) + { + if(IsEntity()) { + ((Entity*)this)->SetSpeed(data->speed); + } + SetSpeed(data->speed); + if(data->use_movement_location_heading) + SetHeading(data->heading); + else if(!IsWidget()) + FaceTarget(data->x, data->z); + // 0 delay at target location, need to set multiple locations + if(data->delay == 0 && movement_loop.size() > 0) { + int16 tmp_index = movement_index+1; + MovementData* data2 = 0; + if(tmp_index < movement_loop.size()) + data2 = movement_loop[tmp_index]; + else + data2 = movement_loop[0]; + AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true); + AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true); + } + // delay at target location, only need to set 1 location + else + AddRunningLocation(data->x, data->y, data->z, data->speed); + } + movement_start_time = 0; + resume_movement = false; + } + // If we are not moving or we have arrived at our destination + else if(!IsRunning() || (data && data->x == GetX() && data->y == GetY() && data->z == GetZ())){ + // If we were moving remove the last running location (the point we just arrived at) + if(IsRunning()) { + RemoveRunningLocation(); + } + + // If this waypoint has a delay and we just arrived here (movement_start_time == 0) + if(data && data->delay > 0 && movement_start_time == 0){ + // Set the current time + movement_start_time = Timer::GetCurrentTime2(); + // If this waypoint had a lua function then call it + if(data->lua_function.length() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str()); + + int16 nextMove; + if ((int16)(movement_index + 1) < movement_loop.size()) + nextMove = movement_index + 1; + else + nextMove = 0; + // Get the next target location + data = movement_loop[nextMove]; + + //Go ahead and face the next location + if(data) { + FaceTarget(data->x, data->z); + } + } + // If this waypoint has no delay or we have waited the required time (current time >= delay + movement_start_time) + else if(data && data->delay == 0 || (data && data->delay > 0 && Timer::GetCurrentTime2() >= (data->delay+movement_start_time))) { + // if no delay at this waypoint but a lua function for it then call the function + if(data->delay == 0 && data->lua_function.length() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str()); + // since we ran a lua function make sure the movement loop is still alive and accurate + if(movement_loop.size() > 0) + { + // Advance the current movement loop index + if((int16)(movement_index+1) < movement_loop.size()) + movement_index++; + else + movement_index = 0; + // Get the next target location + data = movement_loop[movement_index]; + + // set the speed for that location + SetSpeed(data->speed); + + if(!IsWidget()) + // turn towards the location + FaceTarget(data->x, data->z); + + // If 0 delay at location get and set data for the point after it + if(data->delay == 0 && movement_loop.size() > 0){ + MMovementLocations.lock(); + if(movement_locations) + { + while (movement_locations->size()){ + safe_delete(movement_locations->front()); + movement_locations->pop_front(); + } + // clear current target locations + movement_locations->clear(); + } + MMovementLocations.unlock(); + // get the data for the location after out new location + int16 tmp_index = movement_index+1; + MovementData* data2 = 0; + if(tmp_index < movement_loop.size()) + data2 = movement_loop[tmp_index]; + else + data2 = movement_loop[0]; + // set the first location (adds it to movement_locations that we just cleared) + AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, false, "", true); + // set the location after that + AddRunningLocation(data2->x, data2->y, data2->z, data2->speed, 0, true, true, "", true); + } + // there is a delay at the next location so we only need to set it + else { + AddRunningLocation(data->x, data->y, data->z, data->speed, 0, true, true, "", true); + } + + // reset this timer to 0 now that we are moving again + movement_start_time = 0; + } + } + } + // moving and not at target location yet + else if(GetBaseSpeed() > 0) { + CalculateRunningLocation(); + } + // not moving, have a target location but not at it yet + else if (data) { + SetSpeed(data->speed); + AddRunningLocation(data->x, data->y, data->z, data->speed); + } + } + MMovementLoop.releasewritelock(); + } + + if (!movementCase && IsRunning() && !IsPauseMovementTimerActive()) { + CalculateRunningLocation(); + //last_movement_update = Timer::GetCurrentTime2(); + } + else if(movementCase) + { + //last_movement_update = Timer::GetCurrentTime2(); + } + /*else if (IsNPC() && !IsRunning() && !EngagedInCombat() && ((NPC*)this)->GetRunbackLocation()) { + // Is an npc that is not moving and not engaged in combat but has a run back location set then clear the runback location + LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Clear runback location for %s", GetName()); + ((NPC*)this)->ClearRunback(); + resume_movement = true; + NeedsToResumeMovement(false); + }*/ +} + +void Spawn::ResetMovement(){ + MMovementLoop.writelock(); + + vector::iterator itr; + for(itr = movement_loop.begin(); itr != movement_loop.end(); itr++){ + safe_delete(*itr); + } + movement_loop.clear(); + movement_index = 0; + resume_movement = true; + ClearRunningLocations(); + + MMovementLoop.releasewritelock(); + + ValidateRunning(true, true); +} + +void Spawn::AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading){ + + LogWrite(LUA__DEBUG, 5, "LUA", "AddMovementLocation: x: %.2f, y: %.2f, z: %.2f, speed: %.2f, delay: %i, lua: %s", + x, y, z, speed, delay, string(lua_function).c_str()); + + MovementData* data = new MovementData; + data->x = x; + data->y = y; + data->z = z; + data->speed = speed; + data->delay = delay*1000; + if(lua_function) + data->lua_function = string(lua_function); + + data->heading = heading; + data->use_movement_location_heading = include_heading; + MMovementLoop.lock(); + movement_loop.push_back(data); + MMovementLoop.unlock(); +} + +bool Spawn::ValidateRunning(bool lockMovementLocation, bool lockMovementLoop) { + bool movement = false; + + if(lockMovementLocation) { + MMovementLocations.lock_shared(); + } + + if(movement_locations) { + movement = movement_locations->size() > 0; + } + + if(lockMovementLocation) { + MMovementLocations.unlock_shared(); + } + + if(IsPauseMovementTimerActive() || (IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted()))) { + is_running = false; + return false; + } + + if(movement) { + is_running = true; + return true; + } + + if(lockMovementLoop) { + MMovementLoop.lock(); + } + + movement = movement_loop.size() > 0; + + if(movement) { + is_running = true; + } + else { + is_running = false; + } + + if(lockMovementLoop) { + MMovementLoop.unlock(); + } + return movement; +} +bool Spawn::IsRunning(){ + return is_running; +} + +void Spawn::RunToLocation(float x, float y, float z, float following_x, float following_y, float following_z){ + if(IsPauseMovementTimerActive() || (IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted()))) { + is_running = false; + return; + } + + if(!IsWidget() && (!EngagedInCombat() || GetDistance(GetTarget()) > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat())) + FaceTarget(x, z); + SetPos(&appearance.pos.X2, x, false); + SetPos(&appearance.pos.Z2, z, false); + SetPos(&appearance.pos.Y2, y, false); + if(following_x == 0 && following_y == 0 && following_z == 0){ + SetPos(&appearance.pos.X3, x, false); + SetPos(&appearance.pos.Z3, z, false); + SetPos(&appearance.pos.Y3, y, false); + } + else{ + SetPos(&appearance.pos.X3, following_x, false); + SetPos(&appearance.pos.Y3, following_y, false); + SetPos(&appearance.pos.Z3, following_z, false); + } + + is_running = true; + position_changed = true; + changed = true; + GetZone()->AddChangedSpawn(this); +} + +MovementLocation* Spawn::GetCurrentRunningLocation(){ + MovementLocation* ret = 0; + MMovementLocations.lock_shared(); + if(movement_locations && movement_locations->size() > 0){ + ret = movement_locations->front(); + } + MMovementLocations.unlock_shared(); + return ret; +} + +MovementLocation* Spawn::GetLastRunningLocation(){ + MovementLocation* ret = 0; + MMovementLocations.lock_shared(); + if(movement_locations && movement_locations->size() > 0){ + ret = movement_locations->back(); + } + MMovementLocations.unlock_shared(); + return ret; +} + +void Spawn::AddRunningLocation(float x, float y, float z, float speed, float distance_away, bool attackable, bool finished_adding_locations, string lua_function, bool isMapped){ + if(speed == 0) + return; + + if ( IsEntity() ) + ((Entity*)this)->SetSpeed(speed); + else + this->SetSpeed(speed); + + MovementLocation* current_location = 0; + + float distance = GetDistance(x, y, z, distance_away != 0); + if(distance_away != 0){ + distance -= distance_away; + + x = x - (GetX() - x)*distance_away/distance; + z = z - (GetZ() - z)*distance_away/distance; + } + + MMovementLocations.lock(); + if(!movement_locations){ + movement_locations = new deque(); + } + MMovementLocations.unlock(); + + MovementLocation* data = new MovementLocation; + data->mapped = isMapped; + data->x = x; + data->y = y; + data->z = z; + data->speed = speed; + data->attackable = attackable; + data->lua_function = lua_function; + data->gridid = 0; // used for runback defaults + data->reset_hp_on_runback = false; + + MMovementLocations.lock_shared(); + if(movement_locations->size() > 0) + current_location = movement_locations->back(); + MMovementLocations.unlock_shared(); + + if(!current_location){ + SetSpawnOrigX(GetX()); + SetSpawnOrigY(GetY()); + SetSpawnOrigZ(GetZ()); + SetSpawnOrigHeading(GetHeading()); + } + is_running = true; + + MMovementLocations.lock(); + movement_locations->push_back(data); + MMovementLocations.unlock(); + if(!IsPauseMovementTimerActive() && finished_adding_locations){ + MMovementLocations.lock(); + current_location = movement_locations->front(); + SetSpeed(current_location->speed); + if(movement_locations->size() > 1){ + data = movement_locations->at(1); + RunToLocation(current_location->x, current_location->y, current_location->z, data->x, data->y, data->z); + } + else + RunToLocation(current_location->x, current_location->y, current_location->z, 0, 0, 0); + MMovementLocations.unlock(); + } +} + +bool Spawn::RemoveRunningLocation(){ + bool ret = false; + MMovementLocations.lock(); + if(movement_locations && movement_locations->size() > 0){ + delete movement_locations->front(); + movement_locations->pop_front(); + ret = true; + } + MMovementLocations.unlock(); + + ValidateRunning(true, false); + return ret; +} + +void Spawn::ClearRunningLocations(){ + while(RemoveRunningLocation()){} +} + +void Spawn::NewWaypointChange(MovementLocation* data){ + if(data){ + if(NeedsToResumeMovement()){ + resume_movement = true; + NeedsToResumeMovement(false); + } + if(!data->attackable) + SetHeading(GetSpawnOrigHeading()); + } + + if (data && data->lua_function.length() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, 0, data->lua_function.c_str()); + + RemoveRunningLocation(); +} + +bool Spawn::CalculateChange(){ + bool remove_needed = false; + MovementLocation* data = 0; + MovementLocation tmpLoc; + MMovementLocations.lock_shared(); + if(movement_locations){ + if(movement_locations->size() > 0){ + // Target location + data = movement_locations->front(); + if(data) { + tmpLoc.attackable = data->attackable; + tmpLoc.gridid = data->gridid; + tmpLoc.lua_function = string(data->lua_function); + tmpLoc.mapped = data->mapped; + tmpLoc.reset_hp_on_runback = data->reset_hp_on_runback; + tmpLoc.speed = data->speed; + tmpLoc.stage = data->stage; + tmpLoc.x = data->x; + tmpLoc.y = data->y; + tmpLoc.z = data->z; + data = &tmpLoc; + } + // If no target or we are at the target location need to remove this point + if(!data || (data->x == GetX() && data->y == GetY() && data->z == GetZ())) + remove_needed = true; + } + } + MMovementLocations.unlock_shared(); + + if(remove_needed){ + NewWaypointChange(data); + } + else if(data){ + // Speed is per second so we need a time_step (amount of time since the last update) to modify movement by + float time_step = (Timer::GetCurrentTime2() - last_movement_update) * 0.001; // * 0.001 is the same as / 1000, float muliplications is suppose to be faster though + + // Get current location + float nx = GetX(); + float ny = GetY(); + float nz = GetZ(); + + // Get Forward vecotr + float tar_vx = data->x - nx; + float tar_vy = data->y - ny; + float tar_vz = data->z - nz; + + // Multiply speed by the time_step to get how much should have changed over the last tick + float speed = GetSpeed() * time_step; + + // Normalize the forward vector and multiply by speed, this gives us our change in coords, just need to add them to our current coords + float len = sqrtf(tar_vx * tar_vx + tar_vy * tar_vy + tar_vz * tar_vz); + tar_vx = (tar_vx / len) * speed; + tar_vy = (tar_vy / len) * speed; + tar_vz = (tar_vz / len) * speed; + + // Distance less then 0.5 just set the npc to the target location + if (GetDistance(data->x, data->y, data->z, IsWidget() ? false : true) <= speed) { + SetX(data->x, false); + SetZ(data->z, false); + SetY(data->y, false, true); + remove_needed = true; + NewWaypointChange(data); + } + else { + SetX(nx + tar_vx, false); + SetZ(nz + tar_vz, false); + if ( IsWidget() ) + SetY(ny + tar_vy, false, true); + else + SetY(ny + tar_vy, false); + } + + int32 newGrid = GetLocation(); + if (GetMap() != nullptr) { + m_GridMutex.writelock(__FUNCTION__, __LINE__); + std::map::iterator itr = established_grid_id.begin(); + if ( itr == established_grid_id.end() || itr->second.timestamp <= (Timer::GetCurrentTime2())) + { + if(itr != established_grid_id.end() && itr->second.x == GetX() && itr->second.z == GetZ()) { + itr->second.timestamp = Timer::GetCurrentTime2()+1000; + itr->second.npc_save = true; + newGrid = itr->second.grid_id; + } + else { + auto loc = glm::vec3(GetX(), GetZ(), GetY()); + float new_z = FindBestZ(loc, nullptr, &newGrid); + TimedGridData data; + data.grid_id = newGrid; + data.x = GetX(); + data.y = GetY(); + data.z = GetZ(); + data.npc_save = true; + data.zone_ground_y = new_z; + data.offset_y = new_z; + data.timestamp = Timer::GetCurrentTime2()+1000; + established_grid_id.insert(make_pair(0, data)); + } + } + else + newGrid = itr->second.grid_id; + m_GridMutex.releasewritelock(__FUNCTION__, __LINE__); + } + + if ((!IsFlyingCreature() || IsTransportSpawn()) && newGrid != 0 && newGrid != GetLocation()) + SetLocation(newGrid); + } + return remove_needed; +} + +void Spawn::CalculateRunningLocation(bool stop){ + + bool pauseTimerEnabled = IsPauseMovementTimerActive(); + + if (!pauseTimerEnabled && !stop && (last_location_update + 100) > Timer::GetCurrentTime2()) + return; + else if (!pauseTimerEnabled && !stop) + last_location_update = Timer::GetCurrentTime2(); + bool continueElseIf = true; + bool removed = CalculateChange(); + if (stop || pauseTimerEnabled) { + //following = false; + SetPos(&appearance.pos.X2, GetX(), false); + SetPos(&appearance.pos.Y2, GetY(), false); + SetPos(&appearance.pos.Z2, GetZ(), false); + SetPos(&appearance.pos.X3, GetX(), false); + SetPos(&appearance.pos.Y3, GetY(), false); + SetPos(&appearance.pos.Z3, GetZ(), false); + continueElseIf = false; + } + else if (removed) { + MMovementLocations.lock_shared(); + if(movement_locations) { + if(movement_locations->size() > 0) { + MovementLocation* current_location = movement_locations->at(0); + if (movement_locations->size() > 1) { + MovementLocation* data = movement_locations->at(1); + RunToLocation(current_location->x, current_location->y, current_location->z, data->x, data->y, data->z); + } + else + RunToLocation(current_location->x, current_location->y, current_location->z, 0, 0, 0); + + continueElseIf = false; + } + } + MMovementLocations.unlock_shared(); + } + + if (continueElseIf && GetZone() && GetTarget() != NULL && EngagedInCombat()) + { + if (GetDistance(GetTarget()) > rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) + { + if ((IsFlyingCreature() || IsWaterCreature() || InWater()) && CheckLoS(GetTarget())) + AddRunningLocation(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetSpeed(), 0, false); + else + GetZone()->movementMgr->NavigateTo((Entity*)this, GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ()); + } + else + ((Entity*)this)->HaltMovement(); + } + else if (continueElseIf && !following) + { + position_changed = true; + changed = true; + GetZone()->AddChangedSpawn(this); + } +} +float Spawn::GetFaceTarget(float x, float z) { + float angle; + + double diff_x = x - GetX(); + double diff_z = z - GetZ(); + + if (diff_z == 0) { + if (diff_x > 0) + angle = 90; + else + angle = 270; + } + else + angle = ((atan(diff_x / diff_z)) * 180) / 3.14159265358979323846; + + if(angle < 0) + angle = angle + 360; + else if(angle > -0.0000001 && angle < 0.0000001) + angle = 0; + else + angle = angle + 180; + + if ((diff_x < 0 && diff_z != 0.0) || (diff_x == 0 && diff_z > 0.0)) + angle = angle + 180; + + if(angle > 360) + angle = angle - 360.0; + + return angle; +} + +void Spawn::FaceTarget(float x, float z){ + + float angle; + + double diff_x = x - GetX(); + double diff_z = z - GetZ(); + + if (diff_z == 0) { + if (diff_x > 0) + angle = 90; + else + angle = 270; + } + else + angle = ((atan(diff_x / diff_z)) * 180) / 3.14159265358979323846; + + if(angle < 0) + angle = angle + 360; + else if(angle > -0.0000001 && angle < 0.0000001) + angle = 0; + else + angle = angle + 180; + + if ((diff_x < 0 && diff_z != 0.0) || (diff_x == 0 && diff_z > 0.0)) + angle = angle + 180; + + if(angle > 360) + angle = angle - 360.0; + + SetHeading(angle); +} + +void Spawn::FaceTarget(Spawn* target, bool disable_action_state){ + if(!target) + return; + if(GetHP() > 0 && target->IsPlayer() && !EngagedInCombat()){ + if(!IsPet() && disable_action_state) { + if(IsNPC()) { + ((NPC*)this)->StartRunback(); + ((NPC*)this)->PauseMovement(30000); + } + SetTempActionState(0); + } + } + FaceTarget(target->GetX(), target->GetZ()); +} + +bool Spawn::MeetsSpawnAccessRequirements(Player* player){ + bool ret = false; + Quest* quest = 0; + //Check if we meet all quest requirements first.. + m_requiredQuests.readlock(__FUNCTION__, __LINE__); + if (player && required_quests.size() > 0) { + map* >::iterator itr; + for (itr = required_quests.begin(); itr != required_quests.end(); itr++) { + player->AddQuestRequiredSpawn(this, itr->first); + vector* quest_steps = itr->second; + for (int32 i = 0; i < quest_steps->size(); i++) { + quest = player->GetQuest(itr->first); + if (req_quests_continued_access) { + if (quest) { + if (quest->GetQuestStepCompleted(quest_steps->at(i))) { + ret = true; + break; + } + } + else if (player->HasQuestBeenCompleted(itr->first)) { + ret = true; + break; + } + } + if (quest && quest->QuestStepIsActive(quest_steps->at(i))) { + ret = true; + break; + } + } + } + } + else + ret = true; + m_requiredQuests.releasereadlock(__FUNCTION__, __LINE__); + if (!ret) + return ret; + + //Now check if the player meets all history requirements + m_requiredHistory.readlock(__FUNCTION__, __LINE__); + if (required_history.size() > 0){ + map::iterator itr; + for (itr = required_history.begin(); itr != required_history.end(); itr++){ + player->AddHistoryRequiredSpawn(this, itr->first); + LUAHistory* player_history = player->GetLUAHistory(itr->first); + if (player_history){ + if (player_history->Value != itr->second->Value || player_history->Value2 != itr->second->Value2) + ret = false; + } + else + ret = false; + if (!ret) + break; + } + } + m_requiredHistory.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +vector* Spawn::GetSpawnGroup(){ + vector* ret_list = 0; + if(spawn_group_list){ + ret_list = new vector(); + if(MSpawnGroup) + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + ret_list->insert(ret_list->begin(), spawn_group_list->begin(), spawn_group_list->end()); + if(MSpawnGroup) + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } + return ret_list; +} + +bool Spawn::HasSpawnGroup() { + return spawn_group_list && spawn_group_list->size() > 0; +} + +bool Spawn::IsInSpawnGroup(Spawn* spawn) { + bool ret = false; + if (HasSpawnGroup() && spawn) { + vector::iterator itr; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) { + if ((*itr) == spawn) { + ret = true; + break; + } + } + } + return ret; +} + +Spawn* Spawn::IsSpawnGroupMembersAlive(Spawn* ignore_spawn, bool npc_only) { + Spawn* ret = nullptr; + if (MSpawnGroup && HasSpawnGroup()) { + + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) { + if ((*itr) != ignore_spawn && (*itr)->Alive() && (!npc_only || (npc_only && (*itr)->IsNPC()))) { + ret = (*itr); + break; + } + } + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +void Spawn::UpdateEncounterState(int8 new_state) { + if (MSpawnGroup && HasSpawnGroup()) { + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) { + if ((*itr)->Alive() && (*itr)->IsNPC()) { + NPC* npc = (NPC*)(*itr); + (*itr)->SetLockedNoLoot(new_state); + if(new_state == ENCOUNTER_STATE_BROKEN && npc->Brain()) { + npc->Brain()->ClearEncounter(); + } + } + } + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } +} + +void Spawn::CheckEncounterState(Entity* victim, bool test_auto_lock) { + if (!IsEntity() || !victim->IsNPC()) + return; + + Entity* ent = ((Entity*)this); + if (victim->GetLockedNoLoot() == ENCOUNTER_STATE_AVAILABLE) { + + Entity* attacker = nullptr; + if (ent->GetOwner()) + attacker = ent->GetOwner(); + else + attacker = ent; + + bool matchedAutoLock = false; + if (attacker->IsEntity() && ((Entity*)attacker)->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + GroupMemberInfo* gmi = ((Entity*)attacker)->GetGroupMemberInfo(); + if (gmi && gmi->group_id) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group && ((group->GetGroupOptions()->group_lock_method && group->GetGroupOptions()->group_autolock == 1) || attacker->GetGroupMemberInfo()->leader)) + { + matchedAutoLock = true; + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || member->GetZone() != attacker->GetZone()) + continue; + + if (member->IsEntity()) { + if (!member->GetInfoStruct()->get_engaged_encounter()) { + member->GetInfoStruct()->set_engaged_encounter(1); + } + if (((NPC*)victim)->Brain()) { + ((NPC*)victim)->Brain()->AddHate(member, 0); + ((NPC*)victim)->Brain()->AddToEncounter(member); + victim->AddTargetToEncounter(member); + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else if (attacker->GetInfoStruct()->get_group_solo_autolock()) { + matchedAutoLock = true; + if (((NPC*)victim)->Brain()) { + ((NPC*)victim)->Brain()->AddHate(attacker, 0); + ((NPC*)victim)->Brain()->AddToEncounter(attacker); + victim->AddTargetToEncounter(attacker); + } + } + + if (test_auto_lock && !matchedAutoLock) { + return; + } + + if (!ent->GetInfoStruct()->get_engaged_encounter()) { + ent->GetInfoStruct()->set_engaged_encounter(1); + } + + if (!attacker->GetInfoStruct()->get_engaged_encounter()) { + attacker->GetInfoStruct()->set_engaged_encounter(1); + } + + int8 skip_loot_gray_mob_flag = rule_manager.GetGlobalRule(R_Loot, SkipLootGrayMob)->GetInt8(); + + int8 difficulty = attacker->GetArrowColor(victim->GetLevel()); + + int8 new_enc_state = ENCOUNTER_STATE_AVAILABLE; + if (skip_loot_gray_mob_flag && difficulty == ARROW_COLOR_GRAY) { + if (!attacker->IsPlayer() && !attacker->IsBot()) { + new_enc_state = ENCOUNTER_STATE_BROKEN; + } + else { + new_enc_state = ENCOUNTER_STATE_OVERMATCHED; + } + } + else { + if (attacker->IsPlayer() || attacker->IsBot()) { + new_enc_state = ENCOUNTER_STATE_LOCKED; + } + else { + new_enc_state = ENCOUNTER_STATE_BROKEN; + } + } + + victim->SetLockedNoLoot(new_enc_state); + victim->UpdateEncounterState(new_enc_state); + } +} + +void Spawn::AddTargetToEncounter(Entity* entity) { + if (MSpawnGroup && HasSpawnGroup()) { + MSpawnGroup->readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++) { + if ((*itr) != this && (*itr)->Alive() && (*itr)->IsNPC()) { + ((NPC*)(*itr))->Brain()->AddToEncounter(entity); + } + } + MSpawnGroup->releasereadlock(__FUNCTION__, __LINE__); + } +} + +void Spawn::AddSpawnToGroup(Spawn* spawn){ + if(!spawn) + return; + if(!spawn_group_list){ + spawn_group_list = new vector(); + spawn_group_list->push_back(this); + safe_delete(MSpawnGroup); + MSpawnGroup = new Mutex(); + MSpawnGroup->SetName("Spawn::MSpawnGroup"); + } + vector::iterator itr; + MSpawnGroup->writelock(__FUNCTION__, __LINE__); + for(itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++){ + if((*itr) == spawn){ + MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__); + return; + } + } + spawn_group_list->push_back(spawn); + spawn->SetSpawnGroupList(spawn_group_list, MSpawnGroup); + MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__); +} + + +void Spawn::SetSpawnGroupList(vector* list, Mutex* mutex){ + spawn_group_list = list; + MSpawnGroup = mutex; +} + +void Spawn::RemoveSpawnFromGroup(bool erase_all){ + SetSpawnGroupID(0); + bool del = false; + if(MSpawnGroup){ + MSpawnGroup->writelock(__FUNCTION__, __LINE__); + if(spawn_group_list){ + vector::iterator itr; + Spawn* spawn = 0; + if(spawn_group_list->size() == 1) + erase_all = true; + for(itr = spawn_group_list->begin(); itr != spawn_group_list->end(); itr++){ + spawn = *itr; + if (spawn) { + if(!erase_all){ + if(spawn == this){ + spawn_group_list->erase(itr); + MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__); + spawn_group_list = 0; + MSpawnGroup = 0; + return; + } + } + else{ + if (spawn != this) + spawn->SetSpawnGroupList(0, 0); + } + } + } + if (erase_all) + spawn_group_list->clear(); + del = (spawn_group_list->size() == 0); + } + MSpawnGroup->releasewritelock(__FUNCTION__, __LINE__); + if (del){ + safe_delete(MSpawnGroup); + safe_delete(spawn_group_list); + } + } +} + +void Spawn::SetSpawnGroupID(int32 id){ + m_SpawnMutex.writelock(); + group_id = id; + m_SpawnMutex.releasewritelock(); +} + +int32 Spawn::GetSpawnGroupID(){ + int32 groupid = 0; + m_SpawnMutex.readlock(); + groupid = group_id; + m_SpawnMutex.releasereadlock(); + return groupid; +} + +void Spawn::AddChangedZoneSpawn(){ + if(send_spawn_changes && GetZone()) + GetZone()->AddChangedSpawn(this); +} + +void Spawn::RemoveSpawnAccess(Spawn* spawn) { + if (allowed_access.count(spawn->GetID()) > 0) { + allowed_access.erase(spawn->GetID()); + GetZone()->HidePrivateSpawn(this); + } +} + +void Spawn::SetFollowTarget(Spawn* spawn, int32 follow_distance) { + if (spawn && spawn != this) { + m_followTarget = spawn->GetID(); + m_followDistance = follow_distance; + } + else { + m_followTarget = 0; + if (following) + following = false; + m_followDistance = 0; + } +} + +void Spawn::AddTempVariable(string var, string val) { + m_tempVariableTypes[var] = 5; + m_tempVariables[var] = val; +} + +void Spawn::AddTempVariable(string var, Spawn* val) { + m_tempVariableTypes[var] = 1; + m_tempVariableSpawn[var] = val->GetID(); +} + +void Spawn::AddTempVariable(string var, ZoneServer* val) { + m_tempVariableTypes[var] = 2; + m_tempVariableZone[var] = val; +} + +void Spawn::AddTempVariable(string var, Item* val) { + m_tempVariableTypes[var] = 3; + m_tempVariableItem[var] = val; +} + +void Spawn::AddTempVariable(string var, Quest* val) { + m_tempVariableTypes[var] = 4; + m_tempVariableQuest[var] = val; +} + +string Spawn::GetTempVariable(string var) { + string ret = ""; + + if (m_tempVariables.count(var) > 0) + ret = m_tempVariables[var]; + + return ret; +} + +Spawn* Spawn::GetTempVariableSpawn(string var) { + Spawn* ret = 0; + + if (m_tempVariableSpawn.count(var) > 0) + ret = GetZone()->GetSpawnByID(m_tempVariableSpawn[var]); + + return ret; +} + +ZoneServer* Spawn::GetTempVariableZone(string var) { + ZoneServer* ret = 0; + + if (m_tempVariableZone.count(var) > 0) + ret = m_tempVariableZone[var]; + + return ret; +} + +Item* Spawn::GetTempVariableItem(string var) { + Item* ret = 0; + + if (m_tempVariableItem.count(var) > 0) + ret = m_tempVariableItem[var]; + + return ret; +} + +Quest* Spawn::GetTempVariableQuest(string var) { + Quest* ret = 0; + + if (m_tempVariableQuest.count(var) > 0) + ret = m_tempVariableQuest[var]; + + return ret; +} + +int8 Spawn::GetTempVariableType(string var) { + int8 ret = 0; + + if (m_tempVariableTypes.count(var) > 0) + ret = m_tempVariableTypes[var]; + + return ret; +} + +void Spawn::DeleteTempVariable(string var) { + int8 type = GetTempVariableType(var); + + switch (type) { + case 1: + m_tempVariableSpawn.erase(var); + break; + case 2: + m_tempVariableZone.erase(var); + break; + case 3: + m_tempVariableItem.erase(var); + break; + case 4: + m_tempVariableQuest.erase(var); + break; + case 5: + m_tempVariables.erase(var); + break; + } + + m_tempVariableTypes.erase(var); +} + +Spawn* Spawn::GetRunningTo() { + return GetZone()->GetSpawnByID(running_to); +} + +Spawn* Spawn::GetFollowTarget() { + return GetZone()->GetSpawnByID(m_followTarget); +} + +void Spawn::CopySpawnAppearance(Spawn* spawn){ + if (!spawn) + return; + + //This function copies the appearace of the provided spawn to this one + if (spawn->IsEntity() && IsEntity()){ + memcpy(&((Entity*)this)->features, &((Entity*)spawn)->features, sizeof(CharFeatures)); + memcpy(&((Entity*)this)->equipment, &((Entity*)spawn)->equipment, sizeof(EQ2_Equipment)); + } + + SetSize(spawn->GetSize()); + SetModelType(spawn->GetModelType()); +} + +void Spawn::SetY(float y, bool updateFlags, bool disableYMapCheck) +{ + SetPos(&appearance.pos.Y, y, updateFlags); + if (!disableYMapCheck) + FixZ(); +} + +float Spawn::FindDestGroundZ(glm::vec3 dest, float z_offset) +{ + float best_z = BEST_Z_INVALID; + if (GetZone() != nullptr && GetMap() != nullptr) + { + dest.z += z_offset; + best_z = FindBestZ(dest, nullptr); + } + return best_z; +} + +float Spawn::FindBestZ(glm::vec3 loc, glm::vec3* result, int32* new_grid_id, int32* new_widget_id) { + std::shared_lock lock(MIgnoredWidgets); + + if(!GetMap()) + return BEST_Z_INVALID; + + float new_z = GetMap()->FindBestZ(loc, nullptr, &ignored_widgets, new_grid_id, new_widget_id); + return new_z; +} + +float Spawn::GetFixedZ(const glm::vec3& destination, int32 z_find_offset) { + BenchTimer timer; + timer.reset(); + + float new_z = destination.z; + + if (GetZone() != nullptr && GetMap() != nullptr) { + +/* if (flymode == GravityBehavior::Flying) + return new_z; + */ +/* if (zone->HasWaterMap() && zone->watermap->InLiquid(glm::vec3(m_Position))) + return new_z; + */ + /* + * Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors + */ + new_z = this->FindDestGroundZ(destination, z_find_offset); + if (new_z != BEST_Z_INVALID) { + if (new_z < -2000) { + new_z = GetY(); + } + } + + auto duration = timer.elapsed(); + LogWrite(MAP__DEBUG, 0, "Map", "Mob::GetFixedZ() ([{%s}]) returned [{%f}] at [{%f}], [{%f}], [{%f}] - Took [{%f}]", + this->GetName(), + new_z, + destination.x, + destination.y, + destination.z, + duration); + } + + return new_z; +} + + +void Spawn::FixZ(bool forceUpdate) { + if (!GetZone()) { + return; + } + /* + if (flymode == GravityBehavior::Flying) { + return; + }*/ + /* + if (zone->watermap && zone->watermap->InLiquid(m_Position)) { + return; + }*/ + + // we do the inwater check here manually to avoid double calling for a Z coordinate + glm::vec3 current_loc(GetX(), GetZ(), GetY()); + + uint32 GridID = 0; + uint32 WidgetID = 0; + float new_z = GetY(); + if(GetMap() != nullptr) { + new_z = FindBestZ(current_loc, nullptr, &GridID, &WidgetID); + + if ((IsTransportSpawn() || !IsFlyingCreature()) && GridID != 0 && GridID != GetLocation()) { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s left grid %u and entered grid %u", appearance.name, GetLocation(), GridID); + + const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID()); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "leave_location", GetZone(), this, GetLocation()); + } + + SetLocation(GridID); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "enter_location", GetZone(), this, GridID); + } + } + trigger_widget_id = WidgetID; + } + + // no need to go any further for players, flying creatures or objects, just needed the grid id set + if (IsPlayer() || IsFlyingCreature() || IsObject()) { + return; + } + + if ( region_map != nullptr ) + { + glm::vec3 targPos(GetX(), GetY(), GetZ()); + + if(region_map->InWater(targPos, GetLocation())) + return; + } + + if (new_z == GetY()) + return; + + if ((new_z > -2000) && new_z != BEST_Z_INVALID) { + SetY(new_z, forceUpdate, true); + } + else { + LogWrite(MAP__DEBUG, 0, "Map", "[{%s}] is failing to find Z [{%f}]", this->GetName(), std::abs(GetY() - new_z)); + } +} + +bool Spawn::CheckLoS(Spawn* target) +{ + float radiusSrc = 2.0f; + float radiusTarg = 2.0f; + + glm::vec3 targpos(target->GetX(), target->GetZ(), target->GetY()+radiusTarg); + glm::vec3 pos(GetX(), GetZ(), GetY()+radiusSrc); + return CheckLoS(pos, targpos); +} + +bool Spawn::CheckLoS(glm::vec3 myloc, glm::vec3 oloc) +{ + bool res = false; + ZoneServer* zone = GetZone(); + if (zone == NULL || GetMap() == NULL || !GetMap()->IsMapLoaded()) + return true; + else { + MIgnoredWidgets.lock_shared(); + res = GetMap()->CheckLoS(myloc, oloc, &ignored_widgets); + MIgnoredWidgets.unlock_shared(); + } + + return res; +} + +void Spawn::CalculateNewFearpoint() +{ + if (GetZone() && GetZone()->pathing) { + auto Node = zone->pathing->GetRandomLocation(glm::vec3(GetX(), GetZ(), GetY())); + if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) { + AddRunningLocation(Node.x, Node.y, Node.z, GetSpeed(), 0, true, true, "", true); + } + } +} + +Item* Spawn::LootItem(int32 id) { + Item* ret = 0; + vector::iterator itr; + MLootItems.lock(); + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) { + if ((*itr)->details.item_id == id) { + ret = *itr; + loot_items.erase(itr); + break; + } + } + MLootItems.unlock(); + return ret; +} + +void Spawn::TransferLoot(Spawn* spawn) { + if(spawn == this || spawn == nullptr) + return; // mmm no + + vector::iterator itr; + MLootItems.lock(); + for (itr = loot_items.begin(); itr != loot_items.end();) { + if (!(*itr)->IsBodyDrop()) { + spawn->AddLootItem(*itr); + itr = loot_items.erase(itr); + } + else { + itr++; + } + } + MLootItems.unlock(); +} + +int32 Spawn::GetLootItemID() { + int32 ret = 0; + vector::iterator itr; + MLootItems.lock(); + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) { + ret = (*itr)->details.item_id; + break; + } + MLootItems.unlock(); + return ret; +} + +void Spawn::GetLootItemsList(std::vector* out_entries) { + if(!out_entries) + return; + + vector::iterator itr; + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) { + out_entries->push_back((*itr)->details.item_id); + } +} + +bool Spawn::HasLootItemID(int32 id) { + bool ret = false; + + vector::iterator itr; + MLootItems.readlock(__FUNCTION__, __LINE__); + for (itr = loot_items.begin(); itr != loot_items.end(); itr++) { + if ((*itr)->details.item_id == id) { + ret = true; + break; + } + } + MLootItems.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void Spawn::CheckProximities() +{ + if (!has_spawn_proximities) + return; + + if (spawn_proximities.size() > 0) + { + MutexList::iterator itr = spawn_proximities.begin(); + while (itr.Next()) { + SpawnProximity* prox = itr.value; + map::iterator spawnsItr; + for (spawnsItr = prox->spawns_in_proximity.begin(); spawnsItr != prox->spawns_in_proximity.end(); spawnsItr++) { + Spawn* tmpSpawn = 0; + if (spawnsItr->first && + ((prox->spawn_type == SPAWNPROXIMITY_DATABASE_ID && (tmpSpawn = GetZone()->GetSpawnByDatabaseID(spawnsItr->first)) != 0) || + (prox->spawn_type == SPAWNPROXIMITY_LOCATION_ID && (tmpSpawn = GetZone()->GetSpawnByLocationID(spawnsItr->first)) != 0))) + { + if (!spawnsItr->second && tmpSpawn->GetDistance(this) <= prox->distance) + { + if (prox->in_range_lua_function.size() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, tmpSpawn, prox->in_range_lua_function.c_str()); + spawnsItr->second = true; + } + else if (spawnsItr->second && tmpSpawn->GetDistance(this) > prox->distance) + { + if (prox->leaving_range_lua_function.size() > 0) + GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_CUSTOM, tmpSpawn, prox->leaving_range_lua_function.c_str()); + spawnsItr->second = false; + } + } + } + } + } +} + +void Spawn::AddSpawnToProximity(int32 spawnValue, SpawnProximityType type) +{ + if (!has_spawn_proximities) + return; + + if (spawn_proximities.size() > 0) + { + MutexList::iterator itr = spawn_proximities.begin(); + while (itr.Next()) { + SpawnProximity* prox = itr->value; + if (prox->spawn_value == spawnValue && prox->spawn_type == type) + prox->spawns_in_proximity.insert(make_pair(spawnValue, false)); + } + } +} + +void Spawn::RemoveSpawnFromProximity(int32 spawnValue, SpawnProximityType type) +{ + if (!has_spawn_proximities) + return; + + if (spawn_proximities.size() > 0) + { + MutexList::iterator itr = spawn_proximities.begin(); + while (itr.Next()) { + SpawnProximity* prox = itr->value; + if (prox->spawn_value == spawnValue && prox->spawn_type == type && + prox->spawns_in_proximity.count(spawnValue) > 0) + prox->spawns_in_proximity.erase(spawnValue); + } + } +} + +void Spawn::AddPrimaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual, bool defaultDenyList, Player* player) { + + EntityCommand* cmd = FindEntityCommand(string(command), true); + + bool newCommand = false; + if (!cmd) + { + newCommand = true; + cmd = CreateEntityCommand(name, distance, command, error_text, cast_time, spell_visual, !defaultDenyList); + } + + if (defaultDenyList) + SetPermissionToEntityCommand(cmd, player, true); + + if (newCommand) + primary_command_list.push_back(cmd); +} + +void Spawn::RemovePrimaryEntityCommand(const char* command) { + vector::iterator itr; + string tmpStr(command); + for (itr = primary_command_list.begin(); itr != primary_command_list.end(); itr++) { + EntityCommand* cmd = *itr; + if (cmd->command.compare(tmpStr) == 0) + { + primary_command_list.erase(itr); + delete cmd; + break; + } + } +} + +bool Spawn::SetPermissionToEntityCommand(EntityCommand* command, Player* player, bool permissionValue) +{ + if(!player) + return false; + + return SetPermissionToEntityCommandByCharID(command, player->GetCharacterID(), permissionValue); +} + +bool Spawn::SetPermissionToEntityCommandByCharID(EntityCommand* command, int32 charID, bool permissionValue) +{ + map::iterator itr = command->allow_or_deny.find(charID); + if (itr == command->allow_or_deny.end()) + command->allow_or_deny.insert(make_pair(charID, permissionValue)); + else if (itr->second != permissionValue) + itr->second = permissionValue; + + return true; +} + +void Spawn::RemoveSpawnFromPlayer(Player* player) +{ + m_Update.writelock(__FUNCTION__, __LINE__); + player->RemoveSpawn(this); // sets it as removed + m_Update.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Spawn::InWater() +{ + bool inWater = false; + + if ( region_map != nullptr ) + { + glm::vec3 targPos(GetX(), GetY(), GetZ()); + if ( IsGroundSpawn() ) + targPos.y -= .5f; + + if(region_map->InWater(targPos, GetLocation())) + inWater = true; + } + + return inWater; +} + +bool Spawn::InLava() +{ + bool inLava = false; + + if ( region_map != nullptr ) + { + glm::vec3 targPos(GetX(), GetY(), GetZ()); + if ( IsGroundSpawn() ) + targPos.y -= .5f; + + if(region_map->InLava(targPos, GetLocation())) + inLava = true; + } + + return inLava; +} + +void Spawn::DeleteRegion(Region_Node* inNode, ZBSP_Node* rootNode) +{ + map, Region_Status>::iterator testitr; + for (testitr = Regions.begin(); testitr != Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node* node = actualItr->first; + ZBSP_Node* BSP_Root = actualItr->second; + if(inNode == node && rootNode == BSP_Root ) + { + testitr = Regions.erase(testitr); + break; + } + } +} + +bool Spawn::InRegion(Region_Node* inNode, ZBSP_Node* rootNode) +{ + map, Region_Status>::iterator testitr; + for (testitr = Regions.begin(); testitr != Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node* node = actualItr->first; + ZBSP_Node* BSP_Root = actualItr->second; + if(inNode == node && rootNode == BSP_Root ) + { + return testitr->second.inRegion; + } + } + + return false; +} + +int32 Spawn::GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode) +{ + map, Region_Status>::iterator testitr; + for (testitr = Regions.begin(); testitr != Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node* node = actualItr->first; + ZBSP_Node* BSP_Root = actualItr->second; + if(inNode == node && rootNode == BSP_Root ) + { + return testitr->second.regionType; + } + } + + return false; +} + +float Spawn::SpawnAngle(Spawn* target, float selfx, float selfz) +{ + if (!target || target == this) + return 0.0f; + + float angle, lengthb, vectorx, vectorz, dotp; + float spx = (target->GetX()); // mob xloc (inverse because eq) + float spz = -(target->GetZ()); // mob yloc + float heading = target->GetHeading(); // mob heading + if (heading < 270) + heading += 90; + else + heading -= 270; + + heading = heading * 3.1415f / 180.0f; // convert to radians + vectorx = spx + (10.0f * std::cos(heading)); // create a vector based on heading + vectorz = spz + (10.0f * std::sin(heading)); // of spawn length 10 + + // length of spawn to player vector + lengthb = (float) std::sqrt(((selfx - spx) * (selfx - spx)) + ((-selfz - spz) * (-selfz - spz))); + + // calculate dot product to get angle + // Handle acos domain errors due to floating point rounding errors + dotp = ((vectorx - spx) * (selfx - spx) + + (vectorz - spz) * (-selfz - spz)) / (10.0f * lengthb); + + if (dotp > 1) + return 0.0f; + else if (dotp < -1) + return 180.0f; + + angle = std::acos(dotp); + angle = angle * 180.0f / 3.1415f; + + return angle; +} + +void Spawn::StopMovement() +{ + reset_movement = true; +} + +bool Spawn::PauseMovement(int32 period_of_time_ms) +{ + if(period_of_time_ms < 1) + period_of_time_ms = 1; + + RunToLocation(GetX(),GetY(),GetZ()); + pause_timer.Start(period_of_time_ms, true); + + return true; +} + +bool Spawn::IsPauseMovementTimerActive() +{ + if(pause_timer.Check()) + pause_timer.Disable(); + + return pause_timer.Enabled(); +} + +bool Spawn::IsFlyingCreature() +{ + if(!IsEntity()) + return false; + + return ((Entity*)this)->GetInfoStruct()->get_flying_type(); +} + +bool Spawn::IsWaterCreature() +{ + if(!IsEntity()) + return false; + + return ((Entity*)this)->GetInfoStruct()->get_water_type(); +} + + +void Spawn::SetFlyingCreature() { + if(!IsEntity() || !rule_manager.GetGlobalRule(R_Spawn, UseHardCodeFlyingModelType)->GetInt8()) + return; + + if(((Entity*)this)->GetInfoStruct()->get_flying_type() > 0) // DB spawn npc flag already set + return; + + switch (GetModelType()) + { + case 260: + case 295: + ((Entity*)this)->GetInfoStruct()->set_flying_type(1); + is_flying_creature = true; + break; + default: + ((Entity*)this)->GetInfoStruct()->set_flying_type(0); + break; + } +} + +void Spawn::SetWaterCreature() { + if(!IsEntity() || !rule_manager.GetGlobalRule(R_Spawn, UseHardCodeWaterModelType)->GetInt8()) + return; + + if(((Entity*)this)->GetInfoStruct()->get_water_type() > 0) // DB spawn npc flag already set + return; + + switch (GetModelType()) + { + case 194: + case 204: + case 210: + case 241: + case 242: + case 254: + case 10668: + case 20828: + ((Entity*)this)->GetInfoStruct()->set_water_type(1); + break; + default: + ((Entity*)this)->GetInfoStruct()->set_water_type(0); + break; + } +} + +void Spawn::AddRailPassenger(int32 char_id) +{ + std::lock_guard lk(m_RailMutex); + rail_passengers.insert(make_pair(char_id,true)); +} + +void Spawn::RemoveRailPassenger(int32 char_id) +{ + std::lock_guard lk(m_RailMutex); + std::map::iterator itr = rail_passengers.find(char_id); + if(itr != rail_passengers.end()) + rail_passengers.erase(itr); +} + +vector Spawn::GetPassengersOnRail() { + vector tmp_list; + Spawn* spawn; + m_RailMutex.lock(); + std::map::iterator itr = rail_passengers.begin(); + while(itr != rail_passengers.end()){ + Client* client = zone_list.GetClientByCharID(itr->first); + if(!client || !client->GetPlayer()) + continue; + + tmp_list.push_back(client->GetPlayer()); + itr++; + } + m_RailMutex.unlock(); + return tmp_list; +} + +void Spawn::SetAppearancePosition(float x, float y, float z) { + appearance.pos.X = x; + appearance.pos.Y = y; + appearance.pos.Z = z; + appearance.pos.X2 = appearance.pos.X; + appearance.pos.Y2 = appearance.pos.Y; + appearance.pos.Z2 = appearance.pos.Z; + appearance.pos.X3 = appearance.pos.X; + appearance.pos.Y3 = appearance.pos.Y; + appearance.pos.Z3 = appearance.pos.Z; + + SetSpeedX(0); + SetSpeedY(0); + SetSpeedZ(0); + if(IsPlayer()) { + ((Player*)this)->SetSideSpeed(0); + ((Player*)this)->pos_packet_speed = 0; + } +} + + +int32 Spawn::InsertRegionToSpawn(Region_Node* node, ZBSP_Node* bsp_root, WaterRegionType regionType, bool in_region) { + std::map newMap; + newMap.insert(make_pair(node, bsp_root)); + Region_Status status; + status.inRegion = in_region; + status.regionType = regionType; + int32 returnValue = 0; + if(in_region) { + lua_interface->RunRegionScript(node->regionScriptName, "EnterRegion", GetZone(), this, regionType, &returnValue); + } + status.timerTic = returnValue; + status.lastTimerTic = returnValue ? Timer::GetCurrentTime2() : 0; + Regions.insert(make_pair(newMap, status)); + return returnValue; +} + + +bool Spawn::HasRegionTracked(Region_Node* node, ZBSP_Node* bsp_root, bool in_region) { + map, Region_Status>::iterator testitr; + for (testitr = Regions.begin(); testitr != Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node *node = actualItr->first; + ZBSP_Node *BSP_Root = actualItr->second; + if(node == actualItr->first && BSP_Root == actualItr->second) { + if(testitr->second.inRegion == in_region) + return true; + else + break; + } + } + + return false; +} + + +void Spawn::SetLocation(int32 id, bool setUpdateFlags) +{ + if(GetZone()) { + GetZone()->RemoveSpawnFromGrid(this, GetLocation()); + SetPos(&appearance.pos.grid_id, id, setUpdateFlags); + GetZone()->AddSpawnToGrid(this, id); + } + else { + SetPos(&appearance.pos.grid_id, id, setUpdateFlags); + } +} + +int8 Spawn::GetArrowColor(int8 spawn_level){ + int8 color = 0; + sint16 diff = spawn_level - GetLevel(); + if(GetLevel() < 10) + diff *= 3; + else if(GetLevel() <= 20) + diff *= 2; + if(diff >= 9) + color = ARROW_COLOR_RED; + else if(diff >= 5) + color = ARROW_COLOR_ORANGE; + else if(diff >= 1) + color = ARROW_COLOR_YELLOW; + else if(diff == 0) + color = ARROW_COLOR_WHITE; + else if(diff <= -11) + color = ARROW_COLOR_GRAY; + else if(diff <= -6) + color = ARROW_COLOR_GREEN; + else //if(diff < 0) + color = ARROW_COLOR_BLUE; + return color; +} + +void Spawn::AddIgnoredWidget(int32 id) { + std::unique_lock lock(MIgnoredWidgets); + if(ignored_widgets.find(id) == ignored_widgets.end()) { + ignored_widgets.insert(make_pair(id,true)); + } +} + +void Spawn::SendGroupUpdate() { + if (IsEntity() && ((Entity*)this)->GetGroupMemberInfo()) { + ((Entity*)this)->UpdateGroupMemberInfo(); + if (IsPlayer()) { + Client* client = ((Player*)this)->GetClient(); + if(client) { + world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id, client); + } + } + else + world.GetGroupManager()->SendGroupUpdate(((Entity*)this)->GetGroupMemberInfo()->group_id); + } +} + +bool Spawn::AddNeedGreedItemRequest(int32 item_id, int32 spawn_id, bool need_item) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: AddNeedGreedItemRequest Item ID: %u, Spawn ID: %u, Need Item: %u", GetName(), item_id, spawn_id, need_item); + if (HasSpawnNeedGreedEntry(item_id, spawn_id)) { + return false; + } + + need_greed_items.insert(make_pair(item_id, std::make_pair(spawn_id, need_item))); + + AddSpawnLootWindowCompleted(spawn_id, false); + return true; +} + +bool Spawn::AddLottoItemRequest(int32 item_id, int32 spawn_id) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: AddLottoItemRequest Item ID: %u, Spawn ID: %u", GetName(), item_id, spawn_id); + if (HasSpawnLottoEntry(item_id, spawn_id)) { + return false; + } + + lotto_items.insert(make_pair(item_id, spawn_id)); + + AddSpawnLootWindowCompleted(spawn_id, false); + return true; +} + +void Spawn::AddSpawnLootWindowCompleted(int32 spawn_id, bool status_) { + if (loot_complete.find(spawn_id) == loot_complete.end()) { + loot_complete.insert(make_pair(spawn_id, status_)); + } + + is_loot_complete = HasLootWindowCompleted(); +} + +bool Spawn::SetSpawnLootWindowCompleted(int32 spawn_id) { + std::map::iterator itr = loot_complete.find(spawn_id); + if (itr != loot_complete.end()) { + itr->second = true; + is_loot_complete = HasLootWindowCompleted(); + return true; + } + return false; +} + +bool Spawn::HasSpawnLootWindowCompleted(int32 spawn_id) { + std::map::iterator itr = loot_complete.find(spawn_id); + if (itr != loot_complete.end() && itr->second) { + return true; + } + return false; +} + +bool Spawn::HasSpawnNeedGreedEntry(int32 item_id, int32 spawn_id) { + for (auto [itr, rangeEnd] = need_greed_items.equal_range(item_id); itr != rangeEnd; itr++) { + LogWrite(LOOT__DEBUG, 8, "Loot", "%s: HasSpawnNeedGreedEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second.first); + if (spawn_id == itr->second.first) { + return true; + } + } + return false; +} + +bool Spawn::HasSpawnLottoEntry(int32 item_id, int32 spawn_id) { + for (auto [itr, rangeEnd] = lotto_items.equal_range(item_id); itr != rangeEnd; itr++) { + LogWrite(LOOT__DEBUG, 8, "Loot", "%s: HasSpawnLottoEntry Item ID: %u, Spawn ID: %u", GetName(), itr->first, itr->second); + if (spawn_id == itr->second) { + return true; + } + } + return false; +} + +void Spawn::GetSpawnLottoEntries(int32 item_id, std::map* out_entries) { + if (!out_entries) + return; + + std::map spawn_matches; + for (auto [itr, endrange] = lotto_items.equal_range(item_id); itr != endrange; itr++) { + out_entries->insert(std::make_pair(itr->second, (int32)MakeRandomInt(0, 100))); + spawn_matches[itr->second] = true; + } + + // 0xFFFFFFFF represents selecting "All" on the lotto screen + for (auto [itr, endrange] = lotto_items.equal_range(0xFFFFFFFF); itr != endrange; itr++) { + if (spawn_matches.find(itr->second) == spawn_matches.end()) { + out_entries->insert(std::make_pair(itr->second, (int32)MakeRandomInt(0, 100))); + } + } +} + +void Spawn::GetSpawnNeedGreedEntries(int32 item_id, bool need_item, std::map* out_entries) { + if (!out_entries) + return; + + for (auto [itr, rangeEnd] = need_greed_items.equal_range(item_id); itr != rangeEnd; itr++) { + out_entries->insert(std::make_pair(itr->second.first, (int32)MakeRandomInt(0, 100))); + } +} + +bool Spawn::HasLootWindowCompleted() { + std::map::iterator itr; + for (itr = loot_complete.begin(); itr != loot_complete.end(); itr++) { + if (!itr->second) + return false; + } + + return true; +} + +void Spawn::StartLootTimer(Spawn* looter) { + if (!IsLootTimerRunning()) { + int32 loot_timer_time = rule_manager.GetGlobalRule(R_Loot, LootDistributionTime)->GetInt32() * 1000; + if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByDropTime)->GetBool() && loot_timer_time > rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000) { + loot_timer_time = (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeDrop)->GetInt32()*1000) / 2; + } + + if(rule_manager.GetGlobalRule(R_Loot, AllowChestUnlockByTrapTime)->GetBool() && loot_timer_time > rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000) { + loot_timer_time = (rule_manager.GetGlobalRule(R_Loot, ChestUnlockedTimeTrap)->GetInt32()*1000) / 2; + } + + if(loot_timer_time < 1000) { + loot_timer_time = 60000; // hardcode assure they aren't setting some really ridiculous low number + } + + loot_timer.Start(loot_timer_time, true); + } + if (looter) { + looter_spawn_id = looter->GetID(); + } +} + +void Spawn::CloseLoot(Spawn* sender) { + if (sender) { + SetSpawnLootWindowCompleted(sender->GetID()); + } + if (sender && looter_spawn_id > 0 && sender->GetID() != looter_spawn_id) { + LogWrite(LOOT__ERROR, 0, "Loot", "%s: CloseLoot Looter Spawn ID: %u does not match sender %u.", GetName(), looter_spawn_id, sender->GetID()); + return; + } + if (!IsLootTimerRunning() && GetLootMethod() != GroupLootMethod::METHOD_LOTTO && GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) { + loot_timer.Disable(); + } + looter_spawn_id = 0; +} + +void Spawn::SetLootMethod(GroupLootMethod method, int8 item_rarity, int32 group_id) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: Set Loot Method : %u, group id : %u", GetName(), (int32)method, group_id); + loot_group_id = group_id; + loot_method = method; + loot_rarity = item_rarity; + if (loot_name.size() < 1) { + loot_name = std::string(GetName()); + } +} + +bool Spawn::IsItemInLootTier(Item* item) { + if (!item) + return true; + + bool skipItem = true; + switch (GetLootRarity()) { + case LootTier::ITEMS_TREASURED_PLUS: { + if (item->details.tier >= ITEM_TAG_TREASURED) { + skipItem = false; + } + break; + } + case LootTier::ITEMS_LEGENDARY_PLUS: { + if (item->details.tier >= ITEM_TAG_LEGENDARY) { + skipItem = false; + } + break; + } + case LootTier::ITEMS_FABLED_PLUS: { + if (item->details.tier >= ITEM_TAG_FABLED) { + skipItem = false; + } + break; + } + default: { + skipItem = false; + break; + } + } + + return skipItem; +} + +void Spawn::DistributeGroupLoot_RoundRobin(std::vector* item_list, bool roundRobinTrashLoot) { + + std::vector::iterator item_itr; + + for (item_itr = item_list->begin(); item_itr != item_list->end(); item_itr++) { + int32 item_id = *item_itr; + Item* tmpItem = master_item_list.GetItem(item_id); + Spawn* looter = nullptr; + + bool skipItem = IsItemInLootTier(tmpItem); + + if ((skipItem && !roundRobinTrashLoot) || (!skipItem && roundRobinTrashLoot)) + continue; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(GetLootGroupID()); + if (group) { + group->MGroupMembers.writelock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + int8 index = group->GetLastLooterIndex(); + if (index >= members->size()) { + index = 0; + } + + GroupMemberInfo* gmi = members->at(index); + if (gmi) { + looter = gmi->member; + } + bool loopAttempted = false; + while (looter) { + if (!looter->IsPlayer()) { + index++; + if (index >= members->size()) { + if (loopAttempted) { + looter = nullptr; + break; + } + loopAttempted = true; + index = 0; + } + gmi = members->at(index); + if (gmi) { + looter = gmi->member; + } + continue; + } + else { + break; + } + } + index += 1; + group->SetNextLooterIndex(index); + group->MGroupMembers.releasewritelock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + if (looter) { + if (looter->IsPlayer()) { + Item* item = LootItem(item_id); + bool success = false; + success = ((Player*)looter)->GetClient()->HandleLootItem(this, item, ((Player*)looter), roundRobinTrashLoot); + + if (!success) + AddLootItem(item); + } + else { + Item* item = LootItem(item_id); + safe_delete(item); + } + } + } +} + +const double g = 9.81; // acceleration due to gravity (m/s^2) + +void Spawn::CalculateInitialVelocity(float heading, float distanceHorizontal, float distanceVertical, float distanceDepth, float duration) { + float vx = distanceHorizontal / duration; + float vy = (distanceVertical + 0.5 * g * duration * duration) / duration; + float vz = distanceDepth / duration; + + // Convert heading angle to radians + knocked_velocity.x = vx * cos(heading); + knocked_velocity.y = vy; + knocked_velocity.z = vz * sin(heading); +} + +// Function to calculate the projectile position at a given time +glm::vec3 Spawn::CalculateProjectilePosition(glm::vec3 initialVelocity, float time) { + glm::vec3 position; + + position.x = knocked_back_start_x + initialVelocity.x * time; + position.y = knocked_back_start_y + initialVelocity.y * time - 0.5 * g * time * time; + position.z = knocked_back_start_z + initialVelocity.z * time; + + auto loc = glm::vec3(position.x, position.z, position.y); + float new_z = FindBestZ(loc, nullptr); + if(new_z > position.y) + position.y = new_z; + + return position; +} + +bool Spawn::CalculateSpawnProjectilePosition(float x, float y, float z) { + float currentTimeOffset = (Timer::GetCurrentTime2() - last_movement_update) * 0.001; // * 0.001 is the same as / 1000, float muliplications is suppose to be faster though + float stepAheadOne = currentTimeOffset+currentTimeOffset; + + knocked_back_time_step += currentTimeOffset; + if(Timer::GetCurrentTime2() >= knocked_back_end_time) { + ResetKnockedBack(); + FixZ(true); + return false; + } + glm::vec3 position = CalculateProjectilePosition(knocked_velocity, knocked_back_time_step); + glm::vec3 position_two = position; + + if(Timer::GetCurrentTime2() <= knocked_back_end_time+stepAheadOne) { + position_two = CalculateProjectilePosition(knocked_velocity, knocked_back_time_step+stepAheadOne); + } + + if(GetMap()) { + glm::vec3 loc(GetX(), GetZ(), GetY() + .5f); + glm::vec3 dest_loc(position_two.x, position_two.z, position_two.y); + MIgnoredWidgets.lock_shared(); + glm::vec3 outNorm; + float dist = 0.0f; + bool collide_ = GetMap()->DoCollisionCheck(loc, dest_loc, &ignored_widgets, outNorm, dist); + if(collide_) { + LogWrite(SPAWN__ERROR, 0, "Spawn", "Collision Hit: cur loc x,y,z: %f %f %f. to loc %f %f %f. TimeOffset: %f Total Time: %f Duration: %f", GetX(),GetY(),GetZ(),position_two.x,position_two.y,position_two.z, currentTimeOffset, knocked_back_time_step, knocked_back_duration); + MIgnoredWidgets.unlock_shared(); + ResetKnockedBack(); + FixZ(true); + return false; + } + MIgnoredWidgets.unlock_shared(); + } + + LogWrite(SPAWN__ERROR, 0, "Spawn", "x,y,z: %f %f %f. Final %f %f %f. TimeOffset: %f Total Time: %f Duration: %f", GetX(),GetY(),GetZ(),position.x,position.y,position.z, currentTimeOffset, knocked_back_time_step, knocked_back_duration); + + SetX(position.x, false); + SetZ(position.z, false); + SetY(position.y, false, true); + + SetPos(&appearance.pos.X2, position_two.x, false); + SetPos(&appearance.pos.Z2, position_two.z, false); + SetPos(&appearance.pos.Y2, position_two.y, false); + SetPos(&appearance.pos.X3, position_two.x, false); + SetPos(&appearance.pos.Z3, position_two.z, false); + SetPos(&appearance.pos.Y3, position_two.y, false); + + position_changed = true; + changed = true; + GetZone()->AddChangedSpawn(this); + return true; +} + +void Spawn::SetKnockback(Spawn* target, int32 duration, float vertical, float horizontal) { + if(knocked_back) { + return; // already being knocked back + } + + // Calculate the direction vector from source to destination + glm::vec3 direction = {GetX() - target->GetX(), GetZ() - target->GetZ(), GetY() - target->GetY()}; + + // Calculate the heading angle in radians + double headingRad = atan2(direction.y, sqrt(direction.x * direction.x + direction.z * direction.z)); + + knocked_angle = headingRad; + knocked_back_start_x = GetX(); + knocked_back_start_y = GetY(); + knocked_back_start_z = GetZ(); + knocked_back_h_distance = horizontal / 10.0f; + knocked_back_v_distance = vertical / 10.0f; + knocked_back_duration = static_cast(duration) / 1000.0f; + knocked_back_end_time = Timer::GetCurrentTime2() + duration; + CalculateInitialVelocity(knocked_angle, knocked_back_h_distance, knocked_back_h_distance, knocked_back_h_distance, knocked_back_duration); + knocked_back = true; +} + +void Spawn::ResetKnockedBack() { + knocked_back = false; + knocked_back_time_step = 0.0f; + knocked_back_h_distance = 0.0f; + knocked_back_v_distance = 0.0f; + knocked_back_duration = 0.0f; + knocked_back_end_time = 0; + knocked_back_start_x = 0.0f; + knocked_back_start_y = 0.0f; + knocked_back_start_z = 0.0f; + knocked_angle = 0.0f; + knocked_velocity.x = 0.0f; + knocked_velocity.y = 0.0f; + knocked_velocity.z = 0.0f; +} \ No newline at end of file diff --git a/source/WorldServer/Spawn.h b/source/WorldServer/Spawn.h new file mode 100644 index 0000000..373f265 --- /dev/null +++ b/source/WorldServer/Spawn.h @@ -0,0 +1,1568 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_SPAWN__ +#define __EQ2_SPAWN__ + +#include + +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/EQ2_Common_Structs.h" +#include "../common/MiscFunctions.h" +#include "../common/opcodemgr.h" +#include "../common/timer.h" +#include "Commands/Commands.h" +#include "Zone/position.h" +#include "SpawnLists.h" +#include +#include "../common/ConfigReader.h" +#include "Items/Items.h" +#include "Zone/map.h" +#include "Zone/region_map.h" +#include "Zone/region_map_v1.h" +#include "../common/Mutex.h" +#include "MutexList.h" +#include +#include // needed for LS to compile properly on linux +#include +#include + +#define DAMAGE_PACKET_TYPE_SIPHON_SPELL 0x41 +#define DAMAGE_PACKET_TYPE_SIPHON_SPELL2 0x49 +#define DAMAGE_PACKET_TYPE_MULTIPLE_DAMAGE 0x80 +#define DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE 0xC0 +#define DAMAGE_PACKET_TYPE_SPELL_DAMAGE 0xC1 +#define DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG 0xC4 +#define DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG 0xC5 +#define DAMAGE_PACKET_TYPE_SPELL_DAMAGE2 0xC8 +#define DAMAGE_PACKET_TYPE_SPELL_DAMAGE3 0xC9 +#define DAMAGE_PACKET_TYPE_RANGE_DAMAGE 0xE2 +#define DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG 0xE3 +#define DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG2 0xEA + +#define DAMAGE_PACKET_RESULT_NO_DAMAGE 0 +#define DAMAGE_PACKET_RESULT_SUCCESSFUL 1 +#define DAMAGE_PACKET_RESULT_MISS 4 +#define DAMAGE_PACKET_RESULT_DODGE 8 +#define DAMAGE_PACKET_RESULT_PARRY 12 +#define DAMAGE_PACKET_RESULT_RIPOSTE 16 +#define DAMAGE_PACKET_RESULT_BLOCK 20 +#define DAMAGE_PACKET_RESULT_DEATH_BLOW 24 +#define DAMAGE_PACKET_RESULT_INVULNERABLE 28 +#define DAMAGE_PACKET_RESULT_RESIST 36 +#define DAMAGE_PACKET_RESULT_REFLECT 40 +#define DAMAGE_PACKET_RESULT_IMMUNE 44 +#define DAMAGE_PACKET_RESULT_DEFLECT 48 +#define DAMAGE_PACKET_RESULT_COUNTER 52 +#define DAMAGE_PACKET_RESULT_FOCUS 56 // focus damage +#define DAMAGE_PACKET_RESULT_COUNTER_STRIKE 60 +#define DAMAGE_PACKET_RESULT_BASH 64 + +#define DAMAGE_PACKET_DAMAGE_TYPE_SLASH 0 +#define DAMAGE_PACKET_DAMAGE_TYPE_CRUSH 1 +#define DAMAGE_PACKET_DAMAGE_TYPE_PIERCE 2 +#define DAMAGE_PACKET_DAMAGE_TYPE_HEAT 3 +#define DAMAGE_PACKET_DAMAGE_TYPE_COLD 4 +#define DAMAGE_PACKET_DAMAGE_TYPE_MAGIC 5 +#define DAMAGE_PACKET_DAMAGE_TYPE_MENTAL 6 +#define DAMAGE_PACKET_DAMAGE_TYPE_DIVINE 7 +#define DAMAGE_PACKET_DAMAGE_TYPE_DISEASE 8 +#define DAMAGE_PACKET_DAMAGE_TYPE_POISON 9 +#define DAMAGE_PACKET_DAMAGE_TYPE_DROWN 10 +#define DAMAGE_PACKET_DAMAGE_TYPE_FALLING 11 +#define DAMAGE_PACKET_DAMAGE_TYPE_PAIN 12 +#define DAMAGE_PACKET_DAMAGE_TYPE_HIT 13 +#define DAMAGE_PACKET_DAMAGE_TYPE_FOCUS 14 // used as a placeholder to translate over to focus from LUA functions and weapons + + +#define HEAL_PACKET_TYPE_SIMPLE_HEAL 0 +#define HEAL_PACKET_TYPE_CRIT_HEAL 1 +#define HEAL_PACKET_TYPE_ABSORB 2 +#define HEAL_PACKET_TYPE_REGEN_ABSORB 4 +#define HEAL_PACKET_TYPE_SIMPLE_MANA 8 +#define HEAL_PACKET_TYPE_CRIT_MANA 9 +#define HEAL_PACKET_TYPE_SAVAGERY 16 +#define HEAL_PACKET_TYPE_CRIT_SAVAGERY 17 +#define HEAL_PACKET_TYPE_REPAIR 64 +#define HEAL_PACKET_TYPE_CRIT_REPAIR 65 + +#define ARROW_COLOR_GRAY 0 // 3 +#define ARROW_COLOR_GREEN 1 // 1 +#define ARROW_COLOR_BLUE 2 +#define ARROW_COLOR_WHITE 3 // 3 +#define ARROW_COLOR_YELLOW 4 // 4 +#define ARROW_COLOR_ORANGE 5 // 5 +#define ARROW_COLOR_RED 6 + +#define ACTIVITY_STATUS_ROLEPLAYING 1 +#define ACTIVITY_STATUS_ANONYMOUS 2 +#define ACTIVITY_STATUS_LINKDEAD 4 +#define ACTIVITY_STATUS_CAMPING 8 +#define ACTIVITY_STATUS_LFG 16 +#define ACTIVITY_STATUS_LFW 32 +#define ACTIVITY_STATUS_SOLID 64 //used by zone objects to remain solid +#define ACTIVITY_STATUS_IMMUNITY_GAINED 8192 +#define ACTIVITY_STATUS_IMMUNITY_REMAINING 16384 +// WE ARE UNSURE OF THESE OLD CLIENT VALUES USED AS TEMP PLACEHOLDERS FOR NEWER CLIENTS +#define ACTIVITY_STATUS_AFK 32768 // whats the real one? + +#define ACTIVITY_STATUS_CORPSE_561 1 +#define ACTIVITY_STATUS_NPC_561 1<<1 +#define ACTIVITY_STATUS_STATICOBJECT_561 1<<2 +#define ACTIVITY_STATUS_MERCHANT_561 1<<4 +#define ACTIVITY_STATUS_HIDEICON_561 1<<8 +#define ACTIVITY_STATUS_INTERACTABLE_561 1<<9 +#define ACTIVITY_STATUS_NOTARGET_561 1<<10 +#define ACTIVITY_STATUS_ISTRANSPORT_561 1<<11 +#define ACTIVITY_STATUS_SHOWHOUSEICON_561 1<<12 +#define ACTIVITY_STATUS_LOOTABLE_561 1<<13 +#define ACTIVITY_STATUS_INCOMBAT_561 1<<14 +#define ACTIVITY_STATUS_AFK_561 1<<15 +#define ACTIVITY_STATUS_ROLEPLAYING_561 1<<16 +#define ACTIVITY_STATUS_ANONYMOUS_561 1<<17 +#define ACTIVITY_STATUS_LINKDEAD_561 1<<18 +#define ACTIVITY_STATUS_CAMPING_561 1<<19 +#define ACTIVITY_STATUS_LFG_561 1<<20 +#define ACTIVITY_STATUS_LFW_561 1<<21 + +#define ACTIVITY_STATUS_SOLID_561 1<<22 //used by zone objects to remain solid +#define ACTIVITY_STATUS_MENTORING_561 1<<28 +#define ACTIVITY_STATUS_IMMUNITY_GAINED_561 1<<30 +#define ACTIVITY_STATUS_IMMUNITY_REMAINING_561 1<<31 + +#define ACTIVITY_STATUS_MERCENARY_1188 1<<2 +#define ACTIVITY_STATUS_STATICOBJECT_1188 1<<3 +#define ACTIVITY_STATUS_MERCHANT_1188 1<<4 +#define ACTIVITY_STATUS_HIDEICON_1188 1<<9 +#define ACTIVITY_STATUS_INTERACTABLE_1188 1<<10 +#define ACTIVITY_STATUS_NOTARGET_1188 1<<11 +#define ACTIVITY_STATUS_ISTRANSPORT_1188 1<<12 +#define ACTIVITY_STATUS_SHOWHOUSEICON_1188 1<<13 +#define ACTIVITY_STATUS_LOOTABLE_1188 1<<14 +#define ACTIVITY_STATUS_INCOMBAT_1188 1<<15 +#define ACTIVITY_STATUS_AFK_1188 1<<16 +#define ACTIVITY_STATUS_ROLEPLAYING_1188 1<<17 +#define ACTIVITY_STATUS_ANONYMOUS_1188 1<<18 +#define ACTIVITY_STATUS_LINKDEAD_1188 1<<19 +#define ACTIVITY_STATUS_CAMPING_1188 1<<20 +#define ACTIVITY_STATUS_LFG_1188 1<<21 +#define ACTIVITY_STATUS_LFW_1188 1<<22 +#define ACTIVITY_STATUS_SOLID_1188 1<<23 //used by zone objects to remain solid +#define ACTIVITY_STATUS_MENTORING_1188 1<<28 +#define ACTIVITY_STATUS_IMMUNITY_GAINED_1188 1<<30 +#define ACTIVITY_STATUS_IMMUNITY_REMAINING_1188 1<<31 + +#define POS_STATE_KNEELING 64 +#define POS_STATE_SOLID 128 //used by most mobs to remaind solid (cant walk through them) +#define POS_STATE_NOTARGET_CURSOR 256 //cant target and no cursor is displayed +#define POS_STATE_CROUCHING 512 + +#define MERCHANT_TYPE_NO_BUY 1 +#define MERCHANT_TYPE_NO_BUY_BACK 2 +#define MERCHANT_TYPE_SPELLS 4 +#define MERCHANT_TYPE_CRAFTING 8 +#define MERCHANT_TYPE_REPAIR 16 +#define MERCHANT_TYPE_LOTTO 32 +#define MERCHANT_TYPE_CITYMERCHANT 64 + +#define INFO_VIS_FLAG_INVIS 1 +#define INFO_VIS_FLAG_HIDE_HOOD 2 +#define INFO_VIS_FLAG_MOUNTED 4 +#define INFO_VIS_FLAG_CROUCH 8 + +#define ENCOUNTER_STATE_NONE 0 +#define ENCOUNTER_STATE_AVAILABLE 1 +#define ENCOUNTER_STATE_BROKEN 2 +#define ENCOUNTER_STATE_LOCKED 3 +#define ENCOUNTER_STATE_OVERMATCHED 4 +#define ENCOUNTER_STATE_NO_REWARD 5 + +#define VISUAL_STATE_COLLECTION_TURN_IN 6674 +#define VISUAL_STATE_IDLE_AFRAID 17953 + +#define INFO_CLASSIC_FLAG_INVIS 1 +#define INFO_CLASSIC_FLAG_SHOW_HOOD 2 +#define INFO_CLASSIC_FLAG_NOLOOK 4 +#define INFO_CLASSIC_FLAG_CROUCH 8 + +using namespace std; +class Spell; +class ZoneServer; +class Quest; +struct LUAHistory; +struct Cell; + +struct CellInfo { + Cell* CurrentCell; + int CellListIndex; +}; + +struct MovementData{ + float x; + float y; + float z; + float speed; + int32 delay; + string lua_function; + float heading; + bool use_movement_location_heading; +}; + +struct BasicInfoStruct{ + sint32 cur_hp; + sint32 max_hp; + sint32 hp_base; + sint32 hp_base_instance; + sint32 cur_power; + sint32 max_power; + sint32 power_base; + sint32 power_base_instance; + sint32 cur_savagery; + sint32 max_savagery; + sint32 savagery_base; + sint32 cur_dissonance; + sint32 max_dissonance; + sint32 dissonance_base; + sint16 assigned_aa; + sint16 unassigned_aa; + sint16 tradeskill_aa; + sint16 unassigned_tradeskill_aa; + sint16 prestige_aa; + sint16 unassigned_prestige_aa; + sint16 tradeskill_prestige_aa; + sint16 unassigned_tradeskill_prestige_aa; + int32 aaxp_rewards; +}; + +struct MovementLocation{ + float x; + float y; + float z; + float speed; + //int32 start_time; + //int32 end_time; + bool attackable; + string lua_function; + bool mapped; + int32 gridid; + int8 stage; + bool reset_hp_on_runback; +}; + +struct SpawnUpdate { + int32 spawn_id; + bool info_changed; + bool vis_changed; + bool pos_changed; + shared_ptr client; +}; + +struct SpawnData { + Spawn* spawn; + uchar* data; + int32 size; +}; + +struct TimedGridData { + int32 timestamp; + int32 grid_id; + float x; + float y; + float z; + float offset_y; + float zone_ground_y; + bool npc_save; + int32 widget_id; +}; + +enum GroupLootMethod { + METHOD_LEADER=0, + METHOD_FFA=1, + METHOD_LOTTO=2, + METHOD_NEED_BEFORE_GREED=3, + METHOD_ROUND_ROBIN=4 +}; + +enum AutoLootMode { + METHOD_DISABLED=0, + METHOD_ACCEPT=1, + METHOD_DECLINE=2 +}; + +enum LootTier { + ITEMS_ALL=0, + ITEMS_TREASURED_PLUS=1, + ITEMS_LEGENDARY_PLUS=2, + ITEMS_FABLED_PLUS=3 +}; + + +class Spawn { +public: + Spawn(); + virtual ~Spawn(); + + template void Set(Field* field, Value value, bool setUpdateFlags = true){ + if (setUpdateFlags) { + changed = true; + AddChangedZoneSpawn(); + } + *field = value; + } + template void Set(Field* field, const char* value, bool setUpdateFlags = true){ + if (setUpdateFlags) { + changed = true; + AddChangedZoneSpawn(); + } + strcpy(field, value); + } + template void SetPos(Field* field, Value value, bool setUpdateFlags = true){ + if(setUpdateFlags){ + position_changed = true; + } + Set(field, value, setUpdateFlags); + } + template void SetInfo(Field* field, Value value, bool setUpdateFlags = true){ + if(setUpdateFlags){ + info_changed = true; + } + Set(field, value); + } + template void SetVis(Field* field, Value value, bool setUpdateFlags = true){ + if(setUpdateFlags) + vis_changed = true; + Set(field, value); + } + template void SetPos(Field* field, char* value, bool setUpdateFlags = true){ + if(setUpdateFlags){ + position_changed = true; + } + Set(field, value, setUpdateFlags); + } + template void SetInfo(Field* field, char* value, bool setUpdateFlags = true){ + if(setUpdateFlags){ + info_changed = true; + } + Set(field, value); + } + EntityCommand* CreateEntityCommand(EntityCommand* old_command){ + EntityCommand* entity_command = new EntityCommand; + entity_command->name = old_command->name; + entity_command->distance = old_command->distance; + entity_command->command = old_command->command; + entity_command->error_text = old_command->error_text; + entity_command->cast_time = old_command->cast_time; + entity_command->spell_visual = old_command->spell_visual; + entity_command->default_allow_list = old_command->default_allow_list; + return entity_command; + } + EntityCommand* CreateEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual, bool default_allow_list=true){ + EntityCommand* entity_command = new EntityCommand; + entity_command->name = name; + entity_command->distance = distance; + entity_command->command = command; + entity_command->error_text = error_text; + entity_command->cast_time = cast_time; + entity_command->spell_visual = spell_visual; + entity_command->default_allow_list = default_allow_list; + return entity_command; + } + virtual Client* GetClient() { return 0; } + void AddChangedZoneSpawn(); + void AddPrimaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual, bool defaultDenyList = false, Player* player = NULL); + void RemovePrimaryEntityCommand(const char* command); + bool SetPermissionToEntityCommand(EntityCommand* command, Player* player, bool permissionValue); + bool SetPermissionToEntityCommandByCharID(EntityCommand* command, int32 charID, bool permissionValue); + + void RemoveSpawnFromPlayer(Player* player); + + void AddSecondaryEntityCommand(const char* name, float distance, const char* command, const char* error_text, int16 cast_time, int32 spell_visual){ + secondary_command_list.push_back(CreateEntityCommand(name, distance, command, error_text, cast_time, spell_visual)); + } + int8 GetLockedNoLoot(){ + return appearance.locked_no_loot; + } + int16 GetEmoteState(){ + return appearance.emote_state; + } + int8 GetHideHood(){ + return appearance.hide_hood; + } + void SetLockedNoLoot(int8 new_val, bool updateFlags = true){ + SetVis(&appearance.locked_no_loot, new_val, updateFlags); + } + void SetHandFlag(int8 new_val, bool updateFlags = true){ + SetVis(&appearance.display_hand_icon, new_val, updateFlags); + } + void SetHideHood(int8 new_val, bool updateFlags = true){ + SetInfo(&appearance.hide_hood, new_val, updateFlags); + } + void SetEmoteState(int8 new_val, bool updateFlags = true){ + SetInfo(&appearance.emote_state, new_val, updateFlags); + } + void SetName(const char* new_name, bool updateFlags = true){ + SetInfo(appearance.name, new_name, updateFlags); + } + void SetPrefixTitle(const char* new_prefix_title, bool updateFlags = true) { + SetInfo(appearance.prefix_title, new_prefix_title, updateFlags); + } + void SetSuffixTitle(const char* new_suffix_title, bool updateFlags = true) { + SetInfo(appearance.suffix_title, new_suffix_title, updateFlags); + } + void SetSubTitle(const char* new_sub_title, bool updateFlags = true) { + SetInfo(appearance.sub_title, new_sub_title, updateFlags); + } + void SetLastName(const char* new_last_name, bool updateFlags = true) { + SetInfo(appearance.last_name, new_last_name, updateFlags); + } + void SetAdventureClass(int8 new_class, bool updateFlags = true) { + SetInfo(&appearance.adventure_class, new_class, updateFlags); + } + void SetTradeskillClass(int8 new_class, bool updateFlags = true) { + SetInfo(&appearance.tradeskill_class, new_class, updateFlags); + } + void SetSize(int16 new_size, bool updateFlags = true) { + SetPos(&size, new_size, updateFlags); + } + void SetSpeedX(float speed_x, bool updateFlags = true) { + SetPos(&appearance.pos.SpeedX, speed_x, updateFlags); + } + void SetSpeedY(float speed_y, bool updateFlags = true) { + SetPos(&appearance.pos.SpeedY, speed_y, updateFlags); + } + void SetSpeedZ(float speed_z, bool updateFlags = true) { + SetPos(&appearance.pos.SpeedZ, speed_z, updateFlags); + } + void SetX(float x, bool updateFlags = true){ + SetPos(&appearance.pos.X, x, updateFlags); + } + void SetY(float y, bool updateFlags = true, bool disableYMapFix = false); + void SetZ(float z, bool updateFlags = true){ + SetPos(&appearance.pos.Z, z, updateFlags); + } + void SetHeading(sint16 dir1, sint16 dir2, bool updateFlags = true){ + SetPos(&appearance.pos.Dir1, dir1, updateFlags); + SetPos(&appearance.pos.Dir2, dir2, updateFlags); + } + void SetHeading(float heading, bool updateFlags = true){ + last_heading_angle = heading; + if (heading != 180) + heading = (heading - 180) * 64; + SetHeading((sint16)heading, (sint16)heading, updateFlags); + } + void SetPitch(sint16 pitch1, sint16 pitch2, bool updateFlags = true){ + SetPos(&appearance.pos.Pitch1, (sint16)pitch1, updateFlags); + SetPos(&appearance.pos.Pitch2, (sint16)pitch2, updateFlags); + } + void SetPitch(float pitch, bool updateFlags = true){ + if (pitch == 0){ + SetPos(&appearance.pos.Pitch1, (sint16)0, updateFlags); + SetPos(&appearance.pos.Pitch2, (sint16)0, updateFlags); + return; + } + if (pitch != 180) + pitch = (pitch - 180) * 64; + SetPos(&appearance.pos.Pitch1, (sint16)pitch, updateFlags); + SetPos(&appearance.pos.Pitch2, (sint16)pitch, updateFlags); + } + void SetRoll(float roll, bool updateFlags = true){ + if (roll == 0){ + SetPos(&appearance.pos.Roll, (sint16)0, updateFlags); + return; + } + else if (roll != 180) + roll = (roll - 180) * 64; + SetPos(&appearance.pos.Roll, (sint16)roll, updateFlags); + } + void SetVisualState(int16 state, bool updateFlags = true){ + SetInfo(&appearance.visual_state, state, updateFlags); + } + void SetActionState(int16 state, bool updateFlags = true){ + SetInfo(&appearance.action_state, state, updateFlags); + } + void SetMoodState(int16 state, bool updateFlags = true){ + SetInfo(&appearance.mood_state, state, updateFlags); + } + void SetInitialState(int16 state, bool updateFlags = true){ + SetPos(&appearance.pos.state, state, updateFlags); + } + void SetActivityStatus(int16 state, bool updateFlags = true){ + SetInfo(&appearance.activity_status, state, updateFlags); + } + void SetCollisionRadius(int32 radius, bool updateFlags = true){ + SetPos(&appearance.pos.collision_radius, radius, updateFlags); + } + int16 GetCollisionRadius(){ + return appearance.pos.collision_radius; + } + int16 GetVisualState(){ + return appearance.visual_state; + } + int16 GetActionState(){ + return appearance.action_state; + } + int16 GetMoodState(){ + return appearance.mood_state; + } + int16 GetInitialState(){ + return appearance.pos.state; + } + int16 GetActivityStatus(){ + return appearance.activity_status; + } + int32 GetPrimaryCommandListID(){ + return primary_command_list_id; + } + int32 GetSecondaryCommandListID(){ + return secondary_command_list_id; + } + void SetID(int32 in_id){ + Set(&id, in_id); + } + void SetDifficulty(int8 difficulty, bool setUpdateFlags = true){ + SetInfo(&appearance.difficulty, difficulty, setUpdateFlags); + } + virtual void SetLevel(int16 level, bool setUpdateFlags = true){ + SetInfo(&appearance.level, level, setUpdateFlags); + } + void SetTSLevel(int16 tradeskill_level, bool setUpdateFlags = true){ + SetInfo(&appearance.tradeskill_level, tradeskill_level, setUpdateFlags); + } + void SetGender(int8 gender, bool setUpdateFlags = true){ + SetInfo(&appearance.gender, gender, setUpdateFlags); + } + void SetShowName(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.display_name, new_val, setUpdateFlags); + } + void SetShowLevel(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.show_level, new_val, setUpdateFlags); + } + void SetHeroic(int8 new_val, bool setUpdateFlags = true){ + SetInfo(&appearance.heroic_flag, new_val, setUpdateFlags); + } + void SetTargetable(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.targetable, new_val, setUpdateFlags); + } + void SetShowCommandIcon(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.show_command_icon, new_val, setUpdateFlags); + } + void SetShowHandIcon(int8 new_val, bool setUpdateFlags = true){ + SetVis(&appearance.display_hand_icon, new_val, setUpdateFlags); + } + void SetAttackable(int8 new_val, bool setUpdateFlags = true){ + SetInfo(&appearance.attackable, new_val, setUpdateFlags); + } + void SetLocation(int32 id, bool setUpdateFlags = true); + void SetRace(int8 race, bool setUpdateFlags = true){ + SetInfo(&appearance.race, race, setUpdateFlags); + } + void SetIcon(int8 icon, bool setUpdateFlags = true){ + SetInfo(&appearance.icon, icon, setUpdateFlags); + } + void AddIconValue(int8 val){ + if(!(appearance.icon & val)) + SetIcon(appearance.icon+val); + } + void RemoveIconValue(int8 val){ + if((appearance.icon & val)) + SetIcon(appearance.icon-val); + } + int8 GetIconValue(){ + return appearance.icon; + } + virtual void SetSpeed(float speed){ + SetPos(&appearance.pos.Speed1, (int8)speed); + } + virtual float GetSpeed(){ + return (float)appearance.pos.Speed1; + } + virtual float GetBaseSpeed(){ + return (float)appearance.pos.Speed1; + } + void SetSpawnType(int8 new_type){ + SetInfo(&spawn_type, new_type); + } + int8 GetSpawnType(){ + return spawn_type; + } + void SetDatabaseID(int32 new_id){ + database_id = new_id; + } + int32 GetDatabaseID(){ + return database_id; + } + int8 GetShowHandIcon(){ + return appearance.display_hand_icon; + } + int32 GetLocation(){ + return appearance.pos.grid_id; + } + int8 GetAttackable(){ + return appearance.attackable; + } + int8 GetShowName(){ + return appearance.display_name; + } + int8 GetShowLevel(){ + return appearance.show_level; + } + int8 GetHeroic(){ + return appearance.heroic_flag; + } + int8 GetTargetable(){ + return appearance.targetable; + } + int8 GetShowCommandIcon(){ + return appearance.show_command_icon; + } + char* GetName(){ + return appearance.name; + } + char* GetPrefixTitle(){ + return appearance.prefix_title; + } + char* GetSuffixTitle(){ + return appearance.suffix_title; + } + char* GetSubTitle() { + return appearance.sub_title; + } + char* GetLastName() { + return appearance.last_name; + } + int8 GetAdventureClass() { + return appearance.adventure_class; + } + int8 GetTradeskillClass() { + return appearance.tradeskill_class; + } + float GetDestinationX(){ + return appearance.pos.X2; + } + float GetX() { + return appearance.pos.X; + } + float GetSpeedX() { + return appearance.pos.SpeedX; + } + float GetSpeedY() { + return appearance.pos.SpeedY; + } + float GetSpeedZ() { + return appearance.pos.SpeedZ; + } + float GetDestinationY(){ + return appearance.pos.Y2; + } + float GetY(){ + return appearance.pos.Y; + } + float GetDestinationZ(){ + return appearance.pos.Z2; + } + float GetZ(){ + return appearance.pos.Z; + } + float GetHeading(){ + float heading = 0; + if(appearance.pos.Dir1 != 0){ + heading = ((float)appearance.pos.Dir1)/((float)64); + if(heading >= 180) + heading -= 180; + else + heading += 180; + } + return heading; + } + float GetPitch(){ + float pitch = 0; + if(appearance.pos.Pitch1 != 0){ + pitch = ((float)appearance.pos.Pitch1)/((float)64); + if(pitch >= 180) + pitch -= 180; + else + pitch += 180; + } + return pitch; + } + float GetRoll(){ + float roll = 0; + if(appearance.pos.Roll != 0){ + roll = ((float)appearance.pos.Roll)/((float)64); + if(roll >= 180) + roll -= 180; + else + roll += 180; + } + return roll; + } + int32 GetID(){ + return id; + } + float GetDistance(float x1, float y1, float z1, float x2, float y2, float z2); + float GetDistance(float x, float y, float z, float radius, bool ignore_y = false); + float GetDistance(float x, float y, float z, bool ignore_y = false); + float GetDistance(Spawn* spawn, bool ignore_y = false, bool includeRadius=true); + float GetDistance(Spawn* spawn, float x1, float y1, float z1, bool includeRadius=true); + float CalculateRadius(Spawn* target); + + int8 GetDifficulty(){ + return appearance.difficulty; + } + sint32 GetTotalPower(); + sint32 GetPower(); + sint32 GetTotalHP(); + sint32 GetHP(); + sint32 GetTotalHPBase(); + sint32 GetTotalHPBaseInstance(); + sint32 GetTotalPowerBase(); + sint32 GetTotalPowerBaseInstance(); + float GetHPRatio() { return GetHP() == 0 || GetTotalHP() == 0 ? 0 : ((float) GetHP() / GetTotalHP() * 100); } + int GetIntHPRatio() { return GetTotalHP() == 0 ? 0 : static_cast(GetHPRatio()); } + + sint32 GetTotalSavagery(); + sint32 GetSavagery(); + sint32 GetTotalDissonance(); + sint32 GetDissonance(); + sint32 GetTotalSavageryBase(); + sint32 GetTotalDissonanceBase(); + sint16 GetAssignedAA(); + sint16 GetUnassignedAA(); + sint16 GetTradeskillAA(); + sint16 GetUnassignedTradeskillAA(); + sint16 GetPrestigeAA(); + sint16 GetUnassignedPretigeAA(); + sint16 GetTradeskillPrestigeAA(); + sint16 GetUnassignedTradeskillPrestigeAA(); + int32 GetAAXPRewards(); + + void SetTotalPower(sint32 new_val); + void SetTotalHP(sint32 new_val); + void SetTotalSavagery(sint32 new_val); + void SetTotalDissonance(sint32 new_val); + void SetTotalPowerBase(sint32 new_val); + void SetTotalPowerBaseInstance(sint32 new_val); + void SetTotalHPBase(sint32 new_val); + void SetTotalHPBaseInstance(sint32 new_val); + void SetTotalSavageryBase(sint32 new_val); + void SetTotalDissonanceBase(sint32 new_val); + void SetPower(sint32 power, bool setUpdateFlags = true); + void SetHP(sint32 new_val, bool setUpdateFlags = true); + void SetSavagery(sint32 savagery, bool setUpdateFlags = true); + void SetDissonance(sint32 dissonance, bool setUpdateFlags = true); + void SetAssignedAA(sint16 new_val); + void SetUnassignedAA(sint16 new_val); + void SetTradeskillAA(sint16 new_val); + void SetUnassignedTradeskillAA(sint16 new_val); + void SetPrestigeAA(sint16 new_val); + void SetUnassignedPrestigeAA(sint16 new_val); + void SetTradeskillPrestigeAA(sint16 new_val); + void SetUnassignedTradeskillPrestigeAA(sint16 new_val); + void SetAAXPRewards(int32 amount); + void SetPrivateQuestSpawn(bool val) {req_quests_private = val;} + void SetQuestsRequiredOverride(int16 val) {req_quests_override = val;} + void SetQuestsRequiredContinuedAccess(bool val) {req_quests_continued_access = val;} + bool GetPrivateQuestSpawn() {return req_quests_private;} + int16 GetQuestsRequiredOverride() {return req_quests_override;} + bool GetQuestsRequiredContinuedAccess() {return req_quests_continued_access;} + + bool Alive(){ return is_alive; } + void SetAlive(bool val) { is_alive = val; } + + int16 GetLevel(){ + return appearance.level; + } + int16 GetTSLevel(){ + return appearance.tradeskill_level; + } + int8 GetGender(){ + return appearance.gender; + } + int8 GetRace(){ + return appearance.race; + } + int32 GetSize(){ + return size; + } + int32 GetDeviation(){ + return deviation; + } + void SetDeviation(int32 in_dev){ + deviation = in_dev; + } + float GetSpawnOrigHeading(){ + return appearance.pos.SpawnOrigHeading; + } + void SetSpawnOrigHeading(float val){ + appearance.pos.SpawnOrigHeading = val; + } + float GetSpawnOrigX(){ + return appearance.pos.SpawnOrigX; + } + float GetSpawnOrigY(){ + return appearance.pos.SpawnOrigY; + } + float GetSpawnOrigZ(){ + return appearance.pos.SpawnOrigZ; + } + float GetSpawnOrigPitch(){ + return appearance.pos.SpawnOrigPitch; + } + float GetSpawnOrigRoll(){ + return appearance.pos.SpawnOrigRoll; + } + void SetSpawnOrigX(float val){ + appearance.pos.SpawnOrigX = val; + } + void SetSpawnOrigY(float val){ + appearance.pos.SpawnOrigY = val; + } + void SetSpawnOrigZ(float val){ + appearance.pos.SpawnOrigZ = val; + } + void SetSpawnOrigRoll(float val){ + appearance.pos.SpawnOrigRoll = val; + } + void SetSpawnOrigPitch(float val){ + appearance.pos.SpawnOrigPitch = val; + } + void SetSogaModelType(int16 new_val, bool setUpdateFlags = true){ + SetInfo(&appearance.soga_model_type, new_val, setUpdateFlags); + } + void SetModelType(int16 model_type, bool setUpdateFlags = true){ + SetInfo(&appearance.model_type, model_type, setUpdateFlags); + SetInfo(&appearance.soga_model_type, model_type, setUpdateFlags); + SetFlyingCreature(); + SetWaterCreature(); + } + int16 GetSogaModelType(){ + return appearance.soga_model_type; + } + int16 GetModelType(){ + return appearance.model_type; + } + + bool IsFlyingCreature(); + bool IsWaterCreature(); + bool InWater(); + bool InLava(); + + void SetFlyingCreature(); + void SetWaterCreature(); + + void SetPrimaryCommand(const char* name, const char* command, float distance = 10); + void SetPrimaryCommands(vector* commands); + void SetSecondaryCommands(vector* commands); + vector* GetPrimaryCommands() {return &primary_command_list;} + vector* GetSecondaryCommands() {return &secondary_command_list;} + EntityCommand* FindEntityCommand(string command, bool primaryOnly=false); + virtual EQ2Packet* serialize(Player* player, int16 version); + EQ2Packet* spawn_serialize(Player* player, int16 version, int16 offset = 0, int32 value = 0, int16 offset2 = 0, int16 offset3 = 0, int16 offset4 = 0, int32 value2 = 0); + EQ2Packet* spawn_update_packet(Player* player, int16 version, bool override_changes = false, bool override_vis_changes = false); + EQ2Packet* player_position_update_packet(Player* player, int16 version, bool override_ = false); + uchar* spawn_info_changes(Player* spawn, int16 version, int16* info_packet_size); + uchar* spawn_pos_changes(Player* spawn, int16 version, int16* pos_packet_size, bool override_ = false); + uchar* spawn_vis_changes(Player* spawn, int16 version, int16* vis_packet_size); + + uchar* spawn_info_changes_ex(Player* spawn, int16 version, int16* info_packet_size); + uchar* spawn_pos_changes_ex(Player* spawn, int16 version, int16* pos_packet_size); + uchar* spawn_vis_changes_ex(Player* spawn, int16 version, int16* vis_packet_size); + + virtual bool EngagedInCombat(){ return false; } + virtual bool IsObject(){ return false; } + virtual bool IsGroundSpawn(){ return false; } + virtual bool IsNPC(){ return false; } + virtual bool IsEntity(){ return false; } + virtual bool IsPlayer(){ return false; } + virtual bool IsWidget(){ return false; } + virtual bool IsSign(){ return false; } + virtual bool IsBot() { return false; } + + bool HasInfoChanged(){ return info_changed; } + bool HasPositionChanged(){ return position_changed; } + bool HasTarget(){ return target ? true : false; } + + int32 GetRespawnTime(); + void SetRespawnTime(int32 time); + int32 GetExpireTime() { return expire_time; } + void SetExpireTime(int32 new_expire_time) { expire_time = new_expire_time; } + int32 GetExpireOffsetTime(); + void SetExpireOffsetTime(int32 time); + int32 GetSpawnLocationID(); + void SetSpawnLocationID(int32 id); + int32 GetSpawnEntryID(); + void SetSpawnEntryID(int32 id); + int32 GetSpawnLocationPlacementID(); + void SetSpawnLocationPlacementID(int32 id); + float GetXOffset() { return x_offset; } + void SetXOffset(float new_x_offset) { x_offset = new_x_offset; } + float GetYOffset() { return y_offset; } + void SetYOffset(float new_y_offset) { y_offset = new_y_offset; } + float GetZOffset() { return z_offset; } + void SetZOffset(float new_z_offset) { z_offset = new_z_offset; } + + bool HasTrapTriggered() { + return trap_triggered; + } + int32 GetTrapState() { + return trap_state; + } + void SetChestDropTime() { + chest_drop_time = Timer::GetCurrentTime2(); + trap_opened_time = 0; + } + void SetTrapTriggered(bool triggered, int32 state) { + if(!trap_triggered && triggered) + trap_opened_time = Timer::GetCurrentTime2(); + + trap_triggered = triggered; + trap_state = state; + } + int32 GetChestDropTime() { + return chest_drop_time; + } + int32 GetTrapOpenedTime() { + return trap_opened_time; + } + void AddLootItem(int32 id, int16 charges = 1) { + Item* master_item = master_item_list.GetItem(id); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = charges; + LockLoot(); + loot_items.push_back(item); + UnlockLoot(); + } + } + void AddLootItem(Item* item) { + if(item) { + LockLoot(); + loot_items.push_back(item); + UnlockLoot(); + } + } + bool HasLoot() { + LockLoot(); + if (loot_items.size() == 0 && loot_coins == 0) { + UnlockLoot(); + return false; + } + UnlockLoot(); + return true; + } + void TransferLoot(Spawn* spawn); + bool HasLootItemID(int32 id); + int32 GetLootItemID(); + Item* LootItem(int32 id); + vector* GetLootItems() { + return &loot_items; + } + void LockLoot() { + MLootItems.lock(); + } + void UnlockLoot() { + MLootItems.unlock(); + } + void ClearLoot() { + + MLootItems.lock(); + vector::iterator itr; + for (itr = loot_items.begin(); itr != loot_items.end();) { + Item* itm = *itr; + itr++; + safe_delete(itm); + } + + loot_items.clear(); + + MLootItems.unlock(); + } + + int32 GetLootCount() { + int32 loot_item_count = 0; + MLootItems.lock(); + loot_item_count = loot_items.size(); + MLootItems.unlock(); + + return loot_item_count; + } + + void ClearNonBodyLoot() { + + MLootItems.lock(); + vector::iterator itr; + for (itr = loot_items.begin(); itr != loot_items.end();) { + Item* itm = *itr; + if(!itm->IsBodyDrop()) + { + itr = loot_items.erase(itr); + safe_delete(itm); + } + else + itr++; + } + MLootItems.unlock(); + } + + int32 GetLootCoins() { + LockLoot(); + int32 coins = loot_coins; + UnlockLoot(); + return coins; + } + void SetLootCoins(int32 val, bool lockloot = true) { + if(lockloot) + LockLoot(); + + loot_coins = val; + + if(lockloot) + UnlockLoot(); + } + void AddLootCoins(int32 coins) { + LockLoot(); + loot_coins += coins; + UnlockLoot(); + } + Spawn* GetTarget(); + void SetTarget(Spawn* spawn); + Spawn* GetLastAttacker(); + void SetLastAttacker(Spawn* spawn); + bool TakeDamage(int32 damage); + ZoneServer* GetZone(); + virtual void SetZone(ZoneServer* in_zone, int32 version=0); + void SetFactionID(int32 val) { faction_id = val; } + int32 GetFactionID(){ + return faction_id; + } + static int32 NextID() { + static CriticalSection id_lock; + id_lock.lock(); + int32 ret = ++next_id; + if (next_id == 0xFFFFFFFE) + next_id = 1; + else if ((next_id - 255) % 256 == 0) { //we dont want it to end in 255, it will confuse/crash the client + id_lock.unlock(); + return NextID(); + } + + id_lock.unlock(); + return ret; + } + void AddProvidedQuest(int32 val){ + quest_ids.push_back(val); + } + vector* GetProvidedQuests(){ + return &quest_ids; + } + bool HasProvidedQuests(){ + return (quest_ids.size() > 0); + } + void SetSpawnScript(string name); + const char* GetSpawnScript(); + + vector* GetSpawnGroup(); + bool HasSpawnGroup(); + bool IsInSpawnGroup(Spawn* spawn); + Spawn* IsSpawnGroupMembersAlive(Spawn* ignore_spawn=nullptr, bool npc_only = true); + void UpdateEncounterState(int8 new_state); + void CheckEncounterState(Entity* victim, bool test_auto_lock = false); + void AddTargetToEncounter(Entity* entity); + + void SendSpawnChanges(bool val){ send_spawn_changes = val; } + void SetSpawnGroupID(int32 id); + int32 GetSpawnGroupID(); + void AddSpawnToGroup(Spawn* spawn); + void SetSpawnGroupList(vector* list, Mutex* mutex); + void RemoveSpawnFromGroup(bool erase_all = false); + + void SetRunningTo(Spawn* spawn){ running_to = spawn->GetID(); } + Spawn* GetRunningTo(); + void SetTempVisualState(int val, bool update = true) { SetInfo(&tmp_visual_state, val, update); } + int GetTempVisualState(){ return tmp_visual_state; } + void SetTempActionState(int val, bool update = true) { SetInfo(&tmp_action_state, val, update); } + int GetTempActionState(){ return tmp_action_state; } + void AddAllowAccessSpawn(Spawn* spawn){ allowed_access[spawn->GetID()] = 1; } + void RemoveSpawnAccess(Spawn* spawn); + bool IsPrivateSpawn(){ return allowed_access.size() > 0 ;} + bool AllowedAccess(Spawn* spawn){ return allowed_access.count(spawn->GetID()) > 0; } + void MakeSpawnPublic() { allowed_access.clear(); } + void SetSizeOffset(int8 offset); + int8 GetSizeOffset(); + void SetMerchantID(int32 val); + int32 GetMerchantID(); + void SetMerchantType(int8 val); + int8 GetMerchantType(); + void SetCollector(bool is_it) { is_collector = is_it; } + bool IsCollector() { return is_collector; } + void SetMerchantLevelRange(int32 minLvl = 0, int32 maxLvl = 0); + bool IsClientInMerchantLevelRange(Client* ent, bool sendMessageIfDenied = true); + int32 GetMerchantMinLevel(); + int32 GetMerchantMaxLevel(); + void SetQuestsRequired(Spawn* new_spawn); + void SetQuestsRequired(int32 quest_id, int16 quest_step); + bool HasQuestsRequired(); + bool HasHistoryRequired(); + void SetRequiredHistory(int32 event_id, int32 value1, int32 value2); + void SetTransporterID(int32 id); + int32 GetTransporterID(); + bool MeetsSpawnAccessRequirements(Player* player); + + void RemovePrimaryCommands(); + + void InitializePosPacketData(Player* player, PacketStruct* packet, bool bSpawnUpdate = false); + void InitializeInfoPacketData(Player* player, PacketStruct* packet); + void InitializeVisPacketData(Player* player, PacketStruct* packet); + void InitializeHeaderPacketData(Player* player, PacketStruct* packet, int16 index); + void InitializeFooterPacketData(Player* player, PacketStruct* packet); + + void MoveToLocation(Spawn* spawn, float distance, bool immediate = true, bool isMappedLocation = false); + void AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading = false); + void ProcessMovement(bool isSpawnListLocked=false); + void ResetMovement(); + bool ValidateRunning(bool lockMovementLocation, bool lockMovementLoop); + bool IsRunning(); + void CalculateRunningLocation(bool stop = false); + void RunToLocation(float x, float y, float z, float following_x = 0, float following_y = 0, float following_z = 0); + + MovementLocation* GetCurrentRunningLocation(); + MovementLocation* GetLastRunningLocation(); + void NewWaypointChange(MovementLocation* data); + bool CalculateChange(); + void AddRunningLocation(float x, float y, float z, float speed, float distance_away = 0, bool attackable = true, bool finished_adding_locations = true, string lua_function = "", bool isMapped=false); + bool RemoveRunningLocation(); + void ClearRunningLocations(); + + void CopySpawnAppearance(Spawn* spawn); + + bool MovementInterrupted(){ return movement_interrupted; } + void MovementInterrupted(bool val) { movement_interrupted = val; } + bool NeedsToResumeMovement(){ return attack_resume_needed; } + void NeedsToResumeMovement(bool val) { attack_resume_needed = val; } + bool HasMovementLoop(){ return movement_loop.size() > 0; } + bool HasMovementLocations() { + bool hasLocations = false; + MMovementLocations.lock_shared(); + hasLocations = movement_locations ? movement_locations->size() > 0 : false; + MMovementLocations.unlock_shared(); + return hasLocations; + } + + Timer* GetRunningTimer(); + float GetFaceTarget(float x, float z); + void FaceTarget(float x, float z); + void FaceTarget(Spawn* target, bool disable_action_state = true); + void SetInvulnerable(bool val); + bool GetInvulnerable(); + void SetScaredByStrongPlayers(bool val) { scared_by_strong_players = val; } + bool IsScaredByStrongPlayers() { return scared_by_strong_players; } + std::atomic changed; + std::atomic position_changed; + std::atomic info_changed; + std::atomic vis_changed; + std::atomic is_running; + int16 size; + int32 faction_id; + int8 oversized_packet; //0xff + int32 id; + int8 unknown1; + int32 unknown2; + int32 primary_command_list_id; + int32 secondary_command_list_id; + vector primary_command_list; + vector secondary_command_list; + int32 group_id; + int8 group_len; + vector* spawn_group_list; + AppearanceData appearance; + int32 last_movement_update; + int32 last_location_update; + bool forceMapCheck; + bool is_water_creature; + bool is_flying_creature; + int32 trigger_widget_id; + + std::atomic following; + bool IsPet() { return is_pet; } + void SetPet(bool val) { is_pet = val; } + Mutex m_requiredQuests; + Mutex m_requiredHistory; + + void SetFollowTarget(Spawn* spawn, int32 followDistance=0); + Spawn* GetFollowTarget(); + + /// Sets a user defined variable + /// Variable we are setting + /// Value to set the variable to + void AddTempVariable(string var, string val); + + void AddTempVariable(string var, Spawn* val); + void AddTempVariable(string var, ZoneServer* val); + void AddTempVariable(string var, Quest* val); + void AddTempVariable(string var, Item* val); + /// Gets the value for the given variable + /// Variable to check + /// The value for the given variable, "" if variable was not set + string GetTempVariable(string var); + + Spawn* GetTempVariableSpawn(string var); + ZoneServer* GetTempVariableZone(string var); + Item* GetTempVariableItem(string var); + Quest* GetTempVariableQuest(string var); + int8 GetTempVariableType(string var); + void DeleteTempVariable(string var); + + void SetIllusionModel(int16 val, bool setUpdateFlags = true) { + SetInfo(&m_illusionModel, val, setUpdateFlags); + } + int16 GetIllusionModel() { return m_illusionModel; } + + CellInfo Cell_Info; + + + int32 GetSpawnAnim() { return m_spawnAnim; } + void SetSpawnAnim(int32 value) { m_spawnAnim = value; } + int32 GetAddedToWorldTimestamp() { return m_addedToWorldTimestamp; } + void SetAddedToWorldTimestamp(int32 value) { m_addedToWorldTimestamp = value; } + int16 GetSpawnAnimLeeway() { return m_spawnAnimLeeway; } + void SetSpawnAnimLeeway(int16 value) { m_spawnAnimLeeway = value; } + + float FindDestGroundZ(glm::vec3 dest, float z_offset); + float FindBestZ(glm::vec3 loc, glm::vec3* result=nullptr, int32* new_grid_id=nullptr, int32* new_widget_id=nullptr); + float GetFixedZ(const glm::vec3& destination, int32 z_find_offset = 1); + void FixZ(bool forceUpdate=false); + bool CheckLoS(Spawn* target); + bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc); + + void CalculateNewFearpoint(); + + enum SpawnProximityType { + SPAWNPROXIMITY_DATABASE_ID = 0, + SPAWNPROXIMITY_LOCATION_ID = 1 + }; + + struct SpawnProximity { + float x; + float y; + float z; + int32 spawn_value; + int8 spawn_type; + float distance; + string in_range_lua_function; + string leaving_range_lua_function; + map spawns_in_proximity; + }; + + void AddSpawnToProximity(int32 spawnValue, SpawnProximityType type); + void RemoveSpawnFromProximity(int32 spawnValue, SpawnProximityType type); + + SpawnProximity* AddLUASpawnProximity(int32 spawnValue, SpawnProximityType type, float distance, string in_range_function, string leaving_range_function) { + SpawnProximity* prox = new SpawnProximity; + prox->spawn_value = spawnValue; + prox->spawn_type = type; + prox->distance = distance; + prox->in_range_lua_function = in_range_function; + prox->leaving_range_lua_function = leaving_range_function; + spawn_proximities.Add(prox); + has_spawn_proximities = true; + return prox; + } + + void RemoveSpawnProximities() { + MutexList::iterator itr = spawn_proximities.begin(); + while (itr.Next()) { + safe_delete(itr->value); + } + spawn_proximities.clear(); + has_spawn_proximities = false; + } + + Mutex MCommandMutex; + bool has_spawn_proximities; + + void SetPickupItemID(int32 itemid) + { + pickup_item_id = itemid; + } + + void SetPickupUniqueItemID(int32 uniqueid) + { + pickup_unique_item_id = uniqueid; + } + + int32 GetPickupItemID() { return pickup_item_id; } + int32 GetPickupUniqueItemID() { return pickup_unique_item_id; } + + bool IsSoundsDisabled() { return disable_sounds; } + void SetSoundsDisabled(bool val) { disable_sounds = val; } + + RegionMap* GetRegionMap() { return region_map; } + Map* GetMap() { return current_map; } + std::map established_grid_id; + + void DeleteRegion(Region_Node* inNode, ZBSP_Node* rootNode); + bool InRegion(Region_Node* inNode, ZBSP_Node* rootNode); + int32 GetRegionType(Region_Node* inNode, ZBSP_Node* rootNode); + + float SpawnAngle(Spawn* target, float selfx, float selfz); + bool BehindSpawn(Spawn *target, float selfx, float selfz) + { return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) > 90.0f; } + bool InFrontSpawn(Spawn *target, float selfx, float selfz) + { return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) < 63.0f; } + bool IsFlankingSpawn(Spawn *target, float selfx, float selfz) + { return (!target || target == this) ? false : SpawnAngle(target, selfx, selfz) > 63.0f; } + + std::map, Region_Status> Regions; + Mutex RegionMutex; + + virtual void StopMovement(); + virtual bool PauseMovement(int32 period_of_time_ms); + virtual bool IsPauseMovementTimerActive(); + bool IsTransportSpawn() { return is_transport_spawn; } + void SetTransportSpawn(bool val) { is_transport_spawn = val; } + + sint64 GetRailID() { return rail_id; } + void SetRailID(sint64 val) { rail_id = val; } + void AddRailPassenger(int32 char_id); + void RemoveRailPassenger(int32 char_id); + vector GetPassengersOnRail(); + + void SetAppearancePosition(float x, float y, float z); + + void SetOmittedByDBFlag(bool val) { is_omitted_by_db_flag = val; } + bool IsOmittedByDBFlag() { return is_omitted_by_db_flag; } + + int32 GetLootTier() { return loot_tier; } + void SetLootTier(int32 tier) { loot_tier = tier; } + + int32 GetLootDropType() { return loot_drop_type; } + void SetLootDropType(int32 type) { loot_drop_type = type; } + + void SetDeletedSpawn(bool val) { deleted_spawn = val; } + bool IsDeletedSpawn() { return deleted_spawn; } + + + int32 InsertRegionToSpawn(Region_Node* node, ZBSP_Node* bsp_root, WaterRegionType regionType, bool in_region = true); + bool HasRegionTracked(Region_Node* node, ZBSP_Node* bsp_root, bool in_region); + + int8 GetArrowColor(int8 spawn_level); + + void AddIgnoredWidget(int32 id); + + void SendGroupUpdate(); + + void OverrideLootMethod(GroupLootMethod newMethod) { loot_method = newMethod; } + void SetLootMethod(GroupLootMethod method, int8 item_rarity = 0, int32 group_id = 0); + int32 GetLootGroupID() { return loot_group_id; } + GroupLootMethod GetLootMethod() { return loot_method; } + int8 GetLootRarity() { return loot_rarity; } + int32 GetLootTimeRemaining() { return loot_timer.GetRemainingTime(); } + bool IsLootTimerRunning() { return loot_timer.Enabled(); } + bool CheckLootTimer() { return loot_timer.Check(); } + void DisableLootTimer() { return loot_timer.Disable(); } + int32 GetLooterSpawnID() { return looter_spawn_id; } + void SetLooterSpawnID(int32 id) { looter_spawn_id = id; } + bool AddNeedGreedItemRequest(int32 item_id, int32 spawn_id, bool need_item); + bool AddLottoItemRequest(int32 item_id, int32 spawn_id); + void AddSpawnLootWindowCompleted(int32 spawn_id, bool status_); + bool SetSpawnLootWindowCompleted(int32 spawn_id); + bool HasSpawnLootWindowCompleted(int32 spawn_id); + bool HasSpawnNeedGreedEntry(int32 item_id, int32 spawn_id); + bool HasSpawnLottoEntry(int32 item_id, int32 spawn_id); + void GetSpawnLottoEntries(int32 item_id, std::map* out_entries); + void GetLootItemsList(std::vector* out_entries); + void GetSpawnNeedGreedEntries(int32 item_id, bool need_item, std::map* out_entries); + + bool HasLootWindowCompleted(); + bool IsLootWindowComplete() { return is_loot_complete; } + void SetLootDispensed() { is_loot_dispensed = true; } + bool IsLootDispensed() { return is_loot_dispensed; } + std::map* GetLootWindowList() { return &loot_complete; } + void StartLootTimer(Spawn* looter); + void CloseLoot(Spawn* sender); + + void SetLootName(char* name) { + if(name != nullptr) { + loot_name = std::string(name); + } + } + + const char* GetLootName() { return loot_name.c_str(); } + + bool IsItemInLootTier(Item* item); + void DistributeGroupLoot_RoundRobin(std::vector* item_list, bool roundRobinTrashLoot = false); // trash loot is what falls under the item tier requirement by group options + + void CalculateInitialVelocity(float heading, float distanceHorizontal, float distanceVertical, float distanceDepth, float duration); + glm::vec3 CalculateProjectilePosition(glm::vec3 initialVelocity, float time); + bool CalculateSpawnProjectilePosition(float x, float y, float z); + void SetKnockback(Spawn* target, int32 duration, float vertical, float horizontal); + void ResetKnockedBack(); + + mutable std::shared_mutex MIgnoredWidgets; + std::map ignored_widgets; + + EquipmentItemList equipment_list; + EquipmentItemList appearance_equipment_list; + +protected: + + bool has_quests_required; + bool has_history_required; + bool send_spawn_changes; + bool invulnerable; + bool attack_resume_needed; + bool resume_movement; + bool movement_interrupted; + int32 running_timer_begin; + int32 running_timer_end; + vector movement_loop; + bool running_timer_updated; + int16 movement_index; + int32 last_grid_update; + int32 movement_start_time; + Mutex MMovementLoop; + map allowed_access; + vector quest_ids; + int32 database_id; + int32 packet_num; + int32 target; + int8 spawn_type; + int32 last_attacker; + int32 merchant_id; + int8 merchant_type; + int32 merchant_min_level; + int32 merchant_max_level; + + int32 transporter_id; + int32 pickup_item_id; + int32 pickup_unique_item_id; + map* > required_quests; + map required_history; + + MutexList spawn_proximities; + + void CheckProximities(); + Timer pause_timer; + + bool IsKnockedBack() { return knocked_back; } +private: + int32 loot_group_id; + GroupLootMethod loot_method; + int8 loot_rarity; + Timer loot_timer; + int32 looter_spawn_id; + vector loot_items; + int32 loot_coins; + std::multimap lotto_items; + std::multimap> need_greed_items; + + std::map loot_complete; + bool is_loot_complete; + bool is_loot_dispensed; + std::string loot_name; + + bool trap_triggered; + int32 trap_state; + int32 chest_drop_time; + int32 trap_opened_time; + deque* movement_locations; + Mutex MLootItems; + mutable std::shared_mutex MMovementLocations; + Mutex* MSpawnGroup; + int8 size_offset; + int tmp_visual_state; + int tmp_action_state; + int32 running_to; + string spawn_script; + static int32 next_id; + ZoneServer* zone; + int32 spawn_location_id; + int32 spawn_entry_id; + int32 spawn_location_spawns_id; + int32 respawn; + int32 expire_time; + int32 expire_offset; + float x_offset; + float y_offset; + float z_offset; + int32 deviation; + BasicInfoStruct basic_info; + //string data; + bool is_pet; + // m_followTarget = spawn to follow around + int32 m_followTarget; + int32 m_followDistance; + bool req_quests_private; + int16 req_quests_override; + bool req_quests_continued_access; + float last_heading_angle; + + map m_tempVariableTypes; + map m_tempVariableSpawn; + map m_tempVariableZone; + map m_tempVariableItem; + map m_tempVariableQuest; + // m_tempVariables = stores user defined variables from lua, will not persist through a zone + map m_tempVariables; + + int16 m_illusionModel; + + int32 m_spawnAnim; + int32 m_addedToWorldTimestamp; + int16 m_spawnAnimLeeway; + + Mutex m_Update; + Mutex m_SpawnMutex; + bool disable_sounds; + + RegionMap* region_map; + Map* current_map; + + bool is_transport_spawn; + sint64 rail_id; + map rail_passengers; + mutex m_RailMutex; + bool is_omitted_by_db_flag; // this particular spawn is omitted by an expansion or holiday flag + + int32 loot_tier; + int32 loot_drop_type; + + std::atomic deleted_spawn; + std::atomic reset_movement; + Mutex m_GridMutex; + bool is_collector; + bool scared_by_strong_players; + std::atomic is_alive; + std::atomic knocked_back; + float knocked_back_time_step; + float knocked_back_h_distance; + float knocked_back_v_distance; + float knocked_back_duration; + float knocked_back_start_x; + float knocked_back_start_y; + float knocked_back_start_z; + int32 knocked_back_end_time; + float knocked_angle; + glm::vec3 knocked_velocity; + +}; + +#endif + diff --git a/source/WorldServer/SpawnLists.h b/source/WorldServer/SpawnLists.h new file mode 100644 index 0000000..2e134dd --- /dev/null +++ b/source/WorldServer/SpawnLists.h @@ -0,0 +1,108 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQ2_SPAWN_LISTS +#define EQ2_SPAWN_LISTS +#include "../common/types.h" +#include +#include + +#define SPAWN_ENTRY_TYPE_NPC 0 +#define SPAWN_ENTRY_TYPE_OBJECT 1 +#define SPAWN_ENTRY_TYPE_WIDGET 2 +#define SPAWN_ENTRY_TYPE_SIGN 3 +#define SPAWN_ENTRY_TYPE_GROUNDSPAWN 4 + +struct EntityCommand{ + string name; + float distance; + string error_text; + string command; + int16 cast_time; + int32 spell_visual; + map allow_or_deny; // this is a map of player IDs and whether they are allowed on the command or denied + bool default_allow_list; // if set to false then its a defaultDenyList +}; +struct SpawnEntry{ + int32 spawn_entry_id; + int32 spawn_location_id; + int8 spawn_type; + int32 spawn_id; + float spawn_percentage; + int32 respawn; + int32 expire_time; + int32 expire_offset; + //devn00b: added spawn location overrides, added these to accomodate. + int32 lvl_override; + int32 hp_override; + int32 mp_override; + int32 str_override; + int32 sta_override; + int32 wis_override; + int32 int_override; + int32 agi_override; + int32 heat_override; + int32 cold_override; + int32 magic_override; + int32 mental_override; + int32 divine_override; + int32 disease_override; + int32 poison_override; + int32 difficulty_override; //aka EncounterLevel +}; +class SpawnLocation{ +public: + SpawnLocation(){ + x = 0; + y = 0; + z = 0; + heading = 0; + total_percentage = 0; + x_offset = 0; + y_offset = 0; + z_offset = 0; + placement_id = 0; + pitch = 0; + roll = 0; + grid_id = 0; + conditional = 0; + } + ~SpawnLocation(){ + for(int32 i=0;i entities; + float x; + float y; + float z; + float heading; + float x_offset; + float y_offset; + float z_offset; + int32 placement_id; + float pitch; + float roll; + float total_percentage; + int32 grid_id; + string script; + int8 conditional; +}; +#endif + diff --git a/source/WorldServer/SpellProcess.cpp b/source/WorldServer/SpellProcess.cpp new file mode 100644 index 0000000..3ee9ca0 --- /dev/null +++ b/source/WorldServer/SpellProcess.cpp @@ -0,0 +1,3039 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "SpellProcess.h" +#include "../common/Log.h" +#include "Tradeskills/Tradeskills.h" +#include "ClientPacketFunctions.h" +#include "Rules/Rules.h" + +extern MasterSpellList master_spell_list; +extern MasterSkillList master_skill_list; +extern ConfigReader configReader; +extern LuaInterface* lua_interface; +extern Commands commands; +extern World world; +extern RuleManager rule_manager; + +SpellProcess::SpellProcess(){ + last_checked_time = 0; + MRemoveTargetList.SetName("SpellProcess::MRemoveTargetList"); + MSoloHO.SetName("SpellProcess::m_soloHO"); + MGroupHO.SetName("SpellProcess:m_groupHO"); + MSpellCancelList.SetName("SpellProcess::SpellCancelList"); +} + +SpellProcess::~SpellProcess(){ + RemoveAllSpells(); +} + +void SpellProcess::RemoveCaster(Spawn* caster){ + MutexList::iterator active_spells_itr = active_spells.begin(); + while(active_spells_itr.Next()){ + LuaSpell* spell = active_spells_itr->value; + spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + if(spell->caster == caster) { + spell->caster = nullptr; + } + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } +} + +void SpellProcess::RemoveAllSpells(bool reload_spells){ + ClearSpellScriptTimerList(); + + MutexList::iterator active_spells_itr = active_spells.begin(); + while(active_spells_itr.Next()){ + DeleteCasterSpell(active_spells_itr->value, "", true, 0, !reload_spells); + } + + MSpellProcess.lock(); + + active_spells_itr = active_spells.begin(); + while(active_spells_itr.Next()){ + active_spells.Remove(active_spells_itr->value, true); + } + active_spells.clear(); + + InterruptStruct* interrupt = 0; + MutexList::iterator interrupt_list_itr = interrupt_list.begin(); + while(interrupt_list_itr.Next()){ + interrupt = interrupt_list_itr->value; + CheckInterrupt(interrupt); + interrupt_list.Remove(interrupt_list_itr->value, true); + } + interrupt_list.clear(); + + MutexList::iterator cast_timers_itr = cast_timers.begin(); + while(cast_timers_itr.Next()){ + safe_delete(cast_timers_itr->value->timer); + safe_delete(cast_timers_itr->value->spell); + cast_timers.Remove(cast_timers_itr->value, true); + } + cast_timers.clear(); + + MutexList::iterator recast_timers_itr = recast_timers.begin(); + while(recast_timers_itr.Next()){ + recast_timers.Remove(recast_timers_itr->value, true); + } + + map*>::iterator remove_itr; + MRemoveTargetList.writelock(__FUNCTION__, __LINE__); + for (remove_itr = remove_target_list.begin(); remove_itr != remove_target_list.end(); remove_itr++){ + safe_delete(remove_itr->second); + } + remove_target_list.clear(); + MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__); + + map::iterator solo_ho_itr; + MSoloHO.writelock(__FUNCTION__, __LINE__); + for (solo_ho_itr = m_soloHO.begin(); solo_ho_itr != m_soloHO.end(); solo_ho_itr++) + safe_delete(solo_ho_itr->second); + m_soloHO.clear(); + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + + map::iterator group_ho_itr; + MGroupHO.writelock(__FUNCTION__, __LINE__); + for (group_ho_itr = m_groupHO.begin(); group_ho_itr != m_groupHO.end(); group_ho_itr++) + safe_delete(group_ho_itr->second); + m_groupHO.clear(); + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); + + recast_timers.clear(); + spell_que.clear(); + + MSpellCancelList.writelock(__FUNCTION__, __LINE__); + SpellCancelList.clear(); + MSpellCancelList.releasewritelock(__FUNCTION__, __LINE__); + + MSpellProcess.unlock(); +} + +void SpellProcess::Process(){ + if(last_checked_time > Timer::GetCurrentTime2()) + return; + last_checked_time = Timer::GetCurrentTime2() + 50; + MSpellProcess.lock_shared(); + CheckSpellScriptTimers(); + if(active_spells.size(true) > 0){ + LuaSpell* spell = 0; + MutexList::iterator itr = active_spells.begin(); + map >::iterator remove_itr; + vector::iterator target_itr; + vector::iterator remove_target_itr; + while(itr.Next()){ + spell = itr->value; + if (!spell->spell->GetSpellData()->duration_until_cancel && spell->timer.Check()){ + spell->num_calls++; + // ProcessSpell(spell, flase) will atempt to call the tick() function in the lua script + // if there is no tick function it will return false, this will cause the server to crash in the event + // of a spell that has a duration but is not a "until canceled" spell or a spell with a tick (tradeskill buffs) + // to counter this check to see if the spell has a call_frequency > 0 before we call ProcessSpell() + if (spell->spell->GetSpellData()->call_frequency > 0 && !ProcessSpell(spell, false)) + active_spells.Remove(spell, true, 2000); + else if (((spell->timer.GetDuration() * spell->num_calls) >= spell->spell->GetSpellData()->duration1 * 100) || + (spell->restored && (spell->timer.GetSetAtTrigger() * spell->num_calls) >= spell->spell->GetSpellData()->duration1 * 100)) + DeleteCasterSpell(spell, "expired"); + } + else + CheckRemoveTargetFromSpell(spell); + } + } + if (SpellCancelList.size() > 0){ + std::vector tmpList; + MSpellCancelList.writelock(__FUNCTION__, __LINE__); + vector::iterator itr = SpellCancelList.begin(); + while (itr != SpellCancelList.end()){ + tmpList.push_back(*itr); + itr++; + } + SpellCancelList.clear(); + MSpellCancelList.releasewritelock(__FUNCTION__, __LINE__); + + itr = tmpList.begin(); + while (itr != tmpList.end()) { + DeleteCasterSpell(*itr, "canceled"); + itr++; + } + } + if(interrupt_list.size(true) > 0){ + InterruptStruct* interrupt = 0; + MutexList::iterator itr = interrupt_list.begin(); + while(itr.Next()){ + interrupt = itr->value; + CheckInterrupt(interrupt); + safe_delete(interrupt); + } + interrupt_list.clear(); + } + if(cast_timers.size(true) > 0){ + CastTimer* cast_timer = 0; + MutexList::iterator itr = cast_timers.begin(); + while (itr.Next()) { + cast_timer = itr->value; + if (cast_timer) { + if (cast_timer->timer->Check(false)) { + if (cast_timer->spell) { + if (cast_timer->spell->caster && cast_timer->spell->caster->IsPlayer()) { + + Client* client = ((Player*)cast_timer->spell->caster)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_FinishCastSpell", client->GetVersion()); + if (packet) { + packet->setMediumStringByName("spell_name", cast_timer->spell->spell->GetSpellData()->name.data.c_str()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + if (cast_timer->spell && cast_timer->spell->caster) + cast_timer->spell->caster->IsCasting(false); + cast_timer->delete_timer = true; + CastProcessedSpell(cast_timer->spell, false, cast_timer->in_heroic_opp); + } + else if (cast_timer->entity_command) { + if (cast_timer->timer->Check(false)) { + cast_timer->delete_timer = true; + Spawn* target = cast_timer->zone->GetSpawnByID(cast_timer->target_id); + CastProcessedEntityCommand(cast_timer->entity_command, cast_timer->caster, target, cast_timer->in_heroic_opp); + } + } + } + if (cast_timer->delete_timer) { + safe_delete(cast_timer->timer); + cast_timers.Remove(cast_timer, true); + } + } + } + } + if (recast_timers.size(true) > 0) { + RecastTimer* recast_timer = 0; + MutexList::iterator itr = recast_timers.begin(); + vector::iterator remove_recast_itr; + while(itr.Next()){ + recast_timer = itr->value; + if(recast_timer->timer->Check(false)){ + MaintainedEffects* effect = 0; + if(recast_timer->caster && (!(effect = recast_timer->caster->GetMaintainedSpell(recast_timer->spell_id)) || !effect->spell->spell->GetSpellData()->duration_until_cancel)) + UnlockSpell(recast_timer->client, recast_timer->spell); + safe_delete(recast_timer->timer); + recast_timers.Remove(recast_timer, true); + } + } + } + if(spell_que.size(true) > 0){ + MutexMap::iterator itr = spell_que.begin(); + while(itr.Next()){ + if(itr->first->IsCasting() == false && IsReady(itr->second, itr->first)){ + RemoveSpellFromQueue(itr->second, itr->first); + ProcessSpell(itr->first->GetZone(), itr->second, itr->first, itr->first->GetTarget()); + } + } + } + + // Check solo HO timers + MSoloHO.writelock(__FUNCTION__, __LINE__); + if (m_soloHO.size() > 0) { + map::iterator itr = m_soloHO.begin(); + while (itr != m_soloHO.end()) { + if (itr->second->GetWheel() && Timer::GetCurrentTime2() >= (itr->second->GetStartTime() + (itr->second->GetTotalTime() * 1000))) { + itr->second->SetComplete(1); + ClientPacketFunctions::SendHeroicOPUpdate(itr->first, itr->second); + safe_delete(itr->second); + itr = m_soloHO.erase(itr); + continue; + } + else + itr++; + } + } + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + + // Check group HO timers + MGroupHO.writelock(__FUNCTION__, __LINE__); + if (m_groupHO.size() > 0) { + map::iterator itr = m_groupHO.begin(); + while (itr != m_groupHO.end()) { + if (itr->second->GetWheel() && Timer::GetCurrentTime2() >= (itr->second->GetStartTime() + (itr->second->GetTotalTime() * 1000))) { + itr->second->SetComplete(1); + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + deque::iterator itr2; + PlayerGroup* group = world.GetGroupManager()->GetGroup(itr->first); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr2 = members->begin(); itr2 != members->end(); itr2++) { + if ((*itr2)->client) + ClientPacketFunctions::SendHeroicOPUpdate((*itr2)->client, itr->second); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + safe_delete(itr->second); + itr = m_groupHO.erase(itr); + continue; + } + else + itr++; + } + } + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); + + MSpellProcess.unlock_shared(); +} +bool SpellProcess::IsReady(Spell* spell, Entity* caster){ + if(caster->IsCasting()) { + return false; + } + bool ret = true; + RecastTimer* recast_timer = 0; + MutexList::iterator itr = recast_timers.begin(); + while(itr.Next()){ + recast_timer = itr->value; + if((recast_timer->spell == spell || recast_timer->spell_id == spell->GetSpellID()) && + recast_timer->caster == caster){ + ret = false; + break; + } + } + return ret; +} +void SpellProcess::CheckRecast(Spell* spell, Entity* caster, float timer_override, bool check_linked_timers) { + if(spell && caster && spell->GetSpellData()->recast > 0.0f){ + RecastTimer* timer = new RecastTimer; + timer->caster = caster; + if(caster->IsPlayer()) + timer->client = ((Player*)caster)->GetClient(); + else + timer->client = 0; + timer->spell = spell; + int32 recast_time = static_cast(spell->GetSpellData()->recast * 1000.0f); + + if(timer_override == 0.0f) { + recast_time = spell->CalculateRecastTimer(caster); + timer->timer = new Timer(recast_time); + } + else { + recast_time = spell->CalculateRecastTimer(caster, timer_override); + timer->timer = new Timer(recast_time); + } + + if(recast_time < 1) { + safe_delete(timer->timer); + safe_delete(timer); + } + else { + timer->type_group_spell_id = spell->GetSpellData()->type_group_spell_id; + timer->linked_timer = spell->GetSpellData()->linked_timer; + timer->spell_id = spell->GetSpellID(); + + recast_timers.Add(timer); + } + if(caster->IsPlayer()){ + if(recast_time < 1) { + ((Player*)caster)->UnlockSpell(spell); + } + else { + ((Player*)caster)->LockSpell(spell, (int16)(recast_time)); + } + if (check_linked_timers && spell->GetSpellData()->linked_timer > 0) { + vector linkedSpells = ((Player*)caster)->GetSpellBookSpellsByTimer(spell, spell->GetSpellData()->linked_timer); + for (int8 i = 0; i < linkedSpells.size(); i++) { + Spell* spell2 = linkedSpells.at(i); + if (spell2) + CheckRecast(spell2, caster, static_cast(recast_time), false); + } + } + } + } +} +void SpellProcess::CheckInterrupt(InterruptStruct* interrupt){ + if(!interrupt || !interrupt->interrupted || !interrupt->interrupted->IsEntity()) + return; + Entity* entity = (Entity*)interrupt->interrupted; + Client* client = nullptr; + + if(entity->IsPlayer()) { + client = ((Player*)entity)->GetClient(); + } + + if(entity->IsPlayer() && client) + SendFinishedCast(GetLuaSpell(entity), client); + RemoveSpellTimersFromSpawn(entity, false); + entity->IsCasting(false); + entity->GetZone()->SendInterruptPacket(entity, interrupt->spell); + if(interrupt->error_code > 0 && client) + entity->GetZone()->SendSpellFailedPacket(client, interrupt->error_code); + if(entity->IsPlayer()) + { + ((Player*)entity)->UnlockSpell(interrupt->spell->spell); + if(client) { + SendSpellBookUpdate(client); + } + } +} + +bool SpellProcess::DeleteCasterSpell(Spawn* caster, Spell* spell, string reason){ + + bool ret = false; + // need to use size(true) to get pending updates to the list as well + if (caster && spell && active_spells.size() > 0) { + LuaSpell* lua_spell = 0; + MutexList::iterator itr = active_spells.begin(); + while (itr.Next()){ + lua_spell = itr->value; + if (lua_spell->spell == spell && lua_spell->caster == caster) { + ret = DeleteCasterSpell(lua_spell, reason); + break; + } + } + } + return ret; +} + +bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removing_all_spells, Spawn* remove_target, bool zone_shutting_down, bool shared_lock_spell){ + if(shared_lock_spell && !removing_all_spells) { + MSpellProcess.lock_shared(); + } + + bool ret = false; + Spawn* target = 0; + bool target_valid = false; + if(spell) { + spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + if(remove_target && spell->targets.size() > 1) { + for (int32 i = 0; i < spell->targets.size(); i++) { + if(remove_target->GetID() == spell->targets.at(i)) { + if(remove_target->IsEntity()){ + spell->removed_targets.push_back(remove_target->GetID()); + ((Entity*)remove_target)->RemoveProc(0, spell); + ((Entity*)remove_target)->RemoveSpellEffect(spell); + ((Entity*)remove_target)->RemoveSpellBonus(spell); + if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel)) + ((Entity*)remove_target)->RemoveDetrimentalSpell(spell); + } + spell->targets.erase(spell->targets.begin()+i, spell->targets.begin()+i+1); + target_valid = true; + break; + } + } + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + if(shared_lock_spell && !removing_all_spells) { + MSpellProcess.unlock_shared(); + } + return target_valid; + } + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + + if (active_spells.count(spell) > 0) + active_spells.Remove(spell); + if (!zone_shutting_down && spell->caster) { // spell->caster ptr cannot be trusted during zone shutdown + if(spell->caster->GetThreatTransfer() && spell->caster->GetThreatTransfer()->Spell == spell) { + spell->caster->SetThreatTransfer(nullptr); + } + if (spell->spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE){ + + int8 actual_concentration = spell->spell->GetSpellData()->req_concentration; + + if (actual_concentration > 0) + { + int8 curConcentration = spell->caster->GetInfoStruct()->get_cur_concentration(); + if(curConcentration >= actual_concentration) + { + spell->caster->GetInfoStruct()->set_cur_concentration(curConcentration - actual_concentration); + if (spell->caster->IsPlayer()&& spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + } + } + if(!spell->spell->GetSpellData()->duration_until_cancel) + { + CheckRecast(spell->spell, spell->caster); + if (spell->caster && spell->caster->IsPlayer()) + SendSpellBookUpdate(((Player*)spell->caster)->GetClient()); + } + } + if(IsReady(spell->spell, spell->caster) && spell->caster->IsPlayer()) { + ((Player*)spell->caster)->UnlockSpell(spell->spell); + SendSpellBookUpdate(((Player*)spell->caster)->GetClient()); + } + + spell->caster->RemoveProc(0, spell); + spell->caster->RemoveMaintainedSpell(spell); + CheckRemoveTargetFromSpell(spell, removing_all_spells, removing_all_spells); + ZoneServer* zone = spell->caster->GetZone(); + if(zone) { + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int32 i = 0; i < spell->targets.size(); i++) { + target = zone->GetSpawnByID(spell->targets.at(i)); + if(target && target->IsEntity()){ + spell->removed_targets.push_back(target->GetID()); + ((Entity*)target)->RemoveProc(0, spell); + ((Entity*)target)->RemoveSpellEffect(spell); + ((Entity*)target)->RemoveSpellBonus(spell); + if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel)) + ((Entity*)target)->RemoveDetrimentalSpell(spell); + } + else{ + spell->caster->RemoveSpellEffect(spell); + spell->caster->RemoveSpellBonus(spell); + if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel)) + spell->caster->RemoveDetrimentalSpell(spell); + } + if(target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message.length() > 0){ + Client* client = ((Player*)target)->GetClient(); + if(client){ + bool send_to_sender = true; + string fade_message = spell->spell->GetSpellData()->fade_message; + if(fade_message.find("%t") != string::npos) + fade_message.replace(fade_message.find("%t"), 2, target->GetName()); + client->Message(CHANNEL_SPELLS_OTHER, fade_message.c_str()); + } + } + if (target && target->IsPlayer() && spell->spell->GetSpellData()->fade_message.length() > 0) { + Client* client = ((Player*)target)->GetClient(); + if (client) { + bool send_to_sender = true; + string fade_message_others = spell->spell->GetSpellData()->fade_message_others; + if (fade_message_others.find("%t") != string::npos) + fade_message_others.replace(fade_message_others.find("%t"), 2, target->GetName()); + if (fade_message_others.find("%c") != string::npos) + fade_message_others.replace(fade_message_others.find("%c"), 2, spell->caster->GetName()); + if (fade_message_others.find("%T") != string::npos) { + fade_message_others.replace(fade_message_others.find("%T"), 2, target->GetName()); + send_to_sender = false; + } + if (fade_message_others.find("%C") != string::npos) { + fade_message_others.replace(fade_message_others.find("%C"), 2, spell->caster->GetName()); + send_to_sender = false; + } + if(spell->caster->GetZone()) { + spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, fade_message_others.c_str(), target, 50, send_to_sender); + } + } + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + ret = true; + } + if(lua_interface && !zone_shutting_down) + lua_interface->RemoveSpell(spell, true, SpellScriptTimersHasSpell(spell), reason, removing_all_spells); + } + + if(shared_lock_spell && !removing_all_spells) { + MSpellProcess.unlock_shared(); + } + return ret; +} + +bool SpellProcess::ProcessSpell(LuaSpell* spell, bool first_cast, const char* function, SpellScriptTimer* timer, bool all_targets) { + bool ret = false; + if(!spell->state) + { + LogWrite(SPELL__ERROR, 0, "Spell", "Error: State is NULL! SpellProcess::ProcessSpell for Spell '%s'", (spell->spell != nullptr) ? spell->spell->GetName() : "Unknown"); + } + else if(lua_interface && !spell->interrupted){ + if(all_targets) + { + for(int t=0;ttargets.size();t++) + { + if(spell->caster->GetZone()) { + Spawn* altSpawn = spell->caster->GetZone()->GetSpawnByID(spell->targets[t]); + if(altSpawn) + { + std::string functionCall = ApplyLuaFunction(spell, first_cast, function, timer, altSpawn); + if(functionCall.length() < 1) + ret = false; + else + ret = lua_interface->CallSpellProcess(spell, 2 + spell->spell->GetLUAData()->size(), functionCall); + } + } + } + return true; + } + std::string functionCall = ApplyLuaFunction(spell, first_cast, function, timer); + if(functionCall.length() < 1) + ret = false; + else + ret = lua_interface->CallSpellProcess(spell, 2 + spell->spell->GetLUAData()->size(), functionCall); + } + return ret; +} + +std::string SpellProcess::ApplyLuaFunction(LuaSpell* spell, bool first_cast, const char* function, SpellScriptTimer* timer, Spawn* altTarget) +{ + std::string functionCall = lua_interface->AddSpawnPointers(spell, first_cast, false, function, timer, false, altTarget); + vector* data = spell->spell->GetLUAData(); + for(int32 i=0;isize();i++){ + switch(data->at(i)->type){ + case 0:{ + lua_interface->SetSInt32Value(spell->state, data->at(i)->int_value); + break; + } + case 1:{ + lua_interface->SetFloatValue(spell->state, data->at(i)->float_value); + break; + } + case 2:{ + lua_interface->SetBooleanValue(spell->state, data->at(i)->bool_value); + break; + } + case 3:{ + lua_interface->SetStringValue(spell->state, data->at(i)->string_value.c_str()); + break; + } + default:{ + LogWrite(SPELL__ERROR, 0, "Spell", "Error: Unknown LUA Type '%i' in SpellProcess::ProcessSpell for Spell '%s'", (int)data->at(i)->type, spell->spell->GetName()); + return string(""); + } + } + } + return functionCall; +} + +bool SpellProcess::CastPassives(Spell* spell, Entity* caster, bool remove) { + CastInstant(spell, caster, caster, remove, true); + return true; +} + +bool SpellProcess::CastInstant(Spell* spell, Entity* caster, Entity* target, bool remove, bool passive) { + LuaSpell* lua_spell = 0; + + if(lua_interface) + lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str()); + + if(!lua_spell) + { + string lua_script = spell->GetSpellData()->lua_script; + lua_script.append(".lua"); + lua_spell = 0; + + if(lua_interface) + lua_spell = lua_interface->GetSpell(lua_script.c_str()); + + if(!lua_spell) { + LogWrite(SPELL__ERROR, 0, "Spell", "Failed to get a LuaSpell for %s (%u)", spell->GetName(), spell->GetSpellID()); + return false; + } + else + spell->GetSpellData()->lua_script = lua_script; + } + + lua_spell->caster = caster; + lua_spell->initial_caster_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + lua_spell->spell = spell; + lua_spell->initial_target = target->GetID(); + lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + GetSpellTargets(lua_spell); + + if (!lua_spell->spell->IsCopiedSpell()) + { + lua_getglobal(lua_spell->state, "customspell"); + if (lua_isfunction(lua_spell->state, lua_gettop(lua_spell->state))) { + lua_pop(lua_spell->state, 1); + Spell* tmpSpell = lua_spell->spell; + lua_spell->spell = new Spell(lua_spell->spell); + lua_interface->AddCustomSpell(lua_spell); + std::string outCall = lua_interface->AddSpawnPointers(lua_spell, false, false, "customspell",0,true); + if (outCall.length() > 0 && lua_pcall(lua_spell->state, 3, 3, 0) != 0) { + lua_interface->RemoveCustomSpell(lua_spell->spell->GetSpellID()); + lua_interface->ResetFunctionStack(lua_spell->state); + safe_delete(lua_spell->spell); + lua_spell->spell = tmpSpell; + } + else + lua_interface->ResetFunctionStack(lua_spell->state); + } + else + lua_interface->ResetFunctionStack(lua_spell->state); + } + + bool result = CastProcessedSpell(lua_spell, passive); + + caster->GetZone()->SendCastSpellPacket(lua_spell, caster); + + if(!result) { + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return result; + } + + if(!remove) + return result; + + lua_interface->RemoveSpell(lua_spell, true, SpellScriptTimersHasSpell(lua_spell)); + return true; +} + +void SpellProcess::SendStartCast(LuaSpell* spell, Client* client){ + if(client){ + PacketStruct* packet = configReader.getStruct("WS_StartCastSpell", client->GetVersion()); + if(packet){ + packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01); + packet->setMediumStringByName("spell_name", spell->spell->GetSpellData()->name.data.c_str()); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } + } +} + +void SpellProcess::SendFinishedCast(LuaSpell* spell, Client* client){ + if(client && spell && spell->spell){ + if (spell->spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) + UnlockAllSpells(client, spell->spell); + else + UnlockAllSpells(client); + + if(spell->resisted && spell->spell->GetSpellData()->recast > 0) + CheckRecast(spell->spell, client->GetPlayer(), 0.5f); // half sec recast on resisted spells + else if (!spell->interrupted) + CheckRecast(spell->spell, client->GetPlayer()); + else if(spell->caster && spell->caster->IsPlayer()) + { + ((Player*)spell->caster)->LockSpell(spell->spell, (int16)(spell->spell->CalculateRecastTimer(spell->caster))); + } + PacketStruct* packet = configReader.getStruct("WS_FinishCastSpell", client->GetVersion()); + if(packet){ + packet->setMediumStringByName("spell_name", spell->spell->GetSpellData()->name.data.c_str()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + SendSpellBookUpdate(client); + } + if (spell) { + if (!spell->interrupted) { + TakePower(spell); + TakeHP(spell); + TakeSavagery(spell); + AddDissonance(spell); + AddConcentration(spell); + if (client && spell->spell && !spell->spell->GetSpellData()->friendly_spell) { + if(!client->GetPlayer()->EngagedInCombat()) { + client->GetPlayer()->InCombat(true, false); + client->GetPlayer()->SetRangeAttack(false); + } + else if(client->GetPlayer()->EngagedInCombat() && + client->GetPlayer()->GetRangeAttack() && spell->spell->GetSpellData()->type == SPELL_BOOK_TYPE_COMBAT_ART) { + Spawn* target = client->GetPlayer()->GetZone()->GetSpawnByID(spell->initial_target); + if(target) { + float distance = client->GetPlayer()->GetDistance(target); + if(distance <= rule_manager.GetGlobalRule(R_Combat, MaxCombatRange)->GetFloat()) { + client->GetPlayer()->InCombat(true, false); + client->GetPlayer()->SetRangeAttack(false); + } + } + } + } + if(client && spell->caster) + client->CheckPlayerQuestsSpellUpdate(spell->spell); + } + } +} + +void SpellProcess::LockAllSpells(Client* client){ + if(client){ + client->GetPlayer()->LockAllSpells(); + SendSpellBookUpdate(client); + } +} + +void SpellProcess::UnlockAllSpells(Client* client, Spell* exception){ + if(client) + client->GetPlayer()->UnlockAllSpells(false, exception); +} + +void SpellProcess::UnlockSpell(Client* client, Spell* spell){ + if(client && client->GetPlayer() && spell) { + client->GetPlayer()->UnlockSpell(spell); + SendSpellBookUpdate(client); + } +} + +bool SpellProcess::CheckPower(LuaSpell* spell){ + int16 req = 0; + if(spell->caster){ + req = spell->spell->GetPowerRequired(spell->caster); + if(spell->caster->GetPower() >= req) + return true; + } + return false; +} + +bool SpellProcess::TakePower(LuaSpell* spell, int32 custom_power_req){ + int16 req = 0; + if(spell->caster){ + + if(custom_power_req) + req = custom_power_req; + else + req = spell->spell->GetPowerRequired(spell->caster); + + if(spell->caster->GetPower() >= req){ + spell->caster->SetPower(spell->caster->GetPower() - req); + if(spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + return true; + } + } + return false; +} + +bool SpellProcess::CheckHP(LuaSpell* spell) { + int16 req = 0; + if(spell->caster){ + req = spell->spell->GetHPRequired(spell->caster); + if(spell->caster->GetHP() >= req) + return true; + } + return false; +} + +bool SpellProcess::TakeHP(LuaSpell* spell, int32 custom_hp_req) { + int16 req = 0; + if(spell->caster){ + if(custom_hp_req) + req = custom_hp_req; + else + req = spell->spell->GetHPRequired(spell->caster); + if(spell->caster->GetHP() >= req){ + spell->caster->SetHP(spell->caster->GetHP() - req); + if(spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + return true; + } + } + return false; +} + +bool SpellProcess::CheckConcentration(LuaSpell* spell) { + if (spell && spell->caster) { + int8 req = spell->spell->GetSpellData()->req_concentration; + if(spell->caster->GetConcentrationCurrent() >= spell->caster->GetConcentrationMax()) { + if(req) { + return false; // req needed, no concentration or beyond our concentration + } + else { + return true; // no req set + } + } + int8 current_avail = spell->caster->GetConcentrationMax() - spell->caster->GetConcentrationCurrent(); + if (current_avail >= req) + return true; + } + return false; +} + +bool SpellProcess::AddConcentration(LuaSpell* spell) { + if (spell && spell->caster) { + int8 req = spell->spell->GetSpellData()->req_concentration; + + if(spell->caster->GetConcentrationCurrent() >= spell->caster->GetConcentrationMax()) { + if(req) { + return false; // req needed, no concentration or beyond our concentration + } + else { + return true; // no req set + } + } + int8 current_avail = spell->caster->GetConcentrationMax() - spell->caster->GetConcentrationCurrent(); + if (current_avail >= req) { + spell->caster->GetInfoStruct()->set_cur_concentration(spell->caster->GetConcentrationCurrent() + req); + if (spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + LogWrite(SPELL__DEBUG, 0, "Spell", "Concentration is now %u on %s (Spell %s)", spell->caster->GetInfoStruct()->get_cur_concentration(), spell->caster->GetName(), spell->spell->GetName()); + return true; + } + } + return false; +} + +bool SpellProcess::CheckSavagery(LuaSpell* spell) { + if (spell && spell->caster) { + int16 req = spell->spell->GetSavageryRequired(spell->caster); + if(spell->caster->GetSavagery() >= req) + return true; + } + return false; +} + +bool SpellProcess::TakeSavagery(LuaSpell* spell) { + int16 req = 0; + if(spell && spell->caster && spell->caster->IsPlayer()){ + req = spell->spell->GetSavageryRequired(spell->caster); + if(spell->caster->GetSavagery() >= req){ + spell->caster->SetSavagery(spell->caster->GetSavagery() - req); + if(spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + return true; + } + } + return false; +} + +bool SpellProcess::CheckDissonance(LuaSpell* spell) { + if (spell && spell->caster) { + int16 req = spell->spell->GetDissonanceRequired(spell->caster); + if(spell->caster->GetDissonance() <= req) + return true; + } + return false; +} + +bool SpellProcess::AddDissonance(LuaSpell* spell) { + int16 req = 0; + if(spell && spell->caster && spell->caster->IsPlayer()){ + req = spell->spell->GetDissonanceRequired(spell->caster); + if(spell->caster->GetDissonance() >= req){ + spell->caster->SetDissonance(spell->caster->GetDissonance() - req); + if(spell->caster->IsPlayer() && spell->caster->GetZone()) + spell->caster->GetZone()->TriggerCharSheetTimer(); + return true; + } + } + return false; +} + +void SpellProcess::AddSpellToQueue(Spell* spell, Entity* caster){ + if(caster && caster->IsPlayer() && spell){ + LogWrite(SPELL__DEBUG, 1, "Spell", "%s AddSpellToQueue casting %s.", caster->GetName(), spell->GetName()); + spell_que.Put(caster, spell); + ((Player*)caster)->QueueSpell(spell); + Client* client = ((Player*)caster)->GetClient(); + if(client) + SendSpellBookUpdate(client); + } +} + +void SpellProcess::RemoveSpellFromQueue(Spell* spell, Entity* caster, bool send_update){ + if(caster && caster->IsPlayer() && spell){ + LogWrite(SPELL__DEBUG, 1, "Spell", "%s RemoveSpellFromQueue casting %s.", caster->GetName(), spell->GetName()); + spell_que.erase(caster); + ((Player*)caster)->UnQueueSpell(spell); + Client* client = ((Player*)caster)->GetClient(); + if(client && send_update) + SendSpellBookUpdate(client); + } +} + +void SpellProcess::RemoveSpellFromQueue(Entity* caster, bool hostile_only) { + if (caster && spell_que.count(caster) > 0) { + Spell* spell = spell_que.Get(caster); + if (spell) { + bool remove = true; + if (hostile_only && spell->GetSpellData()->target_type != SPELL_TARGET_ENEMY) + remove = false; + if (remove) { + + LogWrite(SPELL__DEBUG, 1, "Spell", "%s RemoveSpellFromQueue::Secondary casting %s.", caster->GetName(), spell->GetName()); + spell_que.erase(caster); + ((Player*)caster)->UnQueueSpell(spell); + Client* client = ((Player*)caster)->GetClient(); + if (client) + SendSpellBookUpdate(client); + } + } + } +} + +bool SpellProcess::CheckSpellQueue(Spell* spell, Entity* caster){ + if(caster->IsPlayer()){ + bool add = true; + bool remove = false; + Spell* prevSpell = 0; + if(spell_que.count(caster) > 0){ + prevSpell = spell_que.Get(caster); + remove = true; + if(prevSpell == spell) { + add = false; + } + } + if(remove) + RemoveSpellFromQueue(prevSpell, caster, !add); + if(add) + { + AddSpellToQueue(spell, caster); + return true; + } + } + return false; +} + +void SpellProcess::SendSpellBookUpdate(Client* client){ + if(client && client->IsReadyForSpawns()){ + EQ2Packet* app = client->GetPlayer()->GetSpellBookUpdatePacket(client->GetVersion()); + if(app) + client->QueuePacket(app); + } +} + +LuaSpell* SpellProcess::GetLuaSpell(Entity* caster){ + LuaSpell* spell = 0; + if(caster && cast_timers.size() > 0){ + CastTimer* cast_timer = 0; + MutexList::iterator itr = cast_timers.begin(); + while(itr.Next()){ + cast_timer = itr->value; + if(cast_timer && cast_timer->spell && cast_timer->spell->caster == caster){ + spell = cast_timer->spell; + break; + } + } + } + return spell; +} + +Spell* SpellProcess::GetSpell(Entity* caster){ + Spell* spell = 0; + if(cast_timers.size() > 0){ + CastTimer* cast_timer = 0; + MutexList::iterator itr = cast_timers.begin(); + while(itr.Next()){ + cast_timer = itr->value; + if(cast_timer && cast_timer->spell && cast_timer->spell->caster == caster){ + spell = cast_timer->spell->spell; + break; + } + } + } + return spell; +} + +void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell, LuaSpell* customSpell, int16 custom_cast_time, bool in_heroic_opp) +{ + if((customSpell != 0 || spell != 0) && caster) + { + Client* client = 0; + //int16 version = 0; + + LuaSpell* lua_spell = 0; + + if (customSpell) + { + lua_spell = customSpell; + spell = lua_spell->spell; + } + else if(lua_interface) + lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str()); + + // this will only hit if customSpell is null and we go through the lua_interface + if(!lua_spell) + { + string lua_script = spell->GetSpellData()->lua_script; + lua_script.append(".lua"); + + if(lua_interface) + lua_spell = lua_interface->GetSpell(lua_script.c_str()); + + if(!lua_spell) + return; + else + spell->GetSpellData()->lua_script = lua_script; + } + + if (!target) + target = caster; + + int8 target_type = spell->GetSpellData()->target_type; + int8 spell_type = spell->GetSpellData()->spell_type; + + lua_spell->caster = caster; + lua_spell->initial_caster_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + lua_spell->spell = spell; + + int32 target_id = target->GetID(); + lua_spell->initial_target = target_id; + lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + + if (!harvest_spell) + GetSpellTargets(lua_spell); + else{ + AddLuaSpellTarget(lua_spell, target_id); + } + + + if (target_id == lua_spell->initial_target) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s is casting %s on %s.", caster->GetName(), spell->GetName(), ( target ) ? target->GetName() : "No Target" ); + } + else + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Casting on %s through %s as target.", spell->GetName(), caster->GetZone()->GetSpawnByID(lua_spell->initial_target)->GetName(), target->GetName()); + } + + target = lua_spell->caster->GetZone()->GetSpawnByID(lua_spell->initial_target); + + if(caster->IsPlayer() && zone) + { + client = ((Player*)caster)->GetClient(); + //version = client->GetVersion(); + } + + if (!customSpell && !lua_spell->spell->IsCopiedSpell()) + { + lua_getglobal(lua_spell->state, "customspell"); + if (lua_isfunction(lua_spell->state, lua_gettop(lua_spell->state))) { + lua_pop(lua_spell->state, 1); + Spell* tmpSpell = lua_spell->spell; + lua_spell->spell = new Spell(lua_spell->spell); + lua_interface->AddCustomSpell(lua_spell); + std::string outCall = lua_interface->AddSpawnPointers(lua_spell, false, false, "customspell", 0, true); + if (outCall.length() > 0 && lua_pcall(lua_spell->state, 3, 3, 0) != 0) { + lua_interface->RemoveCustomSpell(lua_spell->spell->GetSpellID()); + lua_interface->ResetFunctionStack(lua_spell->state); + safe_delete(lua_spell->spell); + lua_spell->spell = tmpSpell; + } + else + lua_interface->ResetFunctionStack(lua_spell->state); + } + else + lua_interface->ResetFunctionStack(lua_spell->state); + } + + //If this spell is the toggle cast type and is being toggled off, do this now + if (spell->GetSpellData()->cast_type == SPELL_CAST_TYPE_TOGGLE) + { + bool ret_val = DeleteCasterSpell(caster, spell, "purged"); + + if (ret_val) + { + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + // If a player is casting the spell AND spell is a tradeskill spell + if (client && spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_TRADESKILL) + { + // If the player is currently crafting + if (client->IsCrafting()) + { + Tradeskill* tradeskill = 0; + zone->GetTradeskillMgr()->ReadLock(__FUNCTION__, __LINE__); + tradeskill = zone->GetTradeskillMgr()->GetTradeskill(client); + + // Can not cast a crafting spell that doesn't match the current crafting technique + if (spell->GetSpellData()->mastery_skill != tradeskill->recipe->GetTechnique()) + { + // send a message to the client, used chat_relationship to match other tradeskill messages, not sure if it is accurate though + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You are not using %s on this recipe.", master_skill_list.GetSkill(spell->GetSpellData()->mastery_skill)->name.data.c_str()); + + // Write a spell debug message on why we couldn't cast + LogWrite(SPELL__DEBUG, 1, "Spell", "%s could not cast tradeskill spell (%s), skills did not match. spell mastery skill = %u, tradeskill technique = %u", caster->GetName(), spell->GetName(), spell->GetSpellData()->mastery_skill, tradeskill->recipe->GetTechnique()); + + // make sure to release the lock before we return out + zone->GetTradeskillMgr()->ReleaseReadLock(__FUNCTION__, __LINE__); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + // need to make sure the lock is released if the if passed + zone->GetTradeskillMgr()->ReleaseReadLock(__FUNCTION__, __LINE__); + } + else // If the player is not currently crafting + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast a tradeskill spell (%s) while not crafting.", caster->GetName(), spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_ONLY_WHEN_CRAFTING); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + if (caster != target && target != NULL && !caster->CheckLoS(target)) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot see target %s.", caster->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_CANT_SEE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + LuaSpell* conflictSpell = caster->HasLinkedTimerID(lua_spell, target, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT)); + if(conflictSpell) + { + if(conflictSpell->spell->GetSpellData()->min_class_skill_req > 0 && lua_spell->spell->GetSpellData()->min_class_skill_req > 0) + { + if(conflictSpell->spell->GetSpellData()->min_class_skill_req <= lua_spell->spell->GetSpellData()->min_class_skill_req) + { + if(lua_spell->spell->GetSpellData()->duration_until_cancel && !lua_spell->num_triggers) + { + if(spell->GetSpellData()->friendly_spell) + { + ZoneServer* zone = caster->GetZone(); + Spawn* tmpTarget = zone->GetSpawnByID(conflictSpell->initial_target); + if(tmpTarget && tmpTarget->IsEntity()) + { + zone->RemoveTargetFromSpell(conflictSpell, tmpTarget); + CheckRemoveTargetFromSpell(conflictSpell); + ((Entity*)tmpTarget)->RemoveSpellEffect(conflictSpell); + if(client && IsReady(conflictSpell->spell, client->GetPlayer())) + UnlockSpell(client, conflictSpell->spell); + } + DeleteSpell(lua_spell); + return; + } + else if(lua_spell->spell->GetSpellData()->spell_type == SPELL_TYPE_DEBUFF) + { + SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell); + return; + } + } + } + else + { + SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell); + return; + } + } + } + + if ((caster->IsMezzed() && !spell->CastWhileMezzed()) || (caster->IsStunned() && !spell->CastWhileStunned())) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (mezzed or stunned).", caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_STUNNED); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (caster->IsStifled() && !spell->CastWhileStifled()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (stifled).", caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_STIFFLED); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (caster->IsFeared() && !spell->CastWhileFeared()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot cast (feared).", caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_CANNOT_CAST_FEARED); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if(caster->IsPlayer() && !IsReady(spell, caster)) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "Queuing spell for %s.", caster->GetName()); + bool queueSpell = CheckSpellQueue(spell, caster); + if(!queueSpell) + { + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + } + return; + } + + if (target_type != SPELL_TARGET_SELF && target_type != SPELL_TARGET_GROUP_AE && spell_type != SPELL_TYPE_ALLGROUPTARGETS && target_type != SPELL_TARGET_NONE && spell->GetSpellData()->max_aoe_targets == 0) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Not Self, Not Group AE, Not None, Max Targets = 0", spell->GetName()); + + if (!target) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + float tmpRange = spell->GetSpellData()->range; + + if (client && target != caster && !spell->GetSpellData()->range) + { + bool match = false; + int tmpTier = 0; + Spell* tmpSpell = 0; + for (tmpTier = 0; tmpTier < spell->GetSpellTier(); tmpTier++) + { + tmpSpell = master_spell_list.GetSpell(spell->GetSpellData()->id, tmpTier); + if (tmpSpell && tmpSpell->GetSpellData()->range) + { + match = true; + break; + } + } + if (tmpSpell) + tmpRange = tmpSpell->GetSpellData()->range; + + if (!match) + tmpTier = -1; + + char msg[512]; + snprintf(msg, 512, "SpellCasted without proper spell range set %s ID %i Tier %i Range obtained from tier %i range %f", spell->GetName(), spell->GetSpellID(), spell->GetSpellTier(), tmpTier, tmpRange); + if (!world.CheckTempBugCRC(msg)) + commands.Command_ReportBug(client, new Seperator(msg)); + } + + if(caster->GetDistance(target) > tmpRange) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Too far.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_FAR_AWAY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (caster->GetDistance(target) < spell->GetSpellData()->min_range) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Too close.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_CLOSE); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + if(target_type == SPELL_TARGET_SELF && spell->GetSpellData()->max_aoe_targets == 0) + { + if (harvest_spell) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Harvest, Target Self, Max Targets = 0", spell->GetName()); + + if (!target || !target->IsGroundSpawn()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target or not groundspawn.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + else + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target '%s' is Caster '%s'.", spell->GetName(), ( target ) ? target->GetName() : "None", caster->GetName()); + target = caster; + } + } + + // Is enemy spell AND direct-damage (no AE) + if (target_type == SPELL_TARGET_ENEMY && spell->GetSpellData()->max_aoe_targets == 0) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target Enemy (%s) and Max AE Targets = 0.", spell->GetName(), target->GetName()); + + + if((spell->GetSpellData()->friendly_spell && caster != target && (caster->IsPlayer() || caster->IsBot()) && (target->IsPlayer() || target->IsBot()) && + ((Entity*)target)->GetInfoStruct()->get_engaged_encounter()) && + ((!((Entity*)caster)->GetGroupMemberInfo() || !((Entity*)target)->GetGroupMemberInfo()) || + (((Entity*)caster)->GetGroupMemberInfo()->group_id != ((Entity*)target)->GetGroupMemberInfo()->group_id))) { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is engaged in combat and cannot be assisted.", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + else if (spell->GetSpellData()->friendly_spell && target->IsPlayer() ) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Is Friendly, Target is Player (%s).", spell->GetName(), target->GetName()); + + if (!target->IsEntity()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + /*if (target->appearance.attackable) { + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND); + return; + }*/ + } + else if (spell->GetSpellData()->friendly_spell && target->IsNPC() && target->appearance.attackable) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Is Not Friendly (friendly and NPC)", spell->GetName()); + + if (target->IsPet()) + { + if (((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner()->IsNPC()) + { + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + else if(((Entity*)target)->GetInfoStruct()->get_friendly_target_npc()) { + // it's a bypass! + } + else if (target->IsBot() && (caster->IsPlayer() || caster->IsBot())) { + // Needed so bots or player can cast friendly spells on bots + } + else if (caster->IsNPC()) { + // npcs can cast on other npcs + } + else + { + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_A_FRIEND); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + else if (spell->GetSpellData()->friendly_spell && target->IsNPC() && caster->IsNPC()) { + // TODO: faction checks? some other checks to prevent an npc casting a friendly spell on another npc + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: NPC Cast", spell->GetName()); + } + else + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Is Not Friendly (catch all)", spell->GetName()); + + if (!target->IsEntity()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: No target.", spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NO_ELIGIBLE_TARGET); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (caster == target || (!target->IsPlayer() && !target->appearance.attackable)) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Not an Enemy (Target: %s).", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!target->Alive()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target is not alive (Target: %s).", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ALIVE); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (target->GetInvulnerable()) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target is invulnerable (Target: %s).", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_TARGET_INVULNERABLE); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + bool attackAllowed = (Entity*)caster->AttackAllowed((Entity*)target, 0); + if (!attackAllowed) + { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is player and not attackable.", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (target->IsPet() && ((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner() == caster) { + LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is casters pet and not attackable by caster.", spell->GetName(), target->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_AN_ENEMY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + } + + if (lua_spell->targets.size() == 0 && spell->GetSpellData()->max_aoe_targets == 0) + { + LogWrite(SPELL__ERROR, 0, "Spell", "SpellProcess::ProcessSpell Unable to find any spell targets for spell '%s', spell type: %u, target type %u.", + spell->GetName(), spell->GetSpellData()->spell_type, spell->GetSpellData()->target_type); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if(target_type == SPELL_TARGET_ENEMY_CORPSE || target_type == SPELL_TARGET_GROUP_CORPSE) + { + if(target->Alive()) + { + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_DEAD); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + if(target->IsPlayer() && ((Player*)target)->GetClient() && ((Player*)target)->GetClient()->GetCurrentRez()->active){ + zone->SendSpellFailedPacket(client, SPELL_ERROR_ALREADY_CAST); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + if(!CheckPower(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Power to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_POWER); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!CheckHP(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Health to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_HEALTH); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!CheckSavagery(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Savagery to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_SAVAGERY); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!CheckDissonance(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Dissonance to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_DISSONANCE); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + if (!CheckConcentration(lua_spell)) + { + LogWrite(SPELL__WARNING, 1, "Spell", "%s: Caster Lacked Concentration to cast (%s).", spell->GetName(), caster->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_NOT_ENOUGH_CONC); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + // Precast in lua + if (lua_interface) { + bool result = false; + std::string outCall = lua_interface->AddSpawnPointers(lua_spell, false, true); + if (outCall.length() > 0 && lua_pcall(lua_spell->state, 2, LUA_MULTRET, 0) == 0) { + int8 error = SPELL_ERROR_CANNOT_PREPARE; + if(lua_istable(lua_spell->state, -1)) { + lua_rawgeti(lua_spell->state, -1, 1); + if(lua_isboolean(lua_spell->state, -1)) { + result = lua_toboolean(lua_spell->state,-1); + } + lua_pop(lua_spell->state, 1); + + lua_rawgeti(lua_spell->state, -1, 2); + if(lua_isnumber(lua_spell->state, -1)) { + error = lua_tonumber(lua_spell->state,-1); + } + lua_pop(lua_spell->state, 1); + } + else if (lua_toboolean(lua_spell->state, -1)) + { + result = lua_toboolean(lua_spell->state, -1); + lua_pop(lua_spell->state, 1); + } + lua_interface->ResetFunctionStack(lua_spell->state); + + // need to add back support for error to be modified + if (!result) { + zone->SendSpellFailedPacket(client, error); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + else { + LogWrite(SPELL__DEBUG, 1, "Spell", "No precast function found for %s", lua_spell->spell->GetName()); + lua_interface->ResetFunctionStack(lua_spell->state); + } + } + else + LogWrite(SPELL__DEBUG, 1, "Spell", "Unable to do precast check as there was no lua_interface"); + + if (custom_cast_time > 0) { + spell->GetSpellData()->orig_cast_time = custom_cast_time; + spell->GetSpellData()->cast_time = custom_cast_time; + } + + //Apply casting speed mod + spell->ModifyCastTime(caster); + + //cancel stealth effects on cast + if(caster->IsStealthed() || caster->IsInvis()) + caster->CancelAllStealth(); + + if(caster && caster->IsEntity() && caster->CheckFizzleSpell(lua_spell)) + { + caster->GetZone()->SendCastSpellPacket(0, target ? target : caster, caster); + caster->GetZone()->SendInterruptPacket(caster, lua_spell, true); + caster->IsCasting(false); + + if(caster->IsPlayer()) + { + ((Player*)caster)->UnlockSpell(spell); + SendSpellBookUpdate(((Player*)caster)->GetClient()); + } + + // fizzle takes half + int16 power_req = spell->GetPowerRequired(caster) / 2; + TakePower(lua_spell, power_req); + + int16 hp_req = spell->GetHPRequired(caster) / 2; + TakeHP(lua_spell, hp_req); + + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + + SendStartCast(lua_spell, client); + + LockAllSpells(client); + + if(spell->GetSpellData()->cast_time > 0) + { + CastTimer* cast_timer = new CastTimer; + cast_timer->entity_command = 0; + cast_timer->target_id = target ? target->GetID() : 0; + cast_timer->spell = lua_spell; + cast_timer->spell->caster = caster; + cast_timer->delete_timer = false; + cast_timer->timer = new Timer(spell->GetSpellData()->cast_time * 10); + cast_timer->zone = zone; + cast_timer->in_heroic_opp = in_heroic_opp; + cast_timers.Add(cast_timer); + if(caster) + caster->IsCasting(true); + } + else + { + if(!CastProcessedSpell(lua_spell, false, in_heroic_opp)) + { + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); + return; + } + } + + if(caster) + caster->GetZone()->SendCastSpellPacket(lua_spell, caster); + + } +} + +void SpellProcess::ProcessEntityCommand(ZoneServer* zone, EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock, bool in_heroic_opp) { + if (zone && entity_command && caster && target && !target->IsPlayer()) { + Client* client = ((Player*)caster)->GetClient(); + + if(!client) { + return; + } + if (caster->GetDistance(target) > entity_command->distance) { + zone->SendSpellFailedPacket(client, SPELL_ERROR_TOO_FAR_AWAY); + return; + } + if (entity_command->cast_time > 0) { + PacketStruct* packet = configReader.getStruct("WS_StartCastSpell", client->GetVersion()); + if (packet) { + LockAllSpells(client); + packet->setDataByName("cast_time", entity_command->cast_time * 0.01); + packet->setMediumStringByName("spell_name", entity_command->name.c_str()); + EQ2Packet* outapp = packet->serialize(); + client->QueuePacket(outapp); + safe_delete(packet); + + CastTimer* cast_timer = new CastTimer; + cast_timer->caster = client; + cast_timer->target_id = target ? target->GetID() : 0; + cast_timer->entity_command = entity_command; + cast_timer->spell = 0; + cast_timer->delete_timer = false; + cast_timer->timer = new Timer(entity_command->cast_time * 10); + cast_timer->zone = zone; + cast_timer->in_heroic_opp = in_heroic_opp; + cast_timers.Add(cast_timer); + caster->IsCasting(true); + } + } + else if (!CastProcessedEntityCommand(entity_command, client, target, in_heroic_opp)) + return; + if (entity_command && entity_command->spell_visual > 0) + caster->GetZone()->SendCastEntityCommandPacket(entity_command, caster->GetID(), target->GetID()); + } +} + +bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive, bool in_heroic_opp){ + if(!spell || !spell->caster || !spell->spell || spell->interrupted) + return false; + Client* client = 0; + if(spell->caster && spell->caster->IsPlayer()) + client = ((Player*)spell->caster)->GetClient(); + if (spell->spell->GetSpellData()->max_aoe_targets > 0 && spell->targets.size() == 0) { + GetSpellTargetsTrueAOE(spell); + if (spell->targets.size() == 0) { + if(client) + { + client->GetPlayer()->UnlockAllSpells(true); + SendSpellBookUpdate(client); + } + spell->caster->GetZone()->SendSpellFailedPacket(client, SPELL_ERROR_NO_TARGETS_IN_RANGE); + return false; + } + } + + if(client && client->GetCurrentZone() && + !spell->spell->GetSpellData()->friendly_spell) + { + ZoneServer* zone = client->GetCurrentZone(); + Spawn* tmpTarget = zone->GetSpawnByID(spell->initial_target); + int8 spell_type = spell->spell->GetSpellData()->spell_type; + LuaSpell* conflictSpell = spell->caster->HasLinkedTimerID(spell, tmpTarget, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT)); + if(conflictSpell && tmpTarget && tmpTarget->IsEntity()) + { + ((Entity*)tmpTarget)->RemoveSpellEffect(conflictSpell); + zone->RemoveTargetFromSpell(conflictSpell, tmpTarget); + } + } + + MutexList::iterator itr = active_spells.begin(); + bool processedSpell = false; + + bool allTargets = (spell->spell->GetSpellData()->spell_type == SPELL_TYPE_ALLGROUPTARGETS); + if (!processedSpell) + processedSpell = ProcessSpell(spell, true, 0, 0, allTargets); + + // Quick hack to prevent a crash on spells that zones the caster (Gate) + if (!spell->caster) + return true; + + Skill* skill = spell->caster->GetSkillByID(spell->spell->GetSpellData()->mastery_skill, false); + // trigger potential skill increase if we succeed in casting a mastery skill and it still has room to grow (against this spell) + if(skill && skill->current_val < spell->spell->GetSpellData()->min_class_skill_req) + spell->caster->GetSkillByID(spell->spell->GetSpellData()->mastery_skill, true); + + ZoneServer* zone = spell->caster->GetZone(); + Spawn* target = 0; + if(processedSpell){ + for (int32 i = 0; i < spell->targets.size(); i++) { + target = zone->GetSpawnByID(spell->targets[i]); + if (!target) + continue; + + if (client && client->IsZoning()) + continue; + + // TODO: Establish actual hate per spell + if (!spell->spell->GetSpellData()->friendly_spell && target->IsNPC()) + ((NPC*)target)->AddHate((Entity*)spell->caster, 50); + + if(spell->spell->GetSpellData()->success_message.length() > 0){ + if(client){ + string success_message = spell->spell->GetSpellData()->success_message; + if(success_message.find("%t") != string::npos) + success_message.replace(success_message.find("%t"), 2, spell->caster->GetName()); + client->Message(CHANNEL_SPELLS, success_message.c_str()); + } + } + if(spell->spell->GetSpellData()->effect_message.length() > 0){ + string effect_message = spell->spell->GetSpellData()->effect_message; + bool send_to_sender = true; + if(effect_message.find("%t") != string::npos) + effect_message.replace(effect_message.find("%t"), 2, target->GetName()); + if (effect_message.find("%c") != string::npos) + effect_message.replace(effect_message.find("%c"), 2, spell->caster->GetName()); + if (effect_message.find("%T") != string::npos) { + effect_message.replace(effect_message.find("%T"), 2, target->GetName()); + send_to_sender = false; + } + if (effect_message.find("%C") != string::npos) { + effect_message.replace(effect_message.find("%C"), 2, spell->caster->GetName()); + send_to_sender = false; + } + + if(spell->caster && spell->caster->GetZone()) { + spell->caster->GetZone()->SimpleMessage(CHANNEL_SPELLS_OTHER, effect_message.c_str(), target, 50, send_to_sender); + } + } + if(target->GetZone()) { + target->GetZone()->CallSpawnScript(target, SPAWN_SCRIPT_CASTED_ON, spell->caster, spell->spell->GetName()); + } + } + } + else{ + if (!passive) + SendFinishedCast(spell, client); + return false; + } + if(!spell->resisted && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel || spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_NOT_SHOWN)) { + for (int32 i = 0; i < spell->targets.size(); i++) { + + //LogWrite(SPELL__ERROR, 0, "Spell", "No precast function found for %s", ((Entity*)target)->GetName()); + target = zone->GetSpawnByID(spell->targets.at(i)); + if (!target && spell->targets.at(i) == spell->caster->GetID()) { + target = spell->caster; + } + if (!target) { + if(client) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone has not finished loading process yet. Try again later."); + } + continue; + } + if (i == 0 && !spell->spell->GetSpellData()->not_maintained) { + spell->caster->AddMaintainedSpell(spell); + } + + SpellEffects* effect = ((Entity*)target)->GetSpellEffect(spell->spell->GetSpellID()); + if (effect && effect->tier > spell->spell->GetSpellTier()) { + if(client) { + spell->caster->GetZone()->SendSpellFailedPacket(client, SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "The spell did not take hold as the target already has a better spell of this type."); + } + } + else{ + ((Entity*)target)->AddSpellEffect(spell); + if(spell->spell->GetSpellData()->det_type > 0) + ((Entity*)target)->AddDetrimentalSpell(spell); + } + } + + active_spells.Add(spell); + + if (spell->num_triggers > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, spell->slot_pos, spell->num_triggers, 0); + if (spell->damage_remaining > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, spell->slot_pos, spell->damage_remaining, 1); + + spell->caster->GetZone()->TriggerCharSheetTimer(); + } + spell->num_calls = 1; + if(!spell->resisted && spell->spell->GetSpellData()->duration1 > 0){ + spell->timer.Start(); + if(spell->spell->GetSpellData()->call_frequency > 0) + spell->timer.SetTimer(spell->spell->GetSpellData()->call_frequency*100); + else + spell->timer.SetTimer(spell->spell->GetSpellData()->duration1*100); + if (active_spells.count(spell) < 1) { + active_spells.Add(spell); + } + } + + // if the caster is a player and the spell is a tradeskill spell check for a tradeskill event + if (client && spell->spell->GetSpellData()->spell_book_type == SPELL_BOOK_TYPE_TRADESKILL) { + spell->resisted = (spell->caster->DetermineHit(target ? target : spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, 255, 0, true, spell) == DAMAGE_PACKET_RESULT_RESIST); + client->GetCurrentZone()->GetTradeskillMgr()->CheckTradeskillEvent(client, spell->resisted ? 0 : spell->spell->GetSpellData()->icon); + } + + + if (spell->spell->GetSpellData()->friendly_spell && zone->GetSpawnByID(spell->initial_target)) + spell->caster->CheckProcs(PROC_TYPE_BENEFICIAL, zone->GetSpawnByID(spell->initial_target)); + // Check HO's + if (client && !in_heroic_opp) { + HeroicOP* ho = nullptr; + Spell* ho_spell = nullptr; + int32 ho_target = 0; + + MSoloHO.writelock(__FUNCTION__, __LINE__); + MGroupHO.writelock(__FUNCTION__, __LINE__); + map::iterator soloItr = m_soloHO.find(client); + if (soloItr != m_soloHO.end()) { + ho = soloItr->second; + bool match = false; + LogWrite(SPELL__DEBUG, 0, "HO", "target = %u", ho->GetTarget()); + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (int8 i = 0; i < spell->targets.size(); i++) { + LogWrite(SPELL__DEBUG, 0, "HO", "Target ID: %u", spell->targets.at(i)); + if (spell->targets.at(i) == ho->GetTarget() || spell->spell->GetSpellData()->friendly_spell) { + match = true; + LogWrite(SPELL__DEBUG, 0, "HO", "match found"); + break; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + if(match && !spell->spell) + LogWrite(SPELL__ERROR, 0, "HO", "%s: spell->spell is nullptr", client->GetPlayer()->GetName()); + else if (match && spell->spell && ho->UpdateHeroicOP(spell->spell->GetSpellIconHeroicOp())) { + ClientPacketFunctions::SendHeroicOPUpdate(client, ho); + if (ho->GetComplete() > 0) { + if(!ho->GetWheel()) + LogWrite(SPELL__ERROR, 0, "HO", "%s: Wheel is inactive (nullptr) cannot check for invalid spell", client->GetPlayer()->GetName()); + else + { + ho_spell = master_spell_list.GetSpell(ho->GetWheel()->spell_id, 1); + ho_target = ho->GetTarget(); + if (!ho_spell) + LogWrite(SPELL__ERROR, 0, "HO", "%s: Invalid spell for a HO, spell id = %u", client->GetPlayer()->GetName(), ho->GetWheel()->spell_id); + + safe_delete(ho); + m_soloHO.erase(soloItr); + } + } + } + } + else if (client->GetPlayer()->GetGroupMemberInfo()) { + map::iterator groupItr = m_groupHO.find(client->GetPlayer()->GetGroupMemberInfo()->group_id); + if (groupItr != m_groupHO.end()) { + ho = groupItr->second; + int32 group_id = client->GetPlayer()->GetGroupMemberInfo()->group_id; + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + if (((spell->targets.size() > 0 && spell->targets.at(0) == ho->GetTarget()) || spell->spell->GetSpellData()->friendly_spell) + && ho->UpdateHeroicOP(spell->spell->GetSpellIconHeroicOp())) { + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + if ((*itr)->client) + ClientPacketFunctions::SendHeroicOPUpdate((*itr)->client, ho); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + if (ho->GetComplete() > 0) { + ho_spell = master_spell_list.GetSpell(ho->GetWheel()->spell_id, 1); + ho_target = ho->GetTarget(); + if (!ho_spell) + LogWrite(SPELL__ERROR, 0, "HO", "Invalid spell for a HO, spell id = %u", ho->GetWheel()->spell_id); + + safe_delete(ho); + m_groupHO.erase(groupItr); + } + } + else + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + } + } + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + + if (ho_spell && ho_target != 0) + client->GetCurrentZone()->ProcessSpell(ho_spell, client->GetPlayer(), spell->caster->GetZone()->GetSpawnByID(ho_target)); + + } + + if (spell->spell->GetSpellData()->friendly_spell == 1 && spell->initial_target) + spell->caster->CheckProcs(PROC_TYPE_BENEFICIAL, spell->caster->GetZone()->GetSpawnByID(spell->initial_target)); + + if (!passive) + SendFinishedCast(spell, client); + + return true; +} + +bool SpellProcess::CastProcessedEntityCommand(EntityCommand* entity_command, Client* client, Spawn* target, bool in_heroic_opp) { + bool ret = false; + if (entity_command && client) { + UnlockAllSpells(client); + client->GetPlayer()->IsCasting(false); + if (entity_command->cast_time == 0) { + client->GetPlayer()->GetZone()->CallSpawnScript(target, SPAWN_SCRIPT_CASTED_ON, client->GetPlayer(), entity_command->command.c_str()); + ret = true; + } + if (!ret) { + PacketStruct* packet = configReader.getStruct("WS_FinishCastSpell", client->GetVersion()); + if (packet) { + packet->setMediumStringByName("spell_name", entity_command->name.c_str()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + SendSpellBookUpdate(client); + client->GetPlayer()->GetZone()->CallSpawnScript(target, SPAWN_SCRIPT_CASTED_ON, client->GetPlayer(), entity_command->command.c_str()); + ret = true; + } + } + if (ret) { + EQ2_16BitString command; + command.data = entity_command->command; + command.size = entity_command->command.length(); + int32 handler = commands.GetCommandHandler(command.data.c_str()); + if (handler != 0xFFFFFFFF && handler < 999) + commands.Process(handler, &command, client, target); + } + } + return ret; +} + +void SpellProcess::Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel, bool from_movement) +{ + if(caster) + { + LogWrite(SPELL__DEBUG, 0, "Spell", "'%s' is Interrupting spell of '%s'...", interruptor ? interruptor->GetName() : "unknown", caster->GetName()); + LuaSpell* spell = GetLuaSpell(caster); + + if (spell && ((from_movement && !spell->spell->GetSpellData()->cast_while_moving) || (!from_movement && spell->spell->GetSpellData()->interruptable) || + cancel)) + { + InterruptStruct* interrupt = new InterruptStruct; + interrupt->interrupted = caster; + interrupt->spell = spell; + interrupt->error_code = error_code; + spell->interrupted = true; + interrupt_list.Add(interrupt); + + Client* client = 0; + if(interruptor && interruptor->IsPlayer()) + { + client = ((Player*)interruptor)->GetClient(); + if(client) { + client->Message(CHANNEL_SPELLS_OTHER, "You interrupt %s's ability to cast!", caster->GetName()); + } + } + + } + } +} + +void SpellProcess::RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, bool delete_recast, bool call_expire_function, bool lock_spell_process){ + if(lock_spell_process) + MSpellProcess.lock_shared(); + + int32 i = 0; + if(cast_timers.size() > 0){ + CastTimer* cast_timer = 0; + MutexList::iterator itr = cast_timers.begin(); + while(itr.Next()){ + cast_timer = itr->value; + if(cast_timer && cast_timer->spell && cast_timer->spell->caster == spawn){ + cast_timer->spell->caster = 0; + cast_timer->delete_timer = true; + cast_timer->spell = nullptr; + } + } + } + if(remove_all){ + LuaSpell* spell = 0; + MutexList::iterator itr = active_spells.begin(); + while(itr.Next()){ + spell = itr->value; + if (!spell) + continue; + if(spell->caster == spawn && call_expire_function){ + DeleteCasterSpell(spell, "expired", remove_all, nullptr, false, lock_spell_process); + continue; + } + if (spell->spell->GetSpellData()->persist_through_death) + continue; + + bool foundMatch = false; + + spell->MSpellTargets.readlock(__FUNCTION__, __LINE__); + for (i = 0; i < spell->targets.size(); i++){ + if (spawn->GetID() == spell->targets.at(i)){ + foundMatch = true; + break; + } + } + spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__); + if(foundMatch) { + if (spawn->IsEntity()) + ((Entity*)spawn)->RemoveSpellEffect(spell); + RemoveTargetFromSpell(spell, spawn, remove_all); + } + } + if(recast_timers.size() > 0 && delete_recast){ + RecastTimer* recast_timer = 0; + MutexList::iterator itr = recast_timers.begin(); + while(itr.Next()){ + recast_timer = itr->value; + if(recast_timer && recast_timer->caster == spawn){ + safe_delete(recast_timer->timer); + recast_timers.Remove(recast_timer, true); + } + } + } + if(spell_que.size() > 0 && spawn->IsEntity()){ + spell_que.erase((Entity*)spawn); + } + if(interrupt_list.size() > 0){ + InterruptStruct* interrupt = 0; + MutexList::iterator itr = interrupt_list.begin(); + while(itr.Next()){ + interrupt = itr->value; + if(interrupt && interrupt->interrupted == spawn){ + interrupt_list.Remove(interrupt, true); + } + } + } + } + + if(lock_spell_process) + MSpellProcess.unlock_shared(); +} + +void SpellProcess::GetSpellTargets(LuaSpell* luaspell) +{ + if (luaspell && luaspell->spell && luaspell->caster && luaspell->spell->GetSpellData()->max_aoe_targets == 0) + { + int8 target_type = luaspell->spell->GetSpellData()->target_type; + int8 spell_type = luaspell->spell->GetSpellData()->spell_type; + Spawn* caster = luaspell->caster; + Spawn* target = caster->GetZone()->GetSpawnByID(luaspell->initial_target); + SpellData* data = luaspell->spell->GetSpellData(); + bool implied = false; + Spawn* secondary_target = nullptr; + + //implied target check -- only use this for players + if (target && (target_type == SPELL_TARGET_ENEMY || target_type == SPELL_TARGET_ENEMY_CORPSE || target_type == SPELL_TARGET_GROUP_CORPSE || target_type == SPELL_TARGET_OTHER_GROUP_AE)) + { + if (caster->IsPlayer() && target->HasTarget()) + { + secondary_target = target->GetTarget(); + // check if spell is friendly + if (data->friendly_spell) { + //if target is NPC (and not a bot) on friendly spell, check to see if target is friendly + if (target->IsNPC() && !((Entity*)target)->GetInfoStruct()->get_friendly_target_npc() && !target->IsBot()) { + if (!target->IsPet() || (target->IsPet() && ((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner()->IsNPC())) { + if (secondary_target && secondary_target->IsPlayer()) { + target = secondary_target; + luaspell->initial_target = target->GetID(); + luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, target->GetID()); + } + else if (secondary_target && secondary_target->IsPet() && ((NPC*)secondary_target)->GetOwner() && ((NPC*)secondary_target)->GetOwner()->IsPlayer()) + implied = true; + } + else if (target->IsPet() && ((Entity*)target)->GetOwner()->IsPlayer()) + { + luaspell->initial_target = target->GetID(); + luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, target->GetID()); + } + } + } // if spell is not friendly + else { // check if there is an implied target for this non-friendly spell + if (target->IsPlayer() || (target->IsPet() && ((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner()->IsPlayer())) { + if (secondary_target && secondary_target->IsNPC()) { + if (!secondary_target->IsPet() || (secondary_target->IsPet() && ((NPC*)secondary_target)->GetOwner() && ((NPC*)secondary_target)->GetOwner()->IsNPC())) { + implied = true; + } + } + else if (target->IsPlayer() && ((Entity*)caster)->AttackAllowed((Entity*)target)) + { + secondary_target = target; + implied = true; + luaspell->initial_target = target->GetID(); + luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, target->GetID()); + GetPlayerGroupTargets((Player*)target, caster, luaspell); + + } + else if (secondary_target && target->IsPlayer() && ((Entity*)caster)->AttackAllowed((Entity*)secondary_target)) + { + implied = true; + luaspell->initial_target = secondary_target->GetID(); + luaspell->initial_target_char_id = (secondary_target && secondary_target->IsPlayer()) ? ((Player*)secondary_target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, secondary_target->GetID()); + GetPlayerGroupTargets((Player*)secondary_target, caster, luaspell); + } + } + } // end friendly spell check + } + else if (caster->IsPlayer()) { + if (data->friendly_spell) { + if (target->IsNPC() && !target->IsBot()) { + if (!((Entity*)target)->GetInfoStruct()->get_friendly_target_npc() && (!target->IsPet() || (target->IsPet() && ((NPC*)target)->GetOwner() && ((NPC*)target)->GetOwner()->IsNPC()))) { + target = caster; + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + } + } + else + { + if (target->IsPlayer() && ((Entity*)caster)->AttackAllowed((Entity*)target)) { + luaspell->initial_target = target->GetID(); + luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; + AddLuaSpellTarget(luaspell, target->GetID()); + } + } + } + } + else if (target_type == SPELL_TARGET_GROUP_AE || spell_type == SPELL_TYPE_ALLGROUPTARGETS || target_type == SPELL_TARGET_RAID_AE) { + + // player handling + if (target) + { + if (data->icon_backdrop == 316 || data->icon_backdrop == 312) // using TARGET backdrop icon + { + // PLAYER LOGIC: + if ((data->friendly_spell && (target->IsPlayer() && luaspell->caster->IsPlayer() && target != luaspell->caster && ((Player*)target)->GetGroupMemberInfo() != NULL && ((Player*)luaspell->caster)->GetGroupMemberInfo() != NULL + && ((Player*)target)->GetGroupMemberInfo()->group_id == ((Player*)luaspell->caster)->GetGroupMemberInfo()->group_id)) + || (!data->friendly_spell && (target->IsPlayer() && luaspell->caster->IsPlayer() && target != luaspell->caster && ((Player*)target)->GetGroupMemberInfo() != NULL))) + { + GetPlayerGroupTargets((Player*)target, caster, luaspell, true, false); + } + //TODO: NEED RAID SUPPORT + + // NPC LOGIC: + else if (target->IsNPC()) + { + // Check to see if the npc is a spawn group by getting the group and checikng if valid + vector* group = ((NPC*)target)->GetSpawnGroup(); + if (group) + { + vector::iterator itr; + + // iterate through spawn group members + for (itr = group->begin(); itr != group->end(); itr++) + { + Spawn* group_member = *itr; + + // if NPC group member is (still) an NPC (wtf?) and is alive, send the NPC group member back as a successful target of non-friendly spell group_member->Alive() + if (group_member->GetZone() == caster->GetZone() && + group_member->IsNPC() && (data->friendly_spell || ((Entity*)caster)->AttackAllowed((Entity*)group_member)) && group_member->Alive() && !((Entity*)group_member)->IsAOEImmune() && (!((Entity*)group_member)->IsMezzed() || group_member == target)) + AddLuaSpellTarget(luaspell, group_member->GetID()); + + // note: this should generate some hate towards the caster + } + } // end is spawngroup + else if(data->friendly_spell || ((Entity*)caster)->AttackAllowed((Entity*)target)) + AddLuaSpellTarget(luaspell, target->GetID()); // return single target NPC for non-friendly spell + } + else if(data->friendly_spell) + { + // add self + target = NULL; + AddSelfAndPet(luaspell, caster); + + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + else if(target && target->IsPlayer()) + { + if(!GetPlayerGroupTargets((Player*)target, caster, luaspell, true, false) && ((Entity*)caster)->AttackAllowed((Entity*)target)) + AddSelfAndPet(luaspell, target); + } + } + else // default self cast for group/raid AE + { + target = caster; + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + // spell target versus self cast + } + else if (data->icon_backdrop != 316) // default self cast for group/raid AE and not using TARGET backdrop icon + { + target = caster; + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + } + else if (target_type == SPELL_TARGET_SELF){ + target = caster; + luaspell->initial_target = caster->GetID(); + luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; + } + + //if using implied target, target = the implied target + if (implied) + { + target = secondary_target; + luaspell->initial_target = secondary_target->GetID(); + luaspell->initial_target_char_id = (secondary_target && secondary_target->IsPlayer()) ? ((Player*)secondary_target)->GetCharacterID() : 0; + } + + luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + // Group AE type NOTE: Add support for RAID AE to affect raid members once raids have been completed + if (target_type == SPELL_TARGET_GROUP_AE || spell_type == SPELL_TYPE_ALLGROUPTARGETS || target_type == SPELL_TARGET_RAID_AE) + { + if (data->icon_backdrop == 316) // single target in a group/raid + { + if (target) + AddLuaSpellTarget(luaspell, target->GetID(), false); + } + // is friendly + else if (data->friendly_spell) + { + // caster is an Entity + if (luaspell->caster->IsEntity()) + { + // entity caster is in a player group + if (((Entity*)caster)->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + // get group members + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Entity*)caster)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + + // iterate through list of group members + for (itr = members->begin(); itr != members->end(); itr++) + { + + // get group member player info + Entity* group_member = (*itr)->member; + + if(!group_member){ + continue; + } + + LogWrite(SPELL__DEBUG, 0, "Player", "%s is group member for spell %s", group_member->GetName(), luaspell->spell->GetName()); + // if the group member is in the casters zone, and is alive + + if( group_member->Alive()) + { + if(group_member->GetZone() != caster->GetZone()) + { + SpellProcess::AddSelfAndPetToCharTargets(luaspell, group_member); + } + else if (group_member->GetZone() == luaspell->caster->GetZone()) { + AddLuaSpellTarget(luaspell, group_member->GetID(), false); + if (group_member->HasPet()) { + Entity* pet = group_member->GetPet(); + if (!pet) + pet = group_member->GetCharmedPet(); + if (pet) + AddLuaSpellTarget(luaspell, pet->GetID(), false); + + LogWrite(SPELL__DEBUG, 0, "Player", "%s added a pet %s (%u) for spell %s", group_member->GetName(), pet ? pet->GetName() : "", pet ? pet->GetID() : 0, luaspell->spell->GetName()); + } + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + AddSelfAndPet(luaspell, caster); // else caster is not in a group, thus alone + } + else if (caster->IsNPC()) // caster is NOT a player + { + // caster is NPC and in a spawn group with other NPCs + vector* group = ((NPC*)caster)->GetSpawnGroup(); + if (group) + { + vector::iterator itr; + + for (itr = group->begin(); itr != group->end(); itr++) + { + Spawn* group_member = *itr; + + if (group_member->IsNPC() && group_member->Alive() && ((Entity*)caster)->AttackAllowed(((Entity*)group_member))){ + AddLuaSpellTarget(luaspell, group_member->GetID(), false); + if (((Entity*)group_member)->HasPet()){ + Entity* pet = ((Entity*)group_member)->GetPet(); + if (pet) + AddLuaSpellTarget(luaspell, pet->GetID(), false); + pet = ((Entity*)group_member)->GetCharmedPet(); + if (pet) + AddLuaSpellTarget(luaspell, pet->GetID(), false); + } + } + } + } + else + AddSelfAndPet(luaspell, caster); + + safe_delete(group); + } // end is player + } // end is friendly + } // end is Group AE + + else if (target_type == SPELL_TARGET_SELF && caster) + AddLuaSpellTarget(luaspell, caster->GetID(), false); // if spell is SELF, return caster + + else if (target_type == SPELL_TARGET_CASTER_PET && caster && caster->IsEntity() && ((Entity*)caster)->HasPet()) { + AddSelfAndPet(luaspell, caster, true); + } + + else if (target_type == SPELL_TARGET_ENEMY && target && target->Alive()) // if target is enemy, and is alive + { + // if friendly spell + if (data->friendly_spell > 0) + { + // if caster is a player + if (caster->IsPlayer()) + { + // if spell can affect raid, only group members or is a group spell + if (data->can_effect_raid > 0 || data->affect_only_group_members > 0 || data->group_spell > 0) + { + // if caster is in a group, and target is a player and targeted player is a group member + if (((Player*)caster)->GetGroupMemberInfo() && (target->IsPlayer() || target->IsBot() || target->IsPet()) && ((Player*)caster)->IsGroupMember((Entity*)target)) + AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target + else + AddLuaSpellTarget(luaspell, caster->GetID(), false); // else return the caster + } + else if (target->IsPlayer() || target->IsBot() || (target->IsNPC() && ((Entity*)target)->GetInfoStruct()->get_friendly_target_npc())) // else it is not raid, group only or group spell + AddLuaSpellTarget(luaspell, target->GetID(), false); // return target for single spell + else if ((luaspell->targets.size() < 1) || (!target->IsPet() || (((Entity*)target)->GetOwner() && !((Entity*)target)->GetOwner()->IsPlayer()))) + AddLuaSpellTarget(luaspell, caster->GetID(), false); // and if no target, cast on self + } + else if (caster->IsNPC()) // caster is an NPC + { + // if NPC's spell can affect raid, only group members or is a group spell + if (data->can_effect_raid > 0 || data->affect_only_group_members > 0 || data->group_spell > 0) + { + if (caster->IsBot() && (target->IsBot() || target->IsPlayer())) { + GroupMemberInfo* gmi = ((Entity*)caster)->GetGroupMemberInfo(); + if (gmi && target->IsEntity() && world.GetGroupManager()->IsInGroup(gmi->group_id, (Entity*)target)) { + AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target + } + else + AddSelfAndPet(luaspell, caster); + } + // if NPC caster is in a group, and target is a player and targeted player is a group member + else if (((NPC*)caster)->HasSpawnGroup() && target->IsNPC() && ((NPC*)caster)->IsInSpawnGroup((NPC*)target)) + AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target + else + AddSelfAndPet(luaspell, caster); + } + else if (target->IsNPC()) + AddLuaSpellTarget(luaspell, target->GetID(), false); // return target for single spell + else { + if (caster->IsBot() && (target->IsBot() || target->IsPlayer())) + AddLuaSpellTarget(luaspell, target->GetID(), false); + else + AddLuaSpellTarget(luaspell, caster->GetID(), false); // and if no target, cast on self + } + } // end is player + } // end is friendly + + else if (target && (data->group_spell > 0 || data->icon_backdrop == 312)) // is not friendly, but is a group spell, icon_backdrop 312 is green (encounter AE) + { + // target is non-player + if (target->IsNPC()) + { + // Check to see if the npc is a spawn group by getting the group and checikng if valid + vector* group = ((NPC*)target)->GetSpawnGroup(); + if (group) + { + vector::iterator itr; + + // iterate through spawn group members + for (itr = group->begin(); itr != group->end(); itr++) + { + Spawn* group_member = *itr; + + // if NPC group member is (still) an NPC (wtf?) and is alive, send the NPC group member back as a successful target of non-friendly spell group_member->Alive() + if (group_member->GetZone() == caster->GetZone() && + group_member->IsNPC() && group_member->Alive() && ((Entity*)caster)->AttackAllowed((Entity*)group_member) && !((Entity*)group_member)->IsAOEImmune() && (!((Entity*)group_member)->IsMezzed() || group_member == target)) + AddLuaSpellTarget(luaspell, group_member->GetID(), false); + + // note: this should generate some hate towards the caster + } + } // end is spawngroup + else + AddLuaSpellTarget(luaspell, target->GetID(), false); // return single target NPC for non-friendly spell + + safe_delete(group); + } // end is NPC + + else if (target->IsPlayer() && caster->IsNPC()) // the NPC is casting on a player + { + // player is in a group + if (((Player*)target)->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)target)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + // iterate through players group members + for (itr = members->begin(); itr != members->end(); itr++) + { + Entity* group_member = (*itr)->member; + + // if the group member is in the same zone as caster, and group member is alive, and group member is within distance + if (group_member && group_member->GetZone() == caster->GetZone() && group_member->Alive() && caster->GetDistance(group_member) <= data->range + && (group_member == target || !group_member->IsAOEImmune())) + AddLuaSpellTarget(luaspell, group_member->GetID(), false); // add as target + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + AddLuaSpellTarget(luaspell, target->GetID(), false); // player not in group + } // end is caster player or npc + } + else if (target) + AddLuaSpellTarget(luaspell, target->GetID(), false); // is not friendly nor a group spell + } + //Rez spells + else if(target && target_type == SPELL_TARGET_ENEMY_CORPSE){ + //is friendly + if(data->friendly_spell){ + //target is player + if(target->IsPlayer()){ + AddLuaSpellTarget(luaspell, target->GetID(), false); + } + } + } + else if(target_type == SPELL_TARGET_GROUP_CORPSE){ + //is friendly + if(data->friendly_spell){ + //target is player + if(target && target->IsPlayer()){ + //if target has group + if(((Player*)target)->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)target)->GetGroupMemberInfo()->group_id); + + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + Entity* group_member = 0; + for (itr = members->begin(); itr != members->end(); itr++) { + group_member = (*itr)->member; + //Check if group member is in the same zone in range of the spell and dead + if (group_member && group_member->GetZone() == target->GetZone() && !group_member->Alive() && target->GetDistance(group_member) <= data->radius) { + AddLuaSpellTarget(luaspell, group_member->GetID(), false); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else + AddLuaSpellTarget(luaspell, target->GetID(), false); + } + } + } + luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + + if (luaspell && luaspell->targets.size() > 20) + LogWrite(SPELL__WARNING, 0, "Spell", "Warning in %s: Size of targets array is %u", __FUNCTION__, luaspell->targets.size()); +} + +bool SpellProcess::GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell* luaspell, bool bypassSpellChecks, bool bypassRangeChecks) +{ + if (bypassSpellChecks || luaspell->spell->GetSpellData()->group_spell > 0 || luaspell->spell->GetSpellData()->icon_backdrop == 312) + { + if (((Player*)target)->GetGroupMemberInfo()) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(((Player*)target)->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + GroupMemberInfo* info = 0; + + for (itr = members->begin(); itr != members->end(); itr++) { + info = *itr; + if (info == ((Player*)target)->GetGroupMemberInfo()) + continue; + else if (info && info->client && + info->client->GetPlayer()->GetZone() == ((Player*)target)->GetZone() && info->client->GetPlayer()->Alive() + && (bypassRangeChecks || caster->GetDistance((Entity*)info->client->GetPlayer()) <= luaspell->spell->GetSpellData()->range)) + { + AddSelfAndPet(luaspell, info->client->GetPlayer()); + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + + return true; + } + } + } + + return false; +} + +void SpellProcess::GetSpellTargetsTrueAOE(LuaSpell* luaspell) { + if (luaspell && luaspell->caster && luaspell->spell && luaspell->spell->GetSpellData()->max_aoe_targets > 0) { + if (luaspell->caster->HasTarget() && luaspell->caster->GetTarget() != luaspell->caster){ + //Check if the caster has an implied target + if (luaspell->caster->GetDistance(luaspell->caster->GetTarget()) <= luaspell->spell->GetSpellData()->radius) + { + luaspell->initial_target = luaspell->caster->GetTarget()->GetID(); + luaspell->initial_target_char_id = (luaspell->caster->GetTarget() && luaspell->caster->GetTarget()->IsPlayer()) ? ((Player*)luaspell->caster->GetTarget())->GetCharacterID() : 0; + } + } + int32 ignore_target = 0; + std::vector> spawns = luaspell->caster->GetZone()->GetAttackableSpawnsByDistance(luaspell->caster, luaspell->spell->GetSpellData()->radius); + luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + int32 i = 0; + for (const auto& pair : spawns) { + if (i == 0){ + Spawn* spawn = luaspell->caster->GetZone()->GetSpawnByID(luaspell->initial_target); + if (spawn && luaspell->initial_target && luaspell->caster->GetID() != luaspell->initial_target && luaspell->caster->AttackAllowed((Entity*)spawn)){ + //this is the "Direct" target and aoe can't be avoided + AddLuaSpellTarget(luaspell, luaspell->initial_target, false); + ignore_target = luaspell->initial_target; + } + } + + i++; + + if (luaspell->targets.size() >= luaspell->spell->GetSpellData()->max_aoe_targets) + break; + + int32 target_id = pair.first; + Spawn* spawn = luaspell->caster->GetZone()->GetSpawnByID(target_id); + if(!spawn) { + LogWrite(SPELL__ERROR, 0, "Spell", "Error: Spell target is NULL! SpellProcess::ProcessSpell for Spell '%s' target id %u", (luaspell->spell != nullptr) ? luaspell->spell->GetName() : "Unknown", target_id); + } + //If we have already added this spawn, check the next spawn in the list + if (spawn && spawn->GetID() == ignore_target || (spawn->IsEntity() && !luaspell->caster->AttackAllowed((Entity*)spawn))){ + continue; + } + if (spawn){ + //If this spawn is immune to aoe, continue + if (((Entity*)spawn)->IsAOEImmune() || ((Entity*)spawn)->IsMezzed()) + continue; + AddLuaSpellTarget(luaspell, spawn->GetID(), false); + } + + if (luaspell->targets.size() >= luaspell->spell->GetSpellData()->max_aoe_targets) + break; + } + luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + if (luaspell->targets.size() > 20) + LogWrite(SPELL__DEBUG, 0, "Spell", "Warning in SpellProcess::GetSpellTargetsTrueAOE Size of targets array is %u", luaspell->targets.size()); +} + +void SpellProcess::AddSpellScriptTimer(SpellScriptTimer* timer) { + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + m_spellScriptList.push_back(timer); + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::RemoveSpellScriptTimer(SpellScriptTimer* timer, bool locked) { + if (m_spellScriptList.size() == 0) + return; + + vector::iterator itr; + if(!locked) + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + if ((*itr) == timer) { + SpellScriptTimer* timer = *itr; + if ((*itr) && (*itr)->deleteWhenDone && lua_interface) { + lua_interface->AddPendingSpellDelete(timer->spell); + } + m_spellScriptList.erase(itr); + safe_delete(timer); + break; + } + } + + if(!locked) + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::RemoveSpellScriptTimerBySpell(LuaSpell* spell, bool clearPendingDeletes) { + vector::iterator itr; + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + if (lua_interface && clearPendingDeletes) + lua_interface->DeletePendingSpell(spell); + + if (m_spellScriptList.size() == 0) + { + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); + return; + } + + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); ) { + if ((*itr)->spell == spell) + { + vector::iterator cur = itr; + SpellScriptTimer* timer = *itr; + m_spellScriptList.erase(cur); + safe_delete(timer); + break; + } + else + itr++; + } + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::CheckSpellScriptTimers() { + vector::iterator itr; + vector temp_list; + + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + if (Timer::GetCurrentTime2() >= (*itr)->time) { + temp_list.push_back((*itr)); + ProcessSpell((*itr)->spell, false, (*itr)->customFunction.c_str(), (*itr)); + } + } + + for (itr = temp_list.begin(); itr != temp_list.end(); itr++) { + RemoveSpellScriptTimer(*itr, true); + } + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +bool SpellProcess::SpellScriptTimersHasSpell(LuaSpell* spell) { + bool ret = false; + vector::iterator itr; + + MSpellScriptTimers.readlock(__FUNCTION__, __LINE__); + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + SpellScriptTimer* timer = *itr; + if (timer && timer->spell == spell) { + ret = true; + break; + } + } + MSpellScriptTimers.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +std::string SpellProcess::SpellScriptTimerCustomFunction(LuaSpell* spell) { + bool ret = false; + std::string val = string(""); + vector::iterator itr; + + MSpellScriptTimers.readlock(__FUNCTION__, __LINE__); + for (itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + SpellScriptTimer* timer = *itr; + if (timer && timer->spell == spell) { + val = string(timer->customFunction); + break; + } + } + MSpellScriptTimers.releasereadlock(__FUNCTION__, __LINE__); + + return val; +} + +void SpellProcess::ClearSpellScriptTimerList() { + vector::iterator itr; + MSpellScriptTimers.writelock(__FUNCTION__, __LINE__); + + for(itr = m_spellScriptList.begin(); itr != m_spellScriptList.end(); itr++) { + if ((*itr) && (*itr)->deleteWhenDone && lua_interface) + lua_interface->AddPendingSpellDelete((*itr)->spell); + + safe_delete((*itr)); + } + + m_spellScriptList.clear(); + MSpellScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::RemoveTargetFromSpell(LuaSpell* spell, Spawn* target, bool removeCaster){ + if (!spell || !target) + return; + + LogWrite(SPELL__DEBUG, 0, "Spell", "%s RemoveTargetFromSpell %s (%u).", spell->spell->GetName(), target->GetName(), target->GetID()); + MRemoveTargetList.writelock(__FUNCTION__, __LINE__); + + if(removeCaster && spell->caster && spell->caster == target) + spell->caster = nullptr; + + if (!remove_target_list[spell]) + remove_target_list[spell] = new vector; + remove_target_list[spell]->push_back(target->GetID()); + + MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete, bool removing_all_spells){ + if (!spell) + return; + + MRemoveTargetList.writelock(__FUNCTION__, __LINE__); + if (remove_target_list.size() > 0){ + map*>::iterator remove_itr; + vector::iterator remove_target_itr; + vector::iterator target_itr; + vector* targets; + vector* remove_targets = 0; + Spawn* remove_spawn = 0; + bool should_delete = false; + + for (remove_itr = remove_target_list.begin(); remove_itr != remove_target_list.end(); remove_itr++){ + if (remove_itr->first == spell){ + targets = &spell->targets; + remove_targets = remove_itr->second; + if (remove_targets && targets){ + spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + for (remove_target_itr = remove_targets->begin(); remove_target_itr != remove_targets->end(); remove_target_itr++){ + if(!spell->caster || !spell->caster->GetZone()) + continue; + + remove_spawn = spell->caster->GetZone()->GetSpawnByID((*remove_target_itr)); + if (remove_spawn) { + if(remove_spawn && remove_spawn->IsPlayer()) + { + multimap::iterator entries; + while((entries = spell->char_id_targets.find(((Player*)remove_spawn)->GetCharacterID())) != spell->char_id_targets.end()) + { + spell->char_id_targets.erase(entries); + } + } + for (target_itr = targets->begin(); target_itr != targets->end(); target_itr++) { + if (remove_spawn->GetID() == (*target_itr)) { + ((Entity*)remove_spawn)->RemoveProc(0, spell); + ((Entity*)remove_spawn)->RemoveMaintainedSpell(spell); + LogWrite(SPELL__DEBUG, 0, "Spell", "%s CheckRemoveTargetFromSpell %s (%u).", spell->spell->GetName(), remove_spawn->GetName(), remove_spawn->GetID()); + targets->erase(target_itr); + if (remove_spawn->IsEntity()) + { + if(!removing_all_spells) + { + if(remove_spawn->IsPlayer()) + spell->char_id_targets.insert(make_pair(((Player*)remove_spawn)->GetCharacterID(),0)); + else if(remove_spawn->IsPet() && ((Entity*)remove_spawn)->GetOwner() && ((Entity*)remove_spawn)->GetOwner()->IsPlayer()) + { + Entity* pet = (Entity*)remove_spawn; + Player* ownerPlayer = (Player*)pet->GetOwner(); + spell->char_id_targets.insert(make_pair(ownerPlayer->GetCharacterID(),pet->GetPetType())); + } + } + ((Entity*)remove_spawn)->RemoveEffectsFromLuaSpell(spell); + } + break; + } + } + if (targets->size() == 0 && spell->char_id_targets.size() == 0 && allow_delete) { + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + should_delete = true; + break; + } + } + } + spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + break; + } + } + remove_target_list.erase(spell); + if (remove_targets) + remove_targets->clear(); + safe_delete(remove_targets); + MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__); + if (should_delete) + DeleteCasterSpell(spell, "purged"); + } + else { + MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__); + } +} + +bool SpellProcess::AddHO(Client* client, HeroicOP* ho) { + bool ret = true; + + if (client && ho) { + MSoloHO.writelock(__FUNCTION__, __LINE__); + if (m_soloHO.count(client) > 0) { + if (m_soloHO[client]->GetWheel()) { + ret = false; + } + else { + safe_delete(m_soloHO[client]); + m_soloHO[client] = ho; + } + } + else + m_soloHO[client] = ho; + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + } + + return ret; +} + +bool SpellProcess::AddHO(int32 group_id, HeroicOP* ho) { + bool ret = true; + + if (group_id > 0 && ho) { + MGroupHO.writelock(__FUNCTION__, __LINE__); + if (m_groupHO.count(group_id) > 0) { + if (m_groupHO[group_id]->GetWheel()) { + ret = false; + } + else { + safe_delete(m_groupHO[group_id]); + m_groupHO[group_id] = ho; + } + } + else + m_groupHO[group_id] = ho; + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); + } + + return ret; +} + +void SpellProcess::KillHOBySpawnID(int32 spawn_id) { + // Check solo HO's + MSoloHO.writelock(__FUNCTION__, __LINE__); + map::iterator itr = m_soloHO.begin(); + map::iterator delete_itr; + while (itr != m_soloHO.end()) { + if (itr->second->GetTarget() == spawn_id) { + itr->second->SetComplete(1); + ClientPacketFunctions::SendHeroicOPUpdate(itr->first, itr->second); + delete_itr = itr; + safe_delete(itr->second); + itr++; + m_soloHO.erase(delete_itr); + } + else + itr++; + } + MSoloHO.releasewritelock(__FUNCTION__, __LINE__); + + // Check Group HO's + MGroupHO.writelock(__FUNCTION__, __LINE__); + map::iterator itr2 = m_groupHO.begin(); + map::iterator delete_itr2; + while (itr2 != m_groupHO.end()) { + if (itr2->second->GetTarget() == spawn_id) { + itr2->second->SetComplete(1); + + world.GetGroupManager()->GroupLock(__FUNCTION__ , __LINE__); + deque::iterator itr3; + PlayerGroup* group = world.GetGroupManager()->GetGroup(itr2->first); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr3 = members->begin(); itr3 != members->end(); itr3++) { + if ((*itr3)->client) + ClientPacketFunctions::SendHeroicOPUpdate((*itr3)->client, itr2->second); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + delete_itr2 = itr2; + safe_delete(itr2->second); + itr2++; + m_groupHO.erase(delete_itr2); + } + else + itr2++; + } + MGroupHO.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::AddSpellCancel(LuaSpell* spell){ + MSpellCancelList.writelock(__FUNCTION__, __LINE__); + SpellCancelList.push_back(spell); + MSpellCancelList.releasewritelock(__FUNCTION__, __LINE__); +} + +void SpellProcess::DeleteSpell(LuaSpell* spell) +{ + RemoveSpellFromQueue(spell->spell, spell->caster); + + if (spell->spell->IsCopiedSpell()) + { + lua_interface->RemoveCustomSpell(spell->spell->GetSpellID()); + safe_delete(spell->spell); + } + + lua_interface->SetLuaUserDataStale(spell); + lua_interface->RemoveCurrentSpell(spell->state, true); + + DeleteActiveSpell(spell); +} + +void SpellProcess::SpellCannotStack(ZoneServer* zone, Client* client, Entity* caster, LuaSpell* lua_spell, LuaSpell* conflictSpell) +{ + LogWrite(SPELL__DEBUG, 1, "Spell", "%s cannot stack spell %s, conflicts with %s.", caster->GetName(), lua_spell->spell->GetName(), conflictSpell->spell->GetName()); + zone->SendSpellFailedPacket(client, SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL); + lua_spell->caster->GetZone()->GetSpellProcess()->RemoveSpellScriptTimerBySpell(lua_spell); + DeleteSpell(lua_spell); +} + +void SpellProcess::AddActiveSpell(LuaSpell* spell) +{ + if(!active_spells.count(spell)) + active_spells.Add(spell); +} + +void SpellProcess::AddSelfAndPet(LuaSpell* spell, Spawn* self, bool onlyPet) +{ + if(!onlyPet) + AddLuaSpellTarget(spell, self->GetID(), false); + + if(self->IsEntity() && ((Entity*)self)->HasPet() && ((Entity*)self)->GetPet()) + AddLuaSpellTarget(spell, ((Entity*)self)->GetPet()->GetID(), false); + if(self->IsEntity() && ((Entity*)self)->HasPet() && ((Entity*)self)->GetCharmedPet()) + AddLuaSpellTarget(spell, ((Entity*)self)->GetCharmedPet()->GetID(), false); + if(!onlyPet && self->IsEntity() && ((Entity*)self)->IsPet() && ((Entity*)self)->GetOwner()) + AddLuaSpellTarget(spell, ((Entity*)self)->GetOwner()->GetID(), false); +} + +void SpellProcess::AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bool onlyPet) +{ + if(!caster->IsPlayer()) + return; + + Player* player = ((Player*)caster); + int32 charID = player->GetCharacterID(); + + if(player->HasPet() && player->GetPet()) + spell->char_id_targets.insert(make_pair(charID, player->GetPet()->GetPetType())); + if(player->HasPet() && player->GetCharmedPet()) + spell->char_id_targets.insert(make_pair(charID, player->GetPet()->GetPetType())); + if(!onlyPet) + spell->char_id_targets.insert(make_pair(charID, 0x00)); +} + +void SpellProcess::DeleteActiveSpell(LuaSpell* spell) { + active_spells.Remove(spell, true); +} + +bool SpellProcess::AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_spell_targets) { + bool ret = false; + if(lock_spell_targets) + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + + if(std::find(lua_spell->targets.begin(), lua_spell->targets.end(), id) != lua_spell->targets.end()) { + ret = true; + } + else if(std::find(lua_spell->removed_targets.begin(), lua_spell->removed_targets.end(), id) == lua_spell->removed_targets.end()) { + lua_spell->targets.push_back(id); + ret = true; + } + + if(lock_spell_targets) + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} \ No newline at end of file diff --git a/source/WorldServer/SpellProcess.h b/source/WorldServer/SpellProcess.h new file mode 100644 index 0000000..cb398be --- /dev/null +++ b/source/WorldServer/SpellProcess.h @@ -0,0 +1,423 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_SPELL_PROCESS__ +#define __EQ2_SPELL_PROCESS__ +#include +#include + +#include "client.h" +#include "Spells.h" +#include "zoneserver.h" +#include "LuaInterface.h" +#include "MutexMap.h" +#include "MutexList.h" +#include "World.h" +#include "HeroicOp/HeroicOp.h" + +#define MODIFY_HEALTH 1 +#define MODIFY_FOCUS 2 +#define MODIFY_DEFENSE 3 +#define MODIFY_POWER 4 +#define MODIFY_SPEED 5 +#define MODIFY_INT 6 +#define MODIFY_WIS 7 +#define MODIFY_STR 8 +#define MODIFY_AGI 9 +#define MODIFY_STA 10 +#define MODIFY_COLD_RESIST 11 +#define MODIFY_HEAT_RESIST 12 +#define MODIFY_DISEASE_RESIST 13 +#define MODIFY_POISON_RESIST 14 +#define MODIFY_MAGIC_RESIST 15 +#define MODIFY_MENTAL_RESIST 16 +#define MODIFY_DIVINE_RESIST 17 +#define MODIFY_ATTACK 18 +#define MODIFY_MITIGATION 19 +#define MODIFY_AVOIDANCE 20 +#define MODIFY_CONCENTRATION 21 +#define MODIFY_EXP 22 +#define MODIFY_FACTION 23 +#define CHANGE_SIZE 24 +#define CHANGE_RACE 25 +#define CHANGE_LOCATION 26 +#define CHANGE_ZONE 27 +#define CHANGE_PREFIX_TITLE 28 +#define CHANGE_DEITY 29 +#define CHANGE_LAST_NAME 30 +#define MODIFY_HASTE 31 +#define MODIFY_SKILL 32 +#define CHANGE_TARGET 33 +#define CHANGE_LEVEL 34 +#define MODIFY_SPELL_CAST_TIME 35 +#define MODIFY_SPELL_POWER_REQ 36 +#define MODIFY_SPELL_HEALTH_REQ 37 +#define MODIFY_SPELL_RECOVERY 38 +#define MODIFY_SPELL_RECAST_TIME 39 +#define MODIFY_SPELL_RADIUS 40 +#define MODIFY_SPELL_AOE_TARGETS 41 +#define MODIFY_SPELL_RANGE 42 +#define MODIFY_SPELL_DURATION 43 +#define MODIFY_SPELL_RESISTIBILITY 44 +#define MODIFY_DAMAGE 45 +#define MODIFY_DELAY 46 +#define MODIFY_TRADESKILL_EXP 47 +#define ADD_MOUNT 48 +#define REMOVE_MOUNT 49 +#define MODIFY_SPELL_CRIT_CHANCE 50 +#define MODIFY_CRIT_CHANCE 51 +#define SUMMON_ITEM 52 +#define MODIFY_JUMP 53 +#define MODIFY_FALL_SPEED 54 +#define INFLICT_DAMAGE 55 +#define ADD_DOT 56 +#define REMOVE_DOT 57 +#define HEAL_TARGET 58 +#define HEAL_AOE 59 +#define INFLICT_AOE_DAMAGE 60 +#define HEAL_GROUP_AOE 61 +#define ADD_AOE_DOT 62 +#define REMOVE_AOE_DOT 63 +#define ADD_HOT 64 +#define REMOVE_HOT 65 +#define MODIFY_AGGRO_RANGE 66 +#define BLIND_TARGET 67 +#define UNBLIND_TARGET 68 +#define KILL_TARGET 69 +#define RESURRECT_TARGET 70 +#define CHANGE_SUFFIX_TITLE 71 +#define SUMMON_PET 72 +#define MODIFY_HATE 73 +#define ADD_REACTIVE_HEAL 74 +#define MODIFY_POWER_REGEN 75 +#define MODIFY_HP_REGEN 76 +#define FEIGN_DEATH 77 +#define MODIFY_VISION 78 +#define INVISIBILITY 79 +#define CHARM_TARGET 80 +#define MODIFY_TRADESKILL_DURABILITY 81 +#define MODIFY_TRADESKILL_PROGRESS 82 + + +#define ACTIVE_SPELL_NORMAL 0 +#define ACTIVE_SPELL_ADD 1 +#define ACTIVE_SPELL_REMOVE 2 + +#define GET_VALUE_BAD_VALUE 0xFFFFFFFF +struct InterruptStruct{ + Spawn* interrupted; + LuaSpell* spell; + int16 error_code; +}; +struct CastTimer{ + Client* caster; + int32 target_id; + EntityCommand* entity_command; + LuaSpell* spell; + Timer* timer; + ZoneServer* zone; + bool delete_timer; + bool in_heroic_opp; +}; +struct CastSpell{ + Entity* caster; + Spawn* target; + int32 spell_id; + ZoneServer* zone; +}; +struct RecastTimer{ + Entity* caster; + Client* client; + Spell* spell; + Timer* timer; + int32 spell_id; + int32 linked_timer; + int32 type_group_spell_id; +}; + +/// Handles all spell casts for a zone, only 1 SpellProcess per zone +class SpellProcess{ +public: + SpellProcess(); + ~SpellProcess(); + + /// Remove dead pointers for casters when the Spawn is deconstructed + void RemoveCaster(Spawn* caster); + + /// Remove all spells from the SpellProcess + void RemoveAllSpells(bool reload_spells = false); + + /// Main loop, handles everything (interupts, cast time, recast, ...) + void Process(); + + /// Interrupts the caster (creates the InterruptStruct and adds it to a list) + /// Entity being interrupted + /// Spawn that interrupted the caster + /// The error code + /// Bool if the spell was cancelled not interrupted + void Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel = false, bool from_movement = false); + + /// Does all the checks and actually casts the spell + /// The current ZoneServer + /// The Spell to cast + /// The Entity casting the spell + /// The target(Spawn) of the spell + /// ??? not currently used + /// Is this a harvest spell? + void ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false, LuaSpell* customSpell = 0, int16 custom_cast_time = 0, bool in_heroic_opp = false); + + /// Cast an EntityCommand (right click menu) + /// The current ZoneServer + /// the EntityCommand to cast + /// The Entity casting the EntityCommand + /// The target(Spawn*) of the EntityCommand + /// ??? not currently used + void ProcessEntityCommand(ZoneServer* zone, EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock = true, bool in_heroic_opp = false); + + /// Checks to see if the caster has enough power and takes it + /// LuaSpell to check and take power for (LuaSpell contains the caster) + /// True if caster had enough power + bool TakePower(LuaSpell* spell, int32 custom_power_req = 0); + + /// Check to see if the caster has enough power to cast the spell + /// LuaSpell to check (LuaSpell contains the caster) + /// True if the caster has enough power + bool CheckPower(LuaSpell* spell); + + /// Check to see if the caster has enough hp and take it + /// LuaSpell to check and take hp for (LuaSpell contains the caster) + /// True if the caster had enough hp + bool TakeHP(LuaSpell* spell, int32 custom_hp_req = 0); + + /// Check to see if the caster has enough hp to cast the spell + /// LuaSpell to check (LuaSpell contains the caster) + /// True if the caster had enough hp + bool CheckHP(LuaSpell* spell); + + /// Check to see if the caster has enough concentration available to cast the spell + /// LuaSpell to check (LuaSpell contains the caster) + /// True if the caster has enough concentration + bool CheckConcentration(LuaSpell* spell); + + bool CheckSavagery(LuaSpell* spell); + bool TakeSavagery(LuaSpell* spell); + bool CheckDissonance(LuaSpell* spell); + bool AddDissonance(LuaSpell* spell); + + /// Check to see if the caster has enough concentration available and add to the casters concentration + /// LuaSpell to check (LuaSpell contains the caster) + /// True of the caster had enough concentration + bool AddConcentration(LuaSpell* spell); + + /// Cast the spell, calls ProcessSpell for the given LuaSpell, as well as sends the messages for the spells and calls the casted on function in the targets spawn script + /// LuaSpell to cast + /// Is this a passive spell being cast? + /// True if the spell was casted + bool CastProcessedSpell(LuaSpell* spell, bool passive = false, bool in_heroic_opp = false); + + /// Cast the EntityCommand, calls ProcessEntityCommand for the given EntityCommand, as well as sends the messages for the command and calls the casted on function in the targets spawn script + /// EntityCommand to cast + /// Client casting the entity command + /// True if the spell was casted + bool CastProcessedEntityCommand(EntityCommand* entity_command, Client* client, Spawn* target, bool in_heroic_opp = false); + + /// Sends the start cast packet for the given client + /// LuaSpell being cast + /// The client casting the spell + void SendStartCast(LuaSpell* spell, Client* client); + + /// Send finish cast packet and take power/hp or add conc, also checks for quest updates + /// LuaSpell that just finished casting + /// Client that just finished casting, null if not a player + void SendFinishedCast(LuaSpell* spell, Client* client); + + /// Locks all the spells for the given client (shades them all gray) + /// Client to lock the spells for + void LockAllSpells(Client* client); + + /// Unlock all the spells for the given client + /// Client to unlock the spells for + void UnlockAllSpells(Client* client, Spell* exception = 0); + + /// Unlock a single spell for the given client + /// The client to unlock the spell for + /// The spell to unlock + void UnlockSpell(Client* client, Spell* spell); + + /// Remove the given spell for the given caster from the SpellProcess + /// The spawn to remove the spell for + /// The spell to remove + bool DeleteCasterSpell(Spawn* caster, Spell* spell, string reason = ""); + + /// Remove the given spell from the ZpellProcess + /// LuaSpell to remove + bool DeleteCasterSpell(LuaSpell* spell, string reason="", bool removing_all_spells = false, Spawn* remove_target = nullptr, bool zone_shutting_down = false, bool shared_lock_spell = true); + + /// Interrupt the spell + /// InterruptStruct that contains all the info + void CheckInterrupt(InterruptStruct* interrupt); + + /// Removes the timers for the given spawn + /// Spawn to remove the timers for + /// Remove all timers (cast, recast, active, queue, interrupted)? If false only cast timers are removed + void RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all = false, bool delete_recast = false, bool call_expire_function = true, bool lock_spell_process = false); + + /// Sets the recast timer for the spell + /// The spell to set the recast for + /// The entity to set the recast for + /// Change the recast timer of the spell + /// Set the recast on all other spells the player has with the same timer + void CheckRecast(Spell* spell, Entity* caster, float timer_override = 0, bool check_linked_timers = true); + + /// Add a spell to the queue for the player + /// Spell to add + /// Entity's queue to add the spell to, if not a player function does nothing + void AddSpellToQueue(Spell* spell, Entity* caster); + + /// Removes a spell from the queue for the player + /// Spell to remove from the queue + /// Entity's queue to remove the spell from, if not a player function does nothing + void RemoveSpellFromQueue(Spell* spell, Entity* caster, bool send_update = true); + + /// Clear the queue, or clear only hostile spells from the queue + /// Entity to clear the queue for, if not player function does nothing + /// Set to true to only remove hostile spells, default is false + void RemoveSpellFromQueue(Entity* caster, bool hostile_only = false); + + /// Check the given enities queue for the spell, if found remove, if not found add + /// Spell to check for + /// Entity's queue to check, if not player function does nothing + bool CheckSpellQueue(Spell* spell, Entity* caster); + + /// Checks to see if the entity can cast the spell + /// The spell being cast + /// The entity casting the spell + bool IsReady(Spell* spell, Entity* caster); + + /// Send the spell book update packet to the given client + /// Client to send the packet to + void SendSpellBookUpdate(Client* client); + + /// Gets the target of the currently casting spell for the given entity + /// Entity whos spell we are checking + /// Spawn* - the spells target + Spawn* GetSpellTarget(Entity* caster); + + /// Gets the currently casting spell for the given entity + /// Entity to get the spell for + /// Spell* for the currently casting spell + Spell* GetSpell(Entity* caster); + + /// Gets the currently casting LuaSpell for the given entity + /// Entity to get the LuaSpell for + /// LuaSpell* for the currently casting spell + LuaSpell* GetLuaSpell(Entity* caster); + + /// Gets the targets for the spell and adds them to the LuaSpell targets array + /// LuaSpell to get the targets for + static void GetSpellTargets(LuaSpell* luaspell); + + static bool GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell* luaspell, bool bypassSpellChecks=false, bool bypassRangeChecks=false); + + /// Gets targets for a true aoe spell (not an encounter ae) and adds them to the LuaSpell targets array + /// LuaSpell to get the targets for + static void GetSpellTargetsTrueAOE(LuaSpell* luaspell); + + /// Applies or removes passive spells, bypasses the spell queue and treats the spell as an insta cast spell + /// The passive spell to apply or remove + /// The Entity to apply or remove the passive spell to + /// Tells the function to remove the spell effects of this passive, default is false + bool CastPassives(Spell* spell, Entity* caster, bool remove = false); + bool CastInstant(Spell* spell, Entity* caster, Entity* target, bool remove = false, bool passive=false); + + /// Adds a spell script timer to the list + /// Timer to add + void AddSpellScriptTimer(SpellScriptTimer* timer); + + /// Removes a spell script timer from the list + /// Timer to remove + void RemoveSpellScriptTimer(SpellScriptTimer* timer, bool locked=false); + void RemoveSpellScriptTimerBySpell(LuaSpell* spell, bool clearPendingDeletes=true); + + /// Checks the spell script timers + void CheckSpellScriptTimers(); + + /// Checks to see if the list has the spell + bool SpellScriptTimersHasSpell(LuaSpell* spell); + std::string SpellScriptTimerCustomFunction(LuaSpell* spell); + + void ClearSpellScriptTimerList(); + + MutexList* GetActiveSpells() { return &active_spells; } + + void RemoveTargetFromSpell(LuaSpell* spell, Spawn* target, bool remove_caster = false); + void CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete = true, bool removing_all_spells = false); + void RemoveTargetList(LuaSpell* spell); + + /// Adds a solo HO to the SpellProcess + /// The client who is starting the HO + /// The HO that is being started + bool AddHO(Client* client, HeroicOP* ho); + + /// Adds a group HO to the SpellProcess + /// ID of the group that is starting the HO + /// The HO that is being started + bool AddHO(int32 group_id, HeroicOP* ho); + + /// Stops the HO that targets the given spawn + /// ID of the spawn targeted by the HO we want to stop + void KillHOBySpawnID(int32 spawn_id); + + void AddSpellCancel(LuaSpell* spell); + + void DeleteSpell(LuaSpell* spell); + + void SpellCannotStack(ZoneServer* zone, Client* client, Entity* caster, LuaSpell* lua_spell, LuaSpell* conflictSpell); + + bool ProcessSpell(LuaSpell* spell, bool first_cast = true, const char* function = 0, SpellScriptTimer* timer = 0, bool all_targets = false); + std::string ApplyLuaFunction(LuaSpell* spell, bool first_cast, const char* function, SpellScriptTimer* timer, Spawn* altTarget = 0); + + void AddActiveSpell(LuaSpell* spell); + static void AddSelfAndPet(LuaSpell* spell, Spawn* self, bool onlyPet=false); + static void AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bool onlyPet=false); + void DeleteActiveSpell(LuaSpell* spell); + static bool AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_spell_targets = true); + mutable std::shared_mutex MSpellProcess; +private: + MutexMap spell_que; + MutexList active_spells; + MutexList cast_timers; + MutexListinterrupt_list; + MutexList recast_timers; + int32 last_checked_time; + vector m_spellScriptList; + Mutex MSpellScriptTimers; + map*> remove_target_list; + Mutex MRemoveTargetList; + vector SpellCancelList; + Mutex MSpellCancelList; + + Mutex MSoloHO; + Mutex MGroupHO; + map m_soloHO; + map m_groupHO; +}; + +#endif + diff --git a/source/WorldServer/Spells.cpp b/source/WorldServer/Spells.cpp new file mode 100644 index 0000000..3b4cbc1 --- /dev/null +++ b/source/WorldServer/Spells.cpp @@ -0,0 +1,2453 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Spells.h" +#include "../common/ConfigReader.h" +#include "WorldDatabase.h" +#include "../common/Log.h" +#include "Traits/Traits.h" +#include "AltAdvancement/AltAdvancement.h" +#include +#include "LuaInterface.h" +#include "Rules/Rules.h" + +#include + +extern ConfigReader configReader; +extern WorldDatabase database; +extern MasterTraitList master_trait_list; +extern MasterAAList master_aa_list; +extern MasterSpellList master_spell_list; +extern LuaInterface* lua_interface; +extern MasterSkillList master_skill_list; +extern RuleManager rule_manager; + +Spell::Spell(){ + spell = new SpellData; + heal_spell = false; + buff_spell = false; + damage_spell = false; + control_spell = false; + offense_spell = false; + copied_spell = false; +} + +Spell::Spell(Spell* host_spell, bool unique_spell) +{ + std::shared_lock lock(host_spell->MSpellInfo); + copied_spell = true; + + spell = new SpellData; + + if (host_spell->GetSpellData()) + { + if(!unique_spell) { + spell->id = host_spell->GetSpellData()->id; + } + else { + // try inheriting an existing custom spell id, otherwise obtain the new highest number on the spell list + int32 tmpid = lua_interface->GetFreeCustomSpellID(); + if (tmpid) + spell->id = tmpid; + else + { + spell->id = master_spell_list.GetNewMaxSpellID(); + } + } + + spell->inherited_spell_id = host_spell->GetSpellData()->inherited_spell_id; + + spell->affect_only_group_members = host_spell->GetSpellData()->affect_only_group_members; + spell->call_frequency = host_spell->GetSpellData()->call_frequency; + spell->can_effect_raid = host_spell->GetSpellData()->can_effect_raid; + spell->casting_flags = host_spell->GetSpellData()->casting_flags; + spell->cast_time = host_spell->GetSpellData()->cast_time; + spell->orig_cast_time = host_spell->GetSpellData()->orig_cast_time; + spell->cast_type = host_spell->GetSpellData()->cast_type; + spell->cast_while_moving = host_spell->GetSpellData()->cast_while_moving; + spell->class_skill = host_spell->GetSpellData()->class_skill; + spell->min_class_skill_req = host_spell->GetSpellData()->min_class_skill_req; + spell->control_effect_type = host_spell->GetSpellData()->control_effect_type; + spell->description = EQ2_16BitString(host_spell->GetSpellData()->description); + spell->det_type = host_spell->GetSpellData()->det_type; + spell->display_spell_tier = host_spell->GetSpellData()->display_spell_tier; + + spell->dissonance_req = host_spell->GetSpellData()->dissonance_req; + spell->dissonance_req_percent = host_spell->GetSpellData()->dissonance_req_percent; + spell->dissonance_upkeep = host_spell->GetSpellData()->dissonance_upkeep; + spell->duration1 = host_spell->GetSpellData()->duration1; + spell->duration2 = host_spell->GetSpellData()->duration2; + spell->duration_until_cancel = host_spell->GetSpellData()->duration_until_cancel; + spell->effect_message = string(host_spell->GetSpellData()->effect_message); + spell->fade_message = string(host_spell->GetSpellData()->fade_message); + spell->fade_message_others = string(host_spell->GetSpellData()->fade_message_others); + + spell->friendly_spell = host_spell->GetSpellData()->friendly_spell; + spell->group_spell = host_spell->GetSpellData()->group_spell; + + spell->hit_bonus = host_spell->GetSpellData()->hit_bonus; + + spell->hp_req = host_spell->GetSpellData()->hp_req; + spell->hp_req_percent = host_spell->GetSpellData()->hp_req_percent; + spell->hp_upkeep = host_spell->GetSpellData()->hp_upkeep; + + spell->icon = host_spell->GetSpellData()->icon; + spell->icon_backdrop = host_spell->GetSpellData()->icon_backdrop; + + spell->icon_heroic_op = host_spell->GetSpellData()->icon_heroic_op; + + spell->incurable = host_spell->GetSpellData()->incurable; + spell->interruptable = host_spell->GetSpellData()->interruptable; + spell->is_aa = host_spell->GetSpellData()->is_aa; + + spell->is_active = host_spell->GetSpellData()->is_active; + spell->linked_timer = host_spell->GetSpellData()->linked_timer; + spell->lua_script = string(host_spell->GetSpellData()->lua_script); + + spell->mastery_skill = host_spell->GetSpellData()->mastery_skill; + spell->max_aoe_targets = host_spell->GetSpellData()->max_aoe_targets; + + spell->min_range = host_spell->GetSpellData()->min_range; + spell->name = EQ2_8BitString(host_spell->GetSpellData()->name); + spell->not_maintained = host_spell->GetSpellData()->not_maintained; + spell->num_levels = host_spell->GetSpellData()->num_levels; + spell->persist_through_death = host_spell->GetSpellData()->persist_through_death; + spell->power_by_level = host_spell->GetSpellData()->power_by_level; + spell->power_req = host_spell->GetSpellData()->power_req; + spell->power_req_percent = host_spell->GetSpellData()->power_req_percent; + spell->power_upkeep = host_spell->GetSpellData()->power_upkeep; + spell->radius = host_spell->GetSpellData()->radius; + spell->range = host_spell->GetSpellData()->range; + spell->recast = host_spell->GetSpellData()->recast; + spell->recovery = host_spell->GetSpellData()->recovery; + spell->req_concentration = host_spell->GetSpellData()->req_concentration; + spell->resistibility = host_spell->GetSpellData()->resistibility; + spell->savagery_req = host_spell->GetSpellData()->savagery_req; + spell->savagery_req_percent = host_spell->GetSpellData()->savagery_req_percent; + spell->savagery_upkeep = host_spell->GetSpellData()->savagery_upkeep; + spell->savage_bar = host_spell->GetSpellData()->savage_bar; + spell->savage_bar_slot = host_spell->GetSpellData()->savage_bar_slot; + spell->soe_spell_crc = host_spell->GetSpellData()->soe_spell_crc; + spell->spell_book_type = host_spell->GetSpellData()->spell_book_type; + spell->spell_name_crc = host_spell->GetSpellData()->spell_name_crc; + spell->spell_type = host_spell->GetSpellData()->spell_type; + spell->spell_visual = host_spell->GetSpellData()->spell_visual; + spell->success_message = string(host_spell->GetSpellData()->success_message); + spell->target_type = host_spell->GetSpellData()->target_type; + spell->tier = host_spell->GetSpellData()->tier; + spell->ts_loc_index = host_spell->GetSpellData()->ts_loc_index; + spell->type = host_spell->GetSpellData()->type; + spell->type_group_spell_id = host_spell->GetSpellData()->type_group_spell_id; + spell->can_fizzle = host_spell->GetSpellData()->can_fizzle; + } + + heal_spell = host_spell->IsHealSpell(); + buff_spell = host_spell->IsBuffSpell(); + damage_spell = host_spell->IsDamageSpell();; + control_spell = host_spell->IsControlSpell(); + offense_spell = host_spell->IsOffenseSpell(); + + std::vector::iterator itr; + for (itr = host_spell->levels.begin(); itr != host_spell->levels.end(); itr++) + { + LevelArray* lvlArray = *itr; + AddSpellLevel(lvlArray->adventure_class, lvlArray->tradeskill_class, lvlArray->spell_level); + } + + std::vector::iterator sdeitr; + for (sdeitr = host_spell->effects.begin(); sdeitr != host_spell->effects.end(); sdeitr++) + { + SpellDisplayEffect* sde = *sdeitr; + AddSpellEffect(sde->percentage, sde->subbullet, sde->description); + } + + vector::iterator luaitr; + for (luaitr = host_spell->lua_data.begin(); luaitr != host_spell->lua_data.end(); luaitr++) { + LUAData* data = *luaitr; + AddSpellLuaData(data->type, data->int_value, data->int_value2, data->float_value, data->float_value2, data->bool_value, string(data->string_value), string(data->string_value2), string(data->string_helper)); + } +} + +Spell::Spell(SpellData* in_spell){ + spell = in_spell; + heal_spell = false; + buff_spell = false; + damage_spell = false; + control_spell = false; + offense_spell = false; + copied_spell = false; +} + +Spell::~Spell(){ + vector::iterator itr1; + for(itr1=levels.begin();itr1!=levels.end();itr1++) { + safe_delete(*itr1); + } + vector::iterator itr2; + for(itr2=effects.begin();itr2!=effects.end();itr2++) { + safe_delete(*itr2); + } + vector::iterator itr3; + for(itr3=lua_data.begin();itr3!=lua_data.end();itr3++) { + safe_delete(*itr3); + } + safe_delete(spell); +} + +void Spell::AddSpellLuaData(int8 type, int int_value, int int_value2, float float_value, float float_value2, bool bool_value, string string_value, string string_value2, string helper){ + std::unique_lock lock(MSpellInfo); + LUAData* data = new LUAData; + data->type = type; + data->int_value = int_value; + data->int_value2 = int_value2; + data->float_value = float_value; + data->float_value2 = float_value2; + data->bool_value = bool_value; + data->string_value = string_value; + data->string_value2 = string_value2; + data->string_helper = helper; + + lua_data.push_back(data); +} + +void Spell::AddSpellLuaDataInt(int value, int value2, string helper) { + std::unique_lock lock(MSpellInfo); + LUAData *data = new LUAData; + + data->type = 0; + data->int_value = value; + data->int_value2 = value2; + data->float_value = 0; + data->float_value2 = 0; + data->bool_value = false; + data->string_helper = helper; + + lua_data.push_back(data); +} + +void Spell::AddSpellLuaDataFloat(float value, float value2, string helper) { + std::unique_lock lock(MSpellInfo); + LUAData *data = new LUAData; + + data->type = 1; + data->int_value = 0; + data->int_value2 = 0; + data->float_value = value; + data->float_value2 = value2; + data->bool_value = false; + data->string_helper = helper; + + lua_data.push_back(data); +} + +void Spell::AddSpellLuaDataBool(bool value, string helper) { + std::unique_lock lock(MSpellInfo); + LUAData *data = new LUAData; + + data->type = 2; + data->int_value = 0; + data->float_value = 0; + data->bool_value = value; + data->string_helper = helper; + + lua_data.push_back(data); +} + +void Spell::AddSpellLuaDataString(string value, string value2,string helper) { + std::unique_lock lock(MSpellInfo); + LUAData *data = new LUAData; + + data->type = 3; + data->int_value = 0; + data->int_value2 = 0; + data->float_value = 0; + data->float_value2 = 0; + data->bool_value = false; + data->string_value = value; + data->string_value2 = value2; + data->string_helper = helper; + + lua_data.push_back(data); +} + +int16 Spell::GetLevelRequired(Player* player){ + int16 ret = 0xFFFF; + if(!player) + return ret; + LevelArray* level = 0; + vector::iterator itr; + for(itr = levels.begin(); itr != levels.end(); itr++){ + level = *itr; + if(level && level->adventure_class == player->GetAdventureClass()){ + ret = level->spell_level/10; + break; + } + } + return ret; +} + +void Spell::SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, Client* client, bool display_tier) { + int8 current_tier = client->GetPlayer()->GetSpellTier(spell->id); + Spell* next_spell; + SpellData* spell2; + if (data->maxRank > current_tier) { + next_spell = master_spell_list.GetSpell(spell->id, current_tier + 1); + spell2 = next_spell->GetSpellData(); + } + SpellDisplayEffect* effect2; + + //next_spell->effects[1]->description; + + + int xxx = 0; + + int16 hp_req = 0; + int16 power_req = 0; + + if (current_tier > 0) { + packet->setSubstructDataByName("spell_info", "current_id", spell->id); + packet->setSubstructDataByName("spell_info", "current_icon", spell->icon); + packet->setSubstructDataByName("spell_info", "current_icon2", spell->icon_heroic_op); // fix struct element name eventually + packet->setSubstructDataByName("spell_info", "current_icontype", spell->icon_backdrop); // fix struct element name eventually + + if (packet->GetVersion() >= 63119) { + packet->setSubstructDataByName("spell_info", "current_version", 0x04); + packet->setSubstructDataByName("spell_info", "current_sub_version", 0x24); + } + else if (packet->GetVersion() >= 58617) { + packet->setSubstructDataByName("spell_info", "current_version", 0x03); + packet->setSubstructDataByName("spell_info", "current_sub_version", 0x131A); + } + else { + packet->setSubstructDataByName("spell_info", "current_version", 0x00); + packet->setSubstructDataByName("spell_info", "current_sub_version", 0xD9); + } + + packet->setSubstructDataByName("spell_info", "current_type", spell->type); + packet->setSubstructDataByName("spell_info", "unknown_MJ1d", 1); //63119 test + packet->setSubstructDataByName("spell_info", "current_class_skill", spell->class_skill); + packet->setSubstructDataByName("spell_info", "current_mastery_skill", spell->mastery_skill); + packet->setSubstructDataByName("spell_info", "duration_flag", spell->duration_until_cancel); + + + if (client && spell->type != 2) { + sint8 spell_text_color = client->GetPlayer()->GetArrowColor(GetLevelRequired(client->GetPlayer())); + if (spell_text_color != ARROW_COLOR_WHITE && spell_text_color != ARROW_COLOR_RED && spell_text_color != ARROW_COLOR_GRAY) + spell_text_color = ARROW_COLOR_WHITE; + spell_text_color -= 6; + if (spell_text_color < 0) + spell_text_color *= -1; + packet->setSubstructDataByName("spell_info", "current_spell_text_color", (xxx == 1 ? 0xFFFFFFFF : spell_text_color)); + } + else { + packet->setSubstructDataByName("spell_info", "current_spell_text_color", (xxx == 1 ? 0xFFFFFFFF : 3)); + } + packet->setSubstructDataByName("spell_info", "current_spell_text_color", (xxx == 1 ? 0xFFFFFFFF : 3)); + packet->setSubstructDataByName("spell_info", "current_tier", (spell->tier)); + + if (spell->type != 2) { + packet->setArrayLengthByName("current_num_levels", 0); + for (int32 i = 0; i < levels.size(); i++) { + // revisit when implementing AA and use AppendLevelInformation instead (this struct doesn't even exist yet for KoS) + packet->setArrayDataByName("spell_info_aa_adventure_class", levels[i]->adventure_class, i); + packet->setArrayDataByName("spell_info_aa_tradeskill_class", levels[i]->tradeskill_class, i); + packet->setArrayDataByName("spell_info_aa_spell_level", levels[i]->spell_level, i); + } + } + //packet->setSubstructDataByName("spell_info","unknown9", 20); + + if (client) { + hp_req = GetHPRequired(client->GetPlayer()); + power_req = GetPowerRequired(client->GetPlayer()); + + // might need version checks around these? + if (client->GetVersion() >= 1193) + { + int16 savagery_req = GetSavageryRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "current_savagery_req", savagery_req); + packet->setSubstructDataByName("spell_info", "current_savagery_upkeep", spell->savagery_upkeep); + } + if (client->GetVersion() >= 57048) + { + int16 dissonance_req = GetDissonanceRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "dissonance_req", dissonance_req); + packet->setSubstructDataByName("spell_info", "dissonance_upkeep", spell->dissonance_upkeep); + } + } + packet->setSubstructDataByName("spell_info", "current_health_req", hp_req); + packet->setSubstructDataByName("spell_info", "current_health_upkeep", spell->hp_upkeep); + packet->setSubstructDataByName("spell_info", "current_power_req", power_req); + packet->setSubstructDataByName("spell_info", "current_power_upkeep", spell->power_upkeep); + packet->setSubstructDataByName("spell_info", "current_req_concentration", spell->req_concentration); + //unknown1 savagery??? + packet->setSubstructDataByName("spell_info", "current_cast_time", spell->cast_time); + packet->setSubstructDataByName("spell_info", "current_recovery", spell->recovery); + packet->setSubstructDataByName("spell_info", "current_recast", spell->recast); + packet->setSubstructDataByName("spell_info", "current_radius", spell->radius); + packet->setSubstructDataByName("spell_info", "current_max_aoe_targets", spell->max_aoe_targets); + packet->setSubstructDataByName("spell_info", "current_friendly_spell", spell->friendly_spell); + // rumber of reagents with array + + + + + + + + + + packet->setSubstructArrayLengthByName("spell_info", "current_num_effects", (xxx == 1 ? 0 : effects.size())); + for (int32 i = 0; i < effects.size(); i++) { + packet->setArrayDataByName("current_subbulletflag", effects[i]->subbullet, i); + string effect_message; + if (effects[i]->description.length() > 0) { + effect_message = effects[i]->description; + if (effect_message.find("%LM") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LM"); + int data_index = stoi(effect_message.substr(string_index + 3, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + string strValue = to_string(value); + strValue.erase(strValue.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%LM"), 5, strValue); + } + // Magic damage min + if (effect_message.find("%DML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DML"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%DML"), 6, damage); + } + // Magic damage max + if (effect_message.find("%DMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DMH"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%DMH"), 6, damage); + } + // level based Magic damage min + if (effect_message.find("%LDML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDML"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDML"), 7, damage); + } + // level based Magic damage max + if (effect_message.find("%LDMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDMH"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDMH"), 7, damage); + } + //GetZone()->SimpleMessage(CHANNEL_COLOR_SPELL_EFFECT, effect_message.c_str(), victim, 50); + packet->setArrayDataByName("current_effect", effect_message.c_str(), i); + } + packet->setArrayDataByName("current_percentage", effects[i]->percentage, i); + } + if (display_tier == true) + packet->setSubstructDataByName("spell_info", "current_display_spell_tier", 1);// spell2->display_spell_tier); + else + packet->setSubstructDataByName("spell_info", "current_display_spell_tier", 1);// 0); + + packet->setSubstructDataByName("spell_info", "current_unknown_1", 1);// 0); + //unkown1_1 + packet->setSubstructDataByName("spell_info", "current_minimum_range", spell->min_range); + packet->setSubstructDataByName("spell_info", "current_range", spell->range); + packet->setSubstructDataByName("spell_info", "current_duration_1", spell->duration1); + packet->setSubstructDataByName("spell_info", "current_duration_2", spell->duration2); + + packet->setSubstructDataByName("spell_info", "current_duration_flag", spell->duration_until_cancel); + packet->setSubstructDataByName("spell_info", "current_target", spell->target_type); + + + + + + packet->setSubstructDataByName("spell_info", "current_can_effect_raid", spell->can_effect_raid); + packet->setSubstructDataByName("spell_info", "current_affect_only_group_members", spell->affect_only_group_members); + packet->setSubstructDataByName("spell_info", "current_group_spell", spell->group_spell); + packet->setSubstructDataByName("spell_info", "current_resistibility", spell->resistibility); + packet->setSubstructDataByName("spell_info", "current_name", &(spell->name)); + packet->setSubstructDataByName("spell_info", "current_description", &(spell->description)); + + } + + if (current_tier + 1 <= data->maxRank) { + packet->setSubstructDataByName("spell_info", "next_id", spell2->id); + packet->setSubstructDataByName("spell_info", "next_icon", spell2->icon); + packet->setSubstructDataByName("spell_info", "next_icon2", spell2->icon_heroic_op); // fix struct element name eventually + packet->setSubstructDataByName("spell_info", "next_icontype", spell2->icon_backdrop); // fix struct element name eventually + + if (packet->GetVersion() >= 63119) { + packet->setSubstructDataByName("spell_info", "next_aa_spell_info2", "version", 0x04); + packet->setSubstructDataByName("spell_info", "next_aa_spell_info2", "sub_version", 0x24); + } + else if (packet->GetVersion() >= 58617) { + packet->setSubstructDataByName("spell_info", "next_version", 0x03); + packet->setSubstructDataByName("spell_info", "next_sub_version", 0x131A); + } + else { + packet->setSubstructDataByName("spell_info", "next_version", 0x00); + packet->setSubstructDataByName("spell_info", "next_sub_version", 0xD9); + } + + packet->setSubstructDataByName("spell_info", "next_type", spell2->type); + packet->setSubstructDataByName("spell_info", "next_unknown_MJ1d", 1); //63119 test + packet->setSubstructDataByName("spell_info", "next_class_skill", spell2->class_skill); + packet->setSubstructDataByName("spell_info", "next_mastery_skill", spell2->mastery_skill); + packet->setSubstructDataByName("spell_info", "next_duration_flag", spell2->duration_until_cancel); + if (client && spell->type != 2) { + sint8 spell_text_color = client->GetPlayer()->GetArrowColor(GetLevelRequired(client->GetPlayer())); + if (spell_text_color != ARROW_COLOR_WHITE && spell_text_color != ARROW_COLOR_RED && spell_text_color != ARROW_COLOR_GRAY) + spell_text_color = ARROW_COLOR_WHITE; + spell_text_color -= 6; + if (spell_text_color < 0) + spell_text_color *= -1; + packet->setSubstructDataByName("spell_info", "next_spell_text_color", spell_text_color); + } + else + packet->setSubstructDataByName("spell_info", "next_spell_text_color", 3); + if (spell->type != 2) { + packet->setArrayLengthByName("num_levels", levels.size()); + for (int32 i = 0; i < levels.size(); i++) { + packet->setArrayDataByName("spell_info_aa_adventure_class2", levels[i]->adventure_class, i); + packet->setArrayDataByName("spell_info_aa_tradeskill_class2", levels[i]->tradeskill_class, i); + packet->setArrayDataByName("spell_info_aa_spell_level2", levels[i]->spell_level, i); + } + } + //packet->setSubstructDataByName("spell_info","unknown9", 20); + hp_req = 0; + power_req = 0; + if (client) { + hp_req = GetHPRequired(client->GetPlayer()); + power_req = GetPowerRequired(client->GetPlayer()); + + // might need version checks around these? + if (client->GetVersion() >= 1193) + { + int16 savagery_req = GetSavageryRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "next_savagery_req", savagery_req); + packet->setSubstructDataByName("spell_info", "next_savagery_upkeep", spell->savagery_upkeep); + } + if (client->GetVersion() >= 57048) + { + int16 dissonance_req = GetDissonanceRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "next_dissonance_req", dissonance_req); + packet->setSubstructDataByName("spell_info", "next_dissonance_upkeep", spell->dissonance_upkeep); + } + } + packet->setSubstructDataByName("spell_info", "next_target", spell->target_type); + packet->setSubstructDataByName("spell_info", "next_recovery", spell->recovery); + packet->setSubstructDataByName("spell_info", "next_health_upkeep", spell->hp_upkeep); + packet->setSubstructDataByName("spell_info", "next_health_req", hp_req); + packet->setSubstructDataByName("spell_info", "next_tier", spell->tier); + packet->setSubstructDataByName("spell_info", "next_power_req", power_req); + packet->setSubstructDataByName("spell_info", "next_power_upkeep", spell->power_upkeep); + + packet->setSubstructDataByName("spell_info", "next_cast_time", spell->cast_time); + packet->setSubstructDataByName("spell_info", "next_recast", spell->recast); + packet->setSubstructDataByName("spell_info", "next_radius", spell->radius); + packet->setSubstructDataByName("spell_info", "next_req_concentration", spell->req_concentration); + //packet->setSubstructDataByName("spell_info","req_concentration2", 2); + packet->setSubstructDataByName("spell_info", "next_max_aoe_targets", spell->max_aoe_targets); + packet->setSubstructDataByName("spell_info", "next_friendly_spell", spell->friendly_spell); + packet->setSubstructArrayLengthByName("spell_info", "next_num_effects", next_spell->effects.size()); + for (int32 i = 0; i < next_spell->effects.size(); i++) { + packet->setArrayDataByName("next_subbulletflag", next_spell->effects[i]->subbullet, i); + string effect_message; + if (next_spell->effects[i]->description.length() > 0) { + effect_message = next_spell->effects[i]->description; + if (effect_message.find("%LM") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LM"); + int data_index = stoi(effect_message.substr(string_index + 3, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + string strValue = to_string(value); + strValue.erase(strValue.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%LM"), 5, strValue); + } + // Magic damage min + if (effect_message.find("%DML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DML"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + damage.erase(damage.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%DML"), 6, damage); + } + // Magic damage max + if (effect_message.find("%DMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DMH"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + damage.erase(damage.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%DMH"), 6, damage); + } + // level based Magic damage min + if (effect_message.find("%LDML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDML"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDML"), 7, damage); + } + // level based Magic damage max + if (effect_message.find("%LDMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDMH"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (next_spell->lua_data[data_index]->type == 1) + value = next_spell->lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = next_spell->lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDMH"), 7, damage); + } + //GetZone()->SimpleMessage(CHANNEL_COLOR_SPELL_EFFECT, effect_message.c_str(), victim, 50); + packet->setArrayDataByName("next_effect", effect_message.c_str(), i); + } + + packet->setArrayDataByName("next_percentage", next_spell->effects[i]->percentage, i); + + } + if (display_tier == true) + packet->setSubstructDataByName("spell_info", "next_display_spell_tier", 1);// spell->display_spell_tier); + else + packet->setSubstructDataByName("spell_info", "next_display_spell_tier", 1);//0 + packet->setSubstructDataByName("spell_info", "next_unknown_1", 1);//0 + packet->setSubstructDataByName("spell_info", "next_range", spell2->range); + packet->setSubstructDataByName("spell_info", "next_duration_1", spell2->duration1); + packet->setSubstructDataByName("spell_info", "next_duration_2", spell2->duration2); + + packet->setSubstructDataByName("spell_info", "next_can_effect_raid", spell2->can_effect_raid); + packet->setSubstructDataByName("spell_info", "next_affect_only_group_members", spell2->affect_only_group_members); + packet->setSubstructDataByName("spell_info", "next_group_spell", spell2->group_spell); + packet->setSubstructDataByName("spell_info", "next_resistibility", spell2->resistibility); + packet->setSubstructDataByName("spell_info", "next_name", &(spell2->name)); + packet->setSubstructDataByName("spell_info", "next_description", &(spell2->description)); + + } +} + +void Spell::AppendLevelInformation(PacketStruct* packet) { + if(!packet) + return; + + vector newLevels; + vector * tmpArray = &levels; + if(packet->GetVersion() <= 561) { + for (int32 i = 0; i < tmpArray->size(); i++) { + LevelArray* levelData = tmpArray->at(i); + if((levelData->adventure_class != 255 && levelData->adventure_class > CLASSIC_MAX_ADVENTURE_CLASS) || (levelData->tradeskill_class != 255 && levelData->tradeskill_class > CLASSIC_MAX_TRADESKILL_CLASS)) + continue; + + newLevels.push_back(levelData); + } + tmpArray = &newLevels; + } + + packet->setSubstructArrayLengthByName("spell_info", "num_levels", tmpArray->size()); + for (int32 i = 0; i < tmpArray->size(); i++) { + LevelArray* levelData = tmpArray->at(i); + packet->setArrayDataByName("adventure_class", levelData->adventure_class, i); + packet->setArrayDataByName("tradeskill_class", levelData->tradeskill_class, i); + packet->setArrayDataByName("spell_level", levelData->spell_level, i); + } +} + +sint16 Spell::TranslateClientSpellIcon(int16 version) { + sint16 spell_icon = GetSpellIcon(); + if(version <= 561) { + switch(spell_icon) { + case 772: // tracking + spell_icon = 231; // ?? + break; + case 773: // mining + spell_icon = 33; // OK + break; + case 774: // gathering + spell_icon = 56; // OK + break; + case 775: // fishing + spell_icon = 55; // OK + break; + case 776: // trapping + spell_icon = 46; // OK + break; + case 777: // foresting + spell_icon = 125; // OK + break; + case 778: // collecting + spell_icon = 167; // OK + break; + } + } + return spell_icon; +} + +void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool display_tier) { + packet->setSubstructDataByName("spell_info", "id", spell->id); + packet->setSubstructDataByName("spell_info", "icon", TranslateClientSpellIcon(client->GetVersion())); + packet->setSubstructDataByName("spell_info", "icon2", spell->icon_heroic_op); // fix struct element name eventually + packet->setSubstructDataByName("spell_info", "icontype", spell->icon_backdrop); // fix struct element name eventually + + if (packet->GetVersion() >= 63119) { + packet->setSubstructDataByName("spell_info", "version", 0x04); + packet->setSubstructDataByName("spell_info", "sub_version", 0x24); + } + else if (packet->GetVersion() >= 60114) { + packet->setSubstructDataByName("spell_info", "version", 0x03); + packet->setSubstructDataByName("spell_info", "sub_version", 4890); + } + else if (packet->GetVersion() <= 561) { + packet->setSubstructDataByName("spell_info", "version", 0x10); + packet->setSubstructDataByName("spell_info", "sub_version", 0x0f); + } + else { + packet->setSubstructDataByName("spell_info", "version", 0x11); + packet->setSubstructDataByName("spell_info", "sub_version", 0x14); + } + + packet->setSubstructDataByName("spell_info", "type", spell->type); + packet->setSubstructDataByName("spell_info", "unknown_MJ1d", 1); //63119 test + packet->setSubstructDataByName("spell_info", "class_skill", spell->class_skill); + packet->setSubstructDataByName("spell_info", "min_class_skill_req", spell->min_class_skill_req); + packet->setSubstructDataByName("spell_info", "mastery_skill", spell->mastery_skill); + packet->setSubstructDataByName("spell_info", "duration_flag", spell->duration_until_cancel); + if (client && spell->type != 2) { + sint8 spell_text_color = client->GetPlayer()->GetArrowColor(GetLevelRequired(client->GetPlayer())); + if (spell_text_color != ARROW_COLOR_WHITE && spell_text_color != ARROW_COLOR_RED && spell_text_color != ARROW_COLOR_GRAY) + spell_text_color = ARROW_COLOR_WHITE; + spell_text_color -= 6; + if (spell_text_color < 0) + spell_text_color *= -1; + packet->setSubstructDataByName("spell_info", "spell_text_color", spell_text_color); + } + else + packet->setSubstructDataByName("spell_info", "spell_text_color", 3); + if (spell->type != 2) { + AppendLevelInformation(packet); + } + packet->setSubstructDataByName("spell_info", "unknown9", 20); + int16 hp_req = 0; + int16 power_req = 0; + if (client) { + hp_req = GetHPRequired(client->GetPlayer()); + power_req = GetPowerRequired(client->GetPlayer()); + + // might need version checks around these? + if (client->GetVersion() >= 1193) + { + int16 savagery_req = GetSavageryRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "savagery_req", savagery_req); + packet->setSubstructDataByName("spell_info", "savagery_upkeep", spell->savagery_upkeep); + } + if (client->GetVersion() >= 57048) + { + int16 dissonance_req = GetDissonanceRequired(client->GetPlayer()); // dunno why we need to do this + packet->setSubstructDataByName("spell_info", "dissonance_req", dissonance_req); + packet->setSubstructDataByName("spell_info", "dissonance_upkeep", spell->dissonance_upkeep); + } + } + packet->setSubstructDataByName("spell_info", "target", spell->target_type); + packet->setSubstructDataByName("spell_info", "recovery", spell->recovery); + packet->setSubstructDataByName("spell_info", "health_upkeep", spell->hp_upkeep); + packet->setSubstructDataByName("spell_info", "health_req", hp_req); + packet->setSubstructDataByName("spell_info", "tier", spell->tier); + packet->setSubstructDataByName("spell_info", "power_req", power_req); + packet->setSubstructDataByName("spell_info", "power_upkeep", spell->power_upkeep); + if (packet->GetVersion() <= 561) {//cast times are displayed differently on new clients + packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time/10); + } + else { + packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time); + } + packet->setSubstructDataByName("spell_info", "recast", CalculateRecastTimer(client->GetPlayer())/1000); + packet->setSubstructDataByName("spell_info", "radius", spell->radius); + packet->setSubstructDataByName("spell_info", "req_concentration", spell->req_concentration); + //packet->setSubstructDataByName("spell_info","req_concentration2", 2); + packet->setSubstructDataByName("spell_info", "max_aoe_targets", spell->max_aoe_targets); + packet->setSubstructDataByName("spell_info", "friendly_spell", spell->friendly_spell); + packet->setSubstructArrayLengthByName("spell_info", "num_effects", effects.size()); + for (int32 i = 0; i < effects.size(); i++) { + + packet->setArrayDataByName("subbulletflag", effects[i]->subbullet, i); + string effect_message; + if (effects[i]->description.length() > 0) { + effect_message = effects[i]->description; + if (effect_message.find("%LM") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LM"); + int data_index = stoi(effect_message.substr(string_index + 3, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + string strValue = to_string(value); + strValue.erase(strValue.find_last_not_of('0') + 1, std::string::npos); + effect_message.replace(effect_message.find("%LM"), 5, strValue); + } + // Magic damage min + if (effect_message.find("%DML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DML"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%DML"), 6, damage); + } + // Magic damage max + if (effect_message.find("%DMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%DMH"); + int data_index = stoi(effect_message.substr(string_index + 4, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%DMH"), 6, damage); + } + // level based Magic damage min + if (effect_message.find("%LDML") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDML"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDML"), 7, damage); + } + // level based Magic damage max + if (effect_message.find("%LDMH") < 0xFFFFFFFF) { + int string_index = effect_message.find("%LDMH"); + int data_index = stoi(effect_message.substr(string_index + 5, 2)); + float value; + if (lua_data[data_index]->type == 1) + value = lua_data[data_index]->float_value * client->GetPlayer()->GetLevel(); + else + value = lua_data[data_index]->int_value * client->GetPlayer()->GetLevel(); + + value = client->GetPlayer()->CalculateDamageAmount(nullptr, value, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + string damage = to_string((int)round(value)); + effect_message.replace(effect_message.find("%LDMH"), 7, damage); + } + + boost::regex re("([0-9]{1,10})\\s+\\-\\s+([0-9]{1,10})"); + boost::match_results base_match; + if (boost::regex_search(effect_message, base_match, re, boost::match_partial)) { + boost::ssub_match match = base_match[0]; + std::size_t midPos = match.str().find(" - "); + if(midPos != std::string::npos) + { + int32 minValue = atoul(match.str().substr(0, midPos).c_str()); + int32 maxValue = atoul(match.str().substr(midPos+3).c_str()); + + int32 newMin = minValue; + + bool crit = false; + Skill* skill = master_skill_list.GetSkill(spell->mastery_skill); + std::string skillName = ""; + + if(skill) + skillName = skill->name.data; + + if(skillName == "Aggression") + newMin = (int32)client->GetPlayer()->CalculateHateAmount(nullptr, (sint32)minValue); + else if(spell->friendly_spell && skillName == "Ministration") + newMin = (int32)client->GetPlayer()->CalculateHealAmount(nullptr, (sint32)minValue, 0, &crit, true); + else + newMin = (int32)client->GetPlayer()->CalculateDamageAmount(nullptr, (sint32)minValue, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + + int32 newMax = maxValue; + if(skillName == "Aggression") + newMax = (int32)client->GetPlayer()->CalculateHateAmount(nullptr, (sint32)maxValue); + else if(spell->friendly_spell && skillName == "Ministration") + newMax = (int32)client->GetPlayer()->CalculateHealAmount(nullptr, (sint32)maxValue, 0, &crit, true); + else + newMax = (int32)client->GetPlayer()->CalculateDamageAmount(nullptr, (sint32)maxValue, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, spell->type, spell->target_type); + + std::string newStr = std::to_string(newMin) + " - " + std::to_string(newMax); + + effect_message.replace(effect_message.find(match.str()), match.str().size(), newStr); + } + } + //GetZone()->SimpleMessage(CHANNEL_COLOR_SPELL_EFFECT, effect_message.c_str(), victim, 50); + } + packet->setArrayDataByName("effect", effect_message.c_str(), i); + packet->setArrayDataByName("percentage", effects[i]->percentage, i); + } + if (display_tier == true) + packet->setSubstructDataByName("spell_info", "display_spell_tier", spell->display_spell_tier); + else + packet->setSubstructDataByName("spell_info", "display_spell_tier", 0); + packet->setSubstructDataByName("spell_info", "range", spell->range); + packet->setSubstructDataByName("spell_info", "duration1", spell->duration1); + packet->setSubstructDataByName("spell_info", "duration2", spell->duration2); + + packet->setSubstructDataByName("spell_info", "can_effect_raid", spell->can_effect_raid); + packet->setSubstructDataByName("spell_info", "affect_only_group_members", spell->affect_only_group_members); + packet->setSubstructDataByName("spell_info", "group_spell", spell->group_spell); + packet->setSubstructDataByName("spell_info", "resistibility", spell->resistibility); + packet->setSubstructDataByName("spell_info", "name", &(spell->name)); + packet->setSubstructDataByName("spell_info", "description", &(spell->description)); + //packet->PrintPacket(); +} +EQ2Packet* Spell::SerializeSpecialSpell(Client* client, bool display, int8 packet_type, int8 sub_packet_type) { + if (client->GetVersion() <= 373) + return SerializeSpell(client, display, false, packet_type, 0, "WS_ExaminePartialSpellInfo"); + return SerializeSpell(client, display, false, packet_type, sub_packet_type, "WS_ExamineSpecialSpellInfo"); +} + + +EQ2Packet* Spell::SerializeAASpell(Client* client, int8 tier, AltAdvanceData* data, bool display, bool trait_display, int8 packet_type, int8 sub_packet_type, const char* struct_name) { + if (!client) + return 0; + int16 version = 1; + if (client) + version = client->GetVersion(); + if (!struct_name) + struct_name = "WS_ExamineAASpellInfo"; + PacketStruct* packet = configReader.getStruct(struct_name, version); + if (display) + packet->setSubstructDataByName("info_header", "show_name", 1);//1 + else + if (!trait_display) + packet->setSubstructDataByName("info_header", "show_popup", 1);//1 + else + packet->setSubstructDataByName("info_header", "show_popup", 0); + + if (packet_type > 0) + packet->setSubstructDataByName("info_header", "packettype", packet_type * 256 + 0xFE); + else + packet->setSubstructDataByName("info_header", "packettype", 0x4FFE);// 0x45FE GetItemPacketType(version)); + //packet->setDataByName("unknown2",5); + //packet->setDataByName("unknown7", 1); + //packet->setDataByName("unknown9", 20); + //packet->setDataByName("unknown10", 1, 2); + if (sub_packet_type == 0) + sub_packet_type = 0x83; + packet->setSubstructDataByName("info_header", "packetsubtype", 4);// sub_packet_type); + packet->setSubstructDataByName("spell_info", "aa_id", data->spellID); + packet->setSubstructDataByName("spell_info", "aa_tab_id", data->group); + packet->setSubstructDataByName("spell_info", "aa_icon", data->icon); + packet->setSubstructDataByName("spell_info", "aa_icon2", data->icon2); + packet->setSubstructDataByName("spell_info", "aa_current_rank", tier); // how to get this info to here? + packet->setSubstructDataByName("spell_info", "aa_max_rank", data->maxRank); + packet->setSubstructDataByName("spell_info", "aa_rank_cost", data->rankCost); + packet->setSubstructDataByName("spell_info", "aa_unknown_2", 20); + packet->setSubstructDataByName("spell_info", "aa_name", &(spell->name)); + packet->setSubstructDataByName("spell_info", "aa_description", &(spell->description)); + + + + + //packet->setDataByName("unknown3",2); + //packet->setDataByName("unknown7", 50); + if (sub_packet_type == 0x81) + SetAAPacketInformation(packet, data, client); + else + SetAAPacketInformation(packet, data, client, true); + packet->setSubstructDataByName("spell_info", "uses_remaining", 0xFFFF); + packet->setSubstructDataByName("spell_info", "damage_remaining", 0xFFFF); + //packet->PrintPacket(); + // This adds the second portion to the spell packet. Could be used for bonuses etc.? + string* data1 = packet->serializeString(); + uchar* data2 = (uchar*)data1->c_str(); + uchar* ptr2 = data2; + int32 size = data1->length();// *2; + ////uchar* data3 = new uchar[size]; + ////memcpy(data3, data2, data1->length()); + ////uchar* ptr = data3; + ////size -= 17; + ////memcpy(ptr, &size, sizeof(int32)); + ////size += 3; + ////ptr += data1->length(); + ////ptr2 += 14; + ////memcpy(ptr, ptr2, data1->length() - 14); + + EQ2Packet* outapp = new EQ2Packet(OP_ClientCmdMsg, data2, size); + //DumpPacket(outapp); + //safe_delete_array(data3); + safe_delete(packet); + return outapp; +} + +EQ2Packet* Spell::SerializeSpell(Client* client, bool display, bool trait_display, int8 packet_type, int8 sub_packet_type, const char* struct_name, bool send_partial_packet) { + int16 version = 1; + if (client) + version = client->GetVersion(); + if (!struct_name) + struct_name = "WS_ExamineSpellInfo"; + if (version <= 561) { + if (packet_type == 1) + struct_name = "WS_ExamineEffectInfo"; + else if (!display && (version<=373 || send_partial_packet)) + struct_name = "WS_ExaminePartialSpellInfo"; + else + struct_name = "WS_ExamineSpellInfo"; + } + PacketStruct* packet = configReader.getStruct(struct_name, version); + if (display) + packet->setSubstructDataByName("info_header", "show_name", 1); + else { + if (!trait_display) + packet->setSubstructDataByName("info_header", "show_popup", 1); + else + packet->setSubstructDataByName("info_header", "show_popup", 0); + } + if (version > 561) { + if (packet_type > 0) + packet->setSubstructDataByName("info_header", "packettype", packet_type * 256 + 0xFE); + else + packet->setSubstructDataByName("info_header", "packettype", GetItemPacketType(version)); + } + else { + if (packet_type == 3 || packet_type == 0) + packet->setSubstructDataByName("info_header", "packettype", 3); // 0: item, 1: effect, 2: recipe, 3: spell/ability + else + packet->setSubstructDataByName("info_header", "packettype", 1); + } + //packet->setDataByName("unknown2",5); + //packet->setDataByName("unknown7", 1); + //packet->setDataByName("unknown9", 20); + //packet->setDataByName("unknown10", 1, 2); + if (sub_packet_type == 0) + sub_packet_type = 0x83; + packet->setSubstructDataByName("info_header", "packetsubtype", sub_packet_type); + //packet->setDataByName("unknown3",2); + //packet->setDataByName("unknown7", 50); + if (sub_packet_type == 0x81) + SetPacketInformation(packet, client); + else + SetPacketInformation(packet, client, true); + packet->setSubstructDataByName("spell_info", "uses_remaining", 0xFFFF); + packet->setSubstructDataByName("spell_info", "damage_remaining", 0xFFFF); + //packet->PrintPacket(); + // This adds the second portion to the spell packet. Could be used for bonuses etc.? + int8 offset = 0; + if (packet->GetVersion() == 60114) { + offset = 28; + } + else { + offset = 14; + } + EQ2Packet* outapp = 0; + if (version > 561) { + string* data1 = packet->serializeString(); + uchar* data2 = (uchar*)data1->c_str(); + uchar* ptr2 = data2; + int32 size = data1->length() * 2; + uchar* data3 = new uchar[size]; + memcpy(data3, data2, data1->length()); + uchar* ptr = data3; + size -= offset + 3; + memcpy(ptr, &size, sizeof(int32)); + size += 3; + ptr += data1->length(); + ptr2 += offset; + memcpy(ptr, ptr2, data1->length() - offset); + + outapp = new EQ2Packet(OP_ClientCmdMsg, data3, size); + safe_delete_array(data3); + safe_delete(packet); + } + else + outapp = packet->serialize(); + //DumpPacket(outapp); + return outapp; +} + +void Spell::AddSpellEffect(int8 percentage, int8 subbullet, string description){ + std::unique_lock lock(MSpellInfo); + SpellDisplayEffect* effect = new SpellDisplayEffect; + effect->description = description; + effect->subbullet = subbullet; + effect->percentage = percentage; + + effects.push_back(effect); +} + +int16 Spell::GetHPRequired(Spawn* spawn){ + int16 hp_req = spell->hp_req; + if(spawn && spell->hp_req_percent > 0){ + double result = ((double)spell->hp_req_percent/100)*spawn->GetTotalHP(); + if(result >= (((int16)result) + .5)) + result++; + hp_req = (int16)result; + } + return hp_req; +} + +int16 Spell::GetPowerRequired(Spawn* spawn){ + int16 power_req; + if (spell->power_by_level == true) { + power_req =round( (spell->power_req) * spawn->GetLevel()); + } + else { + power_req = round(spell->power_req); + } + if(spawn && spell->power_req_percent > 0){ + double result = ((double)spell->power_req_percent/100)*spawn->GetTotalPower(); + if(result >= (((int16)result) + .5)) + result++; + power_req = (int16)result; + } + if(spawn && spawn->IsPlayer()) { + int32 ministry_skill_id = rule_manager.GetGlobalRule(R_Spells, MinistrationSkillID)->GetInt32(); + if(spell->mastery_skill == ministry_skill_id) { // ministration offers a power reduction + Skill* skill = ((Player*)spawn)->GetSkillByID(spell->mastery_skill, false); + if(skill) { + float ministry_reduction_percent = rule_manager.GetGlobalRule(R_Spells, MinistrationPowerReductionMax)->GetFloat(); + if(ministry_reduction_percent <= 0.0f) + ministry_reduction_percent = 15.0f; + + int32 ministration_skill_reduce = rule_manager.GetGlobalRule(R_Spells, MinistrationPowerReductionSkill)->GetInt32(); + if(ministration_skill_reduce < 1) + ministration_skill_reduce = 25; + + float item_stat_bonus = 0.0f; + int32 item_stat = master_item_list.GetItemStatIDByName(::ToLower(skill->name.data)); + if(item_stat != 0xFFFFFFFF) { + item_stat_bonus = ((Entity*)spawn)->GetStat(item_stat); + } + + float reduction = (skill->current_val + item_stat_bonus) / ministration_skill_reduce; + + if(reduction > ministry_reduction_percent) + reduction = ministry_reduction_percent; + int16 power_to_reduce = (int16)(float)(power_req * (ministry_reduction_percent/100.0f)) * (reduction / ministry_reduction_percent); + if(power_to_reduce > power_req) { + power_req = 0; + } + else { + power_req = power_req - power_to_reduce; + } + } + } + + float power_reduction = ((Entity*)spawn)->GetStat(ITEM_STAT_POWER_COST_REDUCTION); + if(power_reduction > 0.0f) { + int16 power_to_reduce = (int16)(float)(power_req * (power_reduction/100.0f)); + if(power_to_reduce > power_req) { + power_req = 0; + } + else { + power_req = power_req - power_to_reduce; + } + } + } + return power_req; +} + +int16 Spell::GetSavageryRequired(Spawn* spawn){ + int16 savagery_req = spell->savagery_req; + if(spawn && spell->savagery_req_percent > 0){ + double result = ((double)spell->savagery_req_percent/100)*spawn->GetTotalSavagery(); + if(result >= (((int16)result) + .5)) + result++; + savagery_req = (int16)result; + } + return savagery_req; +} + +int16 Spell::GetDissonanceRequired(Spawn* spawn){ + int16 dissonance_req = spell->dissonance_req; + if(spawn && spell->dissonance_req_percent > 0){ + double result = ((double)spell->dissonance_req_percent/100)*spawn->GetTotalDissonance(); + if(result >= (((int16)result) + .5)) + result++; + dissonance_req = (int16)result; + } + return dissonance_req; +} + +int32 Spell::GetSpellDuration(){ + if(spell->duration1 == spell->duration2) + return spell->duration1; + + int32 difference = 0; + int32 lower = 0; + if(spell->duration2 > spell->duration1){ + difference = spell->duration2 - spell->duration1; + lower = spell->duration1; + } + else{ + difference = spell->duration1 - spell->duration2; + lower = spell->duration2; + } + int32 duration = (rand()%difference) + lower; + return duration; +} + +const char* Spell::GetName(){ + return spell->name.data.c_str(); +} + +const char* Spell::GetDescription(){ + return spell->description.data.c_str(); +} + +void Spell::AddSpellLevel(int8 adventure_class, int8 tradeskill_class, int16 level){ + std::unique_lock lock(MSpellInfo); + LevelArray* lvl = new LevelArray; + lvl->adventure_class = adventure_class; + lvl->tradeskill_class = tradeskill_class; + lvl->spell_level = level; + + levels.push_back(lvl); +} + +int32 Spell::GetSpellID(){ + if (spell) + return spell->id; + return 0; +} + +int8 Spell::GetSpellTier(){ + if (spell) + return spell->tier; + return 0; +} + +vector* Spell::GetLUAData(){ + return &lua_data; +} +SpellData* Spell::GetSpellData(){ + return spell; +} + +bool Spell::GetSpellData(lua_State* state, std::string field) +{ + if (!lua_interface) + return false; + + bool valSet = false; + + if (field == "spell_book_type") + { + lua_interface->SetInt32Value(state, GetSpellData()->spell_book_type); + valSet = true; + } + else if (field == "icon") + { + lua_interface->SetSInt32Value(state, GetSpellData()->icon); + valSet = true; + } + else if (field == "icon_heroic_op") + { + lua_interface->SetInt32Value(state, GetSpellData()->icon_heroic_op); + valSet = true; + } + else if (field == "icon_backdrop") + { + lua_interface->SetInt32Value(state, GetSpellData()->icon_backdrop); + valSet = true; + } + else if (field == "type") + { + lua_interface->SetInt32Value(state, GetSpellData()->type); + valSet = true; + } + else if (field == "class_skill") + { + lua_interface->SetInt32Value(state, GetSpellData()->class_skill); + valSet = true; + } + else if (field == "min_class_skill_req") + { + lua_interface->SetInt32Value(state, GetSpellData()->min_class_skill_req); + valSet = true; + } + else if (field == "mastery_skill") + { + lua_interface->SetInt32Value(state, GetSpellData()->mastery_skill); + valSet = true; + } + else if (field == "ts_loc_index") + { + lua_interface->SetSInt32Value(state, GetSpellData()->ts_loc_index); + valSet = true; + } + else if (field == "num_levels") + { + lua_interface->SetSInt32Value(state, GetSpellData()->num_levels); + valSet = true; + } + else if (field == "tier") + { + lua_interface->SetSInt32Value(state, GetSpellData()->tier); + valSet = true; + } + else if (field == "hp_req") + { + lua_interface->SetSInt32Value(state, GetSpellData()->hp_req); + valSet = true; + } + else if (field == "hp_upkeep") + { + lua_interface->SetSInt32Value(state, GetSpellData()->hp_upkeep); + valSet = true; + } + else if (field == "power_req") + { + lua_interface->SetFloatValue(state, GetSpellData()->power_req); + valSet = true; + } + else if (field == "power_by_level") + { + lua_interface->SetBooleanValue(state, GetSpellData()->power_by_level); + valSet = true; + } + else if (field == "power_upkeep") + { + lua_interface->SetSInt32Value(state, GetSpellData()->power_upkeep); + valSet = true; + } + else if (field == "savagery_req") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savagery_req); + valSet = true; + } + else if (field == "savagery_upkeep") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savagery_upkeep); + valSet = true; + } + else if (field == "dissonance_req") + { + lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_req); + valSet = true; + } + else if (field == "dissonance_upkeep") + { + lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_upkeep); + valSet = true; + } + else if (field == "target_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->target_type); + valSet = true; + } + else if (field == "cast_time") + { + lua_interface->SetSInt32Value(state, GetSpellData()->cast_time); + valSet = true; + } + else if (field == "recovery") + { + lua_interface->SetFloatValue(state, GetSpellData()->recovery); + valSet = true; + } + else if (field == "recast") + { + lua_interface->SetFloatValue(state, GetSpellData()->recast); + valSet = true; + } + else if (field == "linked_timer") + { + lua_interface->SetSInt32Value(state, GetSpellData()->linked_timer); + valSet = true; + } + else if (field == "radius") + { + lua_interface->SetFloatValue(state, GetSpellData()->radius); + valSet = true; + } + else if (field == "max_aoe_targets") + { + lua_interface->SetSInt32Value(state, GetSpellData()->max_aoe_targets); + valSet = true; + } + else if (field == "friendly_spell") + { + lua_interface->SetSInt32Value(state, GetSpellData()->friendly_spell); + valSet = true; + } + else if (field == "req_concentration") + { + lua_interface->SetSInt32Value(state, GetSpellData()->req_concentration); + valSet = true; + } + else if (field == "range") + { + lua_interface->SetFloatValue(state, GetSpellData()->range); + valSet = true; + } + else if (field == "duration1") + { + lua_interface->SetSInt32Value(state, GetSpellData()->duration1); + valSet = true; + } + else if (field == "duration2") + { + lua_interface->SetSInt32Value(state, GetSpellData()->duration2); + valSet = true; + } + else if (field == "resistibility") + { + lua_interface->SetFloatValue(state, GetSpellData()->resistibility); + valSet = true; + } + else if (field == "duration_until_cancel") + { + lua_interface->SetBooleanValue(state, GetSpellData()->duration_until_cancel); + valSet = true; + } + else if (field == "power_req_percent") + { + lua_interface->SetSInt32Value(state, GetSpellData()->power_req_percent); + valSet = true; + } + else if (field == "hp_req_percent") + { + lua_interface->SetSInt32Value(state, GetSpellData()->hp_req_percent); + valSet = true; + } + else if (field == "savagery_req_percent") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savagery_req_percent); + valSet = true; + } + else if (field == "dissonance_req_percent") + { + lua_interface->SetSInt32Value(state, GetSpellData()->dissonance_req_percent); + valSet = true; + } + else if (field == "name") + { + lua_interface->SetStringValue(state, GetSpellData()->name.data.c_str()); + valSet = true; + } + else if (field == "description") + { + lua_interface->SetStringValue(state, GetSpellData()->description.data.c_str()); + valSet = true; + } + else if (field == "success_message") + { + lua_interface->SetStringValue(state, GetSpellData()->success_message.c_str()); + valSet = true; + } + else if (field == "fade_message") + { + lua_interface->SetStringValue(state, GetSpellData()->fade_message.c_str()); + valSet = true; + } + else if (field == "fade_message_others") + { + lua_interface->SetStringValue(state, GetSpellData()->fade_message_others.c_str()); + valSet = true; + } + else if (field == "cast_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->cast_type); + valSet = true; + } + else if (field == "lua_script") + { + lua_interface->SetStringValue(state, GetSpellData()->lua_script.c_str()); + valSet = true; + } + else if (field == "interruptable") + { + lua_interface->SetBooleanValue(state, GetSpellData()->interruptable); + valSet = true; + } + else if (field == "spell_visual") + { + lua_interface->SetSInt32Value(state, GetSpellData()->spell_visual); + valSet = true; + } + else if (field == "effect_message") + { + lua_interface->SetStringValue(state, GetSpellData()->effect_message.c_str()); + valSet = true; + } + else if (field == "min_range") + { + lua_interface->SetFloatValue(state, GetSpellData()->min_range); + valSet = true; + } + else if (field == "can_effect_raid") + { + lua_interface->SetSInt32Value(state, GetSpellData()->can_effect_raid); + valSet = true; + } + else if (field == "affect_only_group_members") + { + lua_interface->SetSInt32Value(state, GetSpellData()->affect_only_group_members); + valSet = true; + } + else if (field == "group_spell") + { + lua_interface->SetSInt32Value(state, GetSpellData()->group_spell); + valSet = true; + } + else if (field == "hit_bonus") + { + lua_interface->SetFloatValue(state, GetSpellData()->hit_bonus); + valSet = true; + } + else if (field == "display_spell_tier") + { + lua_interface->SetSInt32Value(state, GetSpellData()->display_spell_tier); + valSet = true; + } + else if (field == "is_active") + { + lua_interface->SetSInt32Value(state, GetSpellData()->is_active); + valSet = true; + } + else if (field == "det_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->det_type); + valSet = true; + } + else if (field == "incurable") + { + lua_interface->SetBooleanValue(state, GetSpellData()->incurable); + valSet = true; + } + else if (field == "control_effect_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->control_effect_type); + valSet = true; + } + else if (field == "casting_flags") + { + lua_interface->SetSInt32Value(state, GetSpellData()->casting_flags); + valSet = true; + } + else if (field == "cast_while_moving") + { + lua_interface->SetBooleanValue(state, GetSpellData()->cast_while_moving); + valSet = true; + } + else if (field == "persist_through_death") + { + lua_interface->SetBooleanValue(state, GetSpellData()->persist_through_death); + valSet = true; + } + else if (field == "not_maintained") + { + lua_interface->SetBooleanValue(state, GetSpellData()->not_maintained); + valSet = true; + } + else if (field == "is_aa") + { + lua_interface->SetBooleanValue(state, GetSpellData()->is_aa); + valSet = true; + } + else if (field == "savage_bar") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savage_bar); + valSet = true; + } + else if (field == "savage_bar_slot") + { + lua_interface->SetSInt32Value(state, GetSpellData()->savage_bar_slot); + valSet = true; + } + else if (field == "soe_spell_crc") + { + lua_interface->SetSInt32Value(state, GetSpellData()->soe_spell_crc); + valSet = true; + } + else if (field == "spell_type") + { + lua_interface->SetSInt32Value(state, GetSpellData()->spell_type); + valSet = true; + } + else if (field == "spell_name_crc") + { + lua_interface->SetSInt32Value(state, GetSpellData()->spell_name_crc); + valSet = true; + } + else if (field == "type_group_spell_id") + { + lua_interface->SetSInt32Value(state, GetSpellData()->type_group_spell_id); + valSet = true; + } + else if (field == "can_fizzle") + { + lua_interface->SetBooleanValue(state, GetSpellData()->can_fizzle); + valSet = true; + } + + return valSet; +} + +bool Spell::SetSpellData(lua_State* state, std::string field, int8 fieldArg) +{ + if (!lua_interface) + return false; + + bool valSet = false; + + if (field == "spell_book_type") + { + int32 spell_book_type = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->spell_book_type = spell_book_type; + valSet = true; + } + else if (field == "icon") + { + sint16 icon = lua_interface->GetSInt32Value(state, fieldArg); + GetSpellData()->icon = icon; + valSet = true; + } + else if (field == "icon_heroic_op") + { + int16 icon_heroic_op = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->icon_heroic_op = icon_heroic_op; + valSet = true; + } + else if (field == "icon_backdrop") + { + int16 icon_backdrop = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->icon_backdrop = icon_backdrop; + valSet = true; + } + else if (field == "type") + { + int16 type = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->type = type; + valSet = true; + } + else if (field == "class_skill") + { + int32 class_skill = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->class_skill = class_skill; + valSet = true; + } + else if (field == "min_class_skill_req") + { + int16 min_class_skill_req = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->min_class_skill_req = min_class_skill_req; + valSet = true; + } + else if (field == "mastery_skill") + { + int32 mastery_skill = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->mastery_skill = mastery_skill; + valSet = true; + } + else if (field == "ts_loc_index") + { + int8 ts_loc_index = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->ts_loc_index = ts_loc_index; + valSet = true; + } + else if (field == "num_levels") + { + int8 num_levels = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->num_levels = num_levels; + valSet = true; + } + else if (field == "tier") + { + int8 tier = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->tier = tier; + valSet = true; + } + else if (field == "hp_req") + { + int16 hp_req = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->hp_req = hp_req; + valSet = true; + } + else if (field == "hp_upkeep") + { + int16 hp_upkeep = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->hp_upkeep = hp_upkeep; + valSet = true; + } + else if (field == "power_req") + { + float power_req = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->power_req = power_req; + valSet = true; + } + else if (field == "power_by_level") + { + bool power_by_level = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->power_by_level = power_by_level; + valSet = true; + } + else if (field == "power_upkeep") + { + int16 power_upkeep = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->power_upkeep = power_upkeep; + valSet = true; + } + else if (field == "savagery_req") + { + int16 savagery_req = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->savagery_req = savagery_req; + valSet = true; + } + else if (field == "savagery_upkeep") + { + int16 savagery_upkeep = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->savagery_upkeep = savagery_upkeep; + valSet = true; + } + else if (field == "dissonance_req") + { + int16 dissonance_req = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->dissonance_req = dissonance_req; + valSet = true; + } + else if (field == "dissonance_upkeep") + { + int16 dissonance_upkeep = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->dissonance_upkeep = dissonance_upkeep; + valSet = true; + } + else if (field == "target_type") + { + int16 target_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->target_type = target_type; + valSet = true; + } + else if (field == "cast_time") + { + int16 cast_time = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->orig_cast_time = cast_time; + GetSpellData()->cast_time = cast_time; + valSet = true; + } + else if (field == "recovery") + { + float recovery = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->recovery = recovery; + valSet = true; + } + else if (field == "recast") + { + float recast = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->recast = recast; + valSet = true; + } + else if (field == "linked_timer") + { + int32 linked_timer = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->linked_timer = linked_timer; + valSet = true; + } + else if (field == "radius") + { + float radius = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->radius = radius; + valSet = true; + } + else if (field == "max_aoe_targets") + { + int16 max_aoe_targets = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->max_aoe_targets = max_aoe_targets; + valSet = true; + } + else if (field == "friendly_spell") + { + int8 friendly_spell = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->friendly_spell = friendly_spell; + valSet = true; + } + else if (field == "req_concentration") + { + int16 req_concentration = lua_interface->GetInt16Value(state, fieldArg); + GetSpellData()->req_concentration = req_concentration; + valSet = true; + } + else if (field == "range") + { + float range = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->range = range; + valSet = true; + } + else if (field == "duration1") + { + sint32 duration = lua_interface->GetSInt32Value(state, fieldArg); + GetSpellData()->duration1 = duration; + valSet = true; + } + else if (field == "duration2") + { + sint32 duration = lua_interface->GetSInt32Value(state, fieldArg); + GetSpellData()->duration2 = duration; + valSet = true; + } + else if (field == "resistibility") + { + float resistibility = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->resistibility = resistibility; + valSet = true; + } + else if (field == "duration_until_cancel") + { + bool duration_until_cancel = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->duration_until_cancel = duration_until_cancel; + valSet = true; + } + else if (field == "power_req_percent") + { + int8 power_req_percent = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->power_req_percent = power_req_percent; + valSet = true; + } + else if (field == "hp_req_percent") + { + int8 hp_req_percent = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->hp_req_percent = hp_req_percent; + valSet = true; + } + else if (field == "savagery_req_percent") + { + int8 savagery_req_percent = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->savagery_req_percent = savagery_req_percent; + valSet = true; + } + else if (field == "dissonance_req_percent") + { + int8 dissonance_req_percent = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->dissonance_req_percent = dissonance_req_percent; + valSet = true; + } + else if (field == "name") + { + string name = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->name.data = name; + valSet = true; + } + else if (field == "description") + { + string description = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->description.data = description; + valSet = true; + } + else if (field == "success_message") + { + string success_message = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->success_message = success_message; + valSet = true; + } + else if (field == "fade_message") + { + string fade_message = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->fade_message = fade_message; + valSet = true; + } + else if (field == "fade_message_others") + { + string fade_message_others = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->fade_message_others = fade_message_others; + valSet = true; + } + else if (field == "cast_type") + { + int8 cast_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->cast_type = cast_type; + valSet = true; + } + else if (field == "cast_type") + { + int32 call_frequency = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->call_frequency = call_frequency; + valSet = true; + } + else if (field == "interruptable") + { + bool interruptable = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->interruptable = interruptable; + valSet = true; + } + else if (field == "spell_visual") + { + int32 spell_visual = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->spell_visual = spell_visual; + valSet = true; + } + else if (field == "effect_message") + { + string effect_message = lua_interface->GetStringValue(state, fieldArg); + GetSpellData()->effect_message = effect_message; + valSet = true; + } + else if (field == "min_range") + { + float min_range = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->min_range = min_range; + valSet = true; + } + else if (field == "can_effect_raid") + { + int8 can_effect_raid = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->can_effect_raid = can_effect_raid; + valSet = true; + } + else if (field == "affect_only_group_members") + { + int8 affect_only_group_members = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->affect_only_group_members = affect_only_group_members; + valSet = true; + } + else if (field == "group_spell") + { + int8 group_spell = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->group_spell = group_spell; + valSet = true; + } + else if (field == "hit_bonus") + { + float hit_bonus = lua_interface->GetFloatValue(state, fieldArg); + GetSpellData()->hit_bonus = hit_bonus; + valSet = true; + } + else if (field == "display_spell_tier") + { + int8 display_spell_tier = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->display_spell_tier = display_spell_tier; + valSet = true; + } + else if (field == "is_active") + { + int8 is_active = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->is_active = is_active; + valSet = true; + } + else if (field == "det_type") + { + int8 det_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->det_type = det_type; + valSet = true; + } + else if (field == "incurable") + { + bool incurable = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->incurable = incurable; + valSet = true; + } + else if (field == "control_effect_type") + { + int8 control_effect_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->control_effect_type = control_effect_type; + valSet = true; + } + else if (field == "casting_flags") + { + int32 casting_flags = lua_interface->GetInt32Value(state, fieldArg); + GetSpellData()->casting_flags = casting_flags; + valSet = true; + } + else if (field == "cast_while_moving") + { + bool cast_while_moving = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->cast_while_moving = cast_while_moving; + valSet = true; + } + else if (field == "persist_through_death") + { + bool persist_through_death = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->persist_through_death = persist_through_death; + valSet = true; + } + else if (field == "not_maintained") + { + bool not_maintained = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->not_maintained = not_maintained; + valSet = true; + } + else if (field == "is_aa") + { + bool is_aa = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->is_aa = is_aa; + valSet = true; + } + else if (field == "savage_bar") + { + int8 savage_bar = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->savage_bar = savage_bar; + valSet = true; + } + else if (field == "spell_type") + { + int8 spell_type = lua_interface->GetInt8Value(state, fieldArg); + GetSpellData()->spell_type = spell_type; + valSet = true; + } + else if (field == "type_group_spell_id") + { + sint32 type_group_spell_id = lua_interface->GetSInt32Value(state, fieldArg); + GetSpellData()->type_group_spell_id = type_group_spell_id; + valSet = true; + } + else if (field == "can_fizzle") + { + bool can_fizzle = lua_interface->GetBooleanValue(state, fieldArg); + GetSpellData()->can_fizzle = can_fizzle; + valSet = true; + } + + return valSet; +} +int16 Spell::GetSpellIcon(){ + if (spell) + return spell->icon; + return 0; +} + +int16 Spell::GetSpellIconBackdrop(){ + if (spell) + return spell->icon_backdrop; + return 0; +} + +int16 Spell::GetSpellIconHeroicOp(){ + if (spell) + return spell->icon_heroic_op; + return 0; +} + +bool Spell::IsHealSpell(){ + return heal_spell; +} + +bool Spell::IsBuffSpell(){ + return buff_spell; +} + +bool Spell::IsDamageSpell(){ + return damage_spell; +} +bool Spell::IsControlSpell(){ + return control_spell; +} + +bool Spell::IsOffenseSpell() { + return offense_spell; +} + +bool Spell::IsCopiedSpell() { + return copied_spell; +} + +void Spell::ModifyCastTime(Entity* caster){ + int16 cast_time = spell->orig_cast_time; + spell->cast_time = cast_time; + float cast_speed = caster->GetInfoStruct()->get_casting_speed(); + if (cast_speed > 0.0f){ + bool modifiedSpeed = false; + if (cast_speed > 0.0f && (modifiedSpeed = true)) // casting speed can only reduce up to half a cast time + spell->cast_time *= max(0.5f, (float) (1 / (1 + (cast_speed * .01)))); + else if (cast_speed < 0.0f && (modifiedSpeed = true)) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now + spell->cast_time *= min(1.5f, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01)))))); + + if(modifiedSpeed) { + LogWrite(SPELL__DEBUG, 9, "Spells", "%s: spell %s cast time %u to %u based on cast_speed %f", GetName(), caster->GetName(), cast_time, spell->cast_time, cast_speed); + } + } +} + +int32 Spell::CalculateRecastTimer(Entity* caster, float override_timer) { + int32 original_recast = static_cast(GetSpellData()->recast * 1000.0f); + + if(override_timer > 0.0f) { + original_recast = static_cast(override_timer); + } + + int32 recast_time = original_recast; + float cast_speed = caster->GetInfoStruct()->get_spell_reuse_speed(); + if (cast_speed > 0.0f){ + bool modifiedSpeed = false; + if (cast_speed > 0.0f && (modifiedSpeed = true)) // casting speed can only reduce up to half a cast time + recast_time *= max(0.5f, (float) (1 / (1 + (cast_speed * .01)))); + else if (cast_speed < 0.0f && (modifiedSpeed = true)) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now + recast_time *= min(1.5f, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01)))))); + + if(modifiedSpeed) { + LogWrite(SPELL__DEBUG, 9, "Spells", "%s: spell %s recast time %u to %u based on spell_reuse_time %f", GetName(), caster->GetName(), original_recast, recast_time, cast_speed); + } + } + return recast_time; +} + +vector * Spell::GetSpellEffects(){ + std::shared_lock lock(MSpellInfo); + vector * ret = &effects; + return ret; +} + +vector * Spell::GetSpellLevels(){ + std::shared_lock lock(MSpellInfo); + vector * ret = &levels; + return ret; +} + +bool Spell::ScribeAllowed(Player* player){ + std::shared_lock lock(MSpellInfo); + bool ret = false; + if(player){ + for(int32 i=0;!ret && iGetLevel(); + int16 spelllevels = levels[i]->spell_level; + bool advlev = player->GetAdventureClass() == levels[i]->adventure_class; + bool tslev = player->GetTradeskillClass() == levels[i]->tradeskill_class; + bool levelmatch = player->GetLevel() >= levels[i]->spell_level; + if((player->GetAdventureClass() == levels[i]->adventure_class || player->GetTradeskillClass() == levels[i]->tradeskill_class) && player->GetLevel() >= levels[i]->spell_level/10) + ret = true; + } + } + return ret; +} + +MasterSpellList::MasterSpellList(){ + max_spell_id = 0; + MMasterSpellList.SetName("MasterSpellList::MMasterSpellList"); +} + +MasterSpellList::~MasterSpellList(){ + DestroySpells(); +} +void MasterSpellList::DestroySpells(){ + + spell_errors.clear(); + + MMasterSpellList.lock(); + map >::iterator iter; + map::iterator iter2; + for(iter = spell_list.begin();iter != spell_list.end(); iter++){ + for(iter2 = iter->second.begin();iter2 != iter->second.end(); iter2++){ + safe_delete(iter2->second); + } + } + spell_list.clear(); + for(int i=0;i* levels = spell->GetSpellLevels(); + LevelArray* level = 0; + vector::iterator level_itr; + for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){ + level = *level_itr; + if(level->adventure_class && level->adventure_class < MAX_CLASSES){ + class_spell_list[level->adventure_class][id][tier] = spell; + } + if(level->tradeskill_class && level->tradeskill_class < MAX_CLASSES) { + class_spell_list[level->tradeskill_class][id][tier] = spell; + } + } + + spell_name_map[spell->GetName()] = spell; + spell_soecrc_map[spell->GetSpellData()->soe_spell_crc] = spell; + + if (id > max_spell_id) + max_spell_id = id; + + MMasterSpellList.unlock(); +} + +Spell* MasterSpellList::GetSpell(int32 id, int8 tier){ + if (spell_list.count(id) > 0 && spell_list[id].count(tier) > 0) + return spell_list[id][tier]; + else if (spell_list.count(id) > 0 && tier == 0 && spell_list[id].count(1) > 0) + return spell_list[id][1]; + return 0; +} + +Spell* MasterSpellList::GetSpellByName(const char* name){ + if(spell_name_map.count(name) > 0) + return spell_name_map[name]; + return 0; +} + +Spell* MasterSpellList::GetSpellByCRC(int32 spell_crc){ + if(spell_soecrc_map.count(spell_crc) > 0) + return spell_soecrc_map[spell_crc]; + return 0; +} + +EQ2Packet* MasterSpellList::GetSpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type){ + Spell* spell = GetSpell(id, tier); + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + EQ2Packet* pack = 0; + if (tmpSpell) + { + spell = tmpSpell->spell; + pack = spell->SerializeSpell(client, display, packet_type); + } + + lua_interface->FindCustomSpellUnlock(); + return pack; + } + + if(spell) + return spell->SerializeSpell(client, display, packet_type); + return 0; +} +EQ2Packet* MasterSpellList::GetAASpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type) { + Spell* spell = GetSpell(id, (tier == 0 ? 1 : tier)); + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + EQ2Packet* pack = 0; + if (tmpSpell) + { + spell = tmpSpell->spell; + // TODO: this isn't a tested thing yet... need to add custom spells to alt advancement? + AltAdvanceData* data = master_aa_list.GetAltAdvancement(id); + + if(data) + pack = spell->SerializeAASpell(client, tier, data, display, false, packet_type); + } + + lua_interface->FindCustomSpellUnlock(); + return pack; + } + + //Spell* spell2= GetSpell(id, (tier +1)); + AltAdvanceData* data = master_aa_list.GetAltAdvancement(id); + if (spell) + return spell->SerializeAASpell(client,tier, data, display,false, packet_type); + return 0; +} + +EQ2Packet* MasterSpellList::GetSpecialSpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type){ + Spell* spell = GetSpell(id, tier); + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + EQ2Packet* pack = 0; + if (tmpSpell) + { + spell = tmpSpell->spell; + pack = spell->SerializeSpecialSpell(client, display, packet_type, 0x81); + } + + lua_interface->FindCustomSpellUnlock(); + return pack; + } + + if(spell) + return spell->SerializeSpecialSpell(client, display, packet_type, 0x81); + return 0; +} + +vector* MasterSpellList::GetSpellListByAdventureClass(int8 class_id, int16 max_level, int8 max_tier){ + vector* ret = new vector; + if(class_id >= MAX_CLASSES) { + return ret; + } + + Spell* spell = 0; + vector* levels = 0; + LevelArray* level = 0; + vector::iterator level_itr; + MMasterSpellList.lock(); + map >::iterator iter; + map::iterator iter2; + max_level *= 10; //convert to client level format, which is 10 times higher + for(iter = class_spell_list[class_id].begin();iter != class_spell_list[class_id].end(); iter++){ + for(iter2 = iter->second.begin();iter2 != iter->second.end(); iter2++){ + spell = iter2->second; + if(iter2->first <= max_tier && spell && spell->GetSpellData()->given_by_type != GivenByType::GivenBy_SpellScroll && + spell->GetSpellData()->given_by_type != GivenByType::GivenBy_TradeskillClass){ + levels = spell->GetSpellLevels(); + for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){ + level = *level_itr; + if(level->spell_level <= max_level && level->adventure_class == class_id){ + ret->push_back(spell); + break; + } + } + } + } + } + MMasterSpellList.unlock(); + return ret; +} + +vector* MasterSpellList::GetSpellListByTradeskillClass(int8 class_id, int16 max_level, int8 max_tier){ + vector* ret = new vector; + if(class_id >= MAX_CLASSES) { + return ret; + } + + Spell* spell = 0; + vector* levels = 0; + LevelArray* level = 0; + vector::iterator level_itr; + MMasterSpellList.lock(); + map >::iterator iter; + map::iterator iter2; + for(iter = class_spell_list[class_id].begin();iter != class_spell_list[class_id].end(); iter++){ + for(iter2 = iter->second.begin();iter2 != iter->second.end(); iter2++){ + spell = iter2->second; + if(iter2->first <= max_tier && spell && spell->GetSpellData()->given_by_type == GivenByType::GivenBy_TradeskillClass){ + levels = spell->GetSpellLevels(); + for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){ + level = *level_itr; + if(level->spell_level <= max_level && level->tradeskill_class == class_id){ + ret->push_back(spell); + break; + } + } + } + } + } + MMasterSpellList.unlock(); + return ret; +} + +void MasterSpellList::Reload(){ + master_trait_list.DestroyTraits(); + DestroySpells(); + database.LoadSpells(); + database.LoadSpellErrors(); + database.LoadTraits(); +} + +int16 MasterSpellList::GetSpellErrorValue(int16 version, int8 error_index) { + version = GetClosestVersion(version); + + if (spell_errors[version].count(error_index) == 0) { + LogWrite(SPELL__ERROR, 0, "Spells", "No spell error entry. (version = %i, error_index = %i)", version, error_index); + // 1 will give the client a pop up message of "Cannot cast" and a chat message of "[BUG] Cannot cast. Unknown failure casting spell." + return 1; + } + return spell_errors[version][error_index]; +} + +void MasterSpellList::AddSpellError(int16 version, int8 error_index, int16 error_value) { + if (spell_errors[version].count(error_index) == 0) + spell_errors[version][error_index] = error_value; +} + +int16 MasterSpellList::GetClosestVersion(int16 version) { + int16 ret = 0; + map >::iterator itr; + // Get the closest version in the list that is less then or equal to the given version + for (itr = spell_errors.begin(); itr != spell_errors.end(); itr++) { + if (itr->first <= version) { + if (itr->first > ret) + ret = itr->first; + } + } + + return ret; +} + +bool Spell::CastWhileStunned(){ + return (spell->casting_flags & CASTING_FLAG_STUNNED) == CASTING_FLAG_STUNNED; +} + +bool Spell::CastWhileMezzed(){ + return (spell->casting_flags & CASTING_FLAG_MEZZED) == CASTING_FLAG_MEZZED; +} + +bool Spell::CastWhileStifled(){ + return (spell->casting_flags & CASTING_FLAG_STIFLED) == CASTING_FLAG_STIFLED; +} + +bool Spell::CastWhileFeared(){ + return (spell->casting_flags & CASTING_FLAG_FEARED) == CASTING_FLAG_FEARED; +} diff --git a/source/WorldServer/Spells.h b/source/WorldServer/Spells.h new file mode 100644 index 0000000..5272219 --- /dev/null +++ b/source/WorldServer/Spells.h @@ -0,0 +1,446 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_SPELLS__ +#define __EQ2_SPELLS__ +#include +#include +#include +#include +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/MiscFunctions.h" +#include "client.h" +#include "classes.h" +#include "../common/Mutex.h" +#include "AltAdvancement/AltAdvancement.h" + +#include "../LUA/lua.hpp" + +#define SPELL_TARGET_SELF 0 +#define SPELL_TARGET_ENEMY 1 +#define SPELL_TARGET_GROUP_AE 2 +#define SPELL_TARGET_CASTER_PET 3 +#define SPELL_TARGET_ENEMY_PET 4 +#define SPELL_TARGET_ENEMY_CORPSE 5 +#define SPELL_TARGET_GROUP_CORPSE 6 +#define SPELL_TARGET_NONE 7 +#define SPELL_TARGET_RAID_AE 8 +#define SPELL_TARGET_OTHER_GROUP_AE 9 + + +#define SPELL_BOOK_TYPE_SPELL 0 +#define SPELL_BOOK_TYPE_COMBAT_ART 1 +#define SPELL_BOOK_TYPE_ABILITY 2 +#define SPELL_BOOK_TYPE_TRADESKILL 3 +#define SPELL_BOOK_TYPE_NOT_SHOWN 4 + +#define SPELL_CAST_TYPE_NORMAL 0 +#define SPELL_CAST_TYPE_TOGGLE 1 + + +#define SPELL_ERROR_NOT_ENOUGH_KNOWLEDGE 1 +#define SPELL_ERROR_INTERRUPTED 2 +#define SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL 3 +#define SPELL_ERROR_TAKE_EFFECT_SAMESPELL 4 +#define SPELL_ERROR_CANNOT_CAST_DEAD 5 +#define SPELL_ERROR_NOT_ALIVE 6 +#define SPELL_ERROR_NOT_DEAD 7 +#define SPELL_ERROR_CANNOT_CAST_SITTING 8 +#define SPELL_ERROR_CANNOT_CAST_UNCON 9 +#define SPELL_ERROR_ALREADY_CASTING 10 +#define SPELL_ERROR_RECOVERING 11 +#define SPELL_ERROR_NON_COMBAT_ONLY 12 +#define SPELL_ERROR_CANNOT_CAST_STUNNED 13 +#define SPELL_ERROR_CANNOT_CAST_STIFFLED 14 +#define SPELL_ERROR_CANNOT_CAST_CHARMED 15 +#define SPELL_ERROR_NOT_WHILE_MOUNTED 16 +#define SPELL_ERROR_NOT_WHILE_FLYING 17 +#define SPELL_ERROR_NOT_WHILE_CLIMBING 18 +#define SPELL_ERROR_NOT_READY 19 +#define SPELL_ERROR_CANT_SEE_TARGET 20 +#define SPELL_ERROR_INCORRECT_STANCE 21 +#define SPELL_ERROR_CANNOT_CAST_FEIGNDEATH 22 +#define SPELL_ERROR_INVENTORY_FULL 23 +#define SPELL_ERROR_NOT_ENOUGH_COIN 24 +#define SPELL_ERROR_NOT_ALLOWED_HERE 25 +#define SPELL_ERROR_NOT_WHILE_CRAFTING 26 +#define SPELL_ERROR_ONLY_WHEN_CRAFTING 27 +#define SPELL_ERROR_ITEM_NOT_ATTUNED 28 +#define SPELL_ERROR_ITEM_WORN_OUT 29 +#define SPELL_ERROR_MUST_EQUIP_WEAPON 30 +#define SPELL_ERROR_WEAPON_BROKEN 31 +#define SPELL_ERROR_CANNOT_CAST_FEARED 32 +#define SPELL_ERROR_TARGET_IMMUNE_HOSTILE 33 +#define SPELL_ERROR_TARGET_IMMUNE_BENEFICIAL 34 +#define SPELL_ERROR_NO_TAUNT_SPELLS 35 +#define SPELL_ERROR_CANNOT_USE_IN_BATTLEGROUNDS 36 +#define SPELL_ERROR_CANNOT_PREPARE 37 +#define SPELL_ERROR_NO_ELIGIBLE_TARGET 38 +#define SPELL_ERROR_NO_TARGETS_IN_RANGE 39 +#define SPELL_ERROR_TOO_CLOSE 40 +#define SPELL_ERROR_TOO_FAR_AWAY 41 +#define SPELL_ERROR_TARGET_TOO_WEAK 42 +#define SPELL_ERROR_TARGET_TOO_POWERFUL 43 +#define SPELL_ERROR_WONT_WORK_ON_TARGET 44 +#define SPELL_ERROR_TARGET_INVULNERABLE 45 +#define SPELL_ERROR_TARGET_IMMUNE 46 +#define SPELL_ERROR_TARGET_ENGAGED 47 +#define SPELL_ERROR_TARGET_NOT_GROUPED 48 +#define SPELL_ERROR_TARGET_IN_USE 49 +#define SPELL_ERROR_TARGET_GROUP_HAS_SPELL 50 +#define SPELL_ERROR_TARGET_ALREADY_ENGAGED 51 +#define SPELL_ERROR_CANNOT_ENGAGE 52 +#define SPELL_ERROR_NOT_A_FRIEND 53 +#define SPELL_ERROR_NOT_AN_ENEMY 54 +#define SPELL_ERROR_TARGET_INVENTORY_FULL 55 +#define SPELL_ERROR_FINISH_DUELING_FIRST 56 +#define SPELL_ERROR_ILLEGAL_TARGET_ATTACK 57 +#define SPELL_ERROR_NOT_WHILE_MENTORING_PVP 58 +#define SPELL_ERROR_NOT_WHILE_MENTORING_BENEFICIAL 59 +#define SPELL_ERROR_ILLEGAL_TARGET_HEAL_OUTSIDE_LEVEL_RANGE 60 +#define SPELL_ERROR_NOTHING_TO_CURE 61 +#define SPELL_ERROR_NOT_ENOUGH_POWER 62 +#define SPELL_ERROR_NOT_ENOUGH_HEALTH 63 +#define SPELL_ERROR_NOT_ENOUGH_CONC 64 +#define SPELL_ERROR_MISSING_COMPONENT 65 +#define SPELL_ERROR_OUT_OF_CHARGES 66 +#define SPELL_ERROR_LACK_AMMO 67 +#define SPELL_ERROR_NO_RANGED_EQUIPPED 68 +#define SPELL_ERROR_RANGED_NEEDS_REPAIR 69 +#define SPELL_ERROR_LACK_WEAPON_TYPE 70 +#define SPELL_ERROR_NOT_ENOUGH_SAVAGERY 71 +#define SPELL_ERROR_ALREADY_PREPARED 72 +#define SPELL_ERROR_ALREADY_HAVE_SPELL 73 +#define SPELL_ERROR_NOT_SMART_ENOUGH 74 // "You lack the intellectual capacity to prepare another spell." +#define SPELL_ERROR_NO_HOSTILE_SPELLS 75 +#define SPELL_ERROR_NO_BENEFICIAL_SPELLS 76 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_SITTING 77 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_DEAD 78 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_CLIMBING 79 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_FORM 80 +#define SPELL_ERROR_CANNOT_MOUNT_NOW_WATER_TO_DEEP 81 +#define SPELL_ERROR_ALREADY_CAST 82 +#define SPELL_ERROR_LOTTERY_IN_PROGRESS 83 +#define SPELL_ERROR_NOT_IN_PVP 84 +#define SPELL_ERROR_NOT_ENOUGH_DISSONANCE 85 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_UNKNOWN_FAILURE 86 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_SPELL_TEMPLATE 87 +#define SPELL_ERROR_NOT_PREPARED_BUG 88 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_NO_GAME_WORLD 89 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_NO_OWNER 90 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_OWNER_TYPE 91 +#define SPELL_ERROR_NOT_CANNOT_CAST_BUG_NO_CASTER 92 +#define SPELL_ERROR_NO_RESPONSE_10 93 +#define SPELL_ERROR_BUG_PARTIAL_INTERUPT 94 +#define SPELL_ERROR_NO_RESPONSE_15 95 +#define SPELL_ERROR_BUG_TARGET_RESISTED 96 +#define SPELL_ERROR_BUG_TARGET_REFLECTED 97 +#define SPELL_ERROR_NO_RESPONSE_18 98 +#define SPELL_ERROR_NO_RESPONSE_35 99 +#define SPELL_ERROR_BUG_UNKNOWN_43 100 +#define SPELL_ERROR_BUG_UNKNOWN_44 101 +#define SPELL_ERROR_BUG_UNKNOWN_47 102 +#define SPELL_ERROR_TARGET_IMMUNE_HEALED_WITH_REPAIRS 103 +#define SPELL_ERROR_NOT_WHILE_MENTORING 104 +#define SPELL_ERROR_BUG_NO_EFFECTS_LANDED 105 +#define SPELL_ERROR_TOO_MUCH_DISSONANCE 106 +#define SPELL_ERROR_BUG_INVALID_SPELL_INDEX 107 +#define SPELL_ERROR_CANNOT_CAST_NOT_FOUND_95 108 +#define SPELL_ERROR_BUG_CONTAINMENT_TYPE 109 +#define SPELL_ERROR_BUG_SLOT_FULL 110 +#define SPELL_ERROR_CANNOT_CAST_NO_SPELL_101 111 +#define SPELL_ERROR_RECOVERING_ITEM_ABILITY 112 +#define SPELL_ERROR_NO_RESPONSE_110 113 +#define SPELL_ERROR_ALREADY_CAST_ON_TARGET 114 + +#define CASTING_FLAG_MEZZED 1 +#define CASTING_FLAG_STIFLED 2 +#define CASTING_FLAG_STUNNED 4 +#define CASTING_FLAG_FEARED 8 + +// Spell type is for AI so code knows what a spell is +#define SPELL_TYPE_UNSET 1 +#define SPELL_TYPE_DD 2 +#define SPELL_TYPE_DOT 3 +#define SPELL_TYPE_HEAL 4 +#define SPELL_TYPE_HOT_WARD 5 +#define SPELL_TYPE_DEBUFF 6 +#define SPELL_TYPE_BUFF 7 +#define SPELL_TYPE_COMBATBUFF 8 +#define SPELL_TYPE_TAUNT 9 +#define SPELL_TYPE_DETAUNT 10 +#define SPELL_TYPE_REZ 11 +#define SPELL_TYPE_CURE 12 +#define SPELL_TYPE_FOOD 13 +#define SPELL_TYPE_DRINK 14 +#define SPELL_TYPE_ROOT 15 +#define SPELL_TYPE_SNARE 16 +#define SPELL_TYPE_ALLGROUPTARGETS 17 + + +struct LUAData{ + int8 type; + sint32 int_value; + bool bool_value; + float float_value; + string string_value; + string string_value2; + sint32 int_value2; + float float_value2; + string string_helper; +}; +struct SpellScriptTimer { + LuaSpell* spell; + string customFunction; + int32 time; + int32 caster; + int32 target; + bool deleteWhenDone; +}; +struct LevelArray{ + int8 adventure_class; + int8 tradeskill_class; + int16 spell_level; +}; +struct SpellDisplayEffect{ + int8 percentage; + int8 subbullet; + string description; +}; + +enum GivenByType { + GivenBy_Unset = 0, + GivenBy_TradeskillClass = 1, + GivenBy_SpellScroll = 2, + GivenBy_AltAdvancement = 3, + GivenBy_Race = 4, + GivenBy_RacialInnate = 5, + GivenBy_RacialTradition = 6, + GivenBy_Class = 7, + GivenBy_CharacterTrait = 8, + GivenBy_FocusAbility = 9, + GivenBy_ClassTraining = 10, + GivenBy_WarderSpell = 11 +}; + +struct SpellData{ + int32 spell_book_type; + int32 id; + int32 inherited_spell_id; + sint16 icon; + int16 icon_heroic_op; + int16 icon_backdrop; + int16 type; + int32 class_skill; + int16 min_class_skill_req; + int32 mastery_skill; + int8 ts_loc_index; + int8 num_levels; + int8 tier; + int16 hp_req; + int16 hp_upkeep; + float power_req; + bool power_by_level; + int16 power_upkeep; + int16 savagery_req; + int16 savagery_upkeep; + int16 dissonance_req; + int16 dissonance_upkeep; + int8 target_type; + int16 cast_time; + int16 orig_cast_time; + float recovery; + float recast; + int32 linked_timer; + float radius; + int16 max_aoe_targets; + int8 friendly_spell; + int16 req_concentration; + float range; + int32 duration1; + int32 duration2; + float resistibility; + bool duration_until_cancel; + int8 power_req_percent; + int8 hp_req_percent; + int8 savagery_req_percent; + int8 dissonance_req_percent; + EQ2_8BitString name; + EQ2_16BitString description; + string success_message; + string fade_message; + string fade_message_others; + int8 cast_type; + string lua_script; + int32 call_frequency; + bool interruptable; + int32 spell_visual; + string effect_message; + float min_range; + int8 can_effect_raid; + int8 affect_only_group_members; + int8 group_spell; + float hit_bonus; + int8 display_spell_tier; + int8 is_active; + int8 det_type; + bool incurable; + int8 control_effect_type; + int32 casting_flags; + bool cast_while_moving; + bool persist_through_death; + bool not_maintained; + bool is_aa; + int8 savage_bar; + int8 savage_bar_slot; + int32 soe_spell_crc; + int8 spell_type; + int32 spell_name_crc; + sint32 type_group_spell_id; + bool can_fizzle; + EQ2_8BitString given_by; + GivenByType given_by_type; +}; + +class Spell{ +public: + ~Spell(); + Spell(); + Spell(SpellData* in_spell); + Spell(Spell* host_spell, bool unique_spell = true); + EQ2Packet* SerializeSpell(Client* client, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0, bool send_partial_packet = false); + EQ2Packet* SerializeSpecialSpell(Client* client, bool display, int8 packet_type = 0, int8 sub_packet_type = 0); + EQ2Packet* SerializeAASpell(Client* client,int8 tier, AltAdvanceData* data, bool display, bool trait_display = false, int8 packet_type = 0, int8 sub_packet_type = 0, const char* struct_name = 0); + void AddSpellLevel(int8 adventure_class, int8 tradeskill_class, int16 level); + void AddSpellEffect(int8 percentage, int8 subbullet, string description); + void AddSpellLuaData(int8 type, int int_value, int int_value2, float float_value, float float_value2, bool bool_value, string string_value,string string_value2, string helper); + void AddSpellLuaDataInt(int value, int value2, string helper); + void AddSpellLuaDataFloat(float value, float value2, string helper); + void AddSpellLuaDataBool(bool value, string helper); + void AddSpellLuaDataString(string value, string value2, string helper); + int32 GetSpellID(); + sint16 TranslateClientSpellIcon(int16 version); + void SetPacketInformation(PacketStruct* packet, Client* client = 0, bool display_tier = false); + void SetAAPacketInformation(PacketStruct* packet, AltAdvanceData* data, Client* client = 0, bool display_tier = false); + void AppendLevelInformation(PacketStruct* packet); + int8 GetSpellTier(); + int32 GetSpellDuration(); + int16 GetSpellIcon(); + int16 GetSpellIconBackdrop(); + int16 GetSpellIconHeroicOp(); + int16 GetLevelRequired(Player* player); + int16 GetHPRequired(Spawn* spawn); + int16 GetPowerRequired(Spawn* spawn); + int16 GetSavageryRequired(Spawn* spawn); + int16 GetDissonanceRequired(Spawn* spawn); + SpellData* GetSpellData(); + bool GetSpellData(lua_State* state, std::string field); + bool SetSpellData(lua_State* state, std::string field, int8 fieldArg); + bool ScribeAllowed(Player* player); + vector* GetLUAData(); + vector * GetSpellLevels(); + vector * GetSpellEffects(); + const char* GetName(); + const char* GetDescription(); + bool IsHealSpell(); + bool IsBuffSpell(); + bool IsDamageSpell(); + bool IsControlSpell(); + bool IsOffenseSpell(); + bool IsCopiedSpell(); + void ModifyCastTime(Entity* caster); + int32 CalculateRecastTimer(Entity* caster, float override_timer = 0.0f); + bool CastWhileStunned(); + bool CastWhileMezzed(); + bool CastWhileStifled(); + bool CastWhileFeared(); + bool GetStayLocked() { return stay_locked; } + void StayLocked(bool val) { stay_locked = val; } + + vector effects; + vector lua_data; + + mutable std::shared_mutex MSpellInfo; +private: + bool stay_locked = false; + bool heal_spell; + bool buff_spell; + bool damage_spell; + bool control_spell; + bool offense_spell; + bool copied_spell; + + SpellData* spell; + + //vector effects; + vector levels; +}; +class MasterSpellList{ +public: + MasterSpellList(); + ~MasterSpellList(); + void DestroySpells(); + map spell_name_map; + map > spell_list; + map > class_spell_list[MAX_CLASSES]; + map spell_soecrc_map; + Spell* GetSpell(int32 id, int8 tier); + vector* GetSpellListByAdventureClass(int8 class_id, int16 max_level, int8 max_tier); + vector* GetSpellListByTradeskillClass(int8 class_id, int16 max_level, int8 max_tier); + Spell* GetSpellByName(const char* name); + Spell* GetSpellByCRC(int32 spell_crc); + void Reload(); + EQ2Packet* GetSpellPacket(int32 id, int8 tier, Client* client = 0, bool display = false, int8 packet_type = 0); + EQ2Packet* GetAASpellPacket(int32 id, int8 group, Client* client, bool display, int8 packet_type); + EQ2Packet* GetSpecialSpellPacket(int32 id, int8 tier, Client* client = 0, bool display = false, int8 packet_type = 0); + void AddSpell(int32 id, int8 tier, Spell* spell); + Mutex MMasterSpellList; + + /// Gets the correct spell error value for the given version + /// Client version + /// ID of the error + /// The int16 value for the given error and version + int16 GetSpellErrorValue(int16 version, int8 error_index); + + /// Adds a spell error to the list + /// Client version for the error + /// ID for the error + /// Value for the error + void AddSpellError(int16 version, int8 error_index, int16 error_value); + + int32 GetNewMaxSpellID() { + int32 id = 0; + MMasterSpellList.lock(); + max_spell_id++; + id = max_spell_id; + MMasterSpellList.unlock(); + return id; + } +private: + /// Helper function that gets the closest version in the spell_errors map that is less then or equal to the given version + /// Client version + /// int16 version that is closest to the given version + int16 GetClosestVersion(int16 version); + // map > + map > spell_errors; + int32 max_spell_id; +}; +#endif + diff --git a/source/WorldServer/Titles.cpp b/source/WorldServer/Titles.cpp new file mode 100644 index 0000000..8c0e39c --- /dev/null +++ b/source/WorldServer/Titles.cpp @@ -0,0 +1,162 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include +#include +#include "Titles.h" +#include "../common/MiscFunctions.h" + +Title::Title(){ + id = 0; + memset(name, 0, sizeof(name)); + prefix = 0; + save_needed = false; +} + +Title::Title(Title* title){ + id = title->id; + strncpy(name, title->GetName(), sizeof(name)); + prefix = title->prefix; + save_needed = title->save_needed; +} + +Title::~Title(){ +} + +MasterTitlesList::MasterTitlesList(){ + MMasterTitleMutex.SetName("MasterTitlesList::MMasterTitleMutex"); +} + +MasterTitlesList::~MasterTitlesList(){ + Clear(); +} + +void MasterTitlesList::Clear(){ + MMasterTitleMutex.writelock(); + map::iterator itr; + for(itr = titles_list.begin(); itr != titles_list.end(); itr++) + safe_delete(itr->second); + titles_list.clear(); + MMasterTitleMutex.releasewritelock(); +} + +void MasterTitlesList::AddTitle(Title* title){ + assert(title); + MMasterTitleMutex.writelock(); + if(titles_list.count(title->GetID()) == 0) + titles_list[title->GetID()] = title; + MMasterTitleMutex.releasewritelock(); +} + +int32 MasterTitlesList::Size(){ + int32 size = 0; + MMasterTitleMutex.readlock(); + size = titles_list.size(); + MMasterTitleMutex.releasereadlock(); + + return size; +} + +Title* MasterTitlesList::GetTitle(sint32 id){ + Title* title = 0; + + MMasterTitleMutex.readlock(); + if(titles_list.count(id) > 0) + title = titles_list[id]; + MMasterTitleMutex.releasereadlock(); + + return title; +} + +Title* MasterTitlesList::GetTitleByName(const char* title_name){ + Title* title = 0; + map::iterator itr; + MMasterTitleMutex.readlock(); + for(itr = titles_list.begin(); itr != titles_list.end(); itr++){ + Title* current_title = itr->second; + if(::ToLower(string(current_title->GetName())) == ::ToLower(string(title_name))){ + title = current_title; + break; + } + } + MMasterTitleMutex.releasereadlock(); + return title; +} + +PlayerTitlesList::PlayerTitlesList(){ + MPlayerTitleMutex.SetName("PlayerTitlesList::MPlayerTitleMutex"); +} + +PlayerTitlesList::~PlayerTitlesList(){ + MPlayerTitleMutex.writelock(); + vector::iterator itr; + for (itr = player_titles_list.begin(); itr != player_titles_list.end(); itr++) + safe_delete(*itr); + + player_titles_list.clear(); + MPlayerTitleMutex.releasewritelock(); +} + +Title* PlayerTitlesList::GetTitle(sint32 index){ + MPlayerTitleMutex.readlock(); + Title* title = 0; + Title* ret = 0; + if ( index < player_titles_list.size() ) + ret = player_titles_list[index]; + + MPlayerTitleMutex.releasereadlock(); + return ret; +} + +Title* PlayerTitlesList::GetTitleByName(const char* title_name){ + Title* resTitle = 0; + vector::iterator itr; + MPlayerTitleMutex.readlock(); + for(itr = player_titles_list.begin(); itr != player_titles_list.end(); itr++){ + Title* title = *itr; + if(::ToLower(string(title->GetName())) == ::ToLower(string(title_name))){ + resTitle = title; + break; + } + } + MPlayerTitleMutex.releasereadlock(); + return resTitle; +} + + +vector* PlayerTitlesList::GetAllTitles(){ + MPlayerTitleMutex.readlock(); + return &player_titles_list; +} + +void PlayerTitlesList::Add(Title* title){ + MPlayerTitleMutex.writelock(); + player_titles_list.push_back(title); + MPlayerTitleMutex.releasewritelock(); +} + +int32 PlayerTitlesList::Size(){ + int32 size = 0; + MPlayerTitleMutex.readlock(); + size = player_titles_list.size(); + MPlayerTitleMutex.releasereadlock(); + + return size; +} \ No newline at end of file diff --git a/source/WorldServer/Titles.h b/source/WorldServer/Titles.h new file mode 100644 index 0000000..78e6f80 --- /dev/null +++ b/source/WorldServer/Titles.h @@ -0,0 +1,83 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef TITLES_H_ +#define TITLES_H_ + +#include +#include +#include +#include "../common/Mutex.h" +#include "../common/types.h" + +using namespace std; + +class Title { +public: + Title(); + Title(Title* title); + ~Title(); + void SetID(int32 id) {this->id = id;} + void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));} + void SetPrefix(int8 prefix) {this->prefix = prefix;} + void SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;} + + sint32 GetID() {return id;} + const char* GetName() {return name;} + int8 GetPrefix() {return prefix;} + bool GetSaveNeeded() {return save_needed;} + +private: + sint32 id; + int8 prefix; + char name[256]; + bool save_needed; +}; + +class MasterTitlesList { +public: + MasterTitlesList(); + ~MasterTitlesList(); + void Clear(); + int32 Size(); + void AddTitle(Title* title); + Title* GetTitle(sint32 id); + Title* GetTitleByName(const char* title_name); + +private: + map titles_list; + Mutex MMasterTitleMutex; +}; + +class PlayerTitlesList { +public: + PlayerTitlesList(); + ~PlayerTitlesList(); + Title* GetTitle(sint32 index); + Title* GetTitleByName(const char* title_name); + vector* GetAllTitles(); + void Add(Title* title); + + int32 Size(); + void ReleaseReadLock() { MPlayerTitleMutex.releasereadlock(); } +private: + vector player_titles_list; + Mutex MPlayerTitleMutex; +}; +#endif \ No newline at end of file diff --git a/source/WorldServer/Trade.cpp b/source/WorldServer/Trade.cpp new file mode 100644 index 0000000..da9159e --- /dev/null +++ b/source/WorldServer/Trade.cpp @@ -0,0 +1,604 @@ +#include "Trade.h" +#include "Items/Items.h" +#include "Entity.h" +#include "Bots/Bot.h" +#include "../common/Log.h" +#include "Rules/Rules.h" + +extern ConfigReader configReader; +extern MasterItemList master_item_list; +extern RuleManager rule_manager; + +Trade::Trade(Entity* trader1, Entity* trader2) { + this->trader1 = trader1; + this->trader2 = trader2; + + trade_max_slots = 12; + + if(trader1->IsPlayer()) { + if(((Player*)trader1)->GetClient() && ((Player*)trader1)->GetClient()->GetVersion() <= 561) { + trade_max_slots = 6; + } + } + + if(trader2->IsPlayer()) { + if(((Player*)trader2)->GetClient() && ((Player*)trader2)->GetClient()->GetVersion() <= 561) { + trade_max_slots = 6; + } + } + trader1_accepted = false; + trader2_accepted = false; + + trader1_coins = 0; + trader2_coins = 0; + + + OpenTradeWindow(); +} + +Trade::~Trade() { + +} + +int8 Trade::AddItemToTrade(Entity* character, Item* item, int8 quantity, int8 slot) { + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) adding item (%u) to slot %u of the trade window", character->GetName(), item->details.item_id, slot); + if (slot == 255) + slot = GetNextFreeSlot(character); + + if (slot < 0) { + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add an item to an invalid trade slot (%u)", character->GetName(), slot); + return 255; + } + else if(slot >= trade_max_slots) { + return 254; + } + + Entity* other = GetTradee(character); + int8 result = CheckItem(character, item, other); + + if (result == 0) { + if (character == trader1) { + Trader1ItemAdd(item, quantity, slot); + // Only trader2 can be a bot so only + // need to do the bot check here + if (trader2->IsBot()) { + ((Bot*)trader2)->TradeItemAdded(item); + } + } + else if (character == trader2) + Trader2ItemAdd(item, quantity, slot); + else { + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add an item to a trade but was neither trader1 or trader2", character->GetName()); + return 255; + } + } + + SendTradePacket(); + return result; +} + +int8 Trade::CheckItem(Entity* trader, Item* item, Entity* other) { + int8 ret = 0; + map* list = 0; + map::iterator itr; + + bool other_is_bot = false; + + if(other) + other_is_bot = other->IsBot(); + + if (trader == trader1) + list = &trader1_items; + else if (trader == trader2) + list = &trader2_items; + + if (list) { + if (trader->IsPlayer()) { + // Check to see if the item is already in the trade + for (itr = list->begin(); itr != list->end(); itr++) { + if (itr->second.item->details.unique_id == item->details.unique_id) { + ret = 1; + break; + } + } + + // Only allow heirloom and no-trade items to be traded with a bot + if (!other_is_bot) { + if (item->CheckFlag(NO_TRADE)) + ret = 2; + if (item->CheckFlag2(HEIRLOOM)) { + if(other->IsPlayer() && item->grouped_char_ids.find(((Player*)other)->GetCharacterID()) != item->grouped_char_ids.end()) { + double diffInSeconds = 0.0; std::difftime(std::time(nullptr), item->created); + if(item->CheckFlag(ATTUNED) || ((diffInSeconds = std::difftime(std::time(nullptr), item->created)) && diffInSeconds >= rule_manager.GetGlobalRule(R_Player, HeirloomItemShareExpiration)->GetFloat())) { + ret = 3; // denied heirloom cannot be transferred to outside of group after 48 hours (by rule setting) or if already attuned + } + } + else { + ret = 3; // not part of the group/raid + } + } + } + } + } + + return ret; +} + +void Trade::RemoveItemFromTrade(Entity* character, int8 slot) { + map* list = 0; + + if (character == trader1) + list = &trader1_items; + else if (character == trader2) + list = &trader2_items; + + if (list) { + if (list->count(slot) > 0) { + list->erase(slot); + SendTradePacket(); + } + else + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to remove an item from a trade slot that was empty ", character->GetName()); + } +} + +void Trade::AddCoinToTrade(Entity* character, int64 amount) { + if (!character->IsPlayer()) + return; + + if (character == trader1) { + trader1_coins += amount; + if (!((Player*)character)->HasCoins(trader1_coins)) { + trader1_coins -= amount; + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add more coins then they had ", character->GetName()); + } + } + else if (character == trader2) { + trader2_coins += amount; + if (!((Player*)character)->HasCoins(trader2_coins)) { + trader2_coins -= amount; + LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add more coins then they had ", character->GetName()); + } + } + + SendTradePacket(); +} + +void Trade::RemoveCoinFromTrade(Entity* character, int64 amount) { + if (character == trader1) + trader1_coins = (amount >= trader1_coins) ? 0 : trader1_coins - amount; + else if (character == trader2) + trader2_coins = (amount >= trader2_coins) ? 0 : trader2_coins - amount; + + SendTradePacket(); +} + +Entity* Trade::GetTradee(Entity* character) { + if (character == trader1) + return trader2; + else if (character == trader2) + return trader1; + + return 0; +} + +bool Trade::SetTradeAccepted(Entity* character) { + if (character == trader1) + trader1_accepted = true; + else if (character == trader2) + trader2_accepted = true; + else + return false; + + Entity* other = GetTradee(character); + if (other) { + if (other->IsPlayer()) { + Client* client = ((Player*)other)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(character)); + packet->setDataByName("type", 16); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + else if (other->IsBot()) { + Client* client = ((Player*)character)->GetClient(); + if (trader1_coins > 0) { + CancelTrade(other); + if (client) + client->SimpleMessage(CHANNEL_ERROR, "Bots can't take coins so the trade was canceled."); + + return true; + } + else { + if (!((Bot*)other)->CheckTradeItems(&trader1_items)) { + CancelTrade(other); + if (client) + client->SimpleMessage(CHANNEL_ERROR, "There was an item the bot could not equip so the trade was canceled."); + + return true; + } + else + trader2_accepted = true; + } + } + + if (HasAcceptedTrade(other)) { + CompleteTrade(); + return true; + } + } + + return false; +} + +bool Trade::HasAcceptedTrade(Entity* character) { + if (character == trader1) + return trader1_accepted; + else if (character == trader2) + return trader2_accepted; + + return false; +} + +void Trade::CancelTrade(Entity* character) { + Entity* other = GetTradee(character); + if (other){ + if (other->IsPlayer()) { + Client* client = ((Player*)other)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(character)); + packet->setDataByName("type", 2); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + else if (other->IsBot()) + ((Bot*)other)->FinishTrade(); + } + + trader1->trade = 0; + trader2->trade = 0; +} + +void Trade::Trader1ItemAdd(Item* item, int8 quantity, int8 slot) { + trader1_items[slot].item = item; + trader1_items[slot].quantity = quantity; +} + +void Trade::Trader2ItemAdd(Item* item, int8 quantity, int8 slot) { + trader2_items[slot].item = item; + trader2_items[slot].quantity = quantity; +} + +Item* Trade::GetTraderSlot(Entity* trader, int8 slot) { + if(trader == GetTrader1()) { + map::iterator itr = trader1_items.find(slot); + if(itr != trader1_items.end()) { + return itr->second.item; + } + } + else if(trader == GetTrader2()) { + map::iterator itr = trader2_items.find(slot); + if(itr != trader2_items.end()) { + return itr->second.item; + } + } + return nullptr; +} + +void Trade::CompleteTrade() { + map::iterator itr; + vector trader1_item_pass; + vector::iterator itr2; + string log_string = "TradeComplete:\n"; + + if (trader1->IsPlayer()) { + Player* player = (Player*)trader1; + Client* client = ((Player*)player)->GetClient(); + if (client) { + log_string += "Trader1 = "; + log_string += trader1->GetName(); + log_string += "(" + to_string(client->GetCharacterID()) + ")\n"; + log_string += "Coins: " + to_string(trader1_coins) + "\n"; + log_string += "Items:\n"; + player->RemoveCoins(trader1_coins); + for (itr = trader1_items.begin(); itr != trader1_items.end(); itr++) { + // client->RemoveItem can delete the item so we need to store the item id's and quantity to give to trader2 + Item* newitem = new Item(itr->second.item); + newitem->details.count = itr->second.quantity; + trader1_item_pass.push_back(newitem); + + log_string += itr->second.item->name + " (" + to_string(itr->second.item->details.item_id) + ") x" + to_string(itr->second.quantity) + "\n"; + client->RemoveItem(itr->second.item, itr->second.quantity); + } + + player->AddCoins(trader2_coins); + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + Item* newitem = new Item(itr->second.item); + newitem->details.count = itr->second.quantity; + client->AddItem(newitem, nullptr); + } + + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader2)); + packet->setDataByName("type", 24); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + // trader1 is the player who starts the trade, will never be a bot, if trader1 is not a player something went horribly wrong. + + + if (trader2->IsPlayer()) { + Player* player = (Player*)trader2; + Client* client = ((Player*)player)->GetClient(); + if (client) { + log_string += "Trader2 = "; + log_string += trader2->GetName(); + log_string += "(" + to_string(client->GetCharacterID()) + ")\n"; + log_string += "Coins: " + to_string(trader2_coins) + "\n"; + log_string += "Items:\n"; + player->RemoveCoins(trader2_coins); + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + log_string += itr->second.item->name + " (" + to_string(itr->second.item->details.item_id) + ") x" + to_string(itr->second.quantity) + "\n"; + client->RemoveItem(itr->second.item, itr->second.quantity); + } + + player->AddCoins(trader1_coins); + for (itr2 = trader1_item_pass.begin(); itr2 != trader1_item_pass.end(); itr2++) { + client->AddItem(*itr2, nullptr); + } + + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader1)); + packet->setDataByName("type", 24); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + else if (trader2->IsBot()) { + Bot* bot = (Bot*)trader2; + log_string += "Trader2 is a bot"; + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + bot->RemoveItem(itr->second.item); + } + + for (itr2 = trader1_item_pass.begin(); itr2 != trader1_item_pass.end(); itr2++) { + bot->GiveItem(*itr2); + } + bot->FinishTrade(); + } + + LogWrite(PLAYER__INFO, 0, "Trade", log_string.c_str()); + + trader1->trade = 0; + trader2->trade = 0; +} + +void Trade::OpenTradeWindow() { + if (trader1->IsPlayer()) { + Client* client = ((Player*)trader1)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader2)); + packet->setDataByName("type", 1); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } + + if (trader2->IsPlayer()) { + Client* client = ((Player*)trader2)->GetClient(); + if(client) { + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader1)); + packet->setDataByName("type", 1); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void Trade::SendTradePacket() { + if (trader1->IsPlayer()) { + Client* client = ((Player*)trader1)->GetClient(); + if(!client) { + return; + } + + PacketStruct* packet = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader2)); + packet->setDataByName("type", 1); + + int8 size = (int8)(trader1_items.size()); + int8 i = 0; + map::iterator itr; + + packet->setArrayLengthByName("your_item_count", size); + for (itr = trader1_items.begin(); itr != trader1_items.end(); itr++) { + packet->setArrayDataByName("your_item_unknown1", 1, i); + packet->setArrayDataByName("your_item_unknown2", 1, i); + packet->setArrayDataByName("your_item_slot", itr->first, i); + packet->setArrayDataByName("your_item_id", itr->second.item->details.item_id, i); + packet->setArrayDataByName("your_item_name", itr->second.item->name.c_str(), i); + packet->setArrayDataByName("your_item_quantity", itr->second.quantity, i); + packet->setArrayDataByName("your_item_icon", itr->second.item->GetIcon(client->GetVersion()), i); + packet->setArrayDataByName("your_item_background", 0, i); // No clue on this value yet + i++; + } + int32 plat = 0; + int32 gold = 0; + int32 silver = 0; + int32 copper = 0; + + CalculateCoins(trader1_coins, plat, gold, silver, copper); + packet->setDataByName("your_copper", copper); + packet->setDataByName("your_silver", silver); + packet->setDataByName("your_gold", gold); + packet->setDataByName("your_plat", plat); + + + size = (int8)(trader2_items.size()); + i = 0; + + packet->setArrayLengthByName("their_item_count", size); + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + packet->setArrayDataByName("their_item_unknown1", 1, i); + packet->setArrayDataByName("their_item_unknown2", 1, i); + packet->setArrayDataByName("their_item_slot", itr->first, i); + packet->setArrayDataByName("their_item_id", itr->second.item->details.item_id, i); + packet->setArrayDataByName("their_item_name", itr->second.item->name.c_str(), i); + packet->setArrayDataByName("their_item_quantity", itr->second.quantity, i); + packet->setArrayDataByName("their_item_icon", itr->second.item->GetIcon(client->GetVersion()), i); + packet->setArrayDataByName("their_item_background", 0, i); // No clue on this value yet + i++; + } + + plat = 0; + gold = 0; + silver = 0; + copper = 0; + + CalculateCoins(trader2_coins, plat, gold, silver, copper); + packet->setDataByName("their_copper", copper); + packet->setDataByName("their_silver", silver); + packet->setDataByName("their_gold", gold); + packet->setDataByName("their_plat", plat); + + LogWrite(PLAYER__ERROR, 0, "Trade", "packet sent"); + packet->PrintPacket(); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + + if (trader2->IsPlayer()) { + Client* client = ((Player*)trader2)->GetClient(); + if(!client) { + return; + } + PacketStruct* packet2 = configReader.getStruct("WS_PlayerTrade", client->GetVersion()); + if (packet2) { + packet2->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(trader1)); + packet2->setDataByName("type", 1); + + int8 size = (int8)(trader2_items.size()); + int8 i = 0; + map::iterator itr; + + packet2->setArrayLengthByName("your_item_count", size); + for (itr = trader2_items.begin(); itr != trader2_items.end(); itr++) { + packet2->setArrayDataByName("your_item_unknown1", 1, i); + packet2->setArrayDataByName("your_item_unknown2", 1, i); + packet2->setArrayDataByName("your_item_slot", itr->first, i); + packet2->setArrayDataByName("your_item_id", itr->second.item->details.item_id, i); + packet2->setArrayDataByName("your_item_name", itr->second.item->name.c_str(), i); + packet2->setArrayDataByName("your_item_quantity", itr->second.quantity, i); + packet2->setArrayDataByName("your_item_icon", itr->second.item->GetIcon(client->GetVersion()), i); + packet2->setArrayDataByName("your_item_background", 0, i); // No clue on this value yet + i++; + } + int32 plat = 0; + int32 gold = 0; + int32 silver = 0; + int32 copper = 0; + + CalculateCoins(trader2_coins, plat, gold, silver, copper); + packet2->setDataByName("your_copper", copper); + packet2->setDataByName("your_silver", silver); + packet2->setDataByName("your_gold", gold); + packet2->setDataByName("your_plat", plat); + + size = (int8)(trader1_items.size()); + i = 0; + + packet2->setArrayLengthByName("their_item_count", size); + for (itr = trader1_items.begin(); itr != trader1_items.end(); itr++) { + packet2->setArrayDataByName("their_item_unknown1", 1, i); + packet2->setArrayDataByName("their_item_unknown2", 1, i); + packet2->setArrayDataByName("their_item_slot", itr->first, i); + packet2->setArrayDataByName("their_item_id", itr->second.item->details.item_id, i); + packet2->setArrayDataByName("their_item_name", itr->second.item->name.c_str(), i); + packet2->setArrayDataByName("their_item_quantity", itr->second.quantity, i); + packet2->setArrayDataByName("their_item_icon", itr->second.item->GetIcon(client->GetVersion()), i); + packet2->setArrayDataByName("their_item_background", 0, i); // No clue on this value yet + i++; + } + + plat = 0; + gold = 0; + silver = 0; + copper = 0; + + CalculateCoins(trader1_coins, plat, gold, silver, copper); + packet2->setDataByName("their_copper", copper); + packet2->setDataByName("their_silver", silver); + packet2->setDataByName("their_gold", gold); + packet2->setDataByName("their_plat", plat); + + LogWrite(PLAYER__ERROR, 0, "Trade", "packet sent #2"); + packet2->PrintPacket(); + client->QueuePacket(packet2->serialize()); + safe_delete(packet2); + } + } +} + +void Trade::CalculateCoins(int64 val, int32& plat, int32& gold, int32& silver, int32& copper) { + int32 tmp = 0; + if (val >= 1000000) { + tmp = val / 1000000; + val -= tmp * 1000000; + plat = tmp; + } + if (val >= 10000) { + tmp = val / 10000; + val -= tmp * 10000; + gold = tmp; + } + if (val >= 100) { + tmp = val / 100; + val -= tmp * 100; + silver = tmp; + } + if (val > 0) { + copper = val; + } +} + +int8 Trade::GetNextFreeSlot(Entity* character) { + map* list = 0; + + if (character == trader1) + list = &trader1_items; + else if (character == trader2) + list = &trader2_items; + else + return 255; + + int8 ret = 255; + for (int8 index = 0; index < 12; index++) { + if (list->count(index) == 0) { + ret = index; + break; + } + } + + return ret; +} \ No newline at end of file diff --git a/source/WorldServer/Trade.h b/source/WorldServer/Trade.h new file mode 100644 index 0000000..2292a4f --- /dev/null +++ b/source/WorldServer/Trade.h @@ -0,0 +1,57 @@ +#pragma once + +#include "../common/types.h" +#include + +class Item; +class Entity; + +struct TradeItemInfo { + Item* item; + int32 quantity; +}; + +class Trade { +public: + Trade(Entity* trader1, Entity* trader2); + ~Trade(); + + int8 AddItemToTrade(Entity* character, Item* item, int8 quantity, int8 slot); + void RemoveItemFromTrade(Entity* character, int8 slot); + void AddCoinToTrade(Entity* character, int64 amount); + void RemoveCoinFromTrade(Entity* character, int64 amount); + Entity* GetTradee(Entity* character); + + bool SetTradeAccepted(Entity* character); + bool HasAcceptedTrade(Entity* character); + void CancelTrade(Entity* character); + + int8 CheckItem(Entity* trader, Item* item, Entity* other); + + Item* GetTraderSlot(Entity* trader, int8 slot); + Entity* GetTrader1() { return trader1; } + Entity* GetTrader2() { return trader2; } + + int8 MaxSlots() { return trade_max_slots; } +private: + + + void Trader1ItemAdd(Item* item, int8 quantity, int8 slot); + void Trader2ItemAdd(Item* item, int8 quantity, int8 slot); + void CompleteTrade(); + void OpenTradeWindow(); + void SendTradePacket(); + void CalculateCoins(int64 val, int32& plat, int32& gold, int32& silver, int32& copper); + int8 GetNextFreeSlot(Entity* character); + + Entity* trader1; + map trader1_items; + int64 trader1_coins; + bool trader1_accepted; + + Entity* trader2; + map trader2_items; + int64 trader2_coins; + bool trader2_accepted; + int32 trade_max_slots; +}; \ No newline at end of file diff --git a/source/WorldServer/Tradeskills/Tradeskills.cpp b/source/WorldServer/Tradeskills/Tradeskills.cpp new file mode 100644 index 0000000..19cac6b --- /dev/null +++ b/source/WorldServer/Tradeskills/Tradeskills.cpp @@ -0,0 +1,869 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include +#include + +#include "Tradeskills.h" +#include "../client.h" +#include "../../common/ConfigReader.h" +#include "../classes.h" +//#include "../../common/debug.h" +#include "../../common/Log.h" +//#include "../zoneserver.h" +//#include "../Skills.h" +//#include "../classes.h" +#include "../World.h" +//#include "../LuaInterface.h" +#include "../ClientPacketFunctions.h" +#include "../WorldDatabase.h" +#include "../Rules/Rules.h" + +extern Classes classes; +extern ConfigReader configReader; +extern MasterSkillList master_skill_list; +extern MasterRecipeList master_recipe_list; +extern MasterTradeskillEventsList master_tradeskillevent_list; +extern WorldDatabase database; +extern RuleManager rule_manager; + +TradeskillMgr::TradeskillMgr() { + m_tradeskills.SetName("TradeskillMgr::tradeskillsList"); + // % chance for each was made up by me (Jabantiz) and may need some tweaking + // 2% for crit fail + m_success = rule_manager.GetGlobalRule(R_World, TradeskillSuccessChance)->GetFloat(); + m_critSuccess = rule_manager.GetGlobalRule(R_World, TradeskillCritSuccessChance)->GetFloat(); + m_fail = rule_manager.GetGlobalRule(R_World, TradeskillFailChance)->GetFloat(); + m_critFail = rule_manager.GetGlobalRule(R_World, TradeskillCritFailChance)->GetFloat(); + m_eventChance = rule_manager.GetGlobalRule(R_World, TradeskillEventChance)->GetFloat(); + + if ((m_success + m_critSuccess + m_fail + m_critFail) != 100.0f) { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "Success, crit success, fail, and crit fail MUST add up to 100, reverting to defaults..."); + m_success = 87.0f; + m_critSuccess = 2.0f; + m_fail = 10.0f; + m_critFail = 1.0f; + } +} + +TradeskillMgr::~TradeskillMgr() { + m_tradeskills.writelock(__FUNCTION__, __LINE__); + + map::iterator itr; + for (itr = tradeskillList.begin(); itr != tradeskillList.end(); itr++) + safe_delete(itr->second); + + tradeskillList.clear(); + + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); +} + +void TradeskillMgr::Process() { + m_tradeskills.writelock(__FUNCTION__, __LINE__); + map::iterator itr = tradeskillList.begin(); + while (itr != tradeskillList.end()) { + Tradeskill* tradeskill = 0; + tradeskill = itr->second; + if (!tradeskill) + continue; + if (Timer::GetCurrentTime2() >= tradeskill->nextUpdateTime) { + Client* client = itr->first; + if(!client->GetPlayer()) { + continue; + } + SetClientIdleVisualState(client, tradeskill); + + sint32 progress = 0; + sint32 durability = 0; + /* + Following was grabbed from + http://eq2.stratics.com/content/guides/padasher_crafting_2.php + old but the base fail/succes should still be the same + + -100 Durability / -50 Progress (Critical Failure) + -50 Durability / 0 Progress (Failure) + -10 Durability / +50 Progress (Standard tick) + +10 Durability / + 100 Progress (Critical Success) + */ + float roll = MakeRandomFloat(0, 100); + int8 effect = 0; //1 is critical success, 2 is success, 3 is failure, and 4 is critical failure. + + float success = m_success; + float crit_success = m_critSuccess; + float fail = m_fail; + float crit_fail = m_critFail; + + // Modify the % chance for success based off of stats + client->GetPlayer()->MStats.lock(); + fail -= client->GetPlayer()->stats[ITEM_STAT_SUCCESS_MOD]; + success += client->GetPlayer()->stats[ITEM_STAT_SUCCESS_MOD]; + client->GetPlayer()->MStats.unlock(); + + // add values together for the if + crit_success += crit_fail; + fail += crit_success; + success += fail; + + // Crit fail + if (roll <= crit_fail) { + progress = -50; + durability = -100; + effect = 4; + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Critical failure!"); + } + // Crit success + else if (roll > crit_fail && roll <= crit_success) { + progress = 100; + durability = 10; + effect = 1; + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Critical success!"); + } + // Fail + else if (roll > crit_success && roll <= fail) { + progress = 0; + durability = -50; + effect = 3; + } + // Success + else if (roll > fail && roll <= success) { + progress = 50; + durability = -10; + effect = 2; + } + else { + // Just a debug, should never end up in this, if we do write out a log but treat as a success for the player + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "Process roll was not within valid range. roll = %f, crit fail = %f, crit success = %f, fail = %f, success = %f", roll, crit_fail, crit_success, fail, success); + progress = 50; + durability = -10; + effect = 2; + } + + // Check to see if there was an event, if there was give out the rewards/penalties for it + if (tradeskill->CurrentEvent) { + if (tradeskill->eventCountered) { + progress += tradeskill->CurrentEvent->SuccessProgress; + durability += tradeskill->CurrentEvent->SuccessDurability; + } + else { + progress += tradeskill->CurrentEvent->FailProgress; + durability += tradeskill->CurrentEvent->FailDurability; + } + } + + // Modify the progress/durability by the players stats + client->GetPlayer()->MStats.lock(); + progress += client->GetPlayer()->stats[ITEM_STAT_PROGRESS_ADD]; + durability += client->GetPlayer()->stats[ITEM_STAT_DURABILITY_ADD]; + client->GetPlayer()->MStats.unlock(); + + tradeskill->currentDurability += durability; + tradeskill->currentProgress += progress; + + PacketStruct* packet = configReader.getStruct("WS_UpdateCreateItem", client->GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(tradeskill->table)); + packet->setDataByName("effect", effect); + packet->setDataByName("total_durability", tradeskill->currentDurability); + packet->setDataByName("total_progress", tradeskill->currentProgress); + packet->setDataByName("durability_change", durability); + packet->setDataByName("progress_change", progress); + + if (tradeskill->currentProgress >= 1000) + packet->setDataByName("progress_level", 4); + else if (tradeskill->currentProgress >= 800) + packet->setDataByName("progress_level", 3); + else if (tradeskill->currentProgress >= 600) + packet->setDataByName("progress_level", 2); + else if (tradeskill->currentProgress >= 400) + packet->setDataByName("progress_level", 1); + else + packet->setDataByName("progress_level", 0); + + // Reset the tradeskill event + tradeskill->CurrentEvent = 0; + tradeskill->eventChecked = false; + tradeskill->eventCountered = false; + + // 15% chance for an event (change this to a rule probably) + + int eventRoll = MakeRandomFloat(0, 100); + if (eventRoll <= m_eventChance) { + // Get a vector of all possible events for this crafting technique + vector* events = master_tradeskillevent_list.GetEventByTechnique(tradeskill->recipe->GetTechnique()); + if (events) { + // Get the size of the vector + int size = events->size(); + // Get a random number from 0 to size - 1 to use as an index + int index = MakeRandomInt(0, size - 1); + // use the index to get an event + TradeskillEvent* TSEvent = events->at(index); + if (TSEvent) { + // Now that we got a random event set it in the packet + packet->setDataByName("reaction_icon", TSEvent->Icon); + packet->setDataByName("reaction_name", TSEvent->Name); + + // Set the current tradeskill event + tradeskill->CurrentEvent = TSEvent; + } + } + } + EQ2Packet* pack = packet->serialize(); + //packet->PrintPacket(); + client->QueuePacket(pack); + safe_delete(packet); + } + + if (tradeskill->currentProgress >= 1000) { + itr++; + StopCrafting(client, false); + continue; + } + else + tradeskill->nextUpdateTime = Timer::GetCurrentTime2() + 4000; + } + itr++; + } + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); +} + +void TradeskillMgr::BeginCrafting(Client* client, vector> components) { + Recipe* recipe = master_recipe_list.GetRecipe(client->GetPlayer()->GetCurrentRecipe()); + + if (!recipe) { + LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Recipe (%u) not found in TradeskillMgr::BeginCrafting()", client->GetPlayer()->GetCurrentRecipe()); + ClientPacketFunctions::StopCrafting(client); + return; + } + + // TODO: use the vecotr to lock inventory slots + vector>::iterator itr; + bool missingItem = false; + int32 itemid = 0; + vector tmpItems; + for (itr = components.begin(); itr != components.end(); itr++) { + itemid = itr->first; + Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(itemid); + int8 qty_req = 0; + if(!item) + { + missingItem = true; + break; + } + + item->details.item_locked = true; + tmpItems.push_back(item); + } + + if(!recipe->ProvidedAllRequiredComponents(client, &tmpItems, &components)) { + LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Recipe (%u) quantity of items incorrect or component missing.", recipe->GetID()); + missingItem = true; + } + + if (missingItem) { + LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Recipe (%u) player missing item when attempting to create recipe.", recipe->GetID()); + vector::iterator itemitr; + for (itemitr = tmpItems.begin(); itemitr != tmpItems.end(); itemitr++) { + Item* tmpItem = *itemitr; + tmpItem->details.item_locked = false; + } + ClientPacketFunctions::StopCrafting(client); + return; + } + + ClientPacketFunctions::SendItemCreationUI(client, recipe); + Tradeskill* tradeskill = new Tradeskill; + tradeskill->player = client->GetPlayer(); + tradeskill->table = client->GetPlayer()->GetTarget(); + tradeskill->recipe = recipe; + tradeskill->currentDurability = 1000; + tradeskill->currentProgress = 0; + tradeskill->nextUpdateTime = Timer::GetCurrentTime2() + 500; + tradeskill->usedComponents = components; + tradeskill->CurrentEvent = 0; + tradeskill->eventChecked = false; + tradeskill->eventCountered = false; + m_tradeskills.writelock(__FUNCTION__, __LINE__); + tradeskillList.insert(make_pair(client, tradeskill)); + + SetClientIdleVisualState(client, tradeskill); + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + + // Unlock TS Spells and lock all others + client->GetPlayer()->UnlockTSSpells(); + + client->ClearSentItemDetails(); + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); +} + +void TradeskillMgr::StopCrafting(Client* client, bool lock) { + + if (lock) + m_tradeskills.writelock(__FUNCTION__, __LINE__); + + if (tradeskillList.count(client) == 0) { + if (lock) + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + return; + } + + Tradeskill* tradeskill = 0; + tradeskill = tradeskillList[client]; + + //TODO: unlock inventory slots, give the product to the player, give tradeskill xp + ClientPacketFunctions::StopCrafting(client); + + int32 dur = tradeskill->currentDurability; + int32 progress = tradeskill->currentProgress; + Recipe* recipe = tradeskill->recipe; + vector>::iterator itr; + Item* item = 0; + int32 item_id = 0; + int8 i = 0; + int8 qty = 0; + + Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID()); + + if(!playerRecipe) + { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting Error finding player recipe in their recipe book for recipe id %u", client->GetPlayer()->GetName(), recipe->GetID()); + client->Message(CHANNEL_COLOR_RED, "%s: StopCrafting Error finding player recipe in their recipe book for recipe id %u!", client->GetPlayer()->GetName(), recipe->GetID()); + if (lock) + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + return; + } + bool updateInvReq = false; + // cycle through the list of used items and remove them + for (itr = tradeskill->usedComponents.begin(); itr != tradeskill->usedComponents.end(); itr++, i++) { + // Get the item in the players inventory and remove or reduce the quantity + int32 itmid = itr->first; + qty = itr->second > 0 ? itr->second : 1; + item = client->GetPlayer()->item_list.GetItemFromUniqueID(itmid); + if (item && item->details.count <= qty) + { + item->details.item_locked = false; + client->GetPlayer()->item_list.RemoveItem(item); + updateInvReq = true; + } + else if(item) { + item->details.count -= qty; + item->details.item_locked = false; + item->save_needed = true; + updateInvReq = true; + } + else + { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting Error finding item %u to remove quantity for recipe id %u", client->GetPlayer()->GetName(), itmid, recipe->GetID()); + client->Message(CHANNEL_COLOR_RED, "%s: StopCrafting Error finding item %u to remove quantity for recipe id %u!", client->GetPlayer()->GetName(), itmid, recipe->GetID()); + } + } + + if(updateInvReq) + { + EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion()); + if (outapp) + client->QueuePacket(outapp); + } + + item = 0; + qty = recipe->GetFuelComponentQuantity(); + item_id = recipe->components[5][0]; + int32 byproduct_itemid = 0; + int16 byproduct_qty = 0; + float tsx = 0; + bool success = false; + int8 HS = playerRecipe->GetHighestStage(); + if (progress < 400) { //stage 0 + if (recipe->products.count(0) > 0) { + item_id = recipe->products[0]->product_id; + qty = recipe->products[0]->product_qty; + byproduct_itemid = recipe->products[0]->byproduct_id; + byproduct_qty = recipe->products[0]->byproduct_qty; + } + tsx = 1; + } + else if (progress >= 400 && progress < 600) { //stage 1 + if (HS & (1 << (1 - 1))) { + } + else { + playerRecipe->SetHighestStage(HS + 1 ); + database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), playerRecipe->GetHighestStage()); + } + if (recipe->products.count(1) > 0) { + item_id = recipe->products[1]->product_id; + qty = recipe->products[1]->product_qty; + byproduct_itemid = recipe->products[1]->byproduct_id; + byproduct_qty = recipe->products[1]->byproduct_qty; + } + tsx = .45; + } + //else if (progress >= 600 && progress < 800) { //stage 2 + else if ((dur < 200 && progress >= 600) || (dur >= 200 && progress >= 600 && progress < 800)) { //stage 2 + if (HS & (1 << (2 - 1))) { + } + else { + playerRecipe->SetHighestStage(HS + 2); + database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), playerRecipe->GetHighestStage()); + } + if (recipe->products.count(2) > 0) { + item_id = recipe->products[2]->product_id; + qty = recipe->products[2]->product_qty; + byproduct_itemid = recipe->products[2]->byproduct_id; + byproduct_qty = recipe->products[2]->byproduct_qty; + } + tsx = .30; + } + else if ((dur >= 200 && dur < 800 && progress >= 800) || (dur >= 800 && progress >= 800 && progress < 1000)) { // stage 3 + //else if (progress >= 800 && progress < 1000) { // stage 3 + if (HS & (1 << (3 - 1))) { + } + else { + playerRecipe->SetHighestStage(HS + 4); + database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), playerRecipe->GetHighestStage()); + } + if (recipe->products.count(3) > 0) { + item_id = recipe->products[3]->product_id; + qty = recipe->products[3]->product_qty; + byproduct_itemid = recipe->products[3]->byproduct_id; + byproduct_qty = recipe->products[3]->byproduct_qty; + } + tsx = .15; + } + else if (dur >= 800 && progress >= 1000) { // stage 4 + //else if (progress >= 1000) { // stage 4 + if (HS & (1 << (4 - 1))) { + } + else { + playerRecipe->SetHighestStage(HS + 8); + database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), playerRecipe->GetHighestStage()); + } + if (recipe->products.count(4) > 0) { + success = true; + item_id = recipe->products[4]->product_id; + qty = recipe->products[4]->product_qty; + byproduct_itemid = recipe->products[4]->byproduct_id; + byproduct_qty = recipe->products[4]->byproduct_qty; + } + } + + if(progress > 1000 && item_id < 1) { + LogWrite(TRADESKILL__INFO, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting progress over 1000, but no item id set with recipe id %u, finding override. Highest Stage: %u, progress: %u, durability %u", client->GetPlayer()->GetName(), recipe->GetID(), HS, progress, dur); + for(int i=4;i>=0;i--) { + if (recipe->products.count(i) > 0) { + item_id = recipe->products[i]->product_id; + qty = recipe->products[i]->product_qty; + byproduct_itemid = recipe->products[i]->byproduct_id; + byproduct_qty = recipe->products[i]->byproduct_qty; + break; + } + } + } + + if(item_id) { + LogWrite(TRADESKILL__INFO, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting crafted item %u with recipe id %u. Highest Stage: %u, progress: %u, durability %u", client->GetPlayer()->GetName(), item_id, recipe->GetID(), HS, progress, dur); + + item = new Item(master_item_list.GetItem(item_id)); + if (!item) { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "Item (%u) not found.", item_id); + } + else { + item->details.count = qty; + // use CHANNEL_COLOR_CHAT_RELATIONSHIP as that is the same value (4) as it is in a log for this message + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You created %s.", item->CreateItemLink(client->GetVersion()).c_str()); + client->AddItem(item); + if(byproduct_itemid) { + Item* byproductItem = new Item(master_item_list.GetItem(byproduct_itemid)); + byproductItem->details.count = byproduct_qty; + client->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "You received %s as a byproduct.", byproductItem->CreateItemLink(client->GetVersion()).c_str()); + client->AddItem(byproductItem); + } + //Check for crafting quest updates + int8 update_amt = 0; + if(item->stack_count > 1) + update_amt = 1; + else + update_amt = qty; + client->GetPlayer()->CheckQuestsCraftUpdate(item, update_amt); + } + } + else { + LogWrite(TRADESKILL__WARNING, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting no item summoned for player with recipe id %u. Highest Stage: %u, progress: %u, durability %u", client->GetPlayer()->GetName(), recipe->GetID(), HS, progress, dur); + } + + float xp = client->GetPlayer()->CalculateTSXP(recipe->GetLevel()); + xp = xp - (xp * tsx); + if (xp > 0) { + int16 level = client->GetPlayer()->GetTSLevel(); + if (client->GetPlayer()->AddTSXP((int32)xp)) { + client->Message(CHANNEL_REWARD, "You gain %u Tradeskill XP!", (int32)xp); + LogWrite(PLAYER__DEBUG, 0, "Player", "Player: %s earned %u tradeskill experience.", client->GetPlayer()->GetName(), (int32)xp); + if(client->GetPlayer()->GetTSLevel() != level) + client->ChangeTSLevel(level, client->GetPlayer()->GetTSLevel()); + client->GetPlayer()->SetCharSheetChanged(true); + } + } + + if(tradeskill && tradeskill->recipe) { + if(success) { + int32 success_anim = GetTechniqueSuccessAnim(client->GetVersion(), tradeskill->recipe->GetTechnique()); + client->GetPlayer()->GetZone()->PlayAnimation(client->GetPlayer(), success_anim); + } + else { + int32 failure_anim = GetTechniqueFailureAnim(client->GetVersion(), tradeskill->recipe->GetTechnique()); + client->GetPlayer()->GetZone()->PlayAnimation(client->GetPlayer(), failure_anim); + } + } + + tradeskillList.erase(client); + safe_delete(tradeskill); + + if (lock) + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + + // Lock TS spells and unlock all others + client->GetPlayer()->LockTSSpells(); +} + +bool TradeskillMgr::IsClientCrafting(Client* client) { + bool ret = false; + + m_tradeskills.readlock(__FUNCTION__, __LINE__); + ret = tradeskillList.count(client) > 0; + m_tradeskills.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void TradeskillMgr::CheckTradeskillEvent(Client* client, int16 icon) { + // Check to see if the given client is crafting + if (!IsClientCrafting(client)) + return; + + m_tradeskills.writelock(__FUNCTION__, __LINE__); + // check to see if the client currently has an event and if it does if we had already tried to counter it this round + if (tradeskillList[client]->CurrentEvent == 0 || tradeskillList[client]->eventChecked) { + // No current event, or we already tried to counter it, return out + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + return; + } + + // set the eventChecked flag so we don't try to counter it again + tradeskillList[client]->eventChecked = true; + // compare the event icon with the given spell icon to see if we countered it and store the result for the update + bool countered = (icon == tradeskillList[client]->CurrentEvent->Icon); + tradeskillList[client]->eventCountered = countered; + + // send the success or fail message to the client + client->Message(CHANNEL_NARRATIVE, "You %s %s.", countered ? "successfully countered" : "failed to counter", tradeskillList[client]->CurrentEvent->Name); + + // unlock the list and send the result packet + m_tradeskills.releasewritelock(__FUNCTION__, __LINE__); + ClientPacketFunctions::CounterReaction(client, countered); +} + +Tradeskill* TradeskillMgr::GetTradeskill(Client* client) { + if (tradeskillList.count(client) == 0) + return 0; + + return tradeskillList[client]; +} + +int32 TradeskillMgr::GetTechniqueSuccessAnim(int16 version, int32 technique) { + switch(technique) { + case SKILL_ID_SCULPTING: { + if(version <= 561) { + return 3007; // leatherworking_success + } + + return 11785; // 3005 = failure, 3006 = idle. 11783 = failure, 11784 = idle + break; + } + case SKILL_ID_ARTISTRY: { + if(version <= 561) { + return 2319; // cooking_success + } + + return 11245; // 2317 = failure, 2318 = idle. 11243 = failure, 11244 = idle + break; + } + case SKILL_ID_FLETCHING: { + if(version <= 561) { + return 2356; // woodworking_success + } + + return 13309; // 2354 = failure, 2355 = idle. 13307 = failure, 13308 = idle + break; + } + case SKILL_ID_METALWORKING: + case SKILL_ID_METALSHAPING: { + if(version <= 561) { + return 2442; // metalworking_success + } + + return 11813; // 2441 = failure, 1810 = idle. 11811 = failure, 11812 = idle + break; + } + case SKILL_ID_TAILORING: { + if(version <= 561) { + return 2352; // tailoring_success + } + + return 13040; // 2350 = failure, 2351 = idle. 13038 = failure, 13039 = idle + break; + } + case SKILL_ID_CHEMISTRY:{ + if(version <= 561) { + return 2298; // alchemy_success + } + + return 10749; // 2296 = failure, 2297 = idle. 10747 = failure, 10748 = idle + break; + } + case SKILL_ID_ARTIFICING:{ + if(version <= 561) { + return 2304; // artificing_success + } + + return 10767; // 2302 = failure, 2303 = idle. 10765 = failure, 10766 = idle + break; + } + case SKILL_ID_SCRIBING: { + if(version <= 561) { + return 0; // ??? + } + + return 0; // ??? = failure, 3131 = idle. ??? = failure, 12193 = idle + break; + } + } + return 0; +} + +int32 TradeskillMgr::GetTechniqueFailureAnim(int16 version, int32 technique) { + switch(technique) { + case SKILL_ID_SCULPTING: { + if(version <= 561) { + return 3005; // leatherworking_failure + } + + return 11783; // 3005 = failure, 3006 = idle. 11783 = failure, 11784 = idle + break; + } + case SKILL_ID_ARTISTRY: { + if(version <= 561) { + return 2317; // cooking_failure + } + + return 11243; // 2317 = failure, 2318 = idle. 11243 = failure, 11244 = idle + break; + } + case SKILL_ID_FLETCHING: { + if(version <= 561) { + return 2354; // woodworking_failure + } + + return 13307; // 2354 = failure, 2355 = idle. 13307 = failure, 13308 = idle + break; + } + case SKILL_ID_METALWORKING: + case SKILL_ID_METALSHAPING: { + if(version <= 561) { + return 2441; // metalworking_failure + } + + return 11811; // 2441 = failure, 1810 = idle. 11811 = failure, 11812 = idle + break; + } + case SKILL_ID_TAILORING: { + if(version <= 561) { + return 2350; // tailoring_failure + } + + return 13038; // 2350 = failure, 2351 = idle. 13038 = failure, 13039 = idle + break; + } + case SKILL_ID_CHEMISTRY:{ + if(version <= 561) { + return 2298; // alchemy_success + } + + return 10749; // 2296 = failure, 2297 = idle. 10747 = failure, 10748 = idle + break; + } + case SKILL_ID_ARTIFICING:{ + if(version <= 561) { + return 2302; // artificing_failure + } + + return 10765; // 2302 = failure, 2303 = idle. 10765 = failure, 10766 = idle + break; + } + case SKILL_ID_SCRIBING: { + if(version <= 561) { + return 0; // ??? + } + + return 0; // ??? = failure, 3131 = idle. ??? = failure, 12193 = idle + break; + } + } + return 0; +} + +int32 TradeskillMgr::GetTechniqueIdleAnim(int16 version, int32 technique) { + switch(technique) { + case SKILL_ID_SCULPTING: { + if(version <= 561) { + return 3006; // leatherworking_idle + } + + return 11784; // 3005 = failure, 3006 = idle. 11783 = failure, 11784 = idle + break; + } + case SKILL_ID_ARTISTRY: { + if(version <= 561) { + return 2318; // cooking_idle + } + + return 11244; // 2317 = failure, 2318 = idle. 11243 = failure, 11244 = idle + break; + } + case SKILL_ID_FLETCHING: { + if(version <= 561) { + return 2355; // woodworking_idle + } + + return 13308; // 2354 = failure, 2355 = idle. 13307 = failure, 13308 = idle + break; + } + case SKILL_ID_METALWORKING: + case SKILL_ID_METALSHAPING: { + if(version <= 561) { + return 1810; // metalworking_idle + } + + return 11812; // 2441 = failure, 1810 = idle. 11811 = failure, 11812 = idle + break; + } + case SKILL_ID_TAILORING: { + if(version <= 561) { + return 2351; // tailoring_idle + } + + return 13039; // 2350 = failure, 2351 = idle. 13038 = failure, 13039 = idle + break; + } + case SKILL_ID_CHEMISTRY:{ + if(version <= 561) { + return 2297; // alchemy_idle + } + + return 10748; // 2296 = failure, 2297 = idle. 10747 = failure, 10748 = idle + break; + } + case SKILL_ID_ARTIFICING:{ + if(version <= 561) { + return 2303; // artificing_idle + } + + return 10766; // 2302 = failure, 2303 = idle. 10765 = failure, 10766 = idle + break; + } + case SKILL_ID_SCRIBING: { + if(version <= 561) { + return 3131; // scribing_idle + } + + return 12193; // ??? = failure, 3131 = idle. ??? = failure, 12193 = idle + break; + } + } + return 0; +} + +int32 TradeskillMgr::GetMissTargetAnim(int16 version) { + if(version <= 561) { + return 1144; + } + + return 11814; // 11815 seems also possible? +} + +int32 TradeskillMgr::GetKillMissTargetAnim(int16 version) { + if(version <= 561) { + return 33912; + } + + return 44582; // 44583 seems also possible? +} + +void TradeskillMgr::SetClientIdleVisualState(Client* client, Tradeskill* ts) { + if(!client || !ts || !client->GetPlayer()) { + return; + } + + int32 idle_anim = 0; + if(ts->recipe) { + idle_anim = GetTechniqueIdleAnim(client->GetVersion(), ts->recipe->GetTechnique()); + } + if(idle_anim) { + client->GetPlayer()->SetTempVisualState(idle_anim); + } +} + +MasterTradeskillEventsList::MasterTradeskillEventsList() { + m_eventList.SetName("MasterTradeskillEventsList::eventList"); +} + +MasterTradeskillEventsList::~MasterTradeskillEventsList() { + m_eventList.writelock(__FUNCTION__, __LINE__); + map >::iterator itr; + vector::iterator ts_itr; + for (itr = eventList.begin(); itr != eventList.end(); itr++){ + for (ts_itr = itr->second.begin(); ts_itr != itr->second.end(); ts_itr++){ + safe_delete(*ts_itr); + } + } + eventList.clear(); + m_eventList.releasewritelock(__FUNCTION__, __LINE__); +} + +void MasterTradeskillEventsList::AddEvent(TradeskillEvent* tradeskillEvent) { + m_eventList.writelock(__FUNCTION__, __LINE__); + eventList[tradeskillEvent->Technique].push_back(tradeskillEvent); + m_eventList.releasewritelock(__FUNCTION__, __LINE__); +} + +vector* MasterTradeskillEventsList::GetEventByTechnique(int32 technique) { + if (eventList.count(technique) == 0) + return 0; + + return &eventList[technique]; +} + +int32 MasterTradeskillEventsList::Size() { + int32 count = 0; + m_eventList.readlock(__FUNCTION__, __LINE__); + map >::iterator itr; + for (itr = eventList.begin(); itr != eventList.end(); itr++) + count += itr->second.size(); + m_eventList.releasereadlock(__FUNCTION__, __LINE__); + return count; +} diff --git a/source/WorldServer/Tradeskills/Tradeskills.h b/source/WorldServer/Tradeskills/Tradeskills.h new file mode 100644 index 0000000..882c331 --- /dev/null +++ b/source/WorldServer/Tradeskills/Tradeskills.h @@ -0,0 +1,153 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_TRADESKILLS__ +#define __EQ2_TRADESKILLS__ + +#include "../../common/types.h" +#include "../../common/Mutex.h" +#include "../Items/Items.h" +#include +class Player; +class Spawn; +class Recipe; +class Client; + +struct TradeskillEvent +{ + char Name[250]; + int16 Icon; + int32 Technique; + int16 SuccessProgress; + int16 SuccessDurability; + int16 SuccessHP; + int16 SuccessPower; + int32 SuccessSpellID; + int32 SuccessItemID; + sint16 FailProgress; + sint16 FailDurability; + sint16 FailHP; + sint16 FailPower; +}; + +struct Tradeskill +{ + Player* player; + Spawn* table; + Recipe* recipe; + int32 currentProgress; + int32 currentDurability; + int32 nextUpdateTime; + vector> usedComponents; + TradeskillEvent* CurrentEvent; + bool eventChecked; + bool eventCountered; +}; + +class TradeskillMgr +{ +public: + TradeskillMgr(); + ~TradeskillMgr(); + + /// Determines if an update is needed if so send one and stop crafting if finished + void Process(); + + /// Starts the actual crafting process + /// Client that is crafting + /// List of items the player is using to craft + void BeginCrafting(Client* client, vector> components); + + /// Stops the crafting process + /// Client that stopped crafting + /// Does the list need a mutex lock? default = true + void StopCrafting(Client* client, bool lock = true); + + /// Checks to see if the given client is crafting + /// The client to check + /// True if the client is crafting + bool IsClientCrafting(Client* client); + + /// Get the tradeskill struct for the given client + /// The client to get the tradeskill struct for + /// Pointer to the clients tradeskill struct, or 0 if they don't have one + Tradeskill* GetTradeskill(Client* client); + + /// Check to see if we countered the tradeskill event + /// The client to check for + /// The icon of the spell we casted + void CheckTradeskillEvent(Client* client, int16 icon); + + /// Lock the tradeskill list for reading, should never need to write to the tradeskill list outside of the TradeskillMgr class + /// Function name that called this lock + /// Line number this lock was called from + void ReadLock(const char* function = (const char*)0, int32 line = 0) { m_tradeskills.readlock(function, line); } + + /// Releases the red lock on the tradeskill list + /// Function name that is releasing the lock + /// Line number that is releasing the lock + void ReleaseReadLock(const char* function = (const char*)0, int32 line = 0) { m_tradeskills.releasereadlock(function, line); } + + int32 GetTechniqueSuccessAnim(int16 version, int32 technique); + int32 GetTechniqueFailureAnim(int16 version, int32 technique); + int32 GetTechniqueIdleAnim(int16 version, int32 technique); + int32 GetMissTargetAnim(int16 version); + int32 GetKillMissTargetAnim(int16 version); + + void SetClientIdleVisualState(Client* client, Tradeskill* ts); +private: + /// Sends the creation window + /// Client to send the window to + /// The recipe being crafted + void SendItemCreationUI(Client* client, Recipe* recipe); + map tradeskillList; + Mutex m_tradeskills; + + float m_critFail; + float m_critSuccess; + float m_fail; + float m_success; + float m_eventChance; +}; + +class MasterTradeskillEventsList +{ +public: + MasterTradeskillEventsList(); + ~MasterTradeskillEventsList(); + + /// Adds a tradeskill event to the master list + /// The event to add + void AddEvent(TradeskillEvent* tradeskillEvent); + + /// Gets a list of tradeskill events for the given technique + /// The skill id of the technique + /// Vector of TradeskillEvent* for the given technique + vector* GetEventByTechnique(int32 technique); + + /// Get the size of the event list + /// int32 containing the size of the list + int32 Size(); + +private: + Mutex m_eventList; + map > eventList; +}; + +#endif diff --git a/source/WorldServer/Tradeskills/TradeskillsDB.cpp b/source/WorldServer/Tradeskills/TradeskillsDB.cpp new file mode 100644 index 0000000..f5c3e7f --- /dev/null +++ b/source/WorldServer/Tradeskills/TradeskillsDB.cpp @@ -0,0 +1,64 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include "../../common/Log.h" +#include "../WorldDatabase.h" +#include "Tradeskills.h" + +extern MasterTradeskillEventsList master_tradeskillevent_list; + +void WorldDatabase::LoadTradeskillEvents() { + TradeskillEvent* TSEvent = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES *res; + + res = query.RunQuery2(Q_SELECT, "SELECT `name`,`icon`,`technique`,`success_progress`,`success_durability`,`success_hp`,`success_power`,`success_spell_id`,`success_item_id`,`fail_progress`,`fail_durability`,`fail_hp`, `fail_power`\n" + "FROM `tradeskillevents`"); + if (res) { + while ((row = mysql_fetch_row(res))) { + TSEvent = new TradeskillEvent; + + strncpy(TSEvent->Name, row[0], sizeof(TSEvent->Name)); + TSEvent->Icon = atoi(row[1]); + TSEvent->Technique = atoul(row[2]); + TSEvent->SuccessProgress = atoi(row[3]); + TSEvent->SuccessDurability = atoi(row[4]); + TSEvent->SuccessHP = atoi(row[5]); + TSEvent->SuccessPower = atoi(row[6]); + TSEvent->SuccessSpellID = atoul(row[7]); + TSEvent->SuccessItemID = atoul(row[8]); + TSEvent->FailProgress = atoi(row[9]); + TSEvent->FailDurability = atoi(row[10]); + TSEvent->FailHP = atoi(row[11]); + TSEvent->FailPower = atoi(row[12]); + + LogWrite(TRADESKILL__DEBUG, 7, "Tradeskills", "Loading tradeskill event: %s", TSEvent->Name); + master_tradeskillevent_list.AddEvent(TSEvent); + } + } + + LogWrite(TRADESKILL__DEBUG, 0, "Tradeskills", "\tLoaded %u tradeskill events", master_tradeskillevent_list.Size()); +} \ No newline at end of file diff --git a/source/WorldServer/Tradeskills/TradeskillsPackets.cpp b/source/WorldServer/Tradeskills/TradeskillsPackets.cpp new file mode 100644 index 0000000..fcfbbb6 --- /dev/null +++ b/source/WorldServer/Tradeskills/TradeskillsPackets.cpp @@ -0,0 +1,610 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include +#include "../ClientPacketFunctions.h" +#include "../client.h" +#include "../../common/ConfigReader.h" +#include "../../common/PacketStruct.h" +#include "../Recipes/Recipe.h" +#include "../../common/Log.h" +#include "../Spells.h" +#include "../../common/MiscFunctions.h" +#include "../World.h" + +extern ConfigReader configReader; +extern MasterRecipeList master_recipe_list; +extern MasterSpellList master_spell_list; +extern World world; + +void ClientPacketFunctions::SendCreateFromRecipe(Client* client, int32 recipeID) { + + // if recipeID is 0 we are repeating the last recipe, if not set the players current recipe to the new one + if (recipeID == 0) + recipeID = client->GetPlayer()->GetCurrentRecipe(); + else + client->GetPlayer()->SetCurrentRecipe(recipeID); + + Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipeID); + + // Get the recipe + Recipe* recipe = master_recipe_list.GetRecipe(recipeID); + + if(!playerRecipe) + { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: ClientPacketFunctions::SendCreateFromRecipe Error finding player recipe %s in their recipe book for recipe id %u", client->GetPlayer()->GetName(), client->GetPlayer()->GetName(), recipe ? recipe->GetID() : 0); + client->Message(CHANNEL_COLOR_RED, "You do not have %s (%u) in your recipe book.", recipe ? recipe->GetName() : "Unknown", recipe ? recipe->GetID() : 0); + client->GetPlayer()->SetCurrentRecipe(0); + return; + } + + if (!recipe) { + client->GetPlayer()->SetCurrentRecipe(0); + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading recipe (%u) in ClientPacketFunctions::SendCreateFromRecipe()", recipeID); + return; + } + + // Create the packet + PacketStruct* packet = configReader.getStruct("WS_CreateFromRecipe", client->GetVersion()); + if (!packet) { + client->GetPlayer()->SetCurrentRecipe(0); + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading WS_CreateFromRecipe in ClientPacketFunctions::SendCreateFromRecipe()"); + return; + } + vector::iterator itr; + vector itemss; + int8 i = 0; + int8 k = 0; + int32 firstID = 0; + int32 IDcount = 0; + int32 primary_comp_id = 0; + Item* first_item = 0; + Item* item = 0; + Item* item_player = 0; + Spawn* target = client->GetPlayer()->GetTarget(); + int32 device_id = GetDeviceID(std::string(recipe->GetDevice())); + if (!target || !target->IsObject() || device_id == 0 || (device_id != 0xFFFFFFFF && ((Object*)target)->GetDeviceID() != device_id)) { + client->GetPlayer()->SetCurrentRecipe(0); + client->Message(CHANNEL_COLOR_YELLOW, "You must be at a %s in order to craft this recipe", recipe->GetDevice()); + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Failed to begin crafting with recipe id %u, recipe device_id is %u (%s), target object is %u.", recipe->GetID(), device_id, recipe->GetDevice(), (target && target->IsObject()) ? ((Object*)target)->GetDeviceID() : 0); + return; + } + + // Recipe and crafting table info + packet->setDataByName("crafting_station", recipe->GetDevice()); + packet->setDataByName("recipe_name", recipe->GetName()); + packet->setDataByName("tier", recipe->GetTier()); + // Mass Production + int32 mpq = 1; + int32 mp = 5; + vector v{ 1,2,4,6,11,21 }; + // mpq will eventually be retrieved from achievement for mass production + mpq = v[mp]; + + packet->setArrayLengthByName("num_mass_production_choices",mpq); + packet->setArrayDataByName("mass_qty", 1, 0); + for (int x = 1; x < mpq; x++) { + packet->setArrayDataByName("mass_qty", x * 5, x); + } + // Product info + item = master_item_list.GetItem(recipe->GetProductID()); + packet->setDataByName("product_name", item->name.c_str()); + packet->setDataByName("icon", item->GetIcon(client->GetVersion())); + packet->setDataByName("product_qty", recipe->GetProductQuantity()); + packet->setDataByName("primary_title", recipe->GetPrimaryBuildComponentTitle()); + packet->setDataByName("primary_qty_needed", recipe->GetPrimaryComponentQuantity()); + packet->setDataByName("unknown6", 11); + packet->setDataByName("unknown3", 18); + // Reset item to 0 + item, item_player = 0; + + // Check to see if we have a primary component (slot = 0) + if (recipe->components.count(0) > 0) { + vector rc = recipe->components[0]; + vector > selected_items; + + int32 total_primary_items = 0; + for (itr = rc.begin(); itr != rc.end(); itr++) { + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) + total_primary_items += itemss.size(); + } + packet->setArrayLengthByName("num_primary_choices", total_primary_items); + + + int16 have_qty = 0; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + if (firstID == 0) + firstID = *itr; + + item = master_item_list.GetItem(*itr); + if (!item) + { + client->GetPlayer()->SetCurrentRecipe(0); + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error creating packet to client missing item %u", *itr); + client->Message(CHANNEL_COLOR_RED, "Error producing create recipe packet! Recipe is trying to find item %u, but it is missing!", *itr); + safe_delete(packet); + return; + } + item_player = 0; + item_player = client->GetPlayer()->item_list.GetItemFromID((*itr)); + + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) { + + int16 needed_qty = recipe->GetPrimaryComponentQuantity(); + if (firstID == 0) + firstID = *itr; + for (int8 i = 0; i < itemss.size(); i++, k++) { + IDcount++; + if (have_qty < needed_qty) { + + int16 stack_count = itemss[i]->details.count; + int16 min_qty = min(stack_count, int16(needed_qty - have_qty)); + selected_items.push_back(make_pair(int32(itemss[i]->details.unique_id), min_qty)); + have_qty += min_qty; + } + packet->setArrayDataByName("primary_component", itemss[i]->name.c_str(), k); + packet->setArrayDataByName("primary_item_id", itemss[i]->details.unique_id, k); + packet->setArrayDataByName("primary_icon", itemss[i]->GetIcon(client->GetVersion()), k); + packet->setArrayDataByName("primary_total_quantity", itemss[i]->details.count, k); + //packet->setArrayDataByName("primary_supply_depot", itemss[i]->details.count, k); // future need + //packet->setArrayDataByName("primary_unknown3a",); // future need + } + packet->setDataByName("primary_id", itemss[0]->details.unique_id); + packet->setDataByName("primary_default_selected_id", itemss[0]->details.unique_id); + for (int8 i = 0; i < selected_items.size(); i++) { + + + + + + } + int16 qty = 0; + if (item) { + qty = (int8)item->details.count; + if (qty > 0 && firstID == primary_comp_id) + qty -= 1; + } + } + else + packet->setDataByName("primary_id",*itr); + } + // store the id of the primary comp + primary_comp_id = firstID; + + // Set the default item id to the first component id + packet->setArrayLengthByName("num_primary_items_selected", selected_items.size()); + for (int8 i = 0; i < selected_items.size(); i++) { + packet->setArrayDataByName("primary_selected_item_qty", selected_items[i].second,i ); + packet->setArrayDataByName("primary_selected_item_id", selected_items[i].first,i); + + } + + // Reset the variables we will reuse + i = 0; + firstID = 0; + item = 0; + k = 0; + } + else { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Recipe has no primary component"); + return; + } + + // Check to see if we have build components (slot = 1 - 4) + int8 total_build_components = 0; + if (recipe->components.count(1) > 0) + total_build_components++; + if (recipe->components.count(2)) + total_build_components++; + if (recipe->components.count(3)) + total_build_components++; + if (recipe->components.count(4)) + total_build_components++; + //--------------------------------------------------------------Start Build Components------------------------------------------------------------- + if (total_build_components > 0) { + packet->setArrayLengthByName("num_build_components", total_build_components); + LogWrite(TRADESKILL__INFO, 0, "Recipes", "num_build_components %u", total_build_components); + for (int8 index = 0; index < 4; index++) { + if (recipe->components.count(index + 1) == 0) + continue; + packet->setArrayDataByName("build_slot", index, index); + vector rc = recipe->components[index + 1]; + int32 total_component_items = 0; + int8 hasComp = 0; + vector > selected_items; + for (itr = rc.begin(); itr != rc.end(); itr++) { + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) + total_component_items += itemss.size(); + } + packet->setSubArrayLengthByName("num_build_choices", total_component_items, index);// get # build choces first + hasComp = 0; + char msgbuf[200] = ""; + for (itr = rc.begin(); itr != rc.end(); itr++) {// iterates through each recipe component to find the stacks in inventory + item = master_item_list.GetItem(*itr); + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + if (itemss.size() > 0) { + int16 needed_qty = 0; + int16 have_qty = 0; + if (index == 0) { + needed_qty = recipe->GetBuild1ComponentQuantity(); + have_qty = 0; + } + else if (index == 1) { + needed_qty = recipe->GetBuild2ComponentQuantity(); + have_qty = 0; + } + else if (index == 2) { + needed_qty = recipe->GetBuild3ComponentQuantity(); + have_qty = 0; + } + else if (index == 3) { + needed_qty = recipe->GetBuild4ComponentQuantity(); + have_qty = 0; + } + if (firstID == 0) + firstID = *itr; + if (hasComp == 0) { + hasComp = 100 + k; + } + for (int8 j = 0; j < itemss.size(); j++, k++) { // go through each stack of a compnent + if (have_qty < needed_qty) { + + int16 stack_count = itemss[j]->details.count; + int16 min_qty = min(stack_count, int16(needed_qty - have_qty)); + selected_items.push_back(make_pair(int32(itemss[j]->details.unique_id), min_qty)); + have_qty += min_qty; + } + item = master_item_list.GetItem(itemss[j]->details.item_id); + packet->setSubArrayDataByName("build_component", itemss[j]->name.c_str(), index, k); + packet->setSubArrayDataByName("build_item_id", itemss[j]->details.unique_id, index, k); + packet->setSubArrayDataByName("build_icon", itemss[j]->GetIcon(client->GetVersion()), index, k); + packet->setSubArrayDataByName("build_total_quantity", itemss[j]->details.count, index, k); + //packet->setSubArrayDataByName("build_supply_depot",); // future need + //packet->setSubArrayDataByName("build_unknown3",); // future need + + } + } + } + packet->setSubArrayLengthByName("num_build_items_selected", selected_items.size(),index ); + for (int8 i = 0; i < selected_items.size(); i++) { + packet->setSubArrayDataByName("build_selected_item_qty", selected_items[i].second,index, i); + packet->setSubArrayDataByName("build_selected_item_id", selected_items[i].first,index, i); + + } + int16 qty = 0; + if (item) { + qty = (int16)item->details.count; + if (qty > 0 && firstID == primary_comp_id) + qty -= 1; + } + if (index == 0) { + packet->setArrayDataByName("build_title", recipe->GetBuild1ComponentTitle(), index); + packet->setArrayDataByName("build_qty_needed", recipe->GetBuild1ComponentQuantity(), index); + if (item) + packet->setArrayDataByName("build_selected_item_qty_have", min(qty, recipe->GetBuild1ComponentQuantity()), index); + } + else if (index == 1) { + packet->setArrayDataByName("build_title", recipe->GetBuild2ComponentTitle(), index); + packet->setArrayDataByName("build_qty_needed", recipe->GetBuild2ComponentQuantity(), index); + if (item) + packet->setArrayDataByName("build_selected_item_qty_have", min(qty, recipe->GetBuild2ComponentQuantity()), index); + } + else if (index == 2) { + packet->setArrayDataByName("build_title", recipe->GetBuild3ComponentTitle(), index); + packet->setArrayDataByName("build_qty_needed", recipe->GetBuild3ComponentQuantity(), index); + if (item) + packet->setArrayDataByName("build_selected_item_qty_have", min(qty, recipe->GetBuild3ComponentQuantity()), index); + } + else { + packet->setArrayDataByName("build_title", recipe->GetBuild4ComponentTitle(), index); + packet->setArrayDataByName("build_qty_needed", recipe->GetBuild4ComponentQuantity(), index); + if (item) + packet->setArrayDataByName("build_selected_item_qty_have", min(qty, recipe->GetBuild4ComponentQuantity()), index); + } + + // Reset the variables we will reuse + i = 0; + firstID = 0; + item = 0; + k = 0; + } + + + int32 xxx = 0; + } + + + // Check to see if we have a fuel component (slot = 5) + if (recipe->components.count(5) > 0) { + vector rc = recipe->components[5]; + vector > selected_items; + for (itr = rc.begin(); itr != rc.end(); itr++, i++) { + if (firstID == 0) + firstID = *itr; + item = master_item_list.GetItem(*itr); + if (!item) + { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error creating packet to client missing item %u", *itr); + client->Message(CHANNEL_COLOR_RED, "Error producing create recipe packet! Recipe is trying to find item %u, but it is missing!", *itr); + safe_delete(packet); + return; + } + item_player = 0; + item_player = client->GetPlayer()->item_list.GetItemFromID((*itr)); + + if(client->GetVersion() <= 561) { + packet->setDataByName("fuel_qty", item->details.count); + packet->setDataByName("fuel_icon", item->GetIcon(client->GetVersion())); + } + + itemss = client->GetPlayer()->item_list.GetAllItemsFromID((*itr)); + packet->setArrayLengthByName("num_fuel_choices", itemss.size()); + if (itemss.size() > 0) { + + int16 needed_qty = recipe->GetFuelComponentQuantity(); + int16 have_qty = 0; + if (firstID == 0) + firstID = *itr; + for (int8 i = 0; i < itemss.size(); i++) { + IDcount++; + if (have_qty < needed_qty) { + + int16 stack_count = itemss[i]->details.count; + int16 min_qty = min(stack_count, int16(needed_qty - have_qty)); + selected_items.push_back(make_pair(int32(itemss[i]->details.unique_id), min_qty)); + have_qty += min_qty; + } + packet->setArrayDataByName("fuel_component", itemss[i]->name.c_str(), i); + packet->setArrayDataByName("fuel_item_id", itemss[i]->details.unique_id, i); + packet->setArrayDataByName("fuel_icon", itemss[i]->GetIcon(client->GetVersion()), i); + packet->setArrayDataByName("fuel_total_quantity", itemss[i]->details.count, i); + //packet->setArrayDataByName("fuel_supply_depot", itemss[i]->details.count, i); // future need + //packet->setArrayDataByName("primary_unknown3a",); // future need + } + packet->setDataByName("fuel_selected_item_id", itemss[0]->details.unique_id); + int16 qty = 0; + if (item) { + qty = (int8)item->details.count; + if (qty > 0 && firstID == primary_comp_id) + qty -= 1; + } + } + else + packet->setDataByName("primary_vvv", *itr); + } + // store the id of the primary comp + primary_comp_id = firstID; + + // Set the default item id to the first component id + packet->setArrayLengthByName("num_fuel_items_selected", selected_items.size()); + for (int8 i = 0; i < selected_items.size(); i++) { + packet->setArrayDataByName("fuel_selected_item_qty", selected_items[i].second, i); + packet->setArrayDataByName("fuel_selected_item_id", selected_items[i].first, i); + } + packet->setDataByName("fuel_title", recipe->GetFuelComponentTitle()); + packet->setDataByName("fuel_qty_needed", recipe->GetFuelComponentQuantity()); + + + // Reset the variables we will reuse + i = 0; + firstID = 0; + item = 0; + } + else { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Recipe has no fuel component"); + return; + } + + packet->setDataByName("recipe_id", recipeID); + + packet->PrintPacket(); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + // Send the packet + client->QueuePacket(outapp); + safe_delete(packet); +} + +void ClientPacketFunctions::SendItemCreationUI(Client* client, Recipe* recipe) { + // Check for valid recipe + if (!recipe) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Recipe = null in ClientPacketFunctions::SendItemCreationUI()"); + return; + } + + // Load the packet + PacketStruct* packet = configReader.getStruct("WS_ShowItemCreation", client->GetVersion()); + if (!packet) { + LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading WS_ShowItemCreation in ClientPacketFunctions::SendItemCreationUI()"); + return; + } + + int16 item_version = GetItemPacketType(packet->GetVersion()); + + Item* item = 0; + RecipeProducts* rp = 0; + + packet->setDataByName("max_possible_durability", 1000); + packet->setDataByName("max_possible_progress", 1000); + + // All the packets I have looked at these unknowns are always the same + // so hardcoding them until they are identified. + packet->setDataByName("unknown2", 1045220557, 0); + packet->setDataByName("unknown2", 1061997773, 1); + + // Highest stage the player has been able to complete + // TODO: store this for the player, for now use 0 (none known) + Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID()); + + if(!playerRecipe) + { + LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: ClientPacketFunctions::SendItemCreationUI Error finding player recipe in their recipe book for recipe id %u", client->GetPlayer()->GetName(), recipe->GetID()); + client->Message(CHANNEL_COLOR_RED, "%s: SendItemCreationUI Error finding player recipe in their recipe book for recipe id %u!", client->GetPlayer()->GetName(), recipe->GetID()); + safe_delete(packet); + return; + } + + packet->setDataByName("progress_levels_known", playerRecipe ? playerRecipe->GetHighestStage() : 0); + + packet->setArrayLengthByName("num_process", 4); + for (int8 i = 0; i < 4; i++) { + + // Don't like this code but need to change the IfVariableNotSet value on unknown3 element + // to point to the currect progress_needed + vector dataStructs = packet->GetDataStructs(); + vector::iterator itr; + for (itr = dataStructs.begin(); itr != dataStructs.end(); itr++) { + DataStruct* data = *itr; + char tmp[20] = {0}; + sprintf(tmp,"_%i",i); + string name = "unknown3"; + name.append(tmp); + if (strcmp(data->GetName(), name.c_str()) == 0) { + name = "progress_needed"; + name.append(tmp); + data->SetIfNotSetVariable(name.c_str()); + } + } + if (i == 1) + packet->setArrayDataByName("progress_needed", 400, i); + else if (i == 2) + packet->setArrayDataByName("progress_needed", 600, i); + else if (i == 3) + packet->setArrayDataByName("progress_needed", 800, i); + + // get the product for this stage, if none found default to fuel + if (recipe->products.count(i) > 0) + rp = recipe->products[i]; + if (!rp || (rp->product_id == 0)) { + rp = new RecipeProducts; + rp->product_id = recipe->components[5].front(); + rp->product_qty = recipe->GetFuelComponentQuantity(); + rp->byproduct_id = 0; + rp->byproduct_qty = 0; + recipe->products[i] = rp; + } + item = master_item_list.GetItem(rp->product_id); + if (!item) { + LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Error loading item (%u) in ClientPacketFunctions::SendItemCreationUI()", rp->product_id); + return; + } + + packet->setArrayDataByName("item_name", item->name.c_str(), i); + packet->setArrayDataByName("item_icon", item->GetIcon(client->GetVersion()), i); + + if(client->GetVersion() < 860) { + packet->setItemArrayDataByName("item", item, client->GetPlayer(), i, 0, client->GetClientItemPacketOffset()); + //packet->setItemArrayDataByName("item", item, client->GetPlayer(), i, 0, -1); + } + else if (client->GetVersion() < 1193) + packet->setItemArrayDataByName("item", item, client->GetPlayer(), i); + else + packet->setItemArrayDataByName("item", item, client->GetPlayer(), i, 0, 2); + + if (rp->byproduct_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->byproduct_id); + if (item) { + packet->setArrayDataByName("item_byproduct_name", item->name.c_str(), i); + packet->setArrayDataByName("item_byproduct_icon", item->GetIcon(client->GetVersion()), i); + } + } + + packet->setArrayDataByName("packettype", item_version, i); + packet->setArrayDataByName("packetsubtype", 0xFF, i); + + item = 0; + rp = 0; + } + packet->setDataByName("product_progress_needed", 1000); + + rp = recipe->products[4]; + item = master_item_list.GetItem(rp->product_id); + + packet->setDataByName("product_item_name", item->name.c_str()); + packet->setDataByName("product_item_icon", item->GetIcon(client->GetVersion())); + + if(client->GetVersion() < 860) + packet->setItemByName("product_item", item, client->GetPlayer(), 0, client->GetClientItemPacketOffset()); + else if (client->GetVersion() < 1193) + packet->setItemByName("product_item", item, client->GetPlayer()); + else + packet->setItemByName("product_item", item, client->GetPlayer(), 0, 2); + + //packet->setItemByName("product_item", item, client->GetPlayer()); + + if (rp->byproduct_id > 0) { + item = 0; + item = master_item_list.GetItem(rp->byproduct_id); + if (item) { + packet->setDataByName("product_byproduct_name", item->name.c_str()); + packet->setDataByName("product_byproduct_icon", item->GetIcon(client->GetVersion())); + } + } + + packet->setDataByName("packettype", item_version); + packet->setDataByName("packetsubtype", 0xFF); + + // Start of basic work to get the skills to show on the tradeskill window + // not even close to accurate but skills do get put on the ui + int8 index = 0; + int8 size = 0; + vector::iterator itr; + vector spells = client->GetPlayer()->GetSpellBookSpellIDBySkill(recipe->GetTechnique()); + for (itr = spells.begin(); itr != spells.end(); itr++) { + size++; + Spell* spell = master_spell_list.GetSpell(*itr,1); + if(!spell) { + + return; + } + if (size > 6) { + // only 6 slots for skills on the ui + break; + } + packet->setDataByName("skill_id", *itr ,spell->GetSpellData()->ts_loc_index -1); + } + + + packet->PrintPacket(); + EQ2Packet* outapp = packet->serialize(); + DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); +} + +void ClientPacketFunctions::StopCrafting(Client* client) { + if(client->GetPlayer()) { + client->GetPlayer()->SetTempVisualState(-1); + } + client->QueuePacket(new EQ2Packet(OP_StopItemCreationMsg, 0, 0)); +} + +void ClientPacketFunctions::CounterReaction(Client* client, bool countered) { + PacketStruct* packet = configReader.getStruct("WS_TSEventReaction", client->GetVersion()); + if (packet) { + packet->setDataByName("counter_reaction", countered ? 1 : 0); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); +} diff --git a/source/WorldServer/Traits/Traits.cpp b/source/WorldServer/Traits/Traits.cpp new file mode 100644 index 0000000..46840a9 --- /dev/null +++ b/source/WorldServer/Traits/Traits.cpp @@ -0,0 +1,932 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#include "Traits.h" +#include "../../common/ConfigReader.h" +#include "../../common/Log.h" +#include "../Spells.h" +#include "../WorldDatabase.h" +#include "../client.h" +#include "../Rules/Rules.h" +#include + +#include + +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern WorldDatabase database; +extern RuleManager rule_manager; + +using namespace boost::assign; + +MasterTraitList::MasterTraitList(){ + MMasterTraitList.SetName("MasterTraitList::TraitList"); +} + +MasterTraitList::~MasterTraitList(){ + DestroyTraits(); +} + +void MasterTraitList::AddTrait(TraitData* data){ + MMasterTraitList.writelock(__FUNCTION__, __LINE__); + TraitList.push_back(data); + MMasterTraitList.releasewritelock(__FUNCTION__, __LINE__); +} + +int MasterTraitList::Size(){ + return TraitList.size(); +} + +bool MasterTraitList::GenerateTraitLists(Client* client, map > >* sortedTraitList, map >* classTraining, + map >* raceTraits, map >* innateRaceTraits, map >* focusEffects, int16 max_level, int8 trait_group) +{ + if (!client) { + LogWrite(SPELL__ERROR, 0, "Traits", "GetTraitListPacket called with an invalid client"); + return false; + } + // Sort the Data + if (Size() == 0 || !sortedTraitList || !classTraining || !raceTraits || !innateRaceTraits || !focusEffects) + return false; + + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + + MMasterTraitList.readlock(__FUNCTION__, __LINE__); + + for (int i=0; i < Size(); i++) { + if(max_level > 0 && TraitList[i]->level > max_level) { + continue; // skip per max level requirement + } + if(trait_group != 255 && trait_group != TraitList[i]->group) { + continue; + } + + // Sort Character Traits + if (TraitList[i]->classReq == 255 && TraitList[i]->raceReq == 255 && !TraitList[i]->isFocusEffect && TraitList[i]->isTrait) { + itr = sortedTraitList->lower_bound(TraitList[i]->group); + if (itr != sortedTraitList->end() && !(sortedTraitList->key_comp()(TraitList[i]->group, itr->first))) { + + itr2 = (itr->second).lower_bound(TraitList[i]->level); + if (itr2 != (itr->second).end() && !((itr->second).key_comp()(TraitList[i]->level, itr2->first))) { + ((itr->second)[itr2->first]).push_back(TraitList[i]); + LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier); + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + (itr->second).insert(make_pair(TraitList[i]->level, tempVec)); + LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier); + } + } + else { + map > tempMap; + vector tempVec; + tempVec.push_back(TraitList[i]); + tempMap.insert(make_pair(TraitList[i]->level, tempVec)); + sortedTraitList->insert(make_pair(TraitList[i]->group, tempMap)); + LogWrite(SPELL__INFO, 0, "Traits", "Added Trait: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier); + } + } + + // Sort Class Training + if (TraitList[i]->classReq == client->GetPlayer()->GetAdventureClass() && TraitList[i]->isTraining) { + itr2 = classTraining->lower_bound(TraitList[i]->level); + if (itr2 != classTraining->end() && !(classTraining->key_comp()(TraitList[i]->level, itr2->first))) { + (itr2->second).push_back(TraitList[i]); + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + classTraining->insert(make_pair(TraitList[i]->level, tempVec)); + } + } + + // Sort Racial Abilities + if (TraitList[i]->raceReq == client->GetPlayer()->GetRace() && !TraitList[i]->isInate && !TraitList[i]->isTraining) { + itr2 = raceTraits->lower_bound(TraitList[i]->group); + if (itr2 != raceTraits->end() && !(raceTraits->key_comp()(TraitList[i]->group, itr2->first))) { + (itr2->second).push_back(TraitList[i]); + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + raceTraits->insert(make_pair(TraitList[i]->group, tempVec)); + } + } + + // Sort Innate Racial Abilities + if (TraitList[i]->raceReq == client->GetPlayer()->GetRace() && TraitList[i]->isInate) { + itr2 = innateRaceTraits->lower_bound(TraitList[i]->group); + if (itr2 != innateRaceTraits->end() && !(innateRaceTraits->key_comp()(TraitList[i]->group, itr2->first))) { + (itr2->second).push_back(TraitList[i]); + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + innateRaceTraits->insert(make_pair(TraitList[i]->group, tempVec)); + } + } + + // Sort Focus Effects + if ((TraitList[i]->classReq == client->GetPlayer()->GetAdventureClass() || TraitList[i]->classReq == 255) && TraitList[i]->isFocusEffect) { + int8 j = 0; + itr2 = focusEffects->lower_bound(TraitList[i]->group); + if (itr2 != focusEffects->end() && !(focusEffects->key_comp()(TraitList[i]->group, itr2->first))) { + + (itr2->second).push_back(TraitList[i]); + //LogWrite(SPELL__INFO, 0, "Traits", "Added Focus Effect: %u Tier: %i", TraitList[i]->spellID, TraitList[i]->tier); + j++; + } + else { + vector tempVec; + tempVec.push_back(TraitList[i]); + focusEffects->insert(make_pair(TraitList[i]->group, tempVec)); + //LogWrite(SPELL__INFO, 0, "Traits", "Added Focus Effect: %u Tier %i", TraitList[i]->spellID, TraitList[i]->tier); + } + } + } + + MMasterTraitList.releasereadlock(__FUNCTION__, __LINE__); + + return true; +} + +bool MasterTraitList::IdentifyNextTrait(Client* client, map >* traitList, vector* collectTraits, vector* tieredTraits, std::map* previousMatchedSpells, bool omitFoundMatches) { + bool found_spell_match = false; + bool match = false; + bool tiered_selection = rule_manager.GetGlobalRule(R_Player, TraitTieringSelection)->GetBool(); + int8 group_to_apply = 255; + int8 count = 0; + map >::iterator itr2; + vector::iterator itr3; + + for (itr2 = traitList->begin(); itr2 != traitList->end(); itr2++) { + //LogWrite(SPELL__INFO, 0, "AA", "Character Traits Size...%i ", traits_size); + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) { + if(tiered_selection) { + if(found_spell_match && (*itr3)->group == group_to_apply) { + continue; // skip! + } + else if((*itr3)->group != group_to_apply) { + if(group_to_apply != 255 && !found_spell_match) { + // found match + LogWrite(SPELL__INFO, 0, "Traits", "Found match to group id %u", group_to_apply); + match = true; + break; + } + else { + LogWrite(SPELL__INFO, 0, "Traits", "Try match to group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group); + found_spell_match = false; + group_to_apply = (*itr3)->group; + count = 0; + if(!omitFoundMatches) + tieredTraits->clear(); + } + } + } + count++; + + std::map::iterator spell_itr = previousMatchedSpells->find((*itr3)->spellID); + + if(spell_itr != previousMatchedSpells->end() && (*itr3)->group > spell_itr->second) { + continue; + } + if(!IsPlayerAllowedTrait(client, (*itr3))) { + LogWrite(SPELL__INFO, 0, "Traits", "We are not allowed any more spells from this type/group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group); + found_spell_match = true; + } + else if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + LogWrite(SPELL__INFO, 0, "Traits", "Found existing spell match to group... spell id %u, group id %u", (*itr3)->spellID, (*itr3)->group); + if(!omitFoundMatches) + found_spell_match = true; + previousMatchedSpells->insert(std::make_pair((*itr3)->spellID,(*itr3)->group)); + } + else { + tieredTraits->push_back((*itr3)); + collectTraits->push_back((*itr3)); + } + } + + if(match) + break; + } + + + if(!match && group_to_apply != 255 && !found_spell_match) { + // found match + match = true; + } + else if(!tiered_selection && collectTraits->size() > 0) { + match = true; + } + return match; +} + +bool MasterTraitList::ChooseNextTrait(Client* client) { + map > >* SortedTraitList = new map > >; + map > >::iterator itr; + bool tiered_selection = rule_manager.GetGlobalRule(R_Player, TraitTieringSelection)->GetBool(); + vector::iterator itr3; + + map >* ClassTraining = new map >; + map >* RaceTraits = new map >; + map >* InnateRaceTraits = new map >; + map >* FocusEffects = new map >; + vector* collectTraits = new vector; + vector* tieredTraits = new vector; + std::map* previousMatchedSpells = new std::map; + bool match = false; + if(GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects, client->GetPlayer()->GetLevel())) { + + vector* endTraits; + if(!match || !tiered_selection) { + match = IdentifyNextTrait(client, ClassTraining, collectTraits, tieredTraits, previousMatchedSpells); + } + if(!match || !tiered_selection) { + match = IdentifyNextTrait(client, RaceTraits, collectTraits, tieredTraits, previousMatchedSpells, true); + + bool overrideMatch = IdentifyNextTrait(client, InnateRaceTraits, collectTraits, tieredTraits, previousMatchedSpells, true); + + if(!match && overrideMatch) + match = true; + } + if(!match || !tiered_selection) { + match = IdentifyNextTrait(client, FocusEffects, collectTraits, tieredTraits, previousMatchedSpells); + } + + if(!tiered_selection && collectTraits->size() > 0) { + endTraits = collectTraits; + } + else if (match) { + endTraits = tieredTraits; + } + if(match) { + PacketStruct* packet = configReader.getStruct("WS_QuestRewardPackMsg", client->GetVersion()); + // 0=enemy mastery, 1=specialized training,2=character trait, 3=racial tradition + int8 packet_type = 0; + int8 item_count = 0; + packet->setSubstructArrayLengthByName("reward_data", "num_select_rewards", endTraits->size()); + for (itr3 = endTraits->begin(); itr3 != endTraits->end(); itr3++) { + + if((*itr3)->item_id) { + //LogWrite(SPELL__INFO, 0, "Traits", "Item %u to be sent", (*itr3)->item_id); + Item* item = master_item_list.GetItem((*itr3)->item_id); + if(item) { + //LogWrite(SPELL__INFO, 0, "Traits", "Item found %s to be sent", item->name.c_str()); + packet->setArrayDataByName("select_reward_id", (*itr3)->item_id, item_count); + packet->setItemArrayDataByName("select_item", item, client->GetPlayer(), item_count, 0, client->GetClientItemPacketOffset()); + item_count++; + } + } + + + // Sort Character Traits + if ((*itr3)->classReq == 255 && (*itr3)->raceReq == 255 && (*itr3)->isTrait) { + packet_type = 2; + } + // Sort Class Training + else if ((*itr3)->classReq == client->GetPlayer()->GetAdventureClass() && (*itr3)->isTraining) { + packet_type = 1; + } + // Sort Racial Abilities + else if ((*itr3)->raceReq == client->GetPlayer()->GetRace() && !(*itr3)->isInate && !(*itr3)->isTraining) { + packet_type = 3; + } + // Sort Innate Racial Abilities + else if ((*itr3)->raceReq == client->GetPlayer()->GetRace() && (*itr3)->isInate) { + packet_type = 3; + } + + //LogWrite(SPELL__INFO, 0, "Traits", "Sending trait %u", (*itr3)->spellID); + } + packet->setSubstructDataByName("reward_data", "unknown1", packet_type); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + + safe_delete(SortedTraitList); + safe_delete(ClassTraining); + safe_delete(RaceTraits); + safe_delete(InnateRaceTraits); + safe_delete(FocusEffects); + + safe_delete(collectTraits); + safe_delete(tieredTraits); + safe_delete(previousMatchedSpells); + return match; +} + +int16 MasterTraitList::GetSpellCount(Client* client, map >* traits, bool onlyCharTraits) { + if(!traits) + return 0; + + int16 count = 0; + map >::iterator itr2; + vector::iterator itr3; + for (itr2 = traits->begin(); itr2 != traits->end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) { + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + if(!onlyCharTraits || (onlyCharTraits && (*itr3)->classReq == 255 && (*itr3)->raceReq == 255 && (*itr3)->isTrait)) { + count++; + } + } + } + } + + return count; +} + +vector PersonalTraitLevelLimits = boost::assign::list_of(0)(8)(14)(22)(28)(36)(42)(46)(48); +vector TrainingTraitLevelLimits = boost::assign::list_of(0)(10)(20)(30)(40)(50); +vector RacialTraitLevelLimits = boost::assign::list_of(0)(18)(26)(34)(44); +vector CharacterTraitLevelLimits = boost::assign::list_of(0)(12)(16)(24)(32)(38); + +/*** +Every other level, beginning at Level 8, +you gain an additional advantage — a +Personal Trait, an Enemy Tactic, a Racial +Tradition or a Training ability. Each time +you reach an even-numbered level, you +can select another advantage from the +appropriate list. You don’t have to select +in order — you may take any of the avail- +able choices. +Level Advantage +8 Personal Trait (1st) +10 Training (1st) +12 Enemy Tactic (1st) +14 Personal Trait (2nd) +16 Enemy Tactic (2nd) +18 Racial Tradition (1st) +20 Training (2nd) +22 Personal Trait (3rd) +24 Enemy Tactic (3rd) +26 Racial Tradition (2nd) +28 Personal Trait (4th) +30 Training (3rd) +32 Enemy Tactic (4th) +34 Racial Tradition (3rd) +36 Personal Trait (5th) +38 Enemy Tactic (5th) +40 Training (4th) +42 Personal Trait (6th) +44 Racial Tradition (4th) +46 Personal Trait (7th) +48 Personal Trait (8th) +50 Training (5th) +***/ + +bool MasterTraitList::IsPlayerAllowedTrait(Client* client, TraitData* trait) { + std::unique_lock(client->GetPlayer()->trait_mutex); + map > >* SortedTraitList = client->GetPlayer()->SortedTraitList; + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + bool use_classic_table = rule_manager.GetGlobalRule(R_Player, ClassicTraitLevelTable)->GetBool(); + + map >* ClassTraining = client->GetPlayer()->ClassTraining; + map >* RaceTraits = client->GetPlayer()->RaceTraits; + map >* InnateRaceTraits = client->GetPlayer()->InnateRaceTraits; + map >* FocusEffects = client->GetPlayer()->FocusEffects; + if(client->GetPlayer()->need_trait_update) { + SortedTraitList->clear(); + ClassTraining->clear(); + RaceTraits->clear(); + InnateRaceTraits->clear(); + FocusEffects->clear(); + } + bool allowed_trait = false; + if(!client->GetPlayer()->need_trait_update || GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects, 0)) { + client->GetPlayer()->need_trait_update = false; + if(trait->isFocusEffect) { + + int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitFocusSelectLevel)->GetInt32(); + int16 num_available_selections = 0; + if(trait_level > 0) { + num_available_selections = client->GetPlayer()->GetLevel() / trait_level; + } + int16 total_used = GetSpellCount(client, FocusEffects); + + int16 classic_avail = 0; + + if(use_classic_table && PersonalTraitLevelLimits.size() > total_used+1) { + int16 classic_level_req = PersonalTraitLevelLimits.at(total_used+1); + if(client->GetPlayer()->GetLevel() >= classic_level_req) + classic_avail = num_available_selections = total_used+1; + else + num_available_selections = 0; + } + else if(use_classic_table) + num_available_selections = 0; + + LogWrite(SPELL__INFO, 9, "Traits", "%s FocusEffects used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail); + if(total_used < num_available_selections) { + allowed_trait = true; + } + } + else if(trait->isTraining) { + int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitTrainingSelectLevel)->GetInt32(); + int16 num_available_selections = 0; + if(trait_level > 0) { + num_available_selections = client->GetPlayer()->GetLevel() / trait_level; + } + int16 total_used = GetSpellCount(client, ClassTraining); + + int16 classic_avail = 0; + + if(use_classic_table && TrainingTraitLevelLimits.size() > total_used+1) { + int16 classic_level_req = TrainingTraitLevelLimits.at(total_used+1); + if(client->GetPlayer()->GetLevel() >= classic_level_req) + classic_avail = num_available_selections = total_used+1; + else + num_available_selections = 0; + } + else if(use_classic_table) + num_available_selections = 0; + + LogWrite(SPELL__INFO, 9, "Traits", "%s ClassTraining used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail); + if(total_used < num_available_selections) { + allowed_trait = true; + } + } + else { + if(trait->raceReq == client->GetPlayer()->GetRace()) { + int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitRaceSelectLevel)->GetInt32(); + int16 num_available_selections = 0; + if(trait_level > 0) { + num_available_selections = client->GetPlayer()->GetLevel() / trait_level; + } + int16 total_used = GetSpellCount(client, RaceTraits); + int16 total_used2 = GetSpellCount(client, InnateRaceTraits); + + int16 classic_avail = 0; + + if(use_classic_table && RacialTraitLevelLimits.size() > total_used+total_used2+1) { + int16 classic_level_req = RacialTraitLevelLimits.at(total_used+total_used2+1); + if(client->GetPlayer()->GetLevel() >= classic_level_req) + classic_avail = num_available_selections = total_used+total_used2+1; + else + num_available_selections = 0; + } + else if(use_classic_table) + num_available_selections = 0; + + LogWrite(SPELL__INFO, 9, "Traits", "%s RaceTraits used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used+total_used2, num_available_selections, classic_avail); + if(total_used+total_used2 < num_available_selections) { + allowed_trait = true; + } + } + else { // character trait? + int16 num_available_selections = 0; + int32 trait_level = rule_manager.GetGlobalRule(R_Player, TraitCharacterSelectLevel)->GetInt32(); + if(trait_level > 0) { + num_available_selections = client->GetPlayer()->GetLevel() / trait_level; + } + int16 total_used = 0; + for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) { + total_used += GetSpellCount(client, &itr->second, true); + } + + int16 classic_avail = 0; + + if(use_classic_table && CharacterTraitLevelLimits.size() > total_used+1) { + int16 classic_level_req = CharacterTraitLevelLimits.at(total_used+1); + if(client->GetPlayer()->GetLevel() >= classic_level_req) + classic_avail = num_available_selections = total_used+1; + else + num_available_selections = 0; + } + else if(use_classic_table) + num_available_selections = 0; + + LogWrite(SPELL__INFO, 9, "Traits", "%s CharacterTraits used %u, available %u, classic available %u", client->GetPlayer()->GetName(), total_used, num_available_selections, classic_avail); + if(total_used < num_available_selections) { + allowed_trait = true; + } + } + } + } + + return allowed_trait; +} + +EQ2Packet* MasterTraitList::GetTraitListPacket (Client* client) +{ + std::unique_lock(client->GetPlayer()->trait_mutex); + + if (!client) { + LogWrite(SPELL__ERROR, 0, "Traits", "GetTraitListPacket called with an invalid client"); + return 0; + } + // Sort the Data + if (Size() == 0) + return NULL; + + map > >* SortedTraitList = client->GetPlayer()->SortedTraitList; + map > >::iterator itr; + map >::iterator itr2; + vector::iterator itr3; + + map >* ClassTraining = client->GetPlayer()->ClassTraining; + map >* RaceTraits = client->GetPlayer()->RaceTraits; + map >* InnateRaceTraits = client->GetPlayer()->InnateRaceTraits; + map >* FocusEffects = client->GetPlayer()->FocusEffects; + + if(client->GetPlayer()->need_trait_update) { + SortedTraitList->clear(); + ClassTraining->clear(); + RaceTraits->clear(); + InnateRaceTraits->clear(); + FocusEffects->clear(); + } + + if(client->GetPlayer()->need_trait_update && !GenerateTraitLists(client, SortedTraitList, ClassTraining, RaceTraits, InnateRaceTraits, FocusEffects)) { + return NULL; + } + + client->GetPlayer()->need_trait_update = false; + + int16 version = 1; + int8 count = 0; + int8 index = 0; + int8 num_traits = 0; + int8 traits_size = 0; + int8 num_focuseffects = 0; + char sTrait [20]; + char temp [20]; + + version = client->GetVersion(); + + // Jabantiz: Get the value for num_traits in the struct (num_traits refers to the number of rows not the total number of traits) + for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) { + num_traits += (itr->second).size(); + } + + PacketStruct* packet = configReader.getStruct("WS_TraitsList", version); + + if (packet == NULL) { + return NULL; + } + + packet->setArrayLengthByName("num_traits", num_traits); + + for (itr = SortedTraitList->begin(); itr != SortedTraitList->end(); itr++) { + + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++, index++) { + traits_size += (itr2->second).size(); + count = 0; + Spell* tmp_spell = 0; + packet->setArrayDataByName("trait_level", (*itr2).first, index); + packet->setArrayDataByName("trait_line", 255, index); + //LogWrite(SPELL__INFO, 0, "AA", "Character Traits Size...%i ", traits_size); + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, count++) { + // Jabantiz: cant have more then 5 traits per line + tmp_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if(!tmp_spell) { + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + continue; + } + if (count > 4) + break; + + strcpy(sTrait, "trait"); + itoa(count, temp, 10); + strcat(sTrait, temp); + + strcpy(temp, sTrait); + strcat(sTrait, "_icon"); + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetSpellIcon(), index); + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + strcpy(sTrait, temp); + strcat(sTrait, "_icon2"); + packet->setArrayDataByName(sTrait, 65535, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_id"); + packet->setArrayDataByName(sTrait, (*itr3)->spellID, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_name"); + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetName(), index); + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + strcpy(sTrait, temp); + strcat(sTrait, "_unknown2"); + packet->setArrayDataByName(sTrait, 1, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_unknown"); + packet->setArrayDataByName(sTrait, 1, index); + + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) + packet->setArrayDataByName("trait_line", count, index); + } + // Jabantiz: If less then 5 fill the rest of the line with FF FF FF FF FF FF FF FF FF FF FF FF + while (count < 5) { + strcpy(sTrait, "trait"); + itoa(count, temp, 10); + strcat(sTrait, temp); + strcpy(temp, sTrait); + strcat(sTrait, "_icon"); + packet->setArrayDataByName(sTrait, 65535, index); // FF FF + strcpy(sTrait, temp); + strcat(sTrait, "_icon2"); + packet->setArrayDataByName(sTrait, 65535, index); // FF FF + strcpy(sTrait, temp); + strcat(sTrait, "_id"); + packet->setArrayDataByName(sTrait, 0xFFFFFFFF, index); + strcpy(sTrait, temp); + strcat(sTrait, "_unknown"); + packet->setArrayDataByName(sTrait, 0xFFFFFFFF, index); + strcpy(sTrait, temp); + strcat(sTrait, "_name"); + packet->setArrayDataByName(sTrait, "", index); + count++; + } + } + } + + // Class Training portion of the packet + packet->setArrayLengthByName("num_trainings", ClassTraining->size()); + index = 0; + for (itr2 = ClassTraining->begin(); itr2 != ClassTraining->end(); itr2++, index++) { + count = 0; + Spell* tmp_spell = 0; + packet->setArrayDataByName("training_level", itr2->first, index); + packet->setArrayDataByName("training_line", 255, index); + + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, count++) { + // Jabantiz: cant have more then 5 traits per line + tmp_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if(!tmp_spell) { + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + continue; + } + if (count > 4) + break; + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + packet->setArrayDataByName("training_line", count, index); + } + strcpy(sTrait, "training"); + itoa(count, temp, 10); + strcat(sTrait, temp); + + strcpy(temp, sTrait); + strcat(sTrait, "_icon"); + + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetSpellIcon(), index); + else + LogWrite(SPELL__ERROR, 0, "Training", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + strcpy(sTrait, temp); + strcat(sTrait, "_icon2"); + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetSpellIconBackdrop(), index); + else + LogWrite(SPELL__ERROR, 0, "Training", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + strcpy(sTrait, temp); + strcat(sTrait, "_id"); + packet->setArrayDataByName(sTrait, (*itr3)->spellID, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_unknown"); + packet->setArrayDataByName(sTrait,0xFFFFFFFF , index); + + strcpy(sTrait, temp); + strcat(sTrait, "_unknown2"); + packet->setArrayDataByName(sTrait, 1, index); + + strcpy(sTrait, temp); + strcat(sTrait, "_name"); + if (tmp_spell) + packet->setArrayDataByName(sTrait, tmp_spell->GetName(), index); + else + LogWrite(SPELL__ERROR, 0, "Training", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) + packet->setArrayDataByName("training_line", count, index); + } + // Jabantiz: If less then 5 fill the rest of the line with FF FF FF FF FF FF FF FF FF FF FF FF + while (count < 5) { + strcpy(sTrait, "training"); + itoa(count, temp, 10); + strcat(sTrait, temp); + strcpy(temp, sTrait); + strcat(sTrait, "_icon"); + packet->setArrayDataByName(sTrait, 65535, index); // FF FF + strcpy(sTrait, temp); + strcat(sTrait, "_icon2"); + packet->setArrayDataByName(sTrait, 65535, index); // FF FF + strcpy(sTrait, temp); + strcat(sTrait, "_id"); + packet->setArrayDataByName(sTrait, 0xFFFFFFFF, index); + strcpy(sTrait, temp); + strcat(sTrait, "_unknown"); + packet->setArrayDataByName(sTrait, 0xFFFFFFFF, index); + strcpy(sTrait, temp); + strcat(sTrait, "_name"); + packet->setArrayDataByName(sTrait, "", index); + count++; + } + } + // Racial Traits + packet->setArrayLengthByName("num_sections", RaceTraits->size()); + index = 0; + string tempStr; + int8 num_selections = 0; + for (itr2 = RaceTraits->begin(); itr2 != RaceTraits->end(); itr2++, index++) { + count = 0; + Spell* tmp_spell = 0; + switch (itr2->first) + { + case TRAITS_ATTRIBUTES: + tempStr = "Attributes"; + break; + case TRAITS_COMBAT: + tempStr = "Combat"; + break; + case TRAITS_NONCOMBAT: + tempStr = "Noncombat"; + break; + case TRAITS_POOLS: + tempStr = "Pools"; + break; + case TRAITS_RESIST: + tempStr = "Resist"; + break; + case TRAITS_TRADESKILL: + tempStr = "Tradeskill"; + break; + default: + tempStr = "Unknown"; + break; + } + packet->setArrayDataByName("section_name", tempStr.c_str(), index); + packet->setSubArrayLengthByName("num_traditions", (itr2->second).size(), index); + + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, count++) { + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + num_selections++; + packet->setSubArrayDataByName("tradition_selected", 1, index, count); + } + else { + packet->setSubArrayDataByName("tradition_selected", 0, index, count); + } + tmp_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if (tmp_spell){ + packet->setSubArrayDataByName("tradition_icon", tmp_spell->GetSpellIcon(), index, count); + packet->setSubArrayDataByName("tradition_icon2", tmp_spell->GetSpellIconBackdrop(), index, count); + packet->setSubArrayDataByName("tradition_id", (*itr3)->spellID, index, count); + packet->setSubArrayDataByName("tradition_name", tmp_spell->GetName(), index, count); + packet->setSubArrayDataByName("tradition_unknown_58617_MJ1", 1, index, count); + } + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + } + } + int8 num_available_selections = client->GetPlayer()->GetLevel() / 10; + if (num_selections < num_available_selections) + packet->setDataByName("allow_select", num_available_selections - num_selections); + else + packet->setDataByName("allow_select", 0); + + // Innate Racial Traits + index = 0; + + // total number of Innate traits + num_traits = 0; + for (itr2 = InnateRaceTraits->begin(); itr2 != InnateRaceTraits->end(); itr2++) { + num_traits += (itr2->second).size(); + } + packet->setArrayLengthByName("num_abilities", num_traits); + for (itr2 = InnateRaceTraits->begin(); itr2 != InnateRaceTraits->end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + Spell* innate_spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if (innate_spell) { + packet->setArrayDataByName("ability_icon", innate_spell->GetSpellIcon(), index); + packet->setArrayDataByName("ability_icon2", innate_spell->GetSpellIconBackdrop(), index); + packet->setArrayDataByName("ability_id", (*itr3)->spellID, index); + packet->setArrayDataByName("ability_name", innate_spell->GetName(), index); + } + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + } + } + + if (client->GetVersion() >= 1188) { + // total number of Focus Effects + num_selections = 0; + num_focuseffects = 0; + index = 0; + for (itr2 = FocusEffects->begin(); itr2 != FocusEffects->end(); itr2++) { + num_focuseffects += (itr2->second).size(); + } + packet->setArrayLengthByName("num_focuseffects", num_focuseffects); + for (itr2 = FocusEffects->begin(); itr2 != FocusEffects->end(); itr2++) { + for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++, index++) { + Spell* spell = master_spell_list.GetSpell((*itr3)->spellID, (*itr3)->tier); + if (client->GetPlayer()->HasSpell((*itr3)->spellID, (*itr3)->tier)) { + num_selections++; + packet->setArrayDataByName("focus_selected", 1, index); + } + else { + packet->setArrayDataByName("focus_selected", 0, index); + } + if (spell) { + packet->setArrayDataByName("focus_unknown2", 1, index); + packet->setArrayDataByName("focus_icon", spell->GetSpellIcon(), index); + packet->setArrayDataByName("focus_icon2", spell->GetSpellIconBackdrop(), index); + packet->setArrayDataByName("focus_spell_crc", (*itr3)->spellID, index); + packet->setArrayDataByName("focus_name", spell->GetName(), index); + packet->setArrayDataByName("focus_unknown_58617_MJ1", 1, index); + } + else + LogWrite(SPELL__ERROR, 0, "Traits", "Could not find SpellID %u and Tier %i in Master Spell List (line: %i)", (*itr3)->spellID, (*itr3)->tier, __LINE__); + } + } + num_available_selections = client->GetPlayer()->GetLevel() / 9; + if (num_selections < num_available_selections) + packet->setDataByName("focus_allow_select", num_available_selections - num_selections); + else + packet->setDataByName("focus_allow_select", 0); + } + LogWrite(SPELL__PACKET, 0, "Traits", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + EQ2Packet* data = packet->serialize(); + EQ2Packet* outapp = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + //DumpPacket(outapp); + safe_delete(packet); + safe_delete(data); + + return outapp; +} + +// Jabantiz: Probably a better way to do this but can't think of it right now +TraitData* MasterTraitList::GetTrait(int32 spellID) { + vector::iterator itr; + TraitData* data = NULL; + + MMasterTraitList.readlock(__FUNCTION__, __LINE__); + for (itr = TraitList.begin(); itr != TraitList.end(); itr++) { + if ((*itr)->spellID == spellID) { + data = (*itr); + break; + } + } + MMasterTraitList.releasereadlock(__FUNCTION__, __LINE__); + + return data; +} + +TraitData* MasterTraitList::GetTraitByItemID(int32 itemID) { + vector::iterator itr; + TraitData* data = NULL; + + MMasterTraitList.readlock(__FUNCTION__, __LINE__); + for (itr = TraitList.begin(); itr != TraitList.end(); itr++) { + if ((*itr)->item_id == itemID) { + data = (*itr); + break; + } + } + MMasterTraitList.releasereadlock(__FUNCTION__, __LINE__); + + return data; +} + +void MasterTraitList::DestroyTraits(){ + MMasterTraitList.writelock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = TraitList.begin(); itr != TraitList.end(); itr++) + safe_delete((*itr)); + TraitList.clear(); + MMasterTraitList.releasewritelock(__FUNCTION__, __LINE__); +} diff --git a/source/WorldServer/Traits/Traits.h b/source/WorldServer/Traits/Traits.h new file mode 100644 index 0000000..678a59c --- /dev/null +++ b/source/WorldServer/Traits/Traits.h @@ -0,0 +1,94 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#ifndef __Traits__ +#define __Traits__ + +#include +#include +#include "../../common/Mutex.h" +#include "../../common/types.h" +#include "../../common/EQPacket.h" + +class Client; + +struct TraitData +{ + int32 spellID; + int8 level; + int8 classReq; + int8 raceReq; + bool isTrait; + bool isInate; + bool isFocusEffect; + bool isTraining; + int8 tier; + int8 group; + int32 item_id; +}; + +#define TRAITS_ATTRIBUTES 0 +#define TRAITS_COMBAT 1 +#define TRAITS_NONCOMBAT 2 +#define TRAITS_POOLS 3 +#define TRAITS_RESIST 4 +#define TRAITS_TRADESKILL 5 + +class MasterTraitList +{ +public: + MasterTraitList(); + ~MasterTraitList(); + + bool IdentifyNextTrait(Client* client, map >* traitList, vector* collectTraits, vector* tieredTraits, std::map* previousMatchedSpells, bool omitFoundMatches = false); + bool ChooseNextTrait(Client* client); + int16 GetSpellCount(Client* client, map >* traits, bool onlyCharTraits = false); + bool IsPlayerAllowedTrait(Client* client, TraitData* trait); + bool GenerateTraitLists(Client* client, map > >* sortedTraitList, map >* classTraining, + map >* raceTraits, map >* innateRaceTraits, map >* focusEffects, int16 max_level = 0, int8 trait_group = 255); + + /// Sorts the traits for the given client and creats and sends the trait packet. + /// The Client calling this function + /// EQ2Packet* + EQ2Packet* GetTraitListPacket(Client* client); + + /// Add trait data to the global list. + /// The trait data to add. + void AddTrait(TraitData* data); + + /// Get the total number of traits in the global list. + int Size(); + + /// Get the trait data for the given spell. + /// Spell ID to get trait data for. + TraitData* GetTrait(int32 spellID); + + /// Get the trait data for the given item. + /// Item ID to map to the trait data. + TraitData* GetTraitByItemID(int32 itemID); + + /// Empties the master trait list + void DestroyTraits(); +private: + vector TraitList; + Mutex MMasterTraitList; +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Transmute.cpp b/source/WorldServer/Transmute.cpp new file mode 100644 index 0000000..f6a05f0 --- /dev/null +++ b/source/WorldServer/Transmute.cpp @@ -0,0 +1,339 @@ +#include "Transmute.h" +#include "../common/MiscFunctions.h" +#include "../common/PacketStruct.h" +#include "client.h" +#include "Items/Items.h" +#include +#include +#include "zoneserver.h" +#include "SpellProcess.h" +#include "../common/Log.h" +#include "WorldDatabase.h" + +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; + +using namespace std; + +int32 Transmute::CreateItemRequest(Client* client, Player* player) { + PacketStruct* p = configReader.getStruct("WS_EqTargetItemCmd", client->GetVersion()); + if (!p) return 0; + + union { + sint32 signed_request_id; + int32 request_id; + }; + + do { + signed_request_id = MakeRandomInt(-2147483648, 2147483647); + } while (signed_request_id == 0); + + map* il = player->GetItemList(); + + p->setDataByName("request_id", request_id); + p->setDataByName("request_type", 1); + p->setDataByName("unknownff", 0xff); + + vector transmutables; + + for (auto& itr : *il) { + if (!itr.second) continue; + + if (ItemIsTransmutable(itr.second)) { + transmutables.push_back(itr.first); + } + } + + p->setArrayLengthByName("item_array_size", transmutables.size()); + + for (int i = 0; i < transmutables.size(); i++) { + p->setArrayDataByName("item_id", transmutables[i], i); + } + + client->QueuePacket(p->serialize()); + + delete il; + delete p; + + client->SetTransmuteID(request_id); + + return request_id; +} + +bool Transmute::ItemIsTransmutable(Item* item) { + //Item level > 0 AND Item is not LORE_EQUP, LORE, NO_VALUE etc AND item rarity is >= 5 + //(4 is treasured but the rarity used for journeyman spells) + //I think flag 16384 is NO-TRANSMUTE but not positive + const int32 disqualifyFlags = NO_ZONE | NO_VALUE | TEMPORARY | NO_DESTROY | NO_TRANSMUTE; + const int32 disqualityFlags2 = ORNATE; + if (item->generic_info.adventure_default_level > 0 + && (item->generic_info.item_flags & disqualifyFlags) == 0 + && (item->generic_info.item_flags2 & disqualityFlags2) == 0 + && item->details.tier >= 5 + && item->stack_count <= 1) + { + return true; + } + + return false; +} + +void Transmute::HandleItemResponse(Client* client, Player* player, int32 req_id, int32 item_id) { + Item* item = player->item_list.GetItemFromUniqueID(item_id); + if (!item) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Could not find the item you wish to transmute. Please try again."); + return; + } + + if (!ItemIsTransmutable(item)) { + client->Message(CHANNEL_COLOR_RED, "%s is not transmutable.", item->name.c_str()); + return; + } + + int32 item_level = item->generic_info.adventure_default_level; + Skill* skill = player->GetSkillByName("Transmuting"); + + int32 required_skill = (std::max(item_level, 5) - 5) * 5; + sint32 item_stat_bonus = player->GetStat(ITEM_STAT_TRANSMUTING); + if (!skill || (skill->current_val+item_stat_bonus) < required_skill) { + client->Message(CHANNEL_COLOR_RED, "You need at least %u Transmuting skill to transmute the %s." + " You have %u Transmuting skill.", required_skill, item->name.c_str(), skill ? (skill->current_val+item_stat_bonus) : 0); + return; + } + + client->SetTransmuteID(item_id); + SendConfirmRequest(client, req_id, item); +} + +void Transmute::SendConfirmRequest(Client* client, int32 req_id, Item* item) { + PacketStruct* p = configReader.getStruct("WS_ChoiceWindow", client->GetVersion()); + if (!p) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Struct error for transmutation. Let a dev know."); + return; + } + + ostringstream ss; + ss << "Are you sure you want to transmute the " << item->name << '?'; + p->setMediumStringByName("text", ss.str().c_str()); + p->setMediumStringByName("accept_text", "OK"); + + ss.str(""); + ss << "targetitem " << req_id << ' ' << item->details.unique_id; + string cancel_command = ss.str(); + ss << " 1"; + string accept_command = ss.str(); + + p->setMediumStringByName("accept_command", accept_command.c_str()); + p->setMediumStringByName("cancel_text", "Cancel"); + p->setMediumStringByName("cancel_command", cancel_command.c_str()); + + client->QueuePacket(p->serialize()); + delete p; +} + +void Transmute::HandleConfirmResponse(Client* client, Player* player, int32 item_id) { + Item* item = player->item_list.GetItemFromUniqueID(item_id); + if (!item) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Item no longer exists!"); + return; + } + + client->SetTransmuteID(item_id); + + ZoneServer* zone = player->GetZone(); + if (!zone) return; + + const int32 transmute_item_spell = 5163; + + Spell* spell = master_spell_list.GetSpell(transmute_item_spell, 1); + + if (!spell) { + LogWrite(SPELL__ERROR, 0, "Transmute", "Could not find the Transmute Item spell : %u", transmute_item_spell); + return; + } + + zone->GetSpellProcess()->ProcessSpell(zone, spell, player); +} + +void Transmute::CompleteTransmutation(Client* client, Player* player) { + int32 item_id = client->GetTransmuteID(); + Item* item = player->item_list.GetItemFromUniqueID(item_id); + if (!item) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Item no longer exists!"); + return; + } + + int32 common_mat_id = 0; + int32 rare_mat_id = 0; + + //Figure out the transmutation tier for our loot roll + int32 item_level = item->generic_info.adventure_default_level; + vector& tiers = GetTransmutingTiers(); + for (auto& itr : tiers) { + if (itr.min_level <= item_level && itr.max_level >= item_level) { + //This is the correct tier + int32 tier = item->details.tier; + + if (tier >= ITEM_TAG_FABLED) { + common_mat_id = itr.infusion_id; + rare_mat_id = itr.mana_id; + } + else if (tier >= ITEM_TAG_LEGENDARY) { + common_mat_id = itr.powder_id; + rare_mat_id = itr.infusion_id; + } + else { + common_mat_id = itr.fragment_id; + rare_mat_id = itr.powder_id; + } + + break; + } + } + + if (common_mat_id == 0 || rare_mat_id == 0) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Could not complete transmutation! Tell a dev!"); + return; + } + + //Do the loot roll + const int32 BOTH_ITEMS_CHANCE_PERCENT = 15; + //The common/rare roll only applies if the both items roll fails + const int32 COMMON_MAT_CHANCE_PERCENT = 75; + const int32 RARE_MAT_CHANCE_PERCENT = 25; + + Item* item1 = nullptr; + Item* item2 = nullptr; + + int32 roll = MakeRandomInt(1, 100); + if (roll <= BOTH_ITEMS_CHANCE_PERCENT) { + item1 = master_item_list.GetItem(rare_mat_id); + if (item1) item1 = new Item(item1); + + item2 = master_item_list.GetItem(common_mat_id); + if (item2) item2 = new Item(item2); + } + else if (roll <= COMMON_MAT_CHANCE_PERCENT) { + item1 = master_item_list.GetItem(common_mat_id); + if (item1) item1 = new Item(item1); + } + else { //rare mat roll + item2 = master_item_list.GetItem(rare_mat_id); + if (item2) item2 = new Item(item2); + } + + client->Message(89, "You transmute %s and create: ", item->CreateItemLink(client->GetVersion(), false).c_str()); + + player->item_list.RemoveItem(item, true); + + PacketStruct* packet = configReader.getStruct("WS_QuestComplete", client->GetVersion()); + if (packet) { + packet->setDataByName("title", "Item Transmuted!"); + } + + if (item1) { + item1->details.count = 1; + client->Message(89, " %s", item1->CreateItemLink(client->GetVersion(), false).c_str()); + bool itemDeleted = false; + client->AddItem(item1, &itemDeleted); + + if (packet && !itemDeleted) { + packet->setArrayDataByName("reward_id", item1->details.item_id, 0); + if (client->GetVersion() < 860) + packet->setItemArrayDataByName("item", item1, player, 0, 0, -1); + else if (client->GetVersion() < 1193) + packet->setItemArrayDataByName("item", item, player); + else + packet->setItemArrayDataByName("item", item1, player, 0, 0, 2); + } + } + + if (item2) { + item2->details.count = 1; + client->Message(89, " %s", item2->CreateItemLink(client->GetVersion(), false).c_str()); + bool itemDeleted = false; + client->AddItem(item2, &itemDeleted); + + if (packet && !itemDeleted) { + int32 dataIndex = 1; + if (!item1) { + packet->setArrayLengthByName("num_rewards", 1); + dataIndex = 0; + } + packet->setArrayDataByName("reward_id", item2->details.item_id, dataIndex); + if (client->GetVersion() < 860) + packet->setItemArrayDataByName("item", item2, player, dataIndex, 0, -1); + else if (client->GetVersion() < 1193) + packet->setItemArrayDataByName("item", item2, player, dataIndex); + else + packet->setItemArrayDataByName("item", item2, player, dataIndex, 0, 2); + } + } + + if (packet) { + client->QueuePacket(packet->serialize()); + delete packet; + } + + //Check if we need to apply a skill-up + Skill* skill = player->GetSkillByName("Transmuting"); + if (!skill) { + //Shouldn't happen, sanity check + LogWrite(SKILL__ERROR, 0, "Skill", "Unable to find the transmuting skill for the player %s", player->GetName()); + return; + } + + //Skill up roll + int32 max_trans_level = skill->current_val / 5 + 5; + sint32 level_dif = (sint32)max_trans_level - (sint32)item_level; + if (level_dif > 10 || skill->current_val >= skill->max_val) { + //No skill up possible + LogWrite(SKILL__DEBUG, 7, "Skill", "Transmuting skill up not possible. level_dif = %u, skill val = %u, skill max val = %u", level_dif, skill->current_val, skill->max_val); + return; + } + + //50% Base chance of a skillup at max item level, 20% overall decrease per level difference + const int32 SKILLUP_PERCENT_CHANCE_MAX = 50; + int32 required_roll = SKILLUP_PERCENT_CHANCE_MAX * (1.f - (item_level <= 5 ? 0.f : (level_dif * .2f))); + roll = MakeRandomInt(1, 100); + //LogWrite(SKILL__ERROR, 0, "Skill", "Skill up roll results, roll = %u, required_roll = %u", roll, required_roll); + if (roll <= required_roll) { + player->skill_list.IncreaseSkill(skill, 1); + } +} + +void WorldDatabase::LoadTransmuting() { + DatabaseResult result; + + if (!database_new.Select(&result, + "SELECT min_level, max_level, fragment, powder, infusion, mana FROM `transmuting`")) { + LogWrite(DATABASE__ERROR, 0, "Transmuting", "Error loading transmuting data!"); + return; + } + + Transmute::ProcessDBResult(result); +} + +vector& Transmute::GetTransmutingTiers() { + static vector gTransmutingTiers; + return gTransmutingTiers; +} + +void Transmute::ProcessDBResult(DatabaseResult& result) { + vector& tiers = GetTransmutingTiers(); + tiers.clear(); + tiers.reserve(result.GetNumRows()); + + while (result.Next()) { + tiers.emplace_back(); + TransmutingTier& t = tiers.back(); + + int32_t i = 0; + t.min_level = result.GetInt32(i++); + t.max_level = result.GetInt32(i++); + t.fragment_id = result.GetInt32(i++); + t.powder_id = result.GetInt32(i++); + t.infusion_id = result.GetInt32(i++); + t.mana_id = result.GetInt32(i++); + } +} \ No newline at end of file diff --git a/source/WorldServer/Transmute.h b/source/WorldServer/Transmute.h new file mode 100644 index 0000000..bd87aec --- /dev/null +++ b/source/WorldServer/Transmute.h @@ -0,0 +1,35 @@ +#ifndef TRANSMUTE_H +#define TRANSMUTE_H + +#include "../common/types.h" +#include + +class Client; +class Player; +class Item; +class DatabaseResult; + +class Transmute { +public: + static int32 CreateItemRequest(Client* client, Player* player); + static void HandleItemResponse(Client* client, Player* player, int32 req_id, int32 item_id); + static bool ItemIsTransmutable(Item* item); + static void SendConfirmRequest(Client* client, int32 req_id, Item* item); + static void HandleConfirmResponse(Client* client, Player* player, int32 item_id); + static void CompleteTransmutation(Client* client, Player* player); + static void ProcessDBResult(DatabaseResult& res); + +private: + struct TransmutingTier { + int32 min_level; + int32 max_level; + int32 fragment_id; + int32 powder_id; + int32 infusion_id; + int32 mana_id; + }; + + static std::vector& GetTransmutingTiers(); +}; + +#endif \ No newline at end of file diff --git a/source/WorldServer/Variables.h b/source/WorldServer/Variables.h new file mode 100644 index 0000000..45d2472 --- /dev/null +++ b/source/WorldServer/Variables.h @@ -0,0 +1,92 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQ2_VARIABLES_H +#define EQ2_VARIABLES_H +#include +#include + +class Variable{ +public: + Variable (const char* name, const char* value, const char* comment){ + variableName = string(name); + variableValue = string(value); + if(comment) + variableComment = string(comment); + } + + const char* GetName() { return variableName.c_str(); } + const char* GetValue() { return variableValue.c_str(); } + const char* GetComment() { return variableComment.c_str(); } + string GetNameValuePair(){ return string(variableName).append(" ").append(variableValue); } + void SetValue(const char* value){ + if(value) + variableValue = string(value); + } +private: + string variableName; + string variableValue; + string variableComment; +}; + +class Variables +{ +public: + ~Variables(){ + ClearVariables(); + } + void AddVariable ( Variable* var ) + { + variables[string(var->GetName())] = var; + } + + void ClearVariables() + { + if(variables.size() == 0) + return; + + map::iterator map_list; + for( map_list = variables.begin(); map_list != variables.end(); map_list++ ) { + safe_delete(map_list->second); + } + variables.clear(); + } + + Variable* FindVariable ( string name ) + { + if(variables.count(name) > 0) + return variables[name]; + return 0; + } + + vector* GetVariables(string partial_name){ + vector* ret = new vector(); + map::iterator itr; + for(itr = variables.begin(); itr != variables.end(); itr++){ + if(itr->first.find(partial_name) < 0xFFFFFFFF) + ret->push_back(itr->second); + } + return ret; + } + +private: + map variables; + +}; +#endif diff --git a/source/WorldServer/VisualStates.h b/source/WorldServer/VisualStates.h new file mode 100644 index 0000000..02d9df5 --- /dev/null +++ b/source/WorldServer/VisualStates.h @@ -0,0 +1,282 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/Log.h" +#include "../../common/MiscFunctions.h" +#include + +using namespace std; + +// Visual States must use a hash table because of the large amount that exists and the large spacing +// between their ID's. String and character arrays could not be used for the first iterator because +// it would require the same pointer to access it from the hash table, which is obviously not possible +// since the text is from the client. + +// maximum amount of iterations it will attempt to find a entree +#define HASH_SEARCH_MAX 20 + +class VisualState +{ +public: + VisualState(int inID, char* inName){ + if(!inName) + return; + name = string(inName); + id = inID; + } + + int GetID() { return id; } + const char* GetName() { return name.c_str(); } + string GetNameString() { return name; } + +private: + int id; + string name; +}; +class Emote{ +public: + Emote(char* in_name, int in_visual_state, char* in_message, char* in_targeted_message){ + if(!in_name) + return; + name = string(in_name); + visual_state = in_visual_state; + if(in_message) + message = string(in_message); + if(in_targeted_message) + targeted_message = string(in_targeted_message); + } + int32 GetVisualState() { return visual_state; } + const char* GetName() { return name.c_str(); } + const char* GetMessage() { return message.c_str(); } + const char* GetTargetedMessage() { return targeted_message.c_str(); } + + string GetNameString() { return name; } + string GetMessageString() { return message; } + string GetTargetedMessageString() { return targeted_message; } +private: + int32 visual_state; + string name; + string message; + string targeted_message; +}; + +class EmoteVersionRange { +public: + EmoteVersionRange(char* in_name) + { + name = string(in_name); + } + + ~EmoteVersionRange() + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + Emote* emote = itr->second; + delete range; + delete emote; + } + + version_map.clear(); + } + + void AddVersionRange(int32 min_version, int32 max_version, + char* in_name, int in_visual_state, char* in_message = nullptr, char* in_targeted_message = nullptr) + { + map::iterator itr = FindVersionRange(min_version, max_version); + if (itr != version_map.end()) + { + VersionRange* range = itr->first; + LogWrite(WORLD__ERROR, 0, "Emotes Table Error: Duplicate emote mapping of %s with range min %u max %u, Existing found with range min %u max %u\n", name.c_str(), min_version, max_version, range->GetMinVersion(), range->GetMaxVersion()); + return; + } + + version_map.insert(make_pair(new VersionRange(min_version, max_version), new Emote(in_name, in_visual_state, in_message, in_targeted_message))); + } + + map::iterator FindVersionRange(int32 min_version, int32 max_version) + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion()) + return itr; + // if the min version is in range, but max range is 0 + else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0) + return itr; + // if min version is 0 and max_version has a cap + else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion()) + return itr; + } + + return version_map.end(); + } + + map::iterator FindEmoteVersion(int32 version) + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (version >= range->GetMinVersion() && (range->GetMaxVersion() == 0 || version <= range->GetMaxVersion())) + return itr; + } + + return version_map.end(); + } + + const char* GetName() { return name.c_str(); } + string GetNameString() { return name; } + + map::iterator GetRangeEnd() { return version_map.end(); } +private: + map version_map; + string name; +}; + +class VisualStates +{ +public: + ~VisualStates(){ + Reset(); + } + + void Reset(){ + ClearVisualStates(); + ClearEmotes(); + ClearSpellVisuals(); + } + + void ClearEmotes(){ + map::iterator map_list; + for(map_list = emoteMap.begin(); map_list != emoteMap.end(); map_list++ ) + safe_delete(map_list->second); + emoteMap.clear(); + } + + void ClearVisualStates(){ + map::iterator map_list; + for(map_list = visualStateMap.begin(); map_list != visualStateMap.end(); map_list++ ) + safe_delete(map_list->second); + visualStateMap.clear(); + } + + void InsertVisualState(VisualState* vs){ + visualStateMap[vs->GetNameString()] = vs; + } + + VisualState* FindVisualState(string var){ + if(visualStateMap.count(var) > 0) + return visualStateMap[var]; + return 0; + } + + void InsertEmoteRange(EmoteVersionRange* emote) { + emoteMap[emote->GetName()] = emote; + } + + EmoteVersionRange* FindEmoteRange(string var) { + if (emoteMap.count(var) > 0) + { + return emoteMap[var]; + } + return 0; + } + + Emote* FindEmote(string var, int32 version){ + if (emoteMap.count(var) > 0) + { + map::iterator itr = emoteMap[var]->FindEmoteVersion(version); + + if (itr != emoteMap[var]->GetRangeEnd()) + { + Emote* emote = itr->second; + return emote; + } + } + return 0; + } + + void InsertSpellVisualRange(EmoteVersionRange* emote, int32 spell_visual_id) { + spellMap[emote->GetName()] = emote; + spellMapID[spell_visual_id] = emote; + } + + EmoteVersionRange* FindSpellVisualRange(string var) { + if (spellMap.count(var) > 0) + { + return spellMap[var]; + } + return 0; + } + + EmoteVersionRange* FindSpellVisualRangeByID(int32 id) { + if (spellMapID.count(id) > 0) + { + return spellMapID[id]; + } + return 0; + } + + Emote* FindSpellVisual(string var, int32 version){ + if (spellMap.count(var) > 0) + { + map::iterator itr = spellMap[var]->FindEmoteVersion(version); + + if (itr != spellMap[var]->GetRangeEnd()) + { + Emote* emote = itr->second; + return emote; + } + } + return 0; + } + + Emote* FindSpellVisualByID(int32 visual_id, int32 version){ + if (spellMapID.count(visual_id) > 0) + { + map::iterator itr = spellMapID[visual_id]->FindEmoteVersion(version); + + if (itr != spellMapID[visual_id]->GetRangeEnd()) + { + Emote* emote = itr->second; + return emote; + } + } + return 0; + } + + void ClearSpellVisuals(){ + map::iterator map_list; + for(map_list = spellMap.begin(); map_list != spellMap.end(); map_list++ ) + safe_delete(map_list->second); + spellMap.clear(); + spellMapID.clear(); + } +private: + map visualStateMap; + map emoteMap; + map spellMap; + map spellMapID; +}; + diff --git a/source/WorldServer/Web/WorldWeb.cpp b/source/WorldServer/Web/WorldWeb.cpp new file mode 100644 index 0000000..c8ff5ec --- /dev/null +++ b/source/WorldServer/Web/WorldWeb.cpp @@ -0,0 +1,84 @@ +#include "../World.h" +#include "../LoginServer.h" + +#include +#include +#include + +extern ZoneList zone_list; +extern World world; +extern LoginServer loginserver; +extern sint32 numclients; + +void World::Web_worldhandle_status(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree pt; + + pt.put("web_status", "online"); + bool world_online = world.world_loaded; + pt.put("world_status", world.world_loaded ? "online" : "offline"); + pt.put("world_uptime", (getCurrentTimestamp() - world.world_uptime)); + auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - world.world_uptime)); + std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds)); + pt.put("world_uptime_string", uptime_str); + pt.put("login_connected", loginserver.Connected() ? "connected" : "disconnected"); + pt.put("player_count", zone_list.GetZonesPlayersCount()); + pt.put("client_count", numclients); + pt.put("zones_connected", zone_list.Count()); + pt.put("world_reloading", world.IsReloadingSubsystems() ? "yes" : "no"); + + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void World::Web_worldhandle_clients(const http::request& req, http::response& res) { + zone_list.PopulateClientList(res); +} + +void ZoneList::PopulateClientList(http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree maintree; + + std::ostringstream oss; + + MClientList.lock(); + map::iterator itr; + for(itr = client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second){ + Client* cur = (Client*)itr->second; + boost::property_tree::ptree pt; + pt.put("character_id", cur->GetCharacterID()); + pt.put("character_name", cur->GetPlayer() ? cur->GetPlayer()->GetName() : "N/A"); + pt.put("class1", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class1() : 0); + pt.put("class2", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class2() : 0); + pt.put("class3", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_class3() : 0); + pt.put("tradeskill_class1", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class1() : 0); + pt.put("tradeskill_class2", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class2() : 0); + pt.put("tradeskill_class3", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_class3() : 0); + pt.put("race", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_race() : 0); + pt.put("level", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_level() : 0); + pt.put("effective_level", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_effective_level() : 0); + pt.put("tradeskill_level", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_tradeskill_level() : 0); + pt.put("account_age", cur->GetPlayer() ? cur->GetPlayer()->GetInfoStruct()->get_account_age_base() : 0); + pt.put("account_id", cur->GetAccountID()); + pt.put("version", cur->GetVersion()); + pt.put("is_zoning", (cur->IsZoning() || !cur->IsReadyForUpdates())); + + bool linkdead = cur->GetPlayer() ? (((cur->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0)) : false; + pt.put("is_linkdead", linkdead); + pt.put("in_zone", cur->IsReadyForUpdates()); + pt.put("zonename", (cur->GetPlayer() && cur->GetPlayer()->GetZone()) ? cur->GetPlayer()->GetZone()->GetZoneName() : "N/A"); + maintree.add_child("Client", pt); + } + } + MClientList.unlock(); + + boost::property_tree::write_json(oss, maintree); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + diff --git a/source/WorldServer/Widget.cpp b/source/WorldServer/Widget.cpp new file mode 100644 index 0000000..37444ca --- /dev/null +++ b/source/WorldServer/Widget.cpp @@ -0,0 +1,468 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include +#include + +#include "Widget.h" +#include "../common/ConfigReader.h" +#include "Spells.h" +#include "World.h" +#include "../common/Log.h" +#include "ClientPacketFunctions.h" +#include "LuaInterface.h" + +extern World world; +extern ConfigReader configReader; +extern MasterSpellList master_spell_list; +extern LuaInterface* lua_interface; + +Widget::Widget(){ + widget_id = 0; + widget_x = 0; + widget_y = 0; + widget_z = 0; + action_spawn = 0; + action_spawn_id = 0; + linked_spawn = 0; + linked_spawn_id = 0; + appearance.pos.state = 1; + appearance.difficulty = 0; + spawn_type = 2; + appearance.activity_status = 64; + include_location = true; + include_heading = true; + is_open = false; + widget_type = 0; + open_heading = -1; + closed_heading = -1; + open_y = 0; + open_x = 0; + open_z = 0; + close_x = 0; + close_z = 0; + movement_index = 0; + movement_interrupted = false; + resume_movement = true; + attack_resume_needed = false; + multi_floor_lift = false; + MMovementLoop.SetName("Widget::MMovementLoop"); + last_movement_update = Timer::GetCurrentTime2(); +} +Widget::~Widget(){ + +} + +int32 Widget::GetWidgetID(){ + return widget_id; +} + +EQ2Packet* Widget::serialize(Player* player, int16 version){ + return spawn_serialize(player, version); +} + +void Widget::SetWidgetID(int32 val){ + widget_id = val; +} + +void Widget::SetWidgetX(float val){ + widget_x = val; +} + +float Widget::GetWidgetX(){ + return widget_x; +} + +void Widget::SetWidgetY(float val){ + widget_y = val; +} + +float Widget::GetWidgetY(){ + return widget_y; +} + +void Widget::SetWidgetZ(float val){ + widget_z = val; +} + +float Widget::GetWidgetZ(){ + return widget_z; +} + +void Widget::SetWidgetIcon(int8 val){ + appearance.icon = val; +} +void Widget::SetOpenDuration(int16 val){ + open_duration = val; +} +int16 Widget::GetOpenDuration(){ + return open_duration; +} + +Widget* Widget::Copy(){ + Widget* new_spawn = new Widget(); + if(GetOpenY() > 0) + appearance.pos.state = 0; + if(GetSizeOffset() > 0){ + int8 offset = GetSizeOffset()+1; + sint32 tmp_size = size + (rand()%offset - rand()%offset); + if(tmp_size < 0) + tmp_size = 1; + else if(tmp_size >= 0xFFFF) + tmp_size = 0xFFFF; + new_spawn->size = (int16)tmp_size; + } + else + new_spawn->size = size; + new_spawn->SetCollector(IsCollector()); + new_spawn->SetMerchantID(merchant_id); + new_spawn->SetMerchantType(merchant_type); + new_spawn->SetMerchantLevelRange(GetMerchantMinLevel(), GetMerchantMaxLevel()); + new_spawn->SetPrimaryCommands(&primary_command_list); + new_spawn->primary_command_list_id = primary_command_list_id; + new_spawn->SetSecondaryCommands(&secondary_command_list); + new_spawn->secondary_command_list_id = secondary_command_list_id; + new_spawn->database_id = database_id; + memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData)); + new_spawn->SetWidgetID(widget_id); + new_spawn->SetWidgetX(widget_x); + new_spawn->SetWidgetY(widget_y); + new_spawn->SetWidgetZ(widget_z); + new_spawn->SetIncludeHeading(include_heading); + new_spawn->SetIncludeLocation(include_location); + new_spawn->SetOpenY(open_y); + new_spawn->SetCloseY(close_y); + new_spawn->SetOpenDuration(open_duration); + if(GetOpenSound()) + new_spawn->SetOpenSound(GetOpenSound()); + if(GetCloseSound()) + new_spawn->SetCloseSound(GetCloseSound()); + new_spawn->SetOpenHeading(open_heading); + new_spawn->SetClosedHeading(closed_heading); + new_spawn->SetWidgetType(widget_type); + new_spawn->SetActionSpawnID(action_spawn_id); + new_spawn->SetLinkedSpawnID(linked_spawn_id); + new_spawn->SetTransporterID(GetTransporterID()); + new_spawn->SetHouseID(GetHouseID()); + new_spawn->SetCloseX(GetCloseX()); + new_spawn->SetCloseZ(GetCloseZ()); + new_spawn->SetOpenX(GetOpenX()); + new_spawn->SetOpenZ(GetOpenZ()); + new_spawn->SetMultiFloorLift(multi_floor_lift); + new_spawn->SetSoundsDisabled(IsSoundsDisabled()); + return new_spawn; +} + +void Widget::SetIncludeLocation(bool val){ + include_location = val; +} +bool Widget::GetIncludeLocation(){ + return include_location; +} +void Widget::SetIncludeHeading(bool val){ + include_heading = val; +} +bool Widget::GetIncludeHeading(){ + return include_heading; +} +float Widget::GetOpenHeading(){ + return open_heading; +} +void Widget::SetOpenHeading(float val){ + open_heading = val; +} +float Widget::GetClosedHeading(){ + return closed_heading; +} +void Widget::SetClosedHeading(float val){ + closed_heading = val; +} +float Widget::GetOpenY(){ + return open_y; +} +void Widget::SetOpenY(float val){ + open_y = val; +} +float Widget::GetCloseY(){ + return close_y; +} +void Widget::SetCloseY(float val){ + close_y = val; +} +bool Widget::IsOpen(){ + std::lock_guard lk(MWidgetMutex); + bool widget_open = is_open; + return widget_open; +} +int8 Widget::GetWidgetType(){ + return widget_type; +} +void Widget::SetWidgetType(int8 val){ + widget_type = val; +} +int32 Widget::GetActionSpawnID(){ + return action_spawn_id; +} +void Widget::SetActionSpawnID(int32 id){ + action_spawn_id = id; +} +int32 Widget::GetLinkedSpawnID(){ + return linked_spawn_id; +} +void Widget::SetLinkedSpawnID(int32 id){ + linked_spawn_id = id; +} +const char* Widget::GetOpenSound(){ + if(open_sound.length() > 0) + return open_sound.c_str(); + else + return 0; +} +void Widget::SetOpenSound(const char* name){ + open_sound = string(name); +} +const char* Widget::GetCloseSound(){ + if(close_sound.length() > 0) + return close_sound.c_str(); + else + return 0; +} +void Widget::SetCloseSound(const char* name){ + close_sound = string(name); +} + +void Widget::HandleTimerUpdate(){ + if(widget_type == WIDGET_TYPE_LIFT) + return; //This Widget is a lift, return. + else if (widget_type == WIDGET_TYPE_DOOR && is_open) + HandleUse(nullptr, ""); +} + +void Widget::OpenDoor(){ + std::lock_guard lk(MWidgetMutex); + if(GetOpenHeading() >= 0) + SetHeading(GetOpenHeading()); + float openX = GetOpenX(); + float openY = GetOpenY(); + float openZ = GetOpenZ(); + if(openX != 0 || openY != 0 || openZ != 0 ) { + float x = GetX(); + float y = GetY(); + float z = GetZ(); + + if(openX != 0) + x = openX; + if(openY != 0) + y = openY; + if(openZ != 0) + z = openZ; + + AddRunningLocation(x, y, z, 4); + + float diff = GetDistance(GetX(), GetY(), GetZ(), x, y, z); + if(diff < 0) + diff*=-1; + GetZone()->AddWidgetTimer(this, diff / 4); + } + if (widget_type != WIDGET_TYPE_LIFT) + SetActivityStatus(0); + is_open = true; + if(open_duration > 0) + GetZone()->AddWidgetTimer(this, open_duration); + + GetZone()->SendSpawnChanges(this); +} + +void Widget::CloseDoor(){ + std::lock_guard lk(MWidgetMutex); + if(GetClosedHeading() > 0) + SetHeading(GetClosedHeading()); + else if(GetOpenHeading() >= 0) + SetHeading(GetSpawnOrigHeading()); + + if (widget_type != WIDGET_TYPE_LIFT) + SetActivityStatus(64); + + if (GetCloseX() != 0 || GetCloseY() != 0 || GetCloseZ() != 0 || GetOpenX() != 0 || GetOpenY() != 0 || GetOpenZ() != 0) { + float x = GetSpawnOrigX(); + float y = GetSpawnOrigY(); + float z = GetSpawnOrigZ(); + + if (GetCloseX() != 0) + x = GetCloseX(); + if (GetCloseY() != 0) + y = GetCloseY(); + if (GetCloseZ() != 0) + z = GetCloseZ(); + + AddRunningLocation(x, y, z, 4); + + float diff = GetDistance(GetX(), GetY(), GetZ(), x, y, z); + + if (diff < 0) + diff *= -1; + GetZone()->AddWidgetTimer(this, diff / 4); + } + + is_open = false; + + GetZone()->SendSpawnChanges(this); +} + +void Widget::ProcessUse(Spawn* caller){ + if(widget_type == WIDGET_TYPE_LIFT && GetZone()->HasWidgetTimer(this)) //this door is a lift and in use, wait until it gets to the + return; + if (GetZone()->CallSpawnScript(this, SPAWN_SCRIPT_USEDOOR, caller, "", is_open)) { + // handled in lua, nothing to do here! + } + else + { + bool wasOpen = IsOpen(); + if(wasOpen) //close + CloseDoor(); + else //open + OpenDoor(); + + bool isOpen = IsOpen(); + if(isOpen){ + if(GetOpenSound()) + GetZone()->PlaySoundFile(0, GetOpenSound(), widget_x, widget_y, widget_z); + } + else + if(GetCloseSound()) + GetZone()->PlaySoundFile(0, GetCloseSound(), widget_x, widget_y, widget_z); + } +} + +void Widget::HandleUse(Client* client, string command, int8 overrideWidgetType){ + vector destinations; + //The following check disables the use of doors and other widgets if the player does not meet the quest requirements + //If this is from a script ignore this check (client will be null) + + if (overrideWidgetType == 0xFF) + overrideWidgetType = widget_type; + + if (client) { + bool meets_quest_reqs = MeetsSpawnAccessRequirements(client->GetPlayer()); + if (!meets_quest_reqs && (GetQuestsRequiredOverride() & 2) == 0) + return; + else if (meets_quest_reqs && appearance.show_command_icon != 1) + return; + } + std::string cmdlower(command); + boost::algorithm::to_lower(cmdlower); + + if (client && GetTransporterID() > 0) + { + client->SetTemporaryTransportID(0); + GetZone()->GetTransporters(&destinations, client, GetTransporterID()); + } + bool skipHouseCommands = (cmdlower == "access" || cmdlower == "visit"); + if (!skipHouseCommands && destinations.size() && client) + client->ProcessTeleport(this, &destinations, GetTransporterID()); + else if (!skipHouseCommands && (overrideWidgetType == WIDGET_TYPE_DOOR || overrideWidgetType == WIDGET_TYPE_LIFT)){ + Widget* widget = this; + if (!action_spawn && action_spawn_id > 0){ + Spawn* spawn = GetZone()->GetSpawnByDatabaseID(action_spawn_id); + if (spawn && spawn->IsWidget()) + action_spawn = (Widget*)spawn; + } + if (!linked_spawn && linked_spawn_id > 0){ + Spawn* spawn = GetZone()->GetSpawnByDatabaseID(linked_spawn_id); + if (spawn && spawn->IsWidget()) + linked_spawn = (Widget*)spawn; + } + if (linked_spawn){ + widget = linked_spawn; + ProcessUse(client ? client->GetPlayer() : nullptr); //fire the first door, then fire the linked door below + } + else if (action_spawn) { + widget = action_spawn; + if (!widget->linked_spawn && widget->linked_spawn_id > 0) { + Spawn* spawn = GetZone()->GetSpawnByDatabaseID(widget->linked_spawn_id); + if (spawn && spawn->IsWidget()) + widget->linked_spawn = (Widget*)spawn; + } + + if (widget->linked_spawn) + widget->linked_spawn->ProcessUse(client ? client->GetPlayer() : nullptr); + } + widget->ProcessUse(client ? client->GetPlayer() : nullptr); + } + else if (client && cmdlower == "access" && GetZone()->GetInstanceID() && + (GetZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE || GetZone()->GetInstanceType() == GUILD_HOUSE_INSTANCE)) { + // Used a door within a house + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(GetZone()->GetInstanceID()); + if (ph) { + HouseZone* hz = world.GetHouseZone(ph->house_id); + if (hz) { + int32 id = client->GetPlayer()->GetIDWithPlayerSpawn(this); + ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id)); + + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id); + } + } + } + else if (client && m_houseID > 0 && cmdlower == "access") { + // Used a door to enter a house + HouseZone* hz = nullptr; + PlayerHouse* ph = nullptr; + + int32 id = 0; + if(client->GetVersion() <= 561) { + id = client->GetPlayer()->GetIDWithPlayerSpawn(this); + ph = world.GetPlayerHouse(client, id, 0, &hz); + } + else { + hz = world.GetHouseZone(m_houseID); + if(hz) { + ph = world.GetPlayerHouseByHouseID(client->GetPlayer()->GetCharacterID(), hz->id); + } + id = client->GetPlayer()->GetID();// reconsider? + } + + if (ph && hz) { + // if we aren't in our own house we should get the full list of houses we can visit + if ( client->GetCurrentZone()->GetInstanceType() != Instance_Type::PERSONAL_HOUSE_INSTANCE && client->GetVersion() > 561 ) + ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id)); + + ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id); + client->GetCurrentZone()->SendHouseItems(client); + } + else { + if (hz) + ClientPacketFunctions::SendHousePurchase(client, hz, 0); + } + } + else if (client && m_houseID > 0 && cmdlower == "visit") { + HouseZone* hz = nullptr; + int32 id = client->GetPlayer()->GetIDWithPlayerSpawn(this); + PlayerHouse* ph = world.GetPlayerHouse(client, id, 0, &hz); + ClientPacketFunctions::SendHousingList(client); + if(hz != nullptr) { + ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id)); + } + } + else if (client && command.length() > 0) { + EntityCommand* entity_command = FindEntityCommand(command); + if (entity_command) + client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget()); + } +} \ No newline at end of file diff --git a/source/WorldServer/Widget.h b/source/WorldServer/Widget.h new file mode 100644 index 0000000..c85b76b --- /dev/null +++ b/source/WorldServer/Widget.h @@ -0,0 +1,133 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_WIDGET__ +#define __EQ2_WIDGET__ +#include "Spawn.h" +#include "client.h" +#include +#include + +using namespace std; +#define WIDGET_TYPE_GENERIC 0 +#define WIDGET_TYPE_DOOR 1 +#define WIDGET_TYPE_LIFT 2 +class Widget : public Spawn{ +public: + Widget(); + virtual ~Widget(); + bool IsWidget(){ return true; } + int32 GetWidgetID(); + void SetWidgetID(int32 val); + void SetWidgetX(float val); + float GetWidgetX(); + void SetWidgetY(float val); + float GetWidgetY(); + void SetWidgetZ(float val); + float GetWidgetZ(); + void SetIncludeLocation(bool val); + bool GetIncludeLocation(); + void SetIncludeHeading(bool val); + bool GetIncludeHeading(); + void SetWidgetIcon(int8 val); + Widget* Copy(); + EQ2Packet* serialize(Player* player, int16 version); + void HandleTimerUpdate(); + void OpenDoor(); + void CloseDoor(); + void HandleUse(Client* client, string command, int8 overrideWidgetType=0xFF); + float GetOpenHeading(); + void SetOpenHeading(float val); + float GetClosedHeading(); + void SetClosedHeading(float val); + float GetOpenY(); + void SetOpenY(float val); + float GetCloseY(); + void SetCloseY(float val); + float GetOpenX(){return open_x;} + float GetOpenZ(){return open_z;} + float GetCloseX(){return close_x;} + float GetCloseZ(){return close_z;} + void SetOpenX(float x){open_x = x;} + void SetOpenZ(float z){open_z = z;} + void SetCloseX(float x){close_x = x;} + void SetCloseZ(float z){close_z = z;} + int8 GetWidgetType(); + void SetWidgetType(int8 val); + bool IsOpen(); + int32 GetActionSpawnID(); + void SetActionSpawnID(int32 id); + int32 GetLinkedSpawnID(); + void SetLinkedSpawnID(int32 id); + const char* GetOpenSound(); + void SetOpenSound(const char* name); + const char* GetCloseSound(); + void SetCloseSound(const char* name); + void SetOpenDuration(int16 val); + int16 GetOpenDuration(); + void ProcessUse(Spawn* caller=nullptr); + void SetHouseID(int32 val) { m_houseID = val; } + int32 GetHouseID() { return m_houseID; } + + void SetMultiFloorLift(bool val) { multi_floor_lift = val; } + bool GetMultiFloorLift() { return multi_floor_lift; } + + static string GetWidgetTypeNameByTypeID(int8 type) + { + switch (type) + { + case WIDGET_TYPE_DOOR: + return string("Door"); + break; + case WIDGET_TYPE_LIFT: + return string("Lift"); + break; + } + + return string("Generic"); + } +private: + int8 widget_type; + bool include_location; + bool include_heading; + float widget_x; + float widget_y; + float widget_z; + int32 widget_id; + float open_heading; + float closed_heading; + float open_y; + float close_y; + Widget* action_spawn; + int32 action_spawn_id; + Widget* linked_spawn; + int32 linked_spawn_id; + bool is_open; + string open_sound; + string close_sound; + int16 open_duration; + int32 m_houseID; + float open_x; + float open_z; + float close_x; + float close_z; + bool multi_floor_lift; + std::mutex MWidgetMutex; +}; +#endif diff --git a/source/WorldServer/World.cpp b/source/WorldServer/World.cpp new file mode 100644 index 0000000..707638a --- /dev/null +++ b/source/WorldServer/World.cpp @@ -0,0 +1,2989 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include +#include "World.h" +#include "Items/Items.h" +#include "Items/Items_ToV.h" +#include "Items/Items_DoV.h" +#include "Spells.h" +#include "client.h" +#include "WorldDatabase.h" +#include "../common/debug.h" +#include "races.h" +#include "classes.h" +#include "VisualStates.h" +#include "Appearances.h" +#include "Skills.h" +#include "LoginServer.h" +#include "Quests.h" +#include "Factions.h" +#include "Guilds/Guild.h" +#include "Collections/Collections.h" +#include "Achievements/Achievements.h" +#include "Recipes/Recipe.h" +#include "Rules/Rules.h" +#include "../common/Log.h" +#include "Traits/Traits.h" +#include "Chat/Chat.h" +#include "Tradeskills/Tradeskills.h" +#include "AltAdvancement/AltAdvancement.h" +#include "LuaInterface.h" +#include "HeroicOp/HeroicOp.h" +#include "RaceTypes/RaceTypes.h" +#include "LuaInterface.h" +#include "../common/version.h" + +#include "Player.h" + +#include +#include +#include + +MasterQuestList master_quest_list; +MasterItemList master_item_list; +MasterSpellList master_spell_list; +MasterTraitList master_trait_list; +MasterHeroicOPList master_ho_list; +MasterSkillList master_skill_list; +MasterFactionList master_faction_list; +MasterCollectionList master_collection_list; +MasterAchievementList master_achievement_list; +MasterRecipeList master_recipe_list; +MasterRecipeBookList master_recipebook_list; +MasterTradeskillEventsList master_tradeskillevent_list; +MasterAAList master_aa_list; +MasterRaceTypeList race_types_list; +MasterAANodeList master_tree_nodes; +ClientList client_list; +ZoneList zone_list; +ZoneAuth zone_auth; +int32 Spawn::next_id = 1; +int32 WorldDatabase::next_id = 0; +Commands commands; +Variables variables; +VisualStates visual_states; +Appearances master_appearance_list; +Classes classes; +Races races; +mapEQOpcodeManager; +map EQOpcodeVersions; +WorldDatabase database; +GuildList guild_list; +Chat chat; +Player player; + +extern ConfigReader configReader; +extern LoginServer loginserver; +extern World world; +extern ZoneList zone_list; +extern RuleManager rule_manager; +extern LuaInterface* lua_interface; +extern sint32 numclients; + +World::World() : save_time_timer(300000), time_tick_timer(3000), vitality_timer(3600000), player_stats_timer(60000), server_stats_timer(60000), /*remove_grouped_player(30000),*/ guilds_timer(60000), lotto_players_timer(500), watchdog_timer(10000) { + save_time_timer.Start(); + time_tick_timer.Start(); + vitality_timer.Start(); + player_stats_timer.Start(); + server_stats_timer.Start(); + //remove_grouped_player.Start(); + guilds_timer.Start(); + lotto_players_timer.Start(); + watchdog_timer.Start(); + xp_rate = -1; + ts_xp_rate = -1; + vitality_frequency = 0xFFFFFFFF; + vitality_amount = -1; + last_checked_time = 0; + items_loaded = false; + spells_loaded = false; + achievments_loaded = false; + merchant_inventory_items.clear(); + MHouseZones.SetName("World::m_houseZones"); + MPlayerHouses.SetName("World::m_playerHouses"); + MWorldMaps.SetName("World::MWorldMaps"); + MWorldRegionMaps.SetName("World::MWorldRegionMaps"); + world_webserver = nullptr; + world_loaded = false; + world_uptime = getCurrentTimestamp(); +} + +World::~World(){ + // At this point the log system is already shut down so no calls to LogWrite are allowed in any of the functions called by this deconstructor + DeleteSpawns(); + if(database.GetStatus() == database.Connected) + WriteServerStatistics(); + RemoveServerStatistics(); + DeleteMerchantsInfo(); + MutexMap::iterator itr = lotto_players.begin(); + while (itr.Next()) + safe_delete(itr->second); + map::iterator itr2; + for (itr2 = m_houseZones.begin(); itr2 != m_houseZones.end(); itr2++) + safe_delete(itr2->second); + m_houseZones.clear(); + + tov_itemstat_conversion.clear(); + + PurgeStartingLists(); + PurgeVoiceOvers(); + + map::iterator itr3; + for (itr3 = region_maps.begin(); itr3 != region_maps.end(); itr3++) + safe_delete(itr3->second); + + map::iterator itr4; + for (itr4 = maps.begin(); itr4 != maps.end(); itr4++) + safe_delete(itr4->second); + + PurgeNPCSpells(); + + safe_delete(world_webserver); +} + +void World::init(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password){ + WorldDatabase::next_id = database.GetMaxHotBarID(); + + LogWrite(COMMAND__DEBUG, 1, "Command", "-Loading Commands..."); + database.LoadCommandList(); + LogWrite(COMMAND__DEBUG, 1, "Command", "-Load Commands complete!"); + + LogWrite(FACTION__DEBUG, 1, "Faction", "-Loading Factions..."); + database.LoadFactionList(); + LogWrite(FACTION__DEBUG, 1, "Faction", "-Load Factions complete!..."); + + LogWrite(SKILL__DEBUG, 1, "Skill", "-Loading Skills..."); + database.LoadSkills(); + LogWrite(SKILL__DEBUG, 1, "Skill", "-Load Skills complete..."); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `variables`..."); + database.LoadGlobalVariables(); + LogWrite(WORLD__DEBUG, 1, "World", "-Load `variables` complete!"); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `appearances`..."); + database.LoadAppearanceMasterList(); + LogWrite(WORLD__DEBUG, 1, "World", "-Load `appearances` complete!"); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `visual_states`..."); + database.LoadVisualStates(); + LogWrite(WORLD__DEBUG, 1, "World", "-Load `visual states` complete!"); + + LoadStartingLists(); + LoadVoiceOvers(); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading EXP Curve From DB..."); + player.InitXPTable(); + LogWrite(WORLD__DEBUG, 1, "World", "-Loading EXP Curve From DB Complete!"); + + LogWrite(WORLD__DEBUG, 1, "World", "-Setting system parameters..."); + Variable* var = variables.FindVariable("gametime"); + const char* time_string = 0; + char default_time[] = "0/0/3800 8:30"; + + if(var) + time_string = var->GetValue(); + + if(!time_string) + time_string = default_time; + int year, month, day, hour, minute; + sscanf (time_string, "%d/%d/%d %d:%d", &month, &day, &year, &hour, &minute); + LogWrite(WORLD__DEBUG, 3, "World", "--Setting World Time to %s...", time_string); + world_time.month = month; + world_time.day = day; + world_time.year = year; + world_time.hour = hour; + world_time.minute = minute; + + LogWrite(WORLD__DEBUG, 3, "World", "--Loading Vitality Information..."); + LoadVitalityInformation(); + + LogWrite(WORLD__DEBUG, 3, "World", "--Loading Server Statistics..."); + database.LoadServerStatistics(); + + LogWrite(WORLD__DEBUG, 3, "World", "--Setting Server Start Time..."); + UpdateServerStatistic(STAT_SERVER_START_TIME, Timer::GetUnixTimeStamp(), true); + + LogWrite(WORLD__DEBUG, 3, "World", "--Resetting Accepted Connections to 0..."); + UpdateServerStatistic(STAT_SERVER_ACCEPTED_CONNECTION, 0, true); + + LogWrite(WORLD__DEBUG, 3, "World", "--Resetting Active Zones to 0..."); + UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, 0, true); + + // Clear all online players at server startup + LogWrite(WORLD__DEBUG, 3, "World", "--Resetting characters online flags..."); + database.ToggleCharacterOnline(); + LogWrite(WORLD__DEBUG, 1, "World", "-Set system parameters complete!"); + + LogWrite(RULESYS__DEBUG, 1, "Rules", "-Loading Rule Sets..."); + database.LoadRuleSets(); + LogWrite(RULESYS__DEBUG, 1, "Rules", "-Load Rule Sets complete!"); + + LoadItemBlueStats(); + //PopulateTOVStatMap(); + group_buff_updates.Start(rule_manager.GetGlobalRule(R_Client, GroupSpellsTimer)->GetInt32()); + + if(web_ipaddr.size() > 0 && web_port > 0) { + try { + world_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password); + + world_webserver->register_route("/status", World::Web_worldhandle_status); + world_webserver->register_route("/clients", World::Web_worldhandle_clients); + world_webserver->run(); + LogWrite(INIT__INFO, 0, "Init", "World Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port); + } + catch (const std::exception& e) { + LogWrite(INIT__ERROR, 0, "Init", "World Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what()); + } + } +} + + + +PacketStruct* World::GetWorldTime(int16 version){ + MWorldTime.readlock(__FUNCTION__, __LINE__); + PacketStruct* packet = configReader.getStruct("WS_GameWorldTime", version); + if(packet){ + packet->setDataByName("year", world_time.year); + packet->setDataByName("month", world_time.month); + packet->setDataByName("day", world_time.day); + packet->setDataByName("hour", world_time.hour); + packet->setDataByName("minute", world_time.minute); + packet->setDataByName("unknown", 250); + packet->setDataByName("unix_time", Timer::GetUnixTimeStamp()); + packet->setDataByName("unknown2", 1); + } + MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + return packet; +} + +float World::GetXPRate(){ + xp_rate = rule_manager.GetGlobalRule(R_Player, XPMultiplier)->GetFloat(); + LogWrite(WORLD__DEBUG, 0, "World", "Setting Global XP Rate to: %.2f", xp_rate); + return xp_rate; +} + +float World::GetTSXPRate(){ + ts_xp_rate = rule_manager.GetGlobalRule(R_Player, TSXPMultiplier)->GetFloat(); + LogWrite(WORLD__DEBUG, 0, "World", "Setting Global Tradeskill XP Rate to: %.2f", ts_xp_rate); + return ts_xp_rate; +} + +void World::Process(){ + if(last_checked_time > Timer::GetCurrentTime2()) + return; + last_checked_time = Timer::GetCurrentTime2() + 1000; + + if(save_time_timer.Check()) + { + MWorldTime.readlock(__FUNCTION__, __LINE__); + database.SaveWorldTime(&world_time); + MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + } + + if(time_tick_timer.Check()) + { + MWorldTime.writelock(__FUNCTION__, __LINE__); + WorldTimeTick(); + MWorldTime.releasewritelock(__FUNCTION__, __LINE__); + } + + if(lua_interface) + lua_interface->Process(); + + if(vitality_timer.Check()) + UpdateVitality(); + if (player_stats_timer.Check()) + WritePlayerStatistics(); + if (server_stats_timer.Check()) + WriteServerStatistics(); + /*if(remove_grouped_player.Check()) + CheckRemoveGroupedPlayer();*/ + if (group_buff_updates.Check()) + GetGroupManager()->UpdateGroupBuffs(); + if (guilds_timer.Check()) + SaveGuilds(); + if (lotto_players_timer.Check()) + CheckLottoPlayers(); + if(watchdog_timer.Check()) + zone_list.WatchdogHeartbeat(); +} + +vector* World::GetClientVariables(){ + return variables.GetVariables("cl_"); +} + +void World::LoadVitalityInformation() +{ + int32 timestamp = Timer::GetUnixTimeStamp(); + int32 diff = 0; + + // fetch vitalitytimer value from `variables` table + Variable* timer_var = variables.FindVariable("vitalitytimer"); + + if(timer_var) + { + try + { + diff = timestamp - atoul(timer_var->GetValue()); + diff *= 1000; //convert seconds to milliseconds + } + catch(...) + { + LogWrite(WORLD__ERROR, 0, "World", "Error parsing vitalitytimer, value: %s", timer_var->GetValue()); + } + } + + // Now using Rules System to set vitality parameters + vitality_amount = rule_manager.GetGlobalRule(R_Player, VitalityAmount)->GetFloat(); + vitality_frequency = rule_manager.GetGlobalRule(R_Player, VitalityFrequency)->GetInt32(); + + vitality_frequency *= 1000; //convert seconds to milliseconds + + if(diff >= vitality_frequency) + UpdateVitality(); //update now + else + vitality_timer.SetTimer(vitality_frequency - diff); +} + +void World::UpdateVitality() +{ + // push new vitalitytimer to `variables` table + database.UpdateVitality(Timer::GetUnixTimeStamp(), vitality_amount); + + if(vitality_timer.GetDuration() != vitality_frequency) + vitality_timer.SetTimer(vitality_frequency); + + zone_list.UpdateVitality(vitality_amount); +} + + +void ZoneList::UpdateVitality(float amount) +{ + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++) + { + tmp = *zone_iter; + if(tmp) + tmp->UpdateVitality(amount); + } + + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::SendTimeUpdate() +{ + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++) + { + tmp = *zone_iter; + if(tmp && !tmp->isZoneShuttingDown()) + tmp->WorldTimeUpdateTrigger(); + } + + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +// should already be ran inside MWorldTime +void World::WorldTimeTick(){ + world_time.minute++; + //I know it looks complicated, but the nested ifs are to avoid checking all of them every 3 seconds + if(world_time.minute >= 60){ // >= in case of bad time from db + world_time.minute = 0; + world_time.hour++; + if(world_time.hour >= 24){ + world_time.hour = 0; + world_time.day++; + if(world_time.day>=30){ + world_time.day = 0; + world_time.month++; + if(world_time.month >= 12){ + world_time.month = 0; + world_time.year++; + } + } + } + } +} + + +ZoneList::ZoneList() { + MZoneList.SetName("ZoneList::MZoneList"); +} + +ZoneList::~ZoneList() { + list::iterator zone_iter; + ZoneServer* zs = 0; + for(zone_iter=zlist.begin(); zone_iter!=zlist.end();){ + zs = *zone_iter; + zone_iter = zlist.erase(zone_iter); + safe_delete(zs); + } +} + +void ZoneList::CheckFriendList(Client* client) { + LogWrite(WORLD__DEBUG, 0, "World", "Sending FriendList..."); + MClientList.lock(); + map::iterator itr; + for(itr = client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second != client && itr->second){ + if(itr->second->GetPlayer()->IsFriend(client->GetPlayer()->GetName())){ + itr->second->SendFriendList(); + itr->second->Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Friend: %s has logged in.", client->GetPlayer()->GetName()); + } + } + } + MClientList.unlock(); +} + +void ZoneList::CheckFriendZoned(Client* client){ + MClientList.lock(); + map::iterator itr; + for(itr = client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second != client && itr->second){ + if(itr->second->GetPlayer()->IsFriend(client->GetPlayer()->GetName())){ + itr->second->SendFriendList(); + } + } + } + MClientList.unlock(); +} + +bool ZoneList::HandleGlobalChatMessage(Client* from, char* to, int16 channel, const char* message, const char* channel_name, int32 current_language_id){ + if (!from) { + LogWrite(WORLD__ERROR, 0, "World", "HandleGlobalChatMessage() called with an invalid client"); + return false; + } + if(channel == CHANNEL_PRIVATE_TELL){ + Client* find_client = zone_list.GetClientByCharName(to); + if(!find_client || find_client->GetPlayer()->IsIgnored(from->GetPlayer()->GetName())) + return false; + else if(find_client == from) + { + from->Message(CHANNEL_COLOR_RED,"You must be very lonely...(ERROR: Cannot send tell to self)"); + } + else + { + const char* whoto = find_client->GetPlayer()->GetName(); + find_client->HandleTellMessage(from, message, whoto, from->GetPlayer()->GetCurrentLanguage()); + from->HandleTellMessage(from, message, whoto, from->GetPlayer()->GetCurrentLanguage()); + if (find_client->GetPlayer()->get_character_flag(CF_AFK)) { + find_client->HandleTellMessage(find_client, find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); + from->HandleTellMessage(find_client, find_client->GetPlayer()->GetAwayMessage().c_str(),whoto, from->GetPlayer()->GetCurrentLanguage()); + } + } + } + + else if(channel == CHANNEL_GROUP_SAY) { + GroupMemberInfo* gmi = from->GetPlayer()->GetGroupMemberInfo(); + if(gmi) + world.GetGroupManager()->GroupMessage(gmi->group_id, message); + } + else{ + list::iterator zone_iter; + ZoneServer* zs = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zs = *zone_iter; + if(zs) + zs->HandleChatMessage(from->GetPlayer(), to, channel, message, 0, channel_name, true, current_language_id); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + return true; +} + +void ZoneList::LoadSpellProcess(){ + list::iterator zone_iter; + ZoneServer* zone = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zone = *zone_iter; + if (zone) + zone->LoadSpellProcess(); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::DeleteSpellProcess(){ + list::iterator zone_iter; + ZoneServer* zone = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zone = *zone_iter; + if (zone) + zone->DeleteSpellProcess(); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::HandleGlobalBroadcast(const char* message) { + list::iterator zone_iter; + ZoneServer* zone = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zone = *zone_iter; + if (zone) + zone->HandleBroadcast(message); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::HandleGlobalAnnouncement(const char* message) { + list::iterator zone_iter; + ZoneServer* zone = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_iter=zlist.begin(); zone_iter!=zlist.end();zone_iter++){ + zone = *zone_iter; + if (zone) + zone->HandleAnnouncement(message); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +int32 ZoneList::Count(){ + return zlist.size(); +} + +void ZoneList::Add(ZoneServer* zone) { + MZoneList.writelock(__FUNCTION__, __LINE__); + zlist.push_back(zone); + MZoneList.releasewritelock(__FUNCTION__, __LINE__); +} +void ZoneList::Remove(ZoneServer* zone) { + const char* zoneName = zone->GetZoneName(); + MZoneList.writelock(__FUNCTION__, __LINE__); + zlist.remove(zone); + MZoneList.releasewritelock(__FUNCTION__, __LINE__); + + ZoneServer* alternativeZone = Get(zoneName, false, false); + if(!alternativeZone && !rule_manager.GetGlobalRule(R_World, MemoryCacheZoneMaps)->GetBool()) { + world.RemoveMaps(std::string(zoneName)); + } +} +ZoneServer* ZoneList::Get(const char* zone_name, bool loadZone, bool skip_existing_zones, bool increment_zone) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + ZoneServer* ret = 0; + + if(!skip_existing_zones) { + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if (!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && strlen(zone_name) == strlen(tmp->GetZoneName()) && + strncasecmp(tmp->GetZoneName(), zone_name, strlen(zone_name))==0){ + if(tmp->NumPlayers() < 30 || tmp->IsCityZone()) { + ret = tmp; + if(increment_zone) { + ret->IncrementIncomingClients(); + } + break; + } + } + } + + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + + if(!ret ) + { + if ( loadZone ) + { + ret = new ZoneServer(zone_name); + database.LoadZoneInfo(ret); + ret->Init(); + } + } + return ret; +} + +ZoneServer* ZoneList::Get(int32 id, bool loadZone, bool skip_existing_zones, bool increment_zone) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + ZoneServer* ret = 0; + if(!skip_existing_zones) { + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(!tmp->isZoneShuttingDown() && !tmp->IsInstanceZone() && tmp->GetZoneID() == id){ + if(tmp->NumPlayers() < 30 || tmp->IsCityZone()) { + ret = tmp; + if(increment_zone) { + ret->IncrementIncomingClients(); + } + break; + } + } + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + + if(ret) { + tmp = ret; + } + else if (loadZone) { + string zonename = database.GetZoneName(id); + if(zonename.length() >0){ + tmp = new ZoneServer(zonename.c_str()); + database.LoadZoneInfo(tmp); + tmp->Init(); + } + } + return tmp; +} + + +void ZoneList::SendZoneList(Client* client) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + int zonesListed = 0; + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if ( zonesListed > 20 ) + { + client->Message(CHANNEL_COLOR_YELLOW,"Reached max zone list of 20."); + break; + } + zonesListed++; + client->Message(CHANNEL_COLOR_YELLOW,"Zone(ID): %s(%i), Instance ID: %i, Description: %s.",tmp->GetZoneName(),tmp->GetZoneID(), + tmp->GetInstanceID(),tmp->GetZoneDescription()); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +ZoneServer* ZoneList::GetByInstanceID(int32 id, int32 zone_id, bool skip_existing_zones, bool increment_zone) { + list::iterator zone_iter; + ZoneServer* tmp = 0; + ZoneServer* ret = 0; + if(!skip_existing_zones) { + MZoneList.readlock(__FUNCTION__, __LINE__); + if ( id > 0 ) + { + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(!tmp->isZoneShuttingDown() && tmp->GetInstanceID() == id){ + ret = tmp; + if(increment_zone) { + ret->IncrementIncomingClients(); + } + break; + } + } + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + + if(ret) { + tmp = ret; + } + else if ( zone_id > 0 ){ + string zonename = database.GetZoneName(zone_id); + if(zonename.length() > 0){ + tmp = new ZoneServer(zonename.c_str()); + + // the player is trying to preload an already existing instance but it isn't loaded + if ( id > 0 ) + tmp->SetupInstance(id); + + database.LoadZoneInfo(tmp); + tmp->Init(); + } + } + return tmp; +} + +ZoneServer* ZoneList::GetByLowestPopulation(int32 zone_id) { + ZoneServer* ret = 0; + ZoneServer* zone = 0; + int32 clients = 0; + list::iterator itr; + MZoneList.readlock(__FUNCTION__, __LINE__); + if (zone_id) { + for (itr = zlist.begin(); itr != zlist.end(); itr++) { + zone = *itr; + if (zone) { + // check the zone id's + if (zone->GetZoneID() == zone_id) { + // check this zones client count + if (clients == 0 || zone->GetClientCount() < clients) { + ret = zone; + clients = zone->GetClientCount(); + } + } + } + } + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} +int32 ZoneList::GetZonesPlayersCount() { + int32 ret = 0; + ZoneServer* zone = nullptr; + list::iterator itr; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (itr = zlist.begin(); itr != zlist.end(); itr++) { + zone = *itr; + if (zone) { + ret += zone->GetClientCount(); + } + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +bool ZoneList::ClientConnected(int32 account_id){ + bool ret = false; + map::iterator itr; + MClientList.lock(); + for(itr=client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second && itr->second->GetAccountID() == account_id && itr->second->getConnection() && itr->second->getConnection()->GetState() != CLOSING && itr->second->getConnection()->GetState() != CLOSED && (itr->second->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) == 0){ + ret = true; + break; + } + else if(!itr->second){ + client_map.erase(itr); + if(client_map.size() > 0){ + itr=client_map.begin(); + if(itr == client_map.end()){ + if(itr->second && itr->second->GetAccountID() == account_id && (itr->second->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) == 0) + ret = true; + break; + } + } + else + break; + } + } + MClientList.unlock(); + return ret; +} + +void ZoneList::RemoveClientZoneReference(ZoneServer* zone){ + map::iterator itr; + MClientList.lock(); + for(itr=client_map.begin(); itr != client_map.end(); itr++){ + if(itr->second) { + if(itr->second->GetCurrentZone() == zone) { + itr->second->SetCurrentZone(nullptr); + } + if(itr->second->GetZoningDestination() == zone) { + itr->second->SetZoningDestination(nullptr); + } + } + } + MClientList.unlock(); + + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(tmp) + tmp->RemoveClientsFromZone(zone); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} +void ZoneList::ReloadClientQuests(){ + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + tmp = *zone_iter; + if(tmp) + tmp->ReloadClientQuests(); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::ProcessWhoQuery(vector* queries, ZoneServer* zone, vector* players, bool isGM){ + Entity* player = 0; + bool add_player = true; + bool found_match = false; + int8 lower = 0; + int8 upper = 0; + vector tmpPlayers; + vector::iterator spawn_iter; + if(!zone->isZoneShuttingDown()){ + tmpPlayers = zone->GetPlayers(); + for(spawn_iter = tmpPlayers.begin(); spawn_iter!=tmpPlayers.end(); spawn_iter++){ + player = *spawn_iter; + add_player = true; + Client* find_client = zone_list.GetClientByCharName(player->GetName()); + if (find_client == NULL) continue; + int flags = find_client->GetPlayer()->GetInfoStruct()->get_flags(); + int flags2 = find_client->GetPlayer()->GetInfoStruct()->get_flags2(); + for(int32 i=0;add_player && queries && isize();i++){ + found_match = false; + if(queries->at(i) == "ALL") + continue; + if(queries->at(i).length() > 3 && classes.GetClassID(queries->at(i).c_str()) > 0){ + if(player->GetAdventureClass() != classes.GetClassID(queries->at(i).c_str())) + add_player = false; + found_match = true; + } + else if(queries->at(i).length() > 2 && races.GetRaceID(queries->at(i).c_str()) > 0){ + if(player->GetRace() != races.GetRaceID(queries->at(i).c_str())) + add_player = false; + found_match = true; + } + if(!found_match && queries->at(i) == "GOOD"){ + if(player->GetDeity() != 1) + add_player = false; + found_match = true; + } + else if(!found_match && queries->at(i) == "EVIL"){ + if(player->GetDeity() == 1) + add_player = false; + found_match = true; + } + if((queries->at(i) == "GUIDE") && (find_client->GetAdminStatus() > 0) && ((find_client->GetAdminStatus() >> 4) < 5)) + found_match = true; + else if((queries->at(i) == "GM") && ((find_client->GetAdminStatus() >> 4) > 4)) + found_match = true; + else if((queries->at(i) == "LFG") && (flags & (1 << CF_LFG))) + found_match = true; + else if((queries->at(i) == "LFW") && (flags & (1 << CF_LFW))) + found_match = true; + else if((queries->at(i) == "ROLEPLAYING") && (flags & (1 << CF_ROLEPLAYING))) + found_match = true; + else if(strspn(queries->at(i).c_str(),"0123456789") == queries->at(i).length()){ + try{ + if(lower == 0) + lower = atoi(queries->at(i).c_str()); + else + upper = atoi(queries->at(i).c_str()); + } + catch(...){} + found_match = true; + } + if(!found_match){ + string name = string(player->GetName()); + name = ToUpper(name); + if(name.find(queries->at(i)) == name.npos) + add_player = false; + } + } + if(lower > 0 && upper > 0){ + if(player->GetLevel() < lower || player->GetLevel() > upper) + add_player = false; + } + else if(lower > 0){ + if(player->GetLevel() != lower) + add_player = false; + } + if((flags2 & (1 << (CF_GM_HIDDEN - 32))) && !isGM) { + add_player = false; + found_match = true; + } + if(add_player) + players->push_back(player); + } + } +} + +void ZoneList::ProcessWhoQuery(const char* query, Client* client){ + list::iterator zone_iter; + vector players; + vector::iterator spawn_iter; + Entity* player = 0; + //for now display all clients + bool all = false; + vector* queries = 0; + bool isGM = ((client->GetAdminStatus() >> 4) > 4); + if(query){ + string query_string = string(query); + query_string = ToUpper(query_string); + queries = SplitString(query_string, ' '); + } + if(queries && queries->size() > 0 && queries->at(0) == "ALL") + all = true; + if(all){ + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + ZoneServer* tmp = *zone_iter; + ProcessWhoQuery(queries, tmp, &players, isGM); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + else{ + ProcessWhoQuery(queries, client->GetCurrentZone(), &players, isGM); + } + + PacketStruct* packet = configReader.getStruct("WS_WhoQueryReply", client->GetVersion()); + if(packet){ + packet->setDataByName("account_id", client->GetAccountID()); + packet->setDataByName("unknown", 0xFFFFFFFF); + int8 num_characters = players.size(); + int8 max_who_results = 10; + int8 max_who_results_status_override = 100; + + Variable* var = variables.FindVariable("max_who_results_status_override"); + if ( var ){ + max_who_results_status_override = atoi(var->GetValue()); + } + + // AdnaeDMorte + if ( client->GetAdminStatus() >= max_who_results_status_override ){ + client->Message(CHANNEL_COLOR_RED, "** ADMIN-MODE ** "); + } + + Variable* var1 = variables.FindVariable("max_who_results"); + if ( var1 ){ + max_who_results = atoi(var1->GetValue()); + } + + if(num_characters > max_who_results && client->GetAdminStatus() < max_who_results_status_override){ + num_characters = max_who_results; + packet->setDataByName("response", 3); //response 1 = error message, 3 == capped + } + else + packet->setDataByName("response", 2); + packet->setArrayLengthByName("num_characters", num_characters); + packet->setDataByName("unknown10", 1); + int i=0; + for(spawn_iter = players.begin(); spawn_iter!=players.end(); spawn_iter++, i++){ + if(i == num_characters) + break; + player = *spawn_iter; + Client* find_client = zone_list.GetClientByCharName(player->GetName()); + int flags = find_client->GetPlayer()->GetInfoStruct()->get_flags(); + int flags2 = find_client->GetPlayer()->GetInfoStruct()->get_flags2(); + packet->setArrayDataByName("char_name", player->GetName(), i); + packet->setArrayDataByName("level", player->GetLevel(), i); + packet->setArrayDataByName("admin_level", ((flags2 & (1 << (CF_HIDE_STATUS - 32))) && !isGM)?0:(find_client->GetAdminStatus() >> 4), i); + packet->setArrayDataByName("class", player->GetAdventureClass(), i); + packet->setArrayDataByName("unknown4", 0xFF, i); //probably tradeskill class + packet->setArrayDataByName("flags", (((flags >> CF_ANONYMOUS) & 1) << 0 ) | + (((flags >> CF_LFG) & 1) << 1 ) | + (((flags >> CF_ANONYMOUS) & 1) << 2 ) | + /*(((flags >> CF_HIDDEN) & 1) << 3 ) |*/ + (((flags >> CF_ROLEPLAYING) & 1) << 4 ) | + (((flags >> CF_AFK) & 1) << 5 ) | + (((flags >> CF_LFW) & 1) << 6 ) /*| + (((flags >> CF_NOTA) & 1) << 7 )*/, i); + packet->setArrayDataByName("race", player->GetRace(), i); + if(player->GetZone() && player->GetZone()->GetZoneDescription()) + packet->setArrayDataByName("zone", player->GetZone()->GetZoneDescription(), i); + if(player->appearance.sub_title) { + int32 sub_title_length = strlen(player->appearance.sub_title); + char tmp_title[255]; + int32 index = 0; + int32 index_tmp = 0; + while (index < sub_title_length) { + if (player->appearance.sub_title[index] != '<' && player->appearance.sub_title[index] != '>') { + memcpy(tmp_title + index_tmp, player->appearance.sub_title + index, 1); + index_tmp++; + } + index++; + } + tmp_title[index_tmp] = '\0'; + packet->setArrayDataByName("guild", tmp_title, i); + } + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +bool ZoneList::DepopFinished(){ + list::iterator zone_iter; + MZoneList.readlock(__FUNCTION__, __LINE__); + bool finished_depop = true; + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + if(!(*zone_iter)->FinishedDepop()) + finished_depop = false; + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + return finished_depop; +} + +void ZoneList::Depop(){ + list::iterator zone_iter; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + (*zone_iter)->Depop(); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::Repop(){ + list::iterator zone_iter; + MZoneList.readlock(__FUNCTION__, __LINE__); + for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){ + (*zone_iter)->Depop(false, true); + } + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::ReloadSpawns() { + MZoneList.readlock(__FUNCTION__, __LINE__); + + list::iterator itr; + for (itr = zlist.begin(); itr != zlist.end(); itr++) + (*itr)->ReloadSpawns(); + + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +bool World::ReportBug(string data, char* player_name, int32 account_id, const char* spawn_name, int32 spawn_id, int32 zone_id){ + //loginserver + vector list; + int32 offset = 0; + int32 old_offset = 0; + while((offset = data.find(7, old_offset+1)) < 0xFFFFFFFF){ + if(old_offset > 0) + list.push_back(data.substr(old_offset+1, offset-old_offset-1)); + else + list.push_back(data.substr(old_offset, offset)); + old_offset = offset; + } + if(list.size() > 0 && list.size() < 7){ + string output = "Invalid bug list:\n"; + for(int32 i=0;ipBuffer; + + if (list.size() < 7) { + strncpy(report->category, "AutoBug", 7); + strncpy(report->subcategory, "AutoGenerate", 12); + strncpy(report->causes_crash, "N", 1); + strncpy(report->reproducible, "Y", 1); + strncpy(report->summary, data.c_str(), data.length() > 127 ? 127 : data.length()); + strncpy(report->description, data.c_str(), data.length() > 1999 ? 1999 : data.length()); + strncpy(report->version, "CUR", 3); + } + else + { + strncpy(report->category, list[0].c_str(), list[0].length() > 63 ? 63 : list[0].length()); + strncpy(report->subcategory, list[1].c_str(), list[1].length() > 63 ? 63 : list[1].length()); + strncpy(report->causes_crash, list[2].c_str(), list[2].length() > 63 ? 63 : list[2].length()); + strncpy(report->reproducible, list[3].c_str(), list[3].length() > 63 ? 63 : list[3].length()); + strncpy(report->summary, list[4].c_str(), list[4].length() > 127 ? 127 : list[4].length()); + strncpy(report->description, list[5].c_str(), list[5].length() > 1999 ? 1999 : list[5].length()); + strncpy(report->version, list[6].c_str(), list[6].length() > 31 ? 31 : list[6].length()); + } + + strncpy(report->player, player_name, strlen(player_name) > 63 ? 63 : strlen(player_name)); + strncpy(report->spawn_name, spawn_name, strlen(spawn_name) > 63 ? 63 : strlen(spawn_name)); + report->spawn_id = spawn_id; + report->account_id = account_id; + report->zone_id = zone_id; + loginserver.SendPacket(outpack); + database.SaveBugReport(report->category, report->subcategory, report->causes_crash, report->reproducible, report->summary, report->description, report->version, report->player, account_id, spawn_name, spawn_id, zone_id); + safe_delete(outpack); + return true; +} + +void ZoneList::WritePlayerStatistics() { + list::iterator zone_itr; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_itr = zlist.begin(); zone_itr != zlist.end(); zone_itr++) + (*zone_itr)->WritePlayerStatistics(); + MZoneList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneList::ShutDownZones(){ + LogWrite(WORLD__INFO, 0, "World", "Shutting down all zones, please wait..."); + list::iterator zone_itr; + int32 size = 0; + MZoneList.readlock(__FUNCTION__, __LINE__); + for (zone_itr = zlist.begin(); zone_itr != zlist.end(); zone_itr++){ + (*zone_itr)->Shutdown(); + } + size = zlist.size(); + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + while(size > 0){ + Sleep(10); + MZoneList.readlock(__FUNCTION__, __LINE__); + size = zlist.size(); + MZoneList.releasereadlock(__FUNCTION__, __LINE__); + } + LogWrite(WORLD__INFO, 0, "World", "Zone shutdown complete"); +} + +void ZoneList::ReloadMail() { + map::iterator itr; + MClientList.writelock(__FUNCTION__, __LINE__); + for (itr = client_map.begin(); itr != client_map.end(); itr++) { + itr->second->GetPlayer()->DeleteMail(); + database.LoadPlayerMail(itr->second); + } + MClientList.releasewritelock(__FUNCTION__, __LINE__); +} + +void World::AddSpawnScript(int32 id, const char* name){ + MSpawnScripts.lock(); + if(name) + spawn_scripts[id] = string(name); + MSpawnScripts.unlock(); +} + +void World::AddSpawnEntryScript(int32 id, const char* name){ + MSpawnScripts.lock(); + if(name) + spawnentry_scripts[id] = string(name); + MSpawnScripts.unlock(); +} + +void World::AddSpawnLocationScript(int32 id, const char* name){ + MSpawnScripts.lock(); + if(name) + spawnlocation_scripts[id] = string(name); + MSpawnScripts.unlock(); +} + +void World::AddZoneScript(int32 id, const char* name) { + MZoneScripts.lock(); + if (name) + zone_scripts[id] = string(name); + MZoneScripts.unlock(); +} + +const char* World::GetSpawnScript(int32 id){ + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + const char* ret = 0; + MSpawnScripts.lock(); + if(spawn_scripts.count(id) > 0) + ret = spawn_scripts[id].c_str(); + MSpawnScripts.unlock(); + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return ret; +} + +const char* World::GetSpawnEntryScript(int32 id){ + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + const char* ret = 0; + MSpawnScripts.lock(); + if(spawnentry_scripts.count(id) > 0) + ret = spawnentry_scripts[id].c_str(); + MSpawnScripts.unlock(); + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return ret; +} + +const char* World::GetSpawnLocationScript(int32 id){ + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + const char* ret = 0; + MSpawnScripts.lock(); + if(spawnlocation_scripts.count(id) > 0) + ret = spawnlocation_scripts[id].c_str(); + MSpawnScripts.unlock(); + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return ret; +} + +const char* World::GetZoneScript(int32 id) { + const char* ret = 0; + MZoneScripts.lock(); + if (zone_scripts.count(id) > 0) + ret = zone_scripts[id].c_str(); + MZoneScripts.unlock(); + return ret; +} + +void World::ResetSpawnScripts(){ + MSpawnScripts.lock(); + spawn_scripts.clear(); + spawnentry_scripts.clear(); + spawnlocation_scripts.clear(); + MSpawnScripts.unlock(); +} + +void World::ResetZoneScripts() { + MZoneScripts.lock(); + zone_scripts.clear(); + MZoneScripts.unlock(); +} + + + +vector* World::GetMerchantItemList(int32 merchant_id, int8 merchant_type, Player* player) +{ + vector* ret = 0; + MMerchantList.lock(); + + if(merchant_info.count(merchant_id) > 0) + { + MerchantInfo* info = merchant_info[merchant_id]; + vector::iterator itr; + int32 inventory_id = 0; + Item* item = 0; + + for(int i=info->inventory_ids.size()-1;i>=0;i--) + { + inventory_id = info->inventory_ids[i]; + + if(merchant_inventory_items.count(inventory_id) > 0) + { + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++) + { + if(!ret) + ret = new vector; + + item = master_item_list.GetItem((*itr).item_id); + + // if NOT spell merchant, OR + // skill req is any skill, OR player has the skill, AND + // skill req2 is any skill, OR player has the skill2 + if(item && ( (merchant_type & MERCHANT_TYPE_SPELLS) == 0 || ( (item->generic_info.skill_req1 == 0xFFFFFFFF || player->GetSkills()->HasSkill(item->generic_info.skill_req1)) && (item->generic_info.skill_req2 == 0xFFFFFFFF || player->GetSkills()->HasSkill(item->generic_info.skill_req2)) ) ) ) + (*ret).push_back(*itr); + } + } + } + } + MMerchantList.unlock(); + return ret; +} + +vector* World::GetMerchantList(int32 merchant_id){ + vector* ret = 0; + MMerchantList.lock(); + if(merchant_info.count(merchant_id) > 0){ + MerchantInfo* info = merchant_info[merchant_id]; + map::iterator itr; + int32 inventory_id = 0; + for(int i=info->inventory_ids.size()-1;i>=0;i--){ + inventory_id = info->inventory_ids[i]; + if(merchant_inventory_items.count(inventory_id) > 0){ + ret = &merchant_inventory_items[inventory_id]; + } + } + } + MMerchantList.unlock(); + return ret; +} + +void World::AddMerchantItem(int32 inventory_id, MerchantItemInfo ItemInfo){ + MMerchantList.lock(); + merchant_inventory_items[inventory_id].push_back(ItemInfo); + MMerchantList.unlock(); +} + +void World::DeleteMerchantItems(){ + MMerchantList.lock(); + merchant_inventory_items.clear(); + MMerchantList.unlock(); +} + +void World::RemoveMerchantItem(int32 inventory_id, int32 item_id){ + MMerchantList.lock(); + if(merchant_inventory_items.count(inventory_id) > 0) { + vector::iterator itr; + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++){ + if ((*itr).item_id == item_id) { + merchant_inventory_items[inventory_id].erase(itr); + break; + } + } + } + MMerchantList.unlock(); +} + +int16 World::GetMerchantItemQuantity(int32 merchant_id, int32 item_id){ + int16 amount = 0; + int32 inventory_id = GetInventoryID(merchant_id, item_id); + if(inventory_id > 0){ + MMerchantList.lock(); + vector::iterator itr; + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++){ + if ((*itr).item_id == item_id) + amount = (*itr).quantity; + } + MMerchantList.unlock(); + } + return amount; +} + +int32 World::GetInventoryID(int32 merchant_id, int32 item_id){ + int32 ret = 0; + MMerchantList.lock(); + if(merchant_info.count(merchant_id) > 0){ + MerchantInfo* info = merchant_info[merchant_id]; + vector::iterator itr; + int32 inventory_id = 0; + for(int i=info->inventory_ids.size()-1;i>=0;i--){ + inventory_id = info->inventory_ids[i]; + if(merchant_inventory_items.count(inventory_id) > 0){ + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++){ + if((*itr).item_id == item_id){ + ret = inventory_id; + break; + } + } + if(ret > 0) + break; + } + } + } + MMerchantList.unlock(); + return ret; +} + +void World::DecreaseMerchantQuantity(int32 merchant_id, int32 item_id, int16 amount){ + int16 total_left = GetMerchantItemQuantity(merchant_id, item_id); + if(total_left > 0 && total_left < 0xFF){ + int32 inventory_id = GetInventoryID(merchant_id, item_id); + if(inventory_id > 0){ + MMerchantList.lock(); + vector::iterator itr; + for(itr = merchant_inventory_items[inventory_id].begin(); itr != merchant_inventory_items[inventory_id].end(); itr++){ + if ((*itr).item_id == item_id) { + if(total_left <= amount) { + merchant_inventory_items[inventory_id].erase(itr); + amount = 0; + break; + } + else + (*itr).quantity -= amount; + amount = (*itr).quantity; + } + } + + MMerchantList.unlock(); + } + } +} + +MerchantInfo* World::GetMerchantInfo(int32 merchant_id){ + MerchantInfo* ret = 0; + MMerchantList.lock(); + if(merchant_info.count(merchant_id) > 0) + ret = merchant_info[merchant_id]; + MMerchantList.unlock(); + return ret; +} + +void World::AddMerchantInfo(int32 merchant_id, MerchantInfo* info){ + MMerchantList.lock(); + if(merchant_info.count(merchant_id) > 0){ + safe_delete(merchant_info[merchant_id]); + } + merchant_info[merchant_id] = info; + MMerchantList.unlock(); +} + +map* World::GetMerchantInfo() { + return &merchant_info; +} + +void World::DeleteMerchantsInfo(){ + MMerchantList.lock(); + map::iterator itr; + for(itr = merchant_info.begin(); itr != merchant_info.end(); itr++){ + safe_delete(itr->second); + } + merchant_info.clear(); + MMerchantList.unlock(); +} + + + + + +void World::DeleteSpawns(){ + //reloading = true; + //ClearLootTables(); + /* + map::iterator npc_list_iter; + for(npc_list_iter=npc_list.begin();npc_list_iter!=npc_list.end();npc_list_iter++) { + safe_delete(npc_list_iter->second); + } + npc_list.clear(); + map::iterator object_list_iter; + for(object_list_iter=object_list.begin();object_list_iter!=object_list.end();object_list_iter++) { + safe_delete(object_list_iter->second); + } + object_list.clear(); + map::iterator groundspawn_list_iter; + for(groundspawn_list_iter=groundspawn_list.begin();groundspawn_list_iter!=groundspawn_list.end();groundspawn_list_iter++) { + safe_delete(groundspawn_list_iter->second); + } + groundspawn_list.clear(); + map::iterator widget_list_iter; + for(widget_list_iter=widget_list.begin();widget_list_iter!=widget_list.end();widget_list_iter++) { + safe_delete(widget_list_iter->second); + } + widget_list.clear(); + map::iterator sign_list_iter; + for(sign_list_iter=sign_list.begin();sign_list_iter!=sign_list.end();sign_list_iter++) { + safe_delete(sign_list_iter->second); + } + sign_list.clear();*/ + map::iterator appearance_list_iter; + for(appearance_list_iter=npc_appearance_list.begin();appearance_list_iter!=npc_appearance_list.end();appearance_list_iter++) { + safe_delete(appearance_list_iter->second); + } + npc_appearance_list.clear(); + + /* + map* >::iterator command_list_iter; + for(command_list_iter=entity_command_list.begin();command_list_iter!=entity_command_list.end();command_list_iter++) { + vector* v = command_list_iter->second; + if(v){ + for(int32 i=0;isize();i++){ + safe_delete(v->at(i)); + } + safe_delete(v); + } + } + entity_command_list.clear(); + */ + + //DeleteGroundSpawnItems(); + //DeleteTransporters(); + //DeleteTransporterMaps(); +} + +void World::ReloadGuilds() { + guild_list.GetGuilds()->clear(true); + database.LoadGuilds(); +} + +int8 World::GetClassID(const char* name){ + return classes.GetClassID(name); +} + +void World::WritePlayerStatistics() { + zone_list.WritePlayerStatistics(); +} + +void World::WriteServerStatistics() { + map::iterator itr; + Statistic* stat = 0; + for (itr = server_statistics.begin(); itr != server_statistics.end(); itr++) { + stat = itr->second; + if (stat->save_needed) { + stat->save_needed = false; + database.WriteServerStatistic(stat); + } + } + database.WriteServerStatisticsNeededQueries(); +} + +void World::AddServerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date) { + if (server_statistics.count(stat_id) == 0) { + Statistic* stat = new Statistic; + stat->stat_id = stat_id; + stat->stat_value = stat_value; + stat->stat_date = stat_date; + stat->save_needed = false; + server_statistics[stat_id] = stat; + } +} + +void World::UpdateServerStatistic(int32 stat_id, sint32 stat_value, bool overwrite) { + if (server_statistics.count(stat_id) == 0) + AddServerStatistic(stat_id, stat_value, 0); + Statistic* stat = server_statistics[stat_id]; + overwrite == true ? stat->stat_value = stat_value : stat->stat_value += stat_value; + stat->stat_date = Timer::GetUnixTimeStamp(); + stat->save_needed = true; +} + +sint32 World::GetServerStatisticValue(int32 stat_id) { + if (server_statistics.count(stat_id) > 0) + return server_statistics[stat_id]->stat_value; + return 0; +} + +void World::RemoveServerStatistics() { + map::iterator stat_itr; + for (stat_itr = server_statistics.begin(); stat_itr != server_statistics.end(); stat_itr++) + safe_delete(stat_itr->second); + server_statistics.clear(); +} + +void World::SendGroupQuests(PlayerGroup* group, Client* client){ + return; + /*if(!group) + return; + GroupMemberInfo* info = 0; + MGroups.readlock(__FUNCTION__, __LINE__); + deque::iterator itr; + for(itr = group->members.begin(); itr != group->members.end(); itr++){ + info = *itr; + if(info->client){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Send Quest Journal..."); + info->client->SendQuestJournal(false, client); + client->SendQuestJournal(false, info->client); + } + } + MGroups.releasereadlock(__FUNCTION__, __LINE__);*/ +} + +/*void World::CheckRemoveGroupedPlayer(){ + map::iterator itr; + GroupMemberInfo* found = 0; + MGroups.readlock(__FUNCTION__, __LINE__); + for(itr = group_removal_pending.begin(); itr != group_removal_pending.end(); itr++){ + if(itr->second < Timer::GetCurrentTime2()){ + found = itr->first; + break; + } + } + MGroups.releasereadlock(__FUNCTION__, __LINE__); + if(found){ + if(!found->client || (found->client && found->client->IsConnected() == false)) + DeleteGroupMember(found); + else{ + MGroups.writelock(__FUNCTION__, __LINE__); + group_removal_pending.erase(found); + MGroups.releasewritelock(__FUNCTION__, __LINE__); + } + } +}*/ + +bool World::RejoinGroup(Client* client, int32 group_id){ + if (!group_id) // no need if no group id! + return false; + + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + deque* members = 0; + if (group) + members = group->GetMembers(); + + string name = string(client->GetPlayer()->GetName()); + if (!members) + { + // group does not exist! + + Query query; + query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "UPDATE characters set group_id = 0 where id = %u", + client->GetCharacterID()); + LogWrite(PLAYER__ERROR, 0, "Player", "Group did not exist for player %s to group id %i, async query to group_id = 0.", name.c_str(), group_id); + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + return false; + } + deque::iterator itr; + GroupMemberInfo* info = 0; + + bool match = false; + group->MGroupMembers.writelock(); + for (itr = members->begin(); itr != members->end(); itr++) { + + info = *itr; + + if (info && info->name == name) + { + info->client = client; + info->member = client->GetPlayer(); + client->GetPlayer()->SetGroup(group); + client->GetPlayer()->SetGroupMemberInfo(info); + client->GetPlayer()->UpdateGroupMemberInfo(true, true); + LogWrite(PLAYER__DEBUG, 0, "Player", "Identified group match for player %s to group id %u", name.c_str(), group_id); + match = true; + break; + } + } + group->MGroupMembers.releasewritelock(); + + // must be done after cause it needs a readlock + if (match) + group->SendGroupUpdate(); + + if (!match) + LogWrite(PLAYER__ERROR, 0, "Player", "Identified group match for player %s to group id %u, however the player name was not present in the group! May be an old group id that has been re-used.", name.c_str(), group_id); + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + + return match; +} + + +void World::AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 value, Entity* entity){ + if(values){ + if(item && entity && entity->IsPlayer()) + { + int32 effective_level = entity->GetInfoStructUInt("effective_level"); + if(effective_level && effective_level < entity->GetLevel() && item->details.recommended_level > effective_level) + { + int32 diff = item->details.recommended_level - effective_level; + float tmpValue = (float)value; + value = (sint32)(float)(tmpValue / (1.0f + ((float)diff * .05f))); + } + } + switch(type){ + case ITEM_STAT_STR:{ + values->str += value; + break; + } + case ITEM_STAT_STA:{ + values->sta += value; + break; + } + case ITEM_STAT_AGI:{ + values->agi += value; + break; + } + case ITEM_STAT_WIS:{ + values->wis += value; + break; + } + case ITEM_STAT_INT:{ + values->int_ += value; + break; + } + case ITEM_STAT_VS_SLASH:{ + values->vs_slash += value; + break; + } + case ITEM_STAT_VS_CRUSH:{ + values->vs_crush += value; + break; + } + case ITEM_STAT_VS_PIERCE:{ + values->vs_pierce += value; + break; + } + case ITEM_STAT_VS_HEAT:{ + values->vs_heat += value; + break; + } + case ITEM_STAT_VS_COLD:{ + values->vs_cold += value; + break; + } + case ITEM_STAT_VS_MAGIC:{ + values->vs_magic += value; + break; + } + case ITEM_STAT_VS_MENTAL:{ + values->vs_mental += value; + break; + } + case ITEM_STAT_VS_DIVINE:{ + values->vs_divine += value; + break; + } + case ITEM_STAT_VS_DISEASE:{ + values->vs_disease += value; + break; + } + case ITEM_STAT_VS_POISON:{ + values->vs_poison += value; + break; + } + case ITEM_STAT_HEALTH:{ + values->health += value; + break; + } + case ITEM_STAT_POWER:{ + values->power += value; + break; + } + case ITEM_STAT_CONCENTRATION:{ + values->concentration += value; + break; + } + case ITEM_STAT_ABILITY_MODIFIER:{ + values->ability_modifier += value; + break; + } + case ITEM_STAT_CRITICALMITIGATION:{ + values->criticalmitigation += value; + break; + } + case ITEM_STAT_EXTRASHIELDBLOCKCHANCE:{ + values->extrashieldblockchance += value; + break; + } + case ITEM_STAT_BENEFICIALCRITCHANCE:{ + values->beneficialcritchance += value; + break; + } + case ITEM_STAT_CRITBONUS:{ + values->critbonus += value; + break; + } + case ITEM_STAT_POTENCY:{ + values->potency += value; + break; + } + case ITEM_STAT_HATEGAINMOD:{ + values->hategainmod += value; + break; + } + case ITEM_STAT_ABILITYREUSESPEED:{ + values->abilityreusespeed += value; + break; + } + case ITEM_STAT_ABILITYCASTINGSPEED:{ + values->abilitycastingspeed += value; + break; + } + case ITEM_STAT_ABILITYRECOVERYSPEED:{ + values->abilityrecoveryspeed += value; + break; + } + case ITEM_STAT_SPELLREUSESPEED:{ + values->spellreusespeed += value; + break; + } + case ITEM_STAT_SPELLMULTIATTACKCHANCE:{ + values->spellmultiattackchance += value; + break; + } + case ITEM_STAT_DPS:{ + values->dps += value; + break; + } + case ITEM_STAT_ATTACKSPEED:{ + values->attackspeed += value; + break; + } + case ITEM_STAT_MULTIATTACKCHANCE:{ + values->multiattackchance += value; + break; + } + case ITEM_STAT_AEAUTOATTACKCHANCE:{ + values->aeautoattackchance += value; + break; + } + case ITEM_STAT_STRIKETHROUGH:{ + values->strikethrough += value; + break; + } + case ITEM_STAT_ACCURACY:{ + values->accuracy += value; + break; + } + /*case ITEM_STAT_OFFENSIVESPEED:{ + values->offensivespeed += value; + break; + }*/ + default: { + if (entity) { + entity->MStats.lock(); + entity->stats[type] += value; + entity->MStats.unlock(); + } + break; + } + } + } +} + +void World::CreateGuild(const char* guild_name, Client* leader, int32 group_id) { + deque::iterator itr; + GroupMemberInfo* gmi; + Guild *guild; + + assert(guild_name); + + guild = new Guild(); + guild->SetName(guild_name); + guild->SetFormedDate(Timer::GetUnixTimeStamp()); + database.LoadGuildDefaultRanks(guild); + database.LoadGuildDefaultEventFilters(guild); + database.SaveGuild(guild, true); + database.SaveGuildEvents(guild); + database.SaveGuildRanks(guild); + database.SaveGuildEventFilters(guild); + database.SaveGuildRecruiting(guild); + guild_list.AddGuild(guild); + if (leader && !leader->GetPlayer()->GetGuild()) + guild->AddNewGuildMember(leader, 0, GUILD_RANK_LEADER); + database.SaveGuildMembers(guild); + if (leader && group_id > 0) { + GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + gmi = *itr; + if (gmi->client && gmi->client != leader && !gmi->client->GetPlayer()->GetGuild()) + guild->InvitePlayer(gmi->client, leader->GetPlayer()->GetName()); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } +} + +void World::SaveGuilds() { + MutexMap* guilds = guild_list.GetGuilds(); + MutexMap::iterator itr = guilds->begin(); + while (itr.Next()) { + Guild* guild = itr.second; + if (guild->GetSaveNeeded()) + database.SaveGuild(guild); + if (guild->GetMemberSaveNeeded()) + database.SaveGuildMembers(guild); + if (guild->GetEventsSaveNeeded()) + database.SaveGuildEvents(guild); + if (guild->GetRanksSaveNeeded()) + database.SaveGuildRanks(guild); + if (guild->GetEventFiltersSaveNeeded()) + database.SaveGuildEventFilters(guild); + if (guild->GetPointsHistorySaveNeeded()) + database.SaveGuildPointsHistory(guild); + if (guild->GetRecruitingSaveNeeded()) + database.SaveGuildRecruiting(guild); + } +} + +void World::PickRandomLottoDigits(int32* digits) { + if (digits) { + for (int32 i = 0; i < 6; i++) { + bool found = true; + int32 num = 0; + while (found) { + num = ((int32)rand() % 36) + 1; + for (int32 j = 0; j < 6; j++) { + if (digits[j] == num) + break; + if (j == 5) + found = false; + } + } + digits[i] = num; + } + } +} + +void World::AddLottoPlayer(int32 character_id, int32 end_time) { + LottoPlayer* lp; + if (lotto_players.count(character_id) == 0) { + lp = new LottoPlayer; + lotto_players.Put(character_id, lp); + } + else + lp = lotto_players.Get(character_id); + lp->end_time = end_time; + lp->num_matches = 0; + lp->set = false; +} + +void World::RemoveLottoPlayer(int32 character_id) { + if (lotto_players.count(character_id) > 0) + lotto_players.erase(character_id, false, true); +} + +void World::SetLottoPlayerNumMatches(int32 character_id, int8 num_matches) { + if (lotto_players.count(character_id) > 0) { + lotto_players.Get(character_id)->num_matches = num_matches; + lotto_players.Get(character_id)->set = true; + } +} + +void World::CheckLottoPlayers() { + MutexMap::iterator itr = lotto_players.begin(); + while (itr.Next()) { + LottoPlayer* lp = itr->second; + if (Timer::GetCurrentTime2() >= lp->end_time && lp->set) { + int8 num_matches = lp->num_matches; + LogWrite(PLAYER__DEBUG, 0, "Player", "Num matches: %u", lp->num_matches); + Client* client = zone_list.GetClientByCharID(itr->first); + if (client && num_matches >= 2) { + if (num_matches == 2) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You receive 10 silver."); + client->SendPopupMessage(0, "Congratulations! You have won 10 silver!", "", 2, 0xFF, 0xFF, 0x99); + client->GetPlayer()->AddCoins(1000); + client->GetPlayer()->GetZone()->SendCastSpellPacket(869, client->GetPlayer()); + } + else if (num_matches == 3) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You receive 50 silver."); + client->SendPopupMessage(0, "Congratulations! You have won 50 silver!", "", 2, 0xFF, 0xFF, 0x99); + client->GetPlayer()->AddCoins(5000); + client->GetPlayer()->GetZone()->SendCastSpellPacket(870, client->GetPlayer()); + } + else if (num_matches == 4) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You receive 2 gold 50 silver."); + client->SendPopupMessage(0, "Congratulations! You have won 2 gold 50 silver!", "", 2, 0xFF, 0xFF, 0x99); + client->GetPlayer()->AddCoins(25000); + client->GetPlayer()->GetZone()->SendCastSpellPacket(871, client->GetPlayer()); + } + else if (num_matches == 5) { + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You receive 25 gold."); + client->SendPopupMessage(0, "Congratulations! You have won 25 gold!", "", 2, 0xFF, 0xFF, 0x99); + client->GetPlayer()->AddCoins(250000); + client->GetPlayer()->GetZone()->SendCastSpellPacket(872, client->GetPlayer()); + } + else if (num_matches == 6) { + Variable* var = variables.FindVariable("gambling_current_jackpot"); + if (var) { + int64 jackpot = 0; + try { + jackpot = atoul(var->GetValue()); + } + catch (...) { + jackpot = 10000; + } + char coin_message[128]; + char message[512]; + char announcement[512]; + memset(coin_message, 0, sizeof(coin_message)); + memset(message, 0, sizeof(message)); + memset(announcement, 0, sizeof(announcement)); + sprintf(coin_message, "%s", client->GetCoinMessage(jackpot).c_str()); + sprintf(message, "Congratulations! You have won %s!", coin_message); + sprintf(announcement, "%s as won the jackpot containing a total of %s!", client->GetPlayer()->GetName(), coin_message); + client->Message(CHANNEL_COLOR_YELLOW, "You receive %s.", coin_message); + client->SendPopupMessage(0, message, "", 2, 0xFF, 0xFF, 0x99); + zone_list.HandleGlobalAnnouncement(announcement); + client->GetPlayer()->AddCoins(jackpot); + client->GetPlayer()->GetZone()->SendCastSpellPacket(843, client->GetPlayer()); + client->GetPlayer()->GetZone()->SendCastSpellPacket(1413, client->GetPlayer()); + } + } + } + RemoveLottoPlayer(itr->first); + } + } +} + +void World::AddHouseZone(int32 id, string name, int64 cost_coins, int32 cost_status, int64 upkeep_coins, int32 upkeep_status, int8 vault_slots, int8 alignment, int8 guild_level, int32 zone_id, int32 exit_zone_id, float exit_x, float exit_y, float exit_z, float exit_heading) { + MHouseZones.writelock(__FUNCTION__, __LINE__); + if (m_houseZones.count(id) == 0) { + HouseZone* hz = new HouseZone; + //ZeroMemory(hz, sizeof(HouseZone)); + hz->id = id; + hz->name = name; + hz->cost_coin = cost_coins; + hz->cost_status = cost_status; + hz->upkeep_coin = upkeep_coins; + hz->upkeep_status = upkeep_status; + hz->vault_slots = vault_slots; + hz->alignment = alignment; + hz->guild_level = guild_level; + hz->zone_id = zone_id; + hz->exit_zone_id = exit_zone_id; + hz->exit_x = exit_x; + hz->exit_y = exit_y; + hz->exit_z = exit_z; + hz->exit_heading = exit_heading; + m_houseZones[id] = hz; + } + else { + LogWrite(WORLD__ERROR, 0, "Housing", "Duplicate house id (%u) for %s", id, name.c_str()); + } + MHouseZones.releasewritelock(__FUNCTION__, __LINE__); +} + +HouseZone* World::GetHouseZone(int32 id) { + HouseZone* ret = 0; + + MHouseZones.readlock(__FUNCTION__, __LINE__); + if (m_houseZones.count(id) > 0) + ret = m_houseZones[id]; + MHouseZones.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void World::AddPlayerHouse(int32 char_id, int32 house_id, int64 unique_id, int32 instance_id, int32 upkeep_due, int64 escrow_coins, int32 escrow_status, string player_name) { + MPlayerHouses.writelock(__FUNCTION__, __LINE__); + if (m_playerHouses.count(house_id) == 0 || m_playerHouses[house_id].count(char_id) == 0) { + PlayerHouse* ph = new PlayerHouse; + ph->house_id = house_id; + ph->unique_id = unique_id; + ph->instance_id = instance_id; + ph->escrow_coins = escrow_coins; + ph->escrow_status = escrow_status; + ph->upkeep_due = upkeep_due; + ph->player_name = player_name; + ReloadHouseData(ph); + m_playerHouses[house_id][char_id] = ph; + } + MPlayerHouses.releasewritelock(__FUNCTION__, __LINE__); +} + +void World::ReloadHouseData(PlayerHouse* ph) +{ + database.LoadDeposits(ph); + database.LoadHistory(ph); +} + +PlayerHouse* World::GetPlayerHouseByHouseID(int32 char_id, int32 house_id) { + PlayerHouse* ret = 0; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + if (m_playerHouses.count(house_id) > 0 && m_playerHouses[house_id].count(char_id) > 0) + ret = m_playerHouses[house_id][char_id]; + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerHouse* World::GetPlayerHouseByUniqueID(int64 unique_id) { + PlayerHouse* ret = 0; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + map >::iterator itr; + for (itr = m_playerHouses.begin(); itr != m_playerHouses.end(); itr++) { + map::iterator itr2; + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + if (itr2->second->unique_id == unique_id) { + ret = itr2->second; + break; + } + } + if (ret) + break; + } + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerHouse* World::GetPlayerHouseByInstanceID(int32 instance_id) { + PlayerHouse* ret = 0; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + map >::iterator itr; + for (itr = m_playerHouses.begin(); itr != m_playerHouses.end(); itr++) { + map::iterator itr2; + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + if (itr2->second->instance_id == instance_id) { + ret = itr2->second; + break; + } + } + if (ret) + break; + } + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +vector World::GetAllPlayerHouses(int32 char_id) { + vector ret; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + map >::iterator itr; + for (itr = m_playerHouses.begin(); itr != m_playerHouses.end(); itr++) { + if (itr->second.count(char_id) > 0) + ret.push_back(itr->second[char_id]); + } + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +vector World::GetAllPlayerHousesByHouseID(int32 house_id) { + vector ret; + + MPlayerHouses.readlock(__FUNCTION__, __LINE__); + if (m_houseZones.count(house_id) > 0) { + map::iterator itr; + for (itr = m_playerHouses[house_id].begin(); itr != m_playerHouses[house_id].end(); itr++) + ret.push_back(itr->second); + } + MPlayerHouses.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +PlayerHouse* World::GetPlayerHouse(Client* client, int32 spawn_id, int64 unique_house_id, HouseZone** set_house_zone) { + PlayerHouse* ph = nullptr; + HouseZone* hz = nullptr; + + if(spawn_id) { + Spawn* houseWidget = client->GetPlayer()->GetSpawnByIndex(spawn_id); + if(houseWidget && houseWidget->IsWidget() && ((Widget*)houseWidget)->GetHouseID()) { + hz = world.GetHouseZone(((Widget*)houseWidget)->GetHouseID()); + if (hz) { + ph = world.GetPlayerHouseByHouseID(client->GetPlayer()->GetCharacterID(), hz->id); + } + } + } + + if(!ph && client->GetCurrentZone()->GetInstanceID()) { + ph = world.GetPlayerHouseByInstanceID(client->GetCurrentZone()->GetInstanceID()); + } + + if(!ph && unique_house_id) { + ph = world.GetPlayerHouseByUniqueID(unique_house_id); + } + + if (ph && !hz) { + hz = world.GetHouseZone(ph->house_id); + } + + if(set_house_zone) + *set_house_zone = hz; + + return ph; +} + +void World::PopulateTOVStatMap() { + //This function populates a map that converts changed CoE to ToV stats + tov_itemstat_conversion[0] = TOV_ITEM_STAT_HPREGEN; + tov_itemstat_conversion[1] = TOV_ITEM_STAT_MANAREGEN; + tov_itemstat_conversion[2] = TOV_ITEM_STAT_HPREGENPPT; + tov_itemstat_conversion[3] = TOV_ITEM_STAT_MPREGENPPT; + tov_itemstat_conversion[4] = TOV_ITEM_STAT_COMBATHPREGENPPT; + tov_itemstat_conversion[5] = TOV_ITEM_STAT_COMBATMPREGENPPT; + tov_itemstat_conversion[6] = TOV_ITEM_STAT_MAXHP; + tov_itemstat_conversion[7] = TOV_ITEM_STAT_MAXHPPERC; + tov_itemstat_conversion[8] = TOV_ITEM_STAT_MAXHPPERCFINAL; + tov_itemstat_conversion[9] = TOV_ITEM_STAT_SPEED; + tov_itemstat_conversion[10] = TOV_ITEM_STAT_SLOW; + tov_itemstat_conversion[11] = TOV_ITEM_STAT_MOUNTSPEED; + tov_itemstat_conversion[12] = TOV_ITEM_STAT_MOUNTAIRSPEED; + tov_itemstat_conversion[13] = TOV_ITEM_STAT_LEAPSPEED; + tov_itemstat_conversion[14] = TOV_ITEM_STAT_LEAPTIME; + tov_itemstat_conversion[15] = TOV_ITEM_STAT_GLIDEEFFICIENCY; + tov_itemstat_conversion[16] = TOV_ITEM_STAT_OFFENSIVESPEED; + tov_itemstat_conversion[17] = TOV_ITEM_STAT_ATTACKSPEED; + tov_itemstat_conversion[18] = 698; + tov_itemstat_conversion[19] = TOV_ITEM_STAT_MAXMANA; + tov_itemstat_conversion[20] = TOV_ITEM_STAT_MAXMANAPERC; + tov_itemstat_conversion[21] = TOV_ITEM_STAT_MAXATTPERC; + tov_itemstat_conversion[22] = TOV_ITEM_STAT_BLURVISION; + tov_itemstat_conversion[23] = TOV_ITEM_STAT_MAGICLEVELIMMUNITY; + tov_itemstat_conversion[24] = TOV_ITEM_STAT_HATEGAINMOD; + tov_itemstat_conversion[25] = TOV_ITEM_STAT_COMBATEXPMOD; + tov_itemstat_conversion[26] = TOV_ITEM_STAT_TRADESKILLEXPMOD; + tov_itemstat_conversion[27] = TOV_ITEM_STAT_ACHIEVEMENTEXPMOD; + tov_itemstat_conversion[28] = TOV_ITEM_STAT_SIZEMOD; + tov_itemstat_conversion[29] = TOV_ITEM_STAT_DPS; + tov_itemstat_conversion[30] = 698; + tov_itemstat_conversion[31] = TOV_ITEM_STAT_STEALTH; + tov_itemstat_conversion[32] = TOV_ITEM_STAT_INVIS; + tov_itemstat_conversion[33] = TOV_ITEM_STAT_SEESTEALTH; + tov_itemstat_conversion[34] = TOV_ITEM_STAT_SEEINVIS; + tov_itemstat_conversion[35] = TOV_ITEM_STAT_EFFECTIVELEVELMOD; + tov_itemstat_conversion[36] = TOV_ITEM_STAT_RIPOSTECHANCE; + tov_itemstat_conversion[37] = TOV_ITEM_STAT_PARRYCHANCE; + tov_itemstat_conversion[38] = TOV_ITEM_STAT_DODGECHANCE; + tov_itemstat_conversion[39] = TOV_ITEM_STAT_AEAUTOATTACKCHANCE; + tov_itemstat_conversion[40] = 698; + tov_itemstat_conversion[41] = TOV_ITEM_STAT_MULTIATTACKCHANCE; + tov_itemstat_conversion[42] = 698; + tov_itemstat_conversion[43] = 698; + tov_itemstat_conversion[44] = 698; + tov_itemstat_conversion[45] = TOV_ITEM_STAT_SPELLMULTIATTACKCHANCE; + tov_itemstat_conversion[46] = 698; + tov_itemstat_conversion[47] = TOV_ITEM_STAT_FLURRY; + tov_itemstat_conversion[48] = 698; + tov_itemstat_conversion[49] = TOV_ITEM_STAT_MELEEDAMAGEMULTIPLIER; + tov_itemstat_conversion[50] = TOV_ITEM_STAT_EXTRAHARVESTCHANCE; + tov_itemstat_conversion[51] = TOV_ITEM_STAT_EXTRASHIELDBLOCKCHANCE; + tov_itemstat_conversion[52] = TOV_ITEM_STAT_ITEMHPREGENPPT; + tov_itemstat_conversion[53] = TOV_ITEM_STAT_ITEMPPREGENPPT; + tov_itemstat_conversion[54] = TOV_ITEM_STAT_MELEECRITCHANCE; + tov_itemstat_conversion[55] = TOV_ITEM_STAT_CRITAVOIDANCE; + tov_itemstat_conversion[56] = TOV_ITEM_STAT_BENEFICIALCRITCHANCE; + tov_itemstat_conversion[57] = TOV_ITEM_STAT_CRITBONUS; + tov_itemstat_conversion[58] = 698; + tov_itemstat_conversion[59] = TOV_ITEM_STAT_POTENCY; + tov_itemstat_conversion[60] = 698; + tov_itemstat_conversion[61] = TOV_ITEM_STAT_UNCONSCIOUSHPMOD; + tov_itemstat_conversion[62] = TOV_ITEM_STAT_ABILITYREUSESPEED; + tov_itemstat_conversion[63] = TOV_ITEM_STAT_ABILITYRECOVERYSPEED; + tov_itemstat_conversion[64] = TOV_ITEM_STAT_ABILITYCASTINGSPEED; + tov_itemstat_conversion[65] = TOV_ITEM_STAT_SPELLREUSESPEED; + tov_itemstat_conversion[66] = TOV_ITEM_STAT_MELEEWEAPONRANGE; + tov_itemstat_conversion[67] = TOV_ITEM_STAT_RANGEDWEAPONRANGE; + tov_itemstat_conversion[68] = TOV_ITEM_STAT_FALLINGDAMAGEREDUCTION; + tov_itemstat_conversion[69] = TOV_ITEM_STAT_RIPOSTEDAMAGE; + tov_itemstat_conversion[70] = TOV_ITEM_STAT_MINIMUMDEFLECTIONCHANCE; + tov_itemstat_conversion[71] = TOV_ITEM_STAT_MOVEMENTWEAVE; + tov_itemstat_conversion[72] = TOV_ITEM_STAT_COMBATHPREGEN; + tov_itemstat_conversion[73] = TOV_ITEM_STAT_COMBATMANAREGEN; + tov_itemstat_conversion[74] = TOV_ITEM_STAT_CONTESTSPEEDBOOST; + tov_itemstat_conversion[75] = TOV_ITEM_STAT_TRACKINGAVOIDANCE; + tov_itemstat_conversion[76] = TOV_ITEM_STAT_STEALTHINVISSPEEDMOD; + tov_itemstat_conversion[77] = TOV_ITEM_STAT_LOOT_COIN; + tov_itemstat_conversion[78] = TOV_ITEM_STAT_ARMORMITIGATIONINCREASE; + tov_itemstat_conversion[79] = TOV_ITEM_STAT_AMMOCONSERVATION; + tov_itemstat_conversion[80] = TOV_ITEM_STAT_STRIKETHROUGH; + tov_itemstat_conversion[81] = TOV_ITEM_STAT_STATUSBONUS; + tov_itemstat_conversion[82] = TOV_ITEM_STAT_ACCURACY; + tov_itemstat_conversion[83] = TOV_ITEM_STAT_COUNTERSTRIKE; + tov_itemstat_conversion[84] = TOV_ITEM_STAT_SHIELDBASH; + tov_itemstat_conversion[85] = TOV_ITEM_STAT_WEAPONDAMAGEBONUS; + tov_itemstat_conversion[86] = 698; + tov_itemstat_conversion[87] = TOV_ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY; + tov_itemstat_conversion[88] = TOV_ITEM_STAT_ADDITIONALRIPOSTECHANCE; + tov_itemstat_conversion[89] = TOV_ITEM_STAT_PVPTOUGHNESS; + tov_itemstat_conversion[90] = TOV_ITEM_STAT_PVPLETHALITY; + tov_itemstat_conversion[91] = TOV_ITEM_STAT_STAMINABONUS; + tov_itemstat_conversion[92] = TOV_ITEM_STAT_WISDOMMITBONUS; + tov_itemstat_conversion[93] = TOV_ITEM_STAT_HEALRECEIVE; + tov_itemstat_conversion[94] = TOV_ITEM_STAT_HEALRECEIVEPERC; + tov_itemstat_conversion[95] = TOV_ITEM_STAT_PVPCRITICALMITIGATION; + tov_itemstat_conversion[96] = TOV_ITEM_STAT_BASEAVOIDANCEBONUS; + tov_itemstat_conversion[97] = TOV_ITEM_STAT_INCOMBATSAVAGERYREGEN; + tov_itemstat_conversion[98] = TOV_ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN; + tov_itemstat_conversion[99] = TOV_ITEM_STAT_SAVAGERYREGEN; + tov_itemstat_conversion[100] = TOV_ITEM_STAT_SAVAGERYGAINMOD; + tov_itemstat_conversion[101] = TOV_ITEM_STAT_MAXSAVAGERYLEVEL; +} + +int32 World::LoadItemBlueStats() { + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT version_range1,version_range2,emu_stat,name,stat from itemstats"); + + if (result && mysql_num_rows(result) > 0) { + while (result && (row = mysql_fetch_row(result))) { + count++; + + if (atoi(row[0]) >= 63119) //KA + ka_itemstat_conversion[atoi(row[2])] = atoi(row[4]); + else if (atoi(row[0]) >= 57101) // ToV + tov_itemstat_conversion[atoi(row[2])] = atoi(row[4]); + else if (atoi(row[0]) >= 1193) // CoE + coe_itemstat_conversion[atoi(row[2])] = atoi(row[4]); + else if (atoi(row[0]) >= 1096) // DoV + dov_itemstat_conversion[atoi(row[2])] = atoi(row[4]); + } + } + return count; +} + +sint64 World::newValue = 0; + +sint16 World::GetItemStatAOMValue(sint16 subtype) { + sint16 tmp_subtype = subtype; + // this is ugly for now cause I didn't want to map it all out, see a better way later but a lot of these are just slightly shifted + if(subtype > 39) + { + // 88 needs to be something else (crit mitigation) + // 19 needs to be something else (ability reuse speed) which is 62 + if(subtype == 21) // ITEM_STAT_MAXATTPERC + tmp_subtype = 20; + else if(subtype == 41) // flurry + tmp_subtype = 39; + else if(subtype == 47) // flurry + tmp_subtype = 41; + else if(subtype == 49) // flurry + tmp_subtype = 42; + else if(subtype == 51) // ITEM_STAT_EXTRASHIELDBLOCKCHANCE + tmp_subtype = 44; + + //tmp_subtype = 43 is bountiful harvest + else if(subtype == 54 && subtype <= 57) // ITEM_STAT_MELEECRITCHANCE + tmp_subtype = subtype - 7; + else if(subtype == 59) // ITEM_STAT_POTENCY + tmp_subtype = 51; + else if(subtype >= 61 && subtype <= 85) // ITEM_STAT_RANGEDWEAPONRANGE + tmp_subtype = subtype - 9; // + else if(subtype >= 86 && subtype <= 101) // ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY + tmp_subtype = subtype - 8; // + else if(subtype == 102) // ITEM_STAT_SPELLWEAPONDAMAGEBONUS + tmp_subtype = 77; // + else if(subtype >= 103 && subtype <= 110) + tmp_subtype = subtype - 9; + else if(subtype == 122) // ITEM_STAT_ABILITYDOUBLECASTCHANCE + tmp_subtype = 40; // + else if(subtype == 124) // ITEM_STAT_STATUSEARNED + tmp_subtype = 27; // + else + tmp_subtype += 1; + + // 80 serves as ranged weapon range increase, but so does 58? + } + else if((subtype > 18 && subtype < 28) || subtype > 30) // max mana was 18 + tmp_subtype = subtype - 1; + else if(subtype == 5) + tmp_subtype = 46; + else if(subtype == 4) + tmp_subtype = 45; + + LogWrite(PLAYER__DEBUG, 0, "Player", "Convert type: %i -> %i", subtype, tmp_subtype); + return tmp_subtype; +} +sint16 World::GetItemStatTOVValue(sint16 subtype) { + return (tov_itemstat_conversion[subtype] - 600); +} +sint16 World::GetItemStatDOVValue(sint16 subtype) { + return (dov_itemstat_conversion[subtype] - 600); +} +sint16 World::GetItemStatCOEValue(sint16 subtype) { + return (coe_itemstat_conversion[subtype] - 600); +} +sint16 World::GetItemStatKAValue(sint16 subtype) { + return (ka_itemstat_conversion[subtype] - 600); +} + +int8 World::TranslateSlotSubTypeToClient(Client* client, int8 stat_type, sint16 sub_type) { + int8 new_subtype = (int8)sub_type; + switch(stat_type) { + case 2: { + if(client->GetVersion() <= 561) { + if(sub_type == (ITEM_STAT_VS_POISON-200)) // poison + new_subtype = 9; + else if(sub_type == (ITEM_STAT_VS_DISEASE-200)) // disease + new_subtype = 8; + else if(sub_type == (ITEM_STAT_VS_COLD-200)) // cold + new_subtype = 4; + else if(sub_type == (ITEM_STAT_VS_HEAT-200) || sub_type == (ITEM_STAT_VS_MAGIC-200)) + new_subtype += 2; + else if(sub_type == (ITEM_STAT_VS_MENTAL-200) || sub_type == (ITEM_STAT_VS_DIVINE-200)) + new_subtype -= 2; + } + else if(client->GetVersion() >= 60085) { // AoM era since its the client we support + if(sub_type == (ITEM_STAT_VS_MENTAL-200) || sub_type == (ITEM_STAT_VS_DIVINE-200) || sub_type == (ITEM_STAT_VS_COLD-200)) { + new_subtype = 255; // omit client cannot properly display + } + } + break; + } + case 6: + case 7: { + if(stat_type == 7){ + new_subtype = sub_type; + } + else if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){ //KA + new_subtype = world.GetItemStatKAValue(sub_type); + } + else if(client->GetVersion() >= 60085 ) { + new_subtype = world.GetItemStatAOMValue(sub_type); + } + else if (client->GetVersion() >= 57107){ //TOV + new_subtype = world.GetItemStatTOVValue(sub_type); + } + else if (client->GetVersion() >= 1193){ //COE + new_subtype = world.GetItemStatCOEValue(sub_type); + //tmp_subtype = stat->stat_subtype; + } + else if (client->GetVersion() >= 1096){ //DOV + new_subtype = world.GetItemStatDOVValue(sub_type); //comment out for testing + //tmp_subtype = stat->stat_subtype; //comment for normal use + } + break; + } + } + + return new_subtype; +} + +bool World::CheckTempBugCRC(char* msg) +{ + MBugReport.writelock(); + + sint32 crc = GetItemNameCrc(std::string(msg)); + + if (bug_report_crc.count(crc) > 0) + { + MBugReport.releasewritelock(); + return true; + } + else + bug_report_crc.insert(make_pair(crc, true)); + + MBugReport.releasewritelock(); + + return false; +} + + +#ifdef WIN32 +ulong World::GetCurrentThreadID(){ + return GetCurrentThreadId(); +} + +int64 World::GetThreadUsageCPUTime(){ + HANDLE handle = GetCurrentThread(); + int64 lpCreationTime; + int64 lpExitTime; + int64 lpKernelTime; + int64 lpUserTime; + if(GetThreadTimes(handle, (FILETIME*)&lpCreationTime, (FILETIME*)&lpExitTime, (FILETIME*)&lpKernelTime, (FILETIME*)&lpUserTime)) + return lpKernelTime + lpUserTime; + return 0; +} +#else + +#endif + + +void World::SyncCharacterAbilities(Client* client) +{ + MStartingLists.readlock(); + + int8 baseClass = classes.GetBaseClass(client->GetPlayer()->GetAdventureClass()); + int8 secondaryClass = classes.GetSecondaryBaseClass(client->GetPlayer()->GetAdventureClass()); + int8 actualClass = client->GetPlayer()->GetAdventureClass(); + int8 baseRace = client->GetPlayer()->GetRace(); + + multimap*>::iterator skill_itr = starting_skills.begin(); + multimap*>::iterator spell_itr = starting_spells.begin(); + bool isProcessing = false; + int8 wait_iterations = 0; // wait 5 iterations and give up if db takes too long + do + { + isProcessing = false; + if (skill_itr != starting_skills.end()) + { + isProcessing = true; + + // race = 255 is wildcard all, otherwise needs to match the race id + if (skill_itr->first == 255 || skill_itr->first == baseRace) + { + multimap::iterator child_itr; + for (child_itr = skill_itr->second->begin(); child_itr != skill_itr->second->end(); child_itr++) + { + // class = 255 is wildcard all, otherwise needs to match the class id + if (child_itr->first == 255 || + child_itr->first == baseClass || + child_itr->first == secondaryClass || + child_itr->first == actualClass) + { + if (!client->GetPlayer()->skill_list.HasSkill(child_itr->second.skill_id)) + { + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding skill %i for race: %i, class: %i for char_id: %u", child_itr->second.skill_id, baseRace, baseClass, client->GetCharacterID()); + query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_skills (char_id, skill_id, current_val, max_val) VALUES (%u, %u, %u, %u)", + client->GetCharacterID(), child_itr->second.skill_id, child_itr->second.current_val, child_itr->second.max_val); + + client->GetPlayer()->AddSkill(child_itr->second.skill_id, child_itr->second.current_val, child_itr->second.max_val); + } + } + } + } + skill_itr++; + } + + if (spell_itr != starting_spells.end()) + { + isProcessing = true; + + // race = 255 is wildcard all, otherwise needs to match the race id + if (spell_itr->first == 255 || spell_itr->first == baseRace) + { + multimap::iterator child_itr; + for (child_itr = spell_itr->second->begin(); child_itr != spell_itr->second->end(); child_itr++) + { + // class = 255 is wildcard all, otherwise needs to match the class id + if (child_itr->first == 255 || + child_itr->first == baseClass || + child_itr->first == secondaryClass || + child_itr->first == actualClass) + { + if (!client->GetPlayer()->HasSpell(child_itr->second.spell_id, child_itr->second.tier, true)) + { + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding spell %i for race: %i, class: %i for char_id: %u", child_itr->second.spell_id, baseRace, baseClass, client->GetCharacterID()); + // knowledge_slot is a signed int in the DB + query.AddQueryAsync(client->GetCharacterID(), &database, Q_INSERT, "INSERT IGNORE INTO character_spells (char_id, spell_id, tier, knowledge_slot) VALUES (%u, %u, %u, %i)", + client->GetCharacterID(), child_itr->second.spell_id, child_itr->second.tier, child_itr->second.knowledge_slot); + + // reload spells, we don't know the spellbook or timer info + client->GetPlayer()->GetInfoStruct()->set_reload_player_spells(1); + } + } + } + } + spell_itr++; + } + } while (isProcessing); + + MStartingLists.releasereadlock(); +} + +void World::LoadStartingLists() +{ + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `starting_skills`..."); + database.LoadStartingSkills(this); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `starting_spells`..."); + database.LoadStartingSpells(this); +} + +void World::PurgeStartingLists() +{ + MStartingLists.writelock(); + + multimap*>::iterator skill_itr; + + for (skill_itr = starting_skills.begin(); skill_itr != starting_skills.end(); skill_itr++) + { + multimap* tmpMap = skill_itr->second; + safe_delete(tmpMap); + } + starting_skills.clear(); + + + multimap*>::iterator spell_itr; + + for (spell_itr = starting_spells.begin(); spell_itr != starting_spells.end(); spell_itr++) + { + multimap* tmpMap = spell_itr->second; + safe_delete(tmpMap); + } + starting_spells.clear(); + + + for(int type=0;type<3;type++) { + multimap*>::iterator vos_itr; + + for (vos_itr = voiceover_map[type].begin(); vos_itr != voiceover_map[type].end(); vos_itr++) + { + multimap* tmpMap = vos_itr->second; + safe_delete(tmpMap); + } + voiceover_map[type].clear(); + } + MStartingLists.releasewritelock(); +} + +void World::SetReloadingSubsystem(string subsystem) { + MReloadingSubsystems.lock(); + reloading_subsystems[subsystem] = Timer::GetCurrentTime2(); + MReloadingSubsystems.unlock(); +} + +void World::RemoveReloadingSubSystem(string subsystem) { + MReloadingSubsystems.lock(); + if (reloading_subsystems.count(subsystem) > 0) + reloading_subsystems.erase(subsystem); + MReloadingSubsystems.unlock(); +} + +bool World::IsReloadingSubsystems() { + bool result = false; + MReloadingSubsystems.lock(); + result = reloading_subsystems.size() > 0; + MReloadingSubsystems.unlock(); + return result; +} + +map World::GetOldestReloadingSubsystem() { + map result; + MReloadingSubsystems.lock(); + int32 current_time = Timer::GetCurrentTime2(); + map::iterator itr; + int32 oldest = current_time; + string oldestname = ""; + for (itr = reloading_subsystems.begin(); itr != reloading_subsystems.end(); itr++) { + if (itr->second < oldest) { + oldestname = itr->first; + result.clear(); + result[oldestname] = oldest; + } + } + MReloadingSubsystems.unlock(); + return result; +} + +void ZoneList::WatchdogHeartbeat() +{ + list::iterator zone_iter; + ZoneServer* tmp = 0; + MZoneList.writelock(__FUNCTION__, __LINE__); + + bool match = false; + for (zone_iter = zlist.begin(); zone_iter != zlist.end(); zone_iter++) + { + tmp = *zone_iter; + if (tmp) + { + int32 curTime = Timer::GetCurrentTime2(); + sint64 diff = (sint64)curTime - (sint64)tmp->GetWatchdogTime(); + if (diff > 120000) + { + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting to cancel threads...", tmp->GetZoneName(), diff); +#ifndef WIN32 + tmp->CancelThreads(); + zlist.erase(zone_iter); + safe_delete(tmp); +#endif + MZoneList.releasewritelock(__FUNCTION__, __LINE__); + match = true; + break; + } + else if (diff > 90000 && !tmp->isZoneShuttingDown()) + { + tmp->SetWatchdogTime(Timer::GetCurrentTime2()); // reset so we don't continuously flood this heartbeat + map oldest_process = world.GetOldestReloadingSubsystem(); + if (oldest_process.size() > 0) { + map::iterator itr = oldest_process.begin(); + if(itr != oldest_process.end()) + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. while waiting for %s to reload...attempting shutdown", tmp->GetZoneName(), diff, itr->first); + else + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds... attempting shutdown", tmp->GetZoneName(), diff); + + } + else + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting shutdown", tmp->GetZoneName(), diff); + tmp->Shutdown(); + } + else if (diff > 30000) + { + if (world.IsReloadingSubsystems()) { + if (world.GetSuppressedWarningTime() == 0) { + world.SetSuppressedWarning(); + map oldest_process = world.GetOldestReloadingSubsystem(); + if (oldest_process.size() > 0) { + map::iterator itr = oldest_process.begin(); + if(itr != oldest_process.end()) + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. while waiting for %s to reload...", tmp->GetZoneName(), diff, itr->first); + else + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds...", tmp->GetZoneName(), diff); + } + } + continue; + } + } + else if (diff > 60000 && !tmp->isZoneShuttingDown()) + { + if (world.IsReloadingSubsystems()) + continue; + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung for %i milliseconds.. attempting shutdown", tmp->GetZoneName(), diff); + tmp->Shutdown(); + } + } + } + if(!match) + MZoneList.releasewritelock(__FUNCTION__, __LINE__); +} + +void World::LoadRegionMaps(std::string zoneFile) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldRegionMaps.writelock(); + std::map::iterator itr; + itr = region_maps.find(zoneToLower); + if (itr == region_maps.end()) + { + RegionMapRange* newRange = new RegionMapRange(); + newRange->AddVersionRange(zoneFile); + + region_maps.insert(make_pair(zoneToLower, newRange)); + } + MWorldRegionMaps.releasewritelock(); +} + +RegionMap* World::GetRegionMap(std::string zoneFile, int32 client_version) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldRegionMaps.readlock(); + std::map::iterator itr; + itr = region_maps.find(zoneToLower); + if ( itr != region_maps.end()) + { + std::map::iterator rmitr; + rmitr = itr->second->FindRegionByVersion(client_version); + if ( rmitr != itr->second->GetRangeEnd()) + { + MWorldRegionMaps.releasereadlock(); + return rmitr->second; + } + } + + MWorldRegionMaps.releasereadlock(); + return nullptr; +} + + +void World::LoadMaps(std::string zoneFile) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldMaps.writelock(); + std::map::iterator itr; + itr = maps.find(zoneToLower); + if (itr == maps.end()) + { + MapRange* newRange = new MapRange(); + newRange->AddVersionRange(zoneFile); + + maps.insert(make_pair(zoneToLower, newRange)); + } + MWorldMaps.releasewritelock(); +} + +void World::RemoveMaps(std::string zoneFile) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldMaps.writelock(); + std::map::iterator itr; + itr = maps.find(zoneToLower); + if (itr != maps.end()) + { + MapRange* range = itr->second; + maps.erase(itr); + MWorldMaps.releasewritelock(); + safe_delete(range); + } + else { + MWorldMaps.releasewritelock(); + } +} + +Map* World::GetMap(std::string zoneFile, int32 client_version) +{ + string zoneToLower(zoneFile); + boost::algorithm::to_lower(zoneToLower); + + MWorldMaps.readlock(); + std::map::iterator itr; + itr = maps.find(zoneToLower); + if ( itr != maps.end()) + { + std::map::iterator rmitr; + rmitr = itr->second->FindMapByVersion(client_version); + if ( rmitr != itr->second->GetRangeEnd()) + { + MWorldMaps.releasereadlock(); + return rmitr->second; + } + } + + MWorldMaps.releasereadlock(); + return nullptr; +} + +void World::SendTimeUpdate() +{ + zone_list.SendTimeUpdate(); +} + +void World::LoadVoiceOvers() +{ + LogWrite(WORLD__DEBUG, 1, "World", "-Loading `voiceovers`..."); + database.LoadVoiceOvers(this); +} + + +void World::PurgeVoiceOvers() +{ + MVoiceOvers.writelock(); + for(int type=0;type*>::iterator vos_itr; + + for (vos_itr = voiceover_map[type].begin(); vos_itr != voiceover_map[type].end(); vos_itr++) + { + multimap* tmpMap = vos_itr->second; + safe_delete(tmpMap); + } + voiceover_map[type].clear(); + } + MVoiceOvers.releasewritelock(); +} + + +bool World::FindVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_, bool* find_garbled, VoiceOverStruct* garble_struct_) { + // if we complete both requirements, based on struct_ and garble_struct_ being passed when required by ptr not being null + bool succeed = false; + if(type > MAX_VOICEOVER_TYPE) { + LogWrite(WORLD__ERROR, 0, "World", "Voice over %u out of range, max voiceover type is %u...", type, MAX_VOICEOVER_TYPE); + return succeed; + } + + MVoiceOvers.readlock(); + multimap*>::iterator itr = voiceover_map[type].find(id); + if(itr != voiceover_map[type].end()) { + std::pair result = itr->second->equal_range(index); + int count = std::distance(result.first, result.second); + bool tries_attempt = true; // abort out the while loop + bool non_garble_found = false; + int rand = 0; // use to randomize the voiceover selection + int pos = 0; + int tries = 0; + bool has_ungarbled = false; + bool has_garbled = false; + int8 garble_link_id = 0; // used to match ungarbled to garbled when the link id is set in the DB + while(tries_attempt) { + pos = 0; + rand = MakeRandomInt(0, count); + if ( tries == 3 || non_garble_found || (find_garbled && (*find_garbled))) + rand = 0; // override, too many tries, or we otherwise found one garbled/ungarbled lets try to link it + for (VOMapIterator it = result.first; it != result.second; it++) { + if(!it->second.is_garbled) { + has_ungarbled = true; + } + else { + has_garbled = true; + } + pos++; + + // if there is only 1 entry in the voiceover list we aren't going to bother skipping + if(count > 1 && pos < rand) { + continue; + } + if(!it->second.is_garbled && (garble_link_id == 0 || it->second.garble_link_id == garble_link_id)) { + garble_link_id = it->second.garble_link_id; + non_garble_found = true; + if(struct_) { + CopyVoiceOver(struct_, &it->second); + } + + if(!find_garbled || ((*find_garbled))) { + if(find_garbled) + *find_garbled = true; + tries_attempt = false; + succeed = true; + break; + } + } + else if(find_garbled && !(*find_garbled) && it->second.is_garbled && (garble_link_id == 0 || it->second.garble_link_id == garble_link_id)) { + *find_garbled = true; + garble_link_id = it->second.garble_link_id; + if(garble_struct_) { + CopyVoiceOver(garble_struct_, &it->second); + if(!struct_ || non_garble_found) { + tries_attempt = false; + succeed = true; + break; + } + } + } + } + tries++; + if(!tries_attempt || (tries > 0 && !has_ungarbled && (!find_garbled || *find_garbled == true || !has_garbled)) || tries > 3) + break; + } + } + MVoiceOvers.releasereadlock(); + + return succeed; +} + +void World::AddVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_) { + if(type > MAX_VOICEOVER_TYPE) { + LogWrite(WORLD__ERROR, 0, "World", "Voice over %u out of range, max voiceover type is %u...", type, MAX_VOICEOVER_TYPE); + return; + } + + VoiceOverStruct tmpStruct; + tmpStruct.mp3_string = std::string(struct_->mp3_string); + tmpStruct.text_string = std::string(struct_->text_string); + tmpStruct.emote_string = std::string(struct_->emote_string); + tmpStruct.key1 = struct_->key1; + tmpStruct.key2 = struct_->key2; + tmpStruct.is_garbled = struct_->is_garbled; + + MVoiceOvers.writelock(); + if(!voiceover_map[type].count(id)) + { + multimap* vo_struct = new multimap(); + vo_struct->insert(make_pair(index, tmpStruct)); + voiceover_map[type].insert(make_pair(id, vo_struct)); + } + else + { + multimap*>::const_iterator itr = voiceover_map[type].find(id); + itr->second->insert(make_pair(index, tmpStruct)); + } + MVoiceOvers.releasewritelock(); +} + +void World::CopyVoiceOver(VoiceOverStruct* struct1, VoiceOverStruct* struct2) { + if(!struct1 || !struct2) + return; + + struct1->mp3_string = std::string(struct2->mp3_string); + struct1->text_string = std::string(struct2->text_string); + struct1->emote_string = std::string(struct2->emote_string); + struct1->key1 = struct2->key1; + struct1->key2 = struct2->key2; + struct1->is_garbled = struct2->is_garbled; + struct1->garble_link_id = struct2->garble_link_id; +} + +void World::AddNPCSpell(int32 list_id, int32 spell_id, int8 tier, bool spawn_cast, bool aggro_cast, sint8 req_hp_ratio){ + std::unique_lock lock(MNPCSpells); + NPCSpell* npc_spell_struct = new NPCSpell; + npc_spell_struct->list_id = list_id; + npc_spell_struct->spell_id = spell_id; + npc_spell_struct->tier = tier; + npc_spell_struct->cast_on_spawn = spawn_cast; + npc_spell_struct->cast_on_initial_aggro = aggro_cast; + npc_spell_struct->required_hp_ratio = req_hp_ratio; + if(npc_spell_list.count(list_id) && npc_spell_list[list_id].count(spell_id)) { + map::iterator spell_itr = npc_spell_list[list_id].find(spell_id); + if(spell_itr != npc_spell_list[list_id].end()) { + safe_delete(spell_itr->second); + npc_spell_list[list_id].erase(spell_itr); + } + } + + npc_spell_list[list_id].insert(make_pair(spell_id, npc_spell_struct)); +} + +vector* World::GetNPCSpells(int32 primary_list, int32 secondary_list){ + std::shared_lock lock(MNPCSpells); + vector* ret = 0; + if(npc_spell_list.count(primary_list) > 0){ + ret = new vector(); + map::iterator itr; + Spell* tmpSpell = 0; + for(itr = npc_spell_list[primary_list].begin(); itr != npc_spell_list[primary_list].end(); itr++){ + tmpSpell = master_spell_list.GetSpell(itr->first, itr->second->tier); + if(tmpSpell) { + NPCSpell* addedSpell = new NPCSpell(itr->second); + ret->push_back(addedSpell); + } + } + } + if(npc_spell_list.count(secondary_list) > 0){ + if(!ret) + ret = new vector(); + map::iterator itr; + Spell* tmpSpell = 0; + for(itr = npc_spell_list[secondary_list].begin(); itr != npc_spell_list[secondary_list].end(); itr++){ + tmpSpell = master_spell_list.GetSpell(itr->first, itr->second->tier); + if(tmpSpell) { + NPCSpell* addedSpell = new NPCSpell(itr->second); + ret->push_back(addedSpell); + } + } + } + if(ret && ret->size() == 0){ + safe_delete(ret); + ret = 0; + } + return ret; +} + +void World::PurgeNPCSpells() { + std::unique_lock lock(MNPCSpells); + map >::iterator list_itr; + map::iterator spell_itr; + Spell* tmpSpell = 0; + for(list_itr = npc_spell_list.begin(); list_itr != npc_spell_list.end(); list_itr++) { + for(spell_itr = npc_spell_list[list_itr->first].begin(); spell_itr != npc_spell_list[list_itr->first].end(); spell_itr++){ + safe_delete(spell_itr->second); + } + } + + npc_spell_list.clear(); +} \ No newline at end of file diff --git a/source/WorldServer/World.h b/source/WorldServer/World.h new file mode 100644 index 0000000..b0a9527 --- /dev/null +++ b/source/WorldServer/World.h @@ -0,0 +1,729 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQ2_WORLD_H +#define EQ2_WORLD_H + +#include +#include +#include +#include +#include +#include +#include +#include "SpawnLists.h" +#include "zoneserver.h" +#include "NPC.h" +#include "Widget.h" +#include "Object.h" +#include "GroundSpawn.h" +#include "Sign.h" +#include "Variables.h" +#include "MutexList.h" + +#include "PlayerGroups.h" +#include "../common/Web/WebServer.h" + +#include "./Zone/region_map.h" +#include "./Zone/map.h" + +using namespace std; +struct MerchantInfo{ + vector inventory_ids; + /*int32 faction_id; + sint32 faction_min; + sint32 faction_max; + float low_buy_multiplier; + float high_buy_multiplier; + float low_sell_multiplier; + float high_sell_multiplier;*/ +}; + +struct MerchantItemInfo{ + int32 item_id; + int16 quantity; + int32 price_item_id; + int32 price_item2_id; + int16 price_item_qty; + int16 price_item2_qty; + int32 price_status; + int64 price_coins; + int32 price_stationcash; +}; + +struct LootTable{ + string name; + int32 mincoin; + int32 maxcoin; + int16 maxlootitems; + float lootdrop_probability; + float coin_probability; +}; + +struct LootDrop{ + int32 item_id; + int16 item_charges; + bool equip_item; + float probability; + int32 no_drop_quest_completed_id; +}; + +struct GroundSpawnEntry { + int16 min_skill_level; + int16 min_adventure_level; + int8 bonus_table; + float harvest1; + float harvest3; + float harvest5; + float harvest_imbue; + float harvest_rare; + float harvest10; + int32 harvest_coin; +}; + +struct GroundSpawnEntryItem { + int32 item_id; + int8 is_rare; + int32 grid_id; +}; + +struct TransportDestination{ + int32 unique_id; + int8 type; + string display_name; + string message; + int32 destination_zone_id; + float destination_x; + float destination_y; + float destination_z; + float destination_heading; + int32 cost; + int8 min_level; + int8 max_level; + int32 req_quest; + int16 req_quest_step; + int32 req_quest_complete; + + int32 map_x; + int32 map_y; + int32 faction_id; + int32 faction_value; + + int32 expansion_flag; + int32 holiday_flag; + + int32 min_client_version; + int32 max_client_version; + + int32 flight_path_id; + + int16 mount_id; + int8 mount_red_color; + int8 mount_green_color; + int8 mount_blue_color; +}; + +struct LocationTransportDestination{ + int32 unique_id; + string message; + int32 destination_zone_id; + float destination_x; + float destination_y; + float destination_z; + float destination_heading; + float trigger_x; + float trigger_y; + float trigger_z; + float trigger_radius; + int32 cost; + int32 faction_id; + int32 faction_value; +}; + +struct LottoPlayer { + int32 end_time; + int8 num_matches; + bool set; +}; + +struct HouseZone { + int32 id; + string name; + int64 cost_coin; + int32 cost_status; + int64 upkeep_coin; + int32 upkeep_status; + int8 vault_slots; + int8 alignment; + int8 guild_level; + int32 zone_id; + int32 exit_zone_id; + float exit_x; + float exit_y; + float exit_z; + float exit_heading; +}; + +struct Deposit { + int32 timestamp; + int64 amount; + int64 last_amount; + int32 status; + int32 last_status; + string name; +}; + +struct HouseHistory { + HouseHistory() + { + timestamp = 0; + amount = 0; + name = string(""); + reason = string(""); + status = 0; + pos_flag = 0; + } + HouseHistory(int32 in_timestamp, int64 in_amount, string in_name, string in_reason, int32 in_status, int8 in_pos_flag) + { + timestamp = in_timestamp; + amount = in_amount; + name = in_name; + reason = in_reason; + status = in_status; + pos_flag = in_pos_flag; + } + int32 timestamp; + int64 amount; + string name; + string reason; + int32 status; + int8 pos_flag; +}; + +struct PlayerHouse { + int32 house_id; + int64 unique_id; + int32 instance_id; + int32 upkeep_due; + int64 escrow_coins; + int32 escrow_status; + string player_name; + list deposits; + map depositsMap; + list history; +}; + +// Constants for STATs counters + +// Server Utilization +#define STAT_SERVER_OS_TYPE 1 // what OS this server is running on +#define STAT_SERVER_CPU_TYPE 2 // cpu type/speed (ie., Intel P4 3.0GHz) +#define STAT_SERVER_CPU_CURRENT 3 // current CPU usage by EQ2World.exe process +#define STAT_SERVER_CPU_PEAK 4 // highest CPU usage by EQ2World.exe this session +#define STAT_SERVER_PHYSICAL_RAM_TOTAL 5 // total RAM in server +#define STAT_SERVER_PHYSICAL_RAM_CURRENT 6 // current RAM usage by EQ2World.exe +#define STAT_SERVER_PHYSICAL_RAM_PEAK 7 // highest RAM usage by EQ2World.exe this session +#define STAT_SERVER_VIRTUAL_RAM_TOTAL 8 // total vRAM in server +#define STAT_SERVER_VIRTUAL_RAM_CURRENT 9 // current vRAM usage by EQ2World.exe +#define STAT_SERVER_VIRTUAL_RAM_PEAK 10 // highest vRAM usage by EQ2World.exe this session +#define STAT_SERVER_DISK_USAGE 11 // size of /eq2emulator folder and contents +#define STAT_SERVER_THREAD_COUNT 12 // thread count of EQ2World.exe process +#define STAT_SERVER_AVG_LATENCY 13 // network latency between world and loginserver + +// Server Stats +#define STAT_SERVER_CREATED 100 // unix_timestamp of date server first came online +#define STAT_SERVER_START_TIME 101 // unix_timestamp of date/time server booted up +#define STAT_SERVER_ACCEPTED_CONNECTION 102 // successful connections since server startup +#define STAT_SERVER_MOST_CONNECTIONS 103 // most players online in history of server +#define STAT_SERVER_NUM_ACCOUNTS 104 // total number of unique accounts +#define STAT_SERVER_NUM_CHARACTERS 105 // total number of player characters +#define STAT_SERVER_AVG_CHAR_LEVEL 106 // average level of player characters +#define STAT_SERVER_NUM_ACTIVE_ZONES 107 // number of currently running/loaded zones +#define STAT_SERVER_NUM_ACTIVE_INSTANCES 108 // number of active zones that are "instances" + +// Player PvE counters +#define STAT_PLAYER_TOTAL_NPC_KILLS 1000 // total NPC kills by player +#define STAT_PLAYER_TOTAL_DEATHS 1001 // total non-PvP deaths of player +#define STAT_PLAYER_KVD_RATIO 1002 // kill-versus-death ratio of player +#define STAT_PLAYER_HIGHEST_MELEE_HIT 1003 // players highest melee hit to date +#define STAT_PLAYER_HIGHEST_MAGIC_HIT 1004 // players highest magic hit to date +#define STAT_PLAYER_HIGHEST_HO_HIT 1005 // players highest heroic opportunity hit +#define STAT_PLAYER_TOTAL_STATUS 1006 // player total status +#define STAT_PLAYER_TOTAL_WEALTH 1007 // player total wealth +#define STAT_PLAYER_QUESTS_COMPLETED 1008 // total quests completed +#define STAT_PLAYER_RECIPES_KNOWN 1009 // total recipes player knows +#define STAT_PLAYER_TOTAL_CRAFTED_ITEMS 1010 // total items crafted by player +#define STAT_PLAYER_ITEMS_DISCOVERED 1011 // total items discovered by player +#define STAT_PLAYER_RARES_HARVESTED 1012 // total rare harvests by player +#define STAT_PLAYER_ITEMS_HARVESTED 1013 // total rare harvests by player +#define STAT_PLAYER_MASTER_ABILITIES 1014 // total master abilities player has +#define STAT_PLAYER_HIGHEST_FALLING_HIT 1015 // player's highest damage amount taken from falling + +// Player PvP counters +#define STAT_PLAYER_TOTAL_PVP_KILLS 1100 // total PVP kills by player +#define STAT_PLAYER_PVP_KILL_STREAK 1101 // longest PVP kill streak of player +#define STAT_PLAYER_TOTAL_PVP_DEATHS 1102 // total PVP deaths of player +#define STAT_PLAYER_PVP_DEATH_STREAK 1103 // longest PVP death streak of player +#define STAT_PLAYER_PVP_KVD_RATIO 1104 // PVP kill-versus-death ratio of player +#define STAT_PLAYER_TOTAL_ARENA_KILLS 1105 // total Arena kills of player + +// MOST stats for players +#define STAT_PLAYER_MOST_NPC_KILLS 1200 // IPvP: Player with most NPC kills +#define STAT_PLAYER_MOST_NPC_DEATHS 1201 // IPvP: Player with most non-PVP deaths +#define STAT_PLAYER_MOST_PVP_KILLS 1202 // IPvP: Player with most PvP kills +#define STAT_PLAYER_MOST_PVP_DEATHS 1203 // IPvP: Player with most PvP deaths +#define STAT_PLAYER_MOST_ARENA_KILLS 1204 // IPvP: Player with most Arena kills +#define STAT_PLAYER_MOST_STATUS 1205 // IPvP: Player with most Status +#define STAT_PLAYER_MOST_WEALTH 1206 // IPvP: Player with most Wealth + +// HIGHEST stats for players +#define STAT_PLAYER_HIGHEST_NPC_KVD_RATIO 1300 // IPvP: Player with highest NPC kill-versus-death ratio +#define STAT_PLAYER_HIGHEST_PVP_KILL_STREAK 1301 // IPvP: Player with longest PvP kill streak +#define STAT_PLAYER_HIGHEST_PVP_DEATH_STREAK 1302 // IPvP: Player with longest PvP death streak +#define STAT_PLAYER_HIGHEST_PVP_KVD_RATIO 1303 // IPvP: Player with highest PvP kill-versus-death ratio +#define STAT_PLAYER_HIGHEST_HP 1304 // IPvP: Player with highest HP on server +#define STAT_PLAYER_HIGHEST_POWER 1305 // IPvP: Player with highest Power on server +#define STAT_PLAYER_HIGHEST_RESISTS 1306 // IPvP: Player with highest Resists on server + + +struct Statistic { + int32 stat_id; + sint32 stat_value; + int32 stat_date; + bool save_needed; +}; + + +// Player EVENT defines +// Some EVENTs are single occurrance (S), while others are cummulative throughout the life of the player (C) +#define PLAYER_EVENT_NEW_ADV_LEVEL 2000 // (C) player achieves a new adventure level +#define PLAYER_EVENT_NEW_TS_LEVEL 2001 // (C) player achieves a new tradeskill level +#define PLAYER_EVENT_NEW_AA 2002 // (C) player earns AA pt +#define PLAYER_EVENT_NEW_ACHIEVEMENT 2003 // (C) player new achievement +#define PLAYER_EVENT_LAST_DEATH 2004 // (S) player was last killed +#define PLAYER_EVENT_LAST_KILL 2005 // (S) player last killed spawn_id +#define PLAYER_EVENT_DISCOVER_POI 2006 // (C) player discovers location_id + +// These maybe should be World stat events, since it is about 1 player discovering a new item? +#define PLAYER_EVENT_DISCOVER_ITEM 2007 // (C) player discovers item_id +#define PLAYER_EVENT_DISCOVER_RECIPE 2008 // (C) player discovers recipe_id + + +struct PlayerHistory { + int32 history_zone; + int32 history_id; + sint32 history_value; + int32 history_date; + bool save_needed; +}; + +struct GlobalLoot { + int8 minLevel; + int8 maxLevel; + int32 table_id; + int32 loot_tier; +}; + +#define TRANSPORT_TYPE_LOCATION 0 +#define TRANSPORT_TYPE_ZONE 1 +#define TRANSPORT_TYPE_GENERIC 2 +#define TRANSPORT_TYPE_FLIGHT 3 + + +// structs MUST start with class_id and race_id, in that order as int8's +struct StartingStructHeader +{ + int8 class_id; + int8 race_id; +}; + +struct StartingSkill +{ + StartingStructHeader header; + int32 skill_id; + int16 current_val; + int16 max_val; + int32 progress; // what is this for..? +}; + +struct StartingSpell +{ + StartingStructHeader header; + int32 spell_id; + int8 tier; + int32 knowledge_slot; +}; + +#define MAX_VOICEOVER_TYPE 2 +struct VoiceOverStruct{ + string mp3_string; + string text_string; + string emote_string; + int32 key1; + int32 key2; + bool is_garbled; + int8 garble_link_id; +}; + +class ZoneList { + public: + ZoneList(); + ~ZoneList(); + + void Add(ZoneServer* zone); + void Remove(ZoneServer* zone); + ZoneServer* Get(int32 id, bool loadZone = true, bool skip_existing_zones = false, bool increment_zone = true); + ZoneServer* Get(const char* zone_name, bool loadZone=true, bool skip_existing_zones = false, bool increment_zone = true); + ZoneServer* GetByInstanceID(int32 id, int32 zone_id=0, bool skip_existing_zones = false, bool increment_zone = true); + + /// Get the instance for the given zone id with the lowest population + /// The id of the zone to look up + /// ZoneServer* of an active zone with the given id + ZoneServer* GetByLowestPopulation(int32 zone_id); + + int32 GetZonesPlayersCount(); + + void AddClientToMap(string name, Client* client){ + name = ToLower(name); + MClientList.lock(); + client_map[name] = client; + MClientList.unlock(); + } + void CheckFriendList(Client* client); + void CheckFriendZoned(Client* client); + + // move to Chat/Chat.h? + bool HandleGlobalChatMessage(Client* from, char* to, int16 channel, const char* message, const char* channel_name = 0, int32 current_language_id = 0); + void HandleGlobalBroadcast(const char* message); + void HandleGlobalAnnouncement(const char* message); + // + + int32 Count(); + Client* GetClientByCharName(string name){ + name = ToLower(name); + Client* ret = 0; + MClientList.lock(); + if(client_map.count(name) > 0) + ret = client_map[name]; + MClientList.unlock(); + return ret; + } + Client* GetClientByCharID(int32 id) { + Client* ret = 0; + MClientList.lock(); + map::iterator itr; + for (itr = client_map.begin(); itr != client_map.end(); itr++) { + if (itr->second->GetCharacterID() == id) { + ret = itr->second; + break; + } + } + MClientList.unlock(); + return ret; + } + Client* GetClientByEQStream(EQStream* eqs) { + Client* ret = 0; + if (eqs) { + MClientList.lock(); + map::iterator itr; + for (itr = client_map.begin(); itr != client_map.end(); itr++) { + if (itr->second->getConnection() == eqs) { + ret = itr->second; + break; + } + } + MClientList.unlock(); + } + return ret; + } + void UpdateVitality(float amount); + void RemoveClientFromMap(string name, Client* client){ + name = ToLower(name); + MClientList.lock(); + if(client_map.count(name) > 0 && client_map[name] == client) + client_map.erase(name); + MClientList.unlock(); + } + bool ClientConnected(int32 account_id); + void RemoveClientZoneReference(ZoneServer* zone); + void ReloadClientQuests(); + bool DepopFinished(); + void Depop(); + void Repop(); + void DeleteSpellProcess(); + void LoadSpellProcess(); + void ProcessWhoQuery(const char* query, Client* client); + void ProcessWhoQuery(vector* queries, ZoneServer* zone, vector* players, bool isGM); + void SendZoneList(Client* client); + void WritePlayerStatistics(); + void ShutDownZones(); + void ReloadMail(); + void ReloadSpawns(); + + void WatchdogHeartbeat(); + + void SendTimeUpdate(); + + void PopulateClientList(http::response& res); +private: + Mutex MClientList; + Mutex MZoneList; + map removed_zoneservers; + map client_map; + list zlist; +}; +class World { +public: + World(); + ~World(); + int8 GetClassID(const char* name); + void Process(); + void init(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password); + PacketStruct* GetWorldTime(int16 version); + void WorldTimeTick(); + float GetXPRate(); + float GetTSXPRate(); + void LoadVitalityInformation(); + void UpdateVitality(); + WorldTime* GetWorldTimeStruct(){ + return &world_time; + } + ulong GetCurrentThreadID(); + int64 GetThreadUsageCPUTime(); + + // These 2 functions are never used. What was their purpose? Should they be removed? + void AddNPCAppearance(int32 id, AppearanceData* appearance){ npc_appearance_list[id] = appearance; } + AppearanceData* GetNPCAppearance(int32 id) { return npc_appearance_list[id]; } + + void ReloadGuilds(); + bool ReportBug(string data, char* player_name, int32 account_id, const char* spawn_name, int32 spawn_id, int32 zone_id); + void AddSpawnScript(int32 id, const char* name); + void AddSpawnEntryScript(int32 id, const char* name); + void AddSpawnLocationScript(int32 id, const char* name); + void AddZoneScript(int32 id, const char* name); + const char* GetSpawnScript(int32 id); + const char* GetSpawnEntryScript(int32 id); + const char* GetSpawnLocationScript(int32 id); + const char* GetZoneScript(int32 id); + void ResetSpawnScripts(); + void ResetZoneScripts(); + int16 GetMerchantItemQuantity(int32 merchant_id, int32 item_id); + void DecreaseMerchantQuantity(int32 merchant_id, int32 item_id, int16 amount); + int32 GetInventoryID(int32 merchant_id, int32 item_id); + void AddMerchantItem(int32 inventory_id, MerchantItemInfo ItemInfo); + void RemoveMerchantItem(int32 inventory_id, int32 item_id); + vector* GetMerchantList(int32 merchant_id); + vector* GetMerchantItemList(int32 merchant_id, int8 merchant_type, Player* player); + MerchantInfo* GetMerchantInfo(int32 merchant_id); + map* GetMerchantInfo(); + void AddMerchantInfo(int32 merchant_id, MerchantInfo* multiplier); + void DeleteMerchantsInfo(); + void DeleteMerchantItems(); + void DeleteSpawns(); + vector* GetClientVariables(); + void WritePlayerStatistics(); + void WriteServerStatistics(); + void AddServerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date); + void UpdateServerStatistic(int32 stat_id, sint32 stat_value, bool overwrite = false); + sint32 GetServerStatisticValue(int32 stat_id); + void RemoveServerStatistics(); + + //PlayerGroup* AddGroup(Client* leader); + //void AddGroupMember(PlayerGroup* group, Client* member); + //void RemoveGroupMember(Client* member, bool immediate = false); + //void DisbandGroup(PlayerGroup* group, bool lock = true); + void SendGroupQuests(PlayerGroup* group, Client* client); + //void UpdateGroupBuffs(); + //void RemoveGroupBuffs(PlayerGroup *group, Client *client); + //void SetPendingGroup(char* name, char* leader); + //void GroupMessage(PlayerGroup* group, const char* message, ...); + //void SimpleGroupMessage(PlayerGroup* group, const char* message); + //void GroupChatMessage(PlayerGroup* group, Spawn* from, const char* message); + //const char* GetPendingGroup(string name); + //void GroupReadLock(); + //void GroupReadUnLock(); + //void CheckRemoveGroupedPlayer(); + //void SendGroupUpdate(PlayerGroup* group, Client* exclude = 0); + bool RejoinGroup(Client* client, int32 group_id); + //bool MakeLeader(Client* leader, string new_leader); + + void AddBonuses(Item* item, ItemStatsValues* values, int16 type, sint32 value, Entity* entity); + void CreateGuild(const char* guild_name, Client* leader = 0, int32 group_id = 0); + void SaveGuilds(); + void PickRandomLottoDigits(int32* digits); + void AddLottoPlayer(int32 character_id, int32 end_time); + void RemoveLottoPlayer(int32 character_id); + void SetLottoPlayerNumMatches(int32 character_id, int8 num_matches); + void CheckLottoPlayers(); + void PopulateTOVStatMap(); + int32 LoadItemBlueStats(); + sint16 GetItemStatAOMValue(sint16 subtype); + sint16 GetItemStatTOVValue(sint16 subtype); + sint16 GetItemStatDOVValue(sint16 subtype); + sint16 GetItemStatCOEValue(sint16 subtype); + sint16 GetItemStatKAValue(sint16 subtype); + sint16 GetItemStatTESTValue(sint16 subtype); + int8 TranslateSlotSubTypeToClient(Client* client, int8 stat_type, sint16 sub_type); + + vector biography; + + volatile bool items_loaded; + volatile bool spells_loaded; + volatile bool achievments_loaded; + + std::atomic world_loaded; + std::atomic world_uptime; + + void AddHouseZone(int32 id, string name, int64 cost_coins, int32 cost_status, int64 upkeep_coins, int32 upkeep_status, int8 vault_slots, int8 alignment, int8 guild_level, int32 zone_id, int32 exit_zone_id, float exit_x, float exit_y, float exit_z, float exit_heading); + HouseZone* GetHouseZone(int32 id); + + void AddPlayerHouse(int32 char_id, int32 house_id, int64 unique_id, int32 instance_id, int32 upkeep_due, int64 escrow_coins, int32 escrow_status, string player_name); + PlayerHouse* GetPlayerHouseByHouseID(int32 char_id, int32 house_id); + PlayerHouse* GetPlayerHouseByUniqueID(int64 unique_id); + PlayerHouse* GetPlayerHouseByInstanceID(int32 instance_id); + vector GetAllPlayerHouses(int32 char_id); + vector GetAllPlayerHousesByHouseID(int32 house_id); + PlayerHouse* GetPlayerHouse(Client* client, int32 spawn_id, int64 unique_house_id, HouseZone** set_house_zone); + + void ReloadHouseData(PlayerHouse* ph); + + PlayerGroupManager* GetGroupManager() { return &m_playerGroupManager; } + + bool CheckTempBugCRC(char* msg); + + void SyncCharacterAbilities(Client* client); + + void LoadStartingLists(); + void PurgeStartingLists(); + multimap*> starting_skills; + multimap*> starting_spells; + Mutex MStartingLists; + void SetReloadingSubsystem(string subsystem); + void RemoveReloadingSubSystem(string subsystem); + + bool IsReloadingSubsystems(); + int32 GetSuppressedWarningTime() { + return suppressed_warning; + } + void SetSuppressedWarning() { suppressed_warning = Timer::GetCurrentTime2(); } + map GetOldestReloadingSubsystem(); + + void LoadRegionMaps(std::string zoneFile); + RegionMap* GetRegionMap(std::string zoneFile, int32 client_version); + + void LoadMaps(std::string zoneFile); + void RemoveMaps(std::string zoneFile); + Map* GetMap(std::string zoneFile, int32 client_version); + + void SendTimeUpdate(); + // just in case we roll over a time as to not send bad times to clients (days before hours, hours before minutes as examples) + Mutex MWorldTime; + + void LoadVoiceOvers(); + void PurgeVoiceOvers(); + typedef std::multimap::iterator VOMapIterator; + bool FindVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_ = nullptr, bool* find_garbled = nullptr, VoiceOverStruct* garble_struct_ = nullptr); + void AddVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_); + void CopyVoiceOver(VoiceOverStruct* struct1, VoiceOverStruct* struct2); + + /* NPC Spells */ + void AddNPCSpell(int32 list_id, int32 spell_id, int8 tier, bool spawn_cast, bool aggro_cast, sint8 req_hp_ratio); + vector* GetNPCSpells(int32 primary_list, int32 secondary_list); + + void PurgeNPCSpells(); + + + static void Web_worldhandle_status(const http::request& req, http::response& res); + static void Web_worldhandle_clients(const http::request& req, http::response& res); + + Mutex MVoiceOvers; + + static sint64 newValue; +private: + multimap*> voiceover_map[3]; + int32 suppressed_warning = 0; + map reloading_subsystems; + //void RemovePlayerFromGroup(PlayerGroup* group, GroupMemberInfo* info, bool erase = true); + //void DeleteGroupMember(GroupMemberInfo* info); + Mutex MReloadingSubsystems; + Mutex MMerchantList; + Mutex MSpawnScripts; + Mutex MZoneScripts; + //Mutex MGroups; + + mutable std::shared_mutex MNPCSpells; + + map merchant_info; + map > merchant_inventory_items; + int32 vitality_frequency; + float vitality_amount; + float xp_rate; + float ts_xp_rate; // JA + WorldTime world_time; + + map npc_appearance_list; + + map spawn_scripts; + map spawnentry_scripts; + map spawnlocation_scripts; + map zone_scripts; + //vector player_groups; + //map group_removal_pending; + //map pending_groups; + map server_statistics; + MutexMap lotto_players; + int32 last_checked_time; + Timer save_time_timer; + Timer time_tick_timer; + Timer vitality_timer; + Timer player_stats_timer; + Timer server_stats_timer; + //Timer remove_grouped_player; + Timer guilds_timer; + Timer lotto_players_timer; + Timer group_buff_updates; + + Timer watchdog_timer; + + map m_houseZones; + // Map > + map > m_playerHouses; + Mutex MHouseZones; + Mutex MPlayerHouses; + + map tov_itemstat_conversion; + map dov_itemstat_conversion; + map coe_itemstat_conversion; + map ka_itemstat_conversion; + + PlayerGroupManager m_playerGroupManager; + + Mutex MBugReport; + map bug_report_crc; + + std::map region_maps; + std::map maps; + Mutex MWorldMaps; + Mutex MWorldRegionMaps; + + map > npc_spell_list; + + WebServer* world_webserver; +}; +#endif diff --git a/source/WorldServer/WorldDatabase.cpp b/source/WorldServer/WorldDatabase.cpp new file mode 100644 index 0000000..6641cbe --- /dev/null +++ b/source/WorldServer/WorldDatabase.cpp @@ -0,0 +1,8563 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "WorldDatabase.h" +#include "../common/debug.h" +#include "../common/packet_dump.h" +#include "../common/GlobalHeaders.h" +#include "Items/Items.h" +#include "Factions.h" +#include "World.h" +#include "Variables.h" +#include "VisualStates.h" +#include "Appearances.h" +#include "Skills.h" +#include "Quests.h" +#include "LuaInterface.h" +#include "classes.h" +#include "../common/Log.h" +#include "Rules/Rules.h" +#include "Titles.h" +#include "Languages.h" +#include "Traits/Traits.h" +#include "ClientPacketFunctions.h" +#include "Zone/ChestTrap.h" +#include "../common/version.h" +#include "SpellProcess.h" +#include "races.h" + + +extern Classes classes; +extern Commands commands; +extern MasterTitlesList master_titles_list; +extern MasterItemList master_item_list; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern MasterFactionList master_faction_list; +extern World world; +extern Variables variables; +extern VisualStates visual_states; +extern Appearances master_appearance_list; +extern MasterSkillList master_skill_list; +extern MasterQuestList master_quest_list; +extern LuaInterface* lua_interface; +extern ZoneList zone_list; +extern GuildList guild_list; +extern MasterCollectionList master_collection_list; +extern RuleManager rule_manager; +extern MasterLanguagesList master_languages_list; +extern ChestTrapList chest_trap_list; + +//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp +#if defined(__GNUC__) +#define stricmp strcasecmp +#define strnicmp strncasecmp +#include +#endif + +WorldDatabase::WorldDatabase(){ +} + +WorldDatabase::~WorldDatabase(){ +} + +bool WorldDatabase::ConnectNewDatabase() { + /* + TESTS + + database_new.Connect(); + DatabaseResult result; + database_new.Select(&result, "select name from characters where id=1"); + if (result.Next()) { + printf("'%s'\n", result.GetStringStr("name")); + printf("'%s'\n", result.GetStringStr("nameBAD")); + printf("'%s'\n", result.GetString(3)); + } + return true; + */ + + return database_new.Connect(); +} + +void WorldDatabase::PingNewDB() +{ + database_new.PingNewDB(); +} + +void WorldDatabase::DeleteBuyBack(int32 char_id, int32 item_id, int16 quantity, int32 price) { + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "Deleting Buyback - Player: %u, Item ID: %u, Qty: %i, Price: %u", char_id, item_id, quantity, price); + + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM character_buyback WHERE char_id = %u AND item_id = %u AND quantity = %i AND price = %u", char_id, item_id, quantity, price); +} + +void WorldDatabase::LoadBuyBacks(Client* client) { + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "Loading Buyback - Player: %u", client->GetCharacterID()); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, item_id, quantity, price FROM character_buyback where char_id = %u ORDER BY id desc limit 10", client->GetCharacterID()); + int8 count = 0; + int32 last_id = 0; + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + LogWrite(MERCHANT__DEBUG, 5, "Merchant", "AddBuyBack: item: %u, qty: %i, price: %u", atoul(row[1]), atoi(row[2]), atoul(row[3])); + last_id = atoul(row[0]); + client->AddBuyBack(last_id, atoul(row[1]), atoi(row[2]), atoul(row[3]), false); + count++; + } + if(count >= 10) + { + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "Deleting excess Buyback from Player: %u", client->GetCharacterID()); + Query query2; + query2.RunQuery2(Q_DELETE, "DELETE FROM character_buyback WHERE char_id = %u AND id < %u", client->GetCharacterID(), last_id); + } + } +} + +void WorldDatabase::SaveBuyBacks(Client* client) +{ + LogWrite(MERCHANT__DEBUG, 3, "Merchant", "Saving Buybacks - Player: %u", client->GetCharacterID()); + + deque* buybacks = client->GetBuyBacks(); + + if(buybacks && buybacks->size() > 0) + { + BuyBackItem* item = 0; + deque::iterator itr; + + for(itr = buybacks->begin(); itr != buybacks->end(); itr++) + { + item = *itr; + + if(item && item->save_needed) + { + LogWrite(MERCHANT__DEBUG, 5, "Merchant", "SaveBuyBack: char: %u, item: %u, qty: %i, price: %u", client->GetCharacterID(), item->item_id, item->quantity, item->price); + SaveBuyBack(client->GetCharacterID(), item->item_id, item->quantity, item->price); + item->save_needed = false; + } + } + } +} + +void WorldDatabase::SaveBuyBack(int32 char_id, int32 item_id, int16 quantity, int32 price) +{ + LogWrite(MERCHANT__DEBUG, 3, "Merchant", "Saving Buyback - Player: %u, Item ID: %u, Qty: %i, Price: %u", char_id, item_id, quantity, price); + + Query query; + string insert = string("INSERT INTO character_buyback (char_id, item_id, quantity, price) VALUES (%u, %u, %i, %u) "); + query.AddQueryAsync(char_id, this, Q_INSERT, insert.c_str(), char_id, item_id, quantity, price); +} + +int32 WorldDatabase::LoadCharacterSpells(int32 char_id, Player* player) +{ + LogWrite(SPELL__DEBUG, 0, "Spells", "Loading Character Spells for player %s...", player->GetName()); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spell_id, tier, knowledge_slot, spell_book_type, linked_timer_id FROM character_spells, spells where character_spells.spell_id = spells.id and character_spells.char_id = %u ORDER BY spell_id, tier desc", char_id); + int32 old_spell_id = 0; + int32 new_spell_id = 0; + int32 count = 0; + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + count++; + new_spell_id = atoul(row[0]); + + if(new_spell_id == old_spell_id) + continue; + + old_spell_id = new_spell_id; + + LogWrite(SPELL__DEBUG, 5, "Spells", "\tLoading SpellID: %u, tier: %i, slot: %i, type: %u linked_timer_id: %u", new_spell_id, atoi(row[1]), atoi(row[2]), atoul(row[3]), atoul(row[4])); + + int8 tier = atoi(row[1]); + + if (player->HasSpell(new_spell_id, tier, true)) + continue; + + player->AddSpellBookEntry(new_spell_id, tier, atoi(row[2]), atoul(row[3]), atoul(row[4])); + } + } + + return count; +} + +void WorldDatabase::SavePlayerSpells(Client* client) +{ + if(!client || client->GetCharacterID() < 1) + return; + + LogWrite(SPELL__DEBUG, 3, "Spells", "Saving Spell(s) for Player: '%s'", client->GetPlayer()->GetName()); + vector* spells = client->GetPlayer()->GetSpellsSaveNeeded(); + + if(spells) + { + vector::iterator itr; + SpellBookEntry* spell = 0; + + for(itr = spells->begin(); itr != spells->end(); itr++) + { + spell = *itr; + Query query; + LogWrite(SPELL__DEBUG, 5, "Spells", "\tSaving SpellID: %u, tier: %i, slot: %i", spell->spell_id, spell->tier, spell->slot); + query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "INSERT INTO character_spells (char_id, spell_id, tier) SELECT %u, %u, %i ON DUPLICATE KEY UPDATE tier = %i", + client->GetPlayer()->GetCharacterID(), spell->spell_id, spell->tier, spell->tier); + spell->save_needed = false; + } + safe_delete(spells); + } +} + +int32 WorldDatabase::LoadCharacterSkills(int32 char_id, Player* player) +{ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT skill_id, current_val, max_val FROM character_skills, skills where character_skills.skill_id = skills.id and character_skills.char_id = %u", char_id); + + if(result && mysql_num_rows(result) >0) + { + while(result && (row = mysql_fetch_row(result))) + { + count++; + LogWrite(SKILL__DEBUG, 5, "Skills", "Loading SkillID: %u, cur_val: %i, max_val: %l", strtoul(row[0], NULL, 0), atoi(row[1]), atoi(row[2])); + player->AddSkill(strtoul(row[0], NULL, 0), atoi(row[1]), atoi(row[2])); + } + } + return count; +} + +void WorldDatabase::DeleteCharacterSkill(int32 char_id, Skill* skill) +{ + if (char_id > 0 && skill) + { + LogWrite(SKILL__DEBUG, 0, "Skills", "Deleting Skill '%s' (%u) from char_id: %u", skill->name.data.c_str(), skill->skill_id, char_id); + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `character_skills` WHERE `char_id`=%u AND `skill_id`=%u", char_id, skill->skill_id); + } +} + +int32 WorldDatabase::LoadSkills() +{ + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, short_name, name, description, skill_type, display FROM skills"); + + if(result) + { + if (mysql_num_rows(result) >0) + { + Skill* skill = 0; + + while(result && (row = mysql_fetch_row(result))) + { + skill = new Skill(); + skill->skill_id = strtoul(row[0], NULL, 0); + skill->short_name.data = string(row[1]); + skill->short_name.size = skill->short_name.data.length(); + skill->name.data = string(row[2]); + skill->name.size = skill->name.data.length(); + skill->description.data = string(row[3]); + skill->description.size = skill->description.data.length(); + skill->skill_type = strtoul(row[4], NULL, 0); + //these two need to be converted to the correct numbers + if(skill->skill_type == 13) + skill->skill_type = SKILL_TYPE_LANGUAGE; + else if(skill->skill_type == 12) + skill->skill_type = SKILL_TYPE_GENERAL; + + skill->display = atoi(row[5]); + master_skill_list.AddSkill(skill); + total++; + LogWrite(SKILL__DEBUG, 5, "Skill", "---Loading Skill: %s (%u)", skill->name.data.c_str(), skill->skill_id); + LogWrite(SKILL__DEBUG, 7, "Skill", "---short_name: %s, type: %i, display: %i", skill->short_name.data.c_str(), skill->skill_type, skill->display); + } + } + } + LogWrite(SKILL__DEBUG, 3, "Skill", "--Loaded %u Skill(s)", total); + return total; +} + +map >* WorldDatabase::LoadCharacterMacros(int32 char_id) +{ + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT macro_number, macro_name, macro_icon, macro_text FROM character_macros where char_id = %u ORDER BY macro_number, id", char_id); + + if(result && mysql_num_rows(result) >0) + { + map >* macros = new map >; + + while(result && (row = mysql_fetch_row(result))) + { + MacroData* data = new MacroData; + data->name = row[1]; + data->icon = atoi(row[2]); + data->text = row[3]; + (*macros)[atoi(row[0])].push_back(data); + total++; + LogWrite(PLAYER__DEBUG, 5, "Player", "\tLoading macro: %i. %s for player: %u", atoi(row[0]), row[1], char_id); + } + LogWrite(PLAYER__DEBUG, 0, "Player", "\tLoaded %u macro%s", total, total == 1 ? "" : "s"); + return macros; + } + return 0; +} + +void WorldDatabase::UpdateCharacterMacro(int32 char_id, int8 number, const char* name, int16 icon, vector* updates) +{ + LogWrite(PLAYER__DEBUG, 0, "Player", "Update player id %u macro: %i", char_id, number); + + Query query; + Query query2; + query.RunQuery2(Q_DELETE, "delete FROM character_macros where char_id = %u and macro_number = %i", char_id, number); + + if(name && updates && updates->size() > 0) + { + for(int8 i=0;isize();i++) + { + query2.RunQuery2(Q_INSERT, "insert into character_macros (char_id, macro_number, macro_name, macro_icon, macro_text) values(%u, %i, '%s', %i, '%s')", char_id, number, getSafeEscapeString(name).c_str(), icon, getSafeEscapeString(updates->at(i).c_str()).c_str()); + LogWrite(PLAYER__DEBUG, 5, "Player", "\tAdding macro: %s, %s (Player: %u)", name, updates->at(i).c_str(), char_id); + } + } +} + +//we use our timestamp just in case db is on another server, otherwise times might be off +void WorldDatabase::UpdateVitality(int32 timestamp, float amount){ + Query query, query2, query3; + + LogWrite(PLAYER__DEBUG, 3, "Player", "Reset Vitality > 100: %f", amount); + query.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=100 where (xp_vitality + %f) > 100", amount); + + LogWrite(PLAYER__DEBUG, 3, "Player", "Update Vitality <= 100: %f", amount); + query2.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=(xp_vitality+%f) where (xp_vitality + %f) <= 100", amount, amount); + + LogWrite(PLAYER__DEBUG, 3, "Player", "Update Vitality Timer: %u", timestamp); + query3.RunQuery2(Q_UPDATE, "update variables set variable_value=%u where variable_name='vitalitytimer'", timestamp); +} + +void WorldDatabase::SaveVariable(const char* name, const char* value, const char* comment){ + + LogWrite(WORLD__DEBUG, 0, "Variables", "Saving Variable: %s = %s", name, value); + Query query; + if(comment){ + query.RunQuery2(Q_REPLACE, "replace into variables (variable_name, variable_value, comment) values('%s', '%s', '%s')", + getSafeEscapeString(name).c_str(), getSafeEscapeString(value).c_str(), getSafeEscapeString(comment).c_str()); + } + else{ + query.RunQuery2(Q_REPLACE, "replace into variables (variable_name, variable_value) values('%s', '%s')", + getSafeEscapeString(name).c_str(), getSafeEscapeString(value).c_str()); + } +} + +void WorldDatabase::LoadGlobalVariables(){ + variables.ClearVariables ( ); + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT variable_name, variable_value, comment FROM variables"); + + while(result && (row = mysql_fetch_row(result))) + { + Variable* newVar = new Variable(row[0], row[1], row[2]); + variables.AddVariable(newVar); + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading variable: '%s' = '%s'", row[0], row[1]); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u variables", total); +} + + +void WorldDatabase::LoadAppearanceMasterList() +{ + DatabaseResult result; + int32 total = 0; + int32 appearance_id; + int16 appearance_version; + + master_appearance_list.ClearAppearances(); + + database_new.Select(&result, "SELECT appearance_id, `name`, min_client_version FROM appearances ORDER BY appearance_id"); + + while( result.Next() ) + { + appearance_id = result.GetInt32Str("appearance_id"); + const char *appearance_name = result.GetStringStr("name"); + appearance_version = result.GetInt16Str("min_client_version"); + Appearance* a = new Appearance(appearance_id, appearance_name, appearance_version); + + master_appearance_list.InsertAppearance(a); + + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading appearances: '%s' (%i)", appearance_name, appearance_id); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u appearances", total); +} + + +void WorldDatabase::LoadVisualStates() +{ + visual_states.Reset(); + int32 total = 0; + Query query; + Query query2; + Query query3; + MYSQL_ROW row; + MYSQL_ROW row2; + MYSQL_ROW row3; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT visual_state_id, name FROM visual_states"); + while(result && (row = mysql_fetch_row(result))) + { + VisualState* vs = new VisualState(atoi(row[0]), row[1]); + visual_states.InsertVisualState(vs); + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading visual state: '%s' (%i)", row[1], atoi(row[0])); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u visual states", total); + + total = 0; + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT name, visual_state_id, message, targeted_message, min_version_range, max_version_range FROM emotes"); + while(result2 && (row2 = mysql_fetch_row(result2))) + { + EmoteVersionRange* range = 0; + if ((range = visual_states.FindEmoteRange(string(row2[0]))) == NULL) + { + range = new EmoteVersionRange(row2[0]); + visual_states.InsertEmoteRange(range); + } + + range->AddVersionRange(atoul(row2[4]),atoul(row2[5]), row2[0], atoul(row2[1]), row2[2], row2[3]); + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading emote state: '%s' (%i)", row2[0], atoul(row2[0])); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u emote state(s)", total); + + + total = 0; + MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT name, spell_visual_id, alternate_spell_visual, min_version_range, max_version_range FROM spell_visuals"); + while(result3 && (row3 = mysql_fetch_row(result3))) + { + EmoteVersionRange* range = 0; + if ((range = visual_states.FindSpellVisualRange(string(row3[0]))) == NULL) + { + range = new EmoteVersionRange(row3[0]); + visual_states.InsertSpellVisualRange(range, atoul(row3[1])); + } + + range->AddVersionRange(atoul(row3[3]),atoul(row3[4]), row3[0], atoul(row3[1]), row3[2]); + total++; + LogWrite(WORLD__DEBUG, 5, "World", "---Loading spell visual state: '%s' (%u)", row3[1], atoul(row3[0])); + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u spell visual state(s)", total); +} + +void WorldDatabase::LoadSubCommandList() +{ + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT command, subcommand, handler, required_status FROM commands where length(subcommand) > 0 ORDER BY handler asc"); + while(result && (row = mysql_fetch_row(result))) + { + commands.GetRemoteCommands()->CheckAddSubCommand(string(row[0]), EQ2_RemoteCommandString(row[1], (int32)strtoul(row[2], NULL, 0), atoi(row[3]))); + total++; + LogWrite(COMMAND__DEBUG, 5, "Command", "---Loading Command: '%s', sub '%s', handler, %u status %i", row[0], row[1], atoul(row[2]), atoi(row[3])); + } + LogWrite(COMMAND__DEBUG, 3, "Command", "--Loaded %i Subcommand(s)", total); +} + +void WorldDatabase::LoadCommandList() +{ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT command, handler, required_status FROM commands where length(subcommand) = 0 ORDER BY handler asc"); + int16 index = 0; + + while(result && (row = mysql_fetch_row(result))) + { + int32 handler = strtoul(row[1], NULL, 0); + while(handler>index && handler != 999) + { + LogWrite(COMMAND__DEBUG, 5, "Command", "---Loading Remote Commands: handler %u, index %u", handler, index); + commands.GetRemoteCommands()->addZero(); + index++; + } + LogWrite(COMMAND__DEBUG, 5, "Command", "---Loading Commands: handler %u, index %u", handler, index); + commands.GetRemoteCommands()->addCommand(EQ2_RemoteCommandString(row[0], handler, atoi(row[2]))); + index++; + } + LogWrite(COMMAND__DEBUG, 3, "Command", "--Loaded %i Command%s", index, index > 0 ? "s" : ""); + LoadSubCommandList(); +} + +int32 WorldDatabase::LoadNPCSpells(){ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spell_list_id, spell_id, spell_tier, on_spawn_cast, on_aggro_cast, required_hp_ratio FROM spawn_npc_spells where spell_list_id > 0"); + while(result && (row = mysql_fetch_row(result))){ + if(!row[0] || !row[1] || !row[2] || !row[3] || !row[4] || !row[5]) { + LogWrite(NPC__ERROR, 0, "NPC", "---Loading NPC Spell List: %u, found NULL values in entry, SKIP!", row[0] ? atoul(row[0]) : 0); + } + else { + world.AddNPCSpell(atoul(row[0]), atoul(row[1]), atoi(row[2]), atoul(row[3]), atoul(row[4]), atoi(row[5])); + count++; + + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC Spell List: %u, spell id: %u, tier: %i", atoul(row[0]), atoul(row[1]), atoi(row[2])); + } + + } + return count; +} + +int32 WorldDatabase::LoadNPCSkills(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT skill_list_id, skill_id, starting_value FROM spawn_npc_skills"); + while(result && (row = mysql_fetch_row(result))){ + zone->AddNPCSkill(atoul(row[0]), atoul(row[1]), atoi(row[2])); + count++; + + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC Skill List: %u, skill id: %u, value: %i", atoul(row[0]), atoul(row[1]), atoi(row[2])); + + } + return count; +} + +int32 WorldDatabase::LoadNPCEquipment(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT equipment_list_id, item_id FROM spawn_npc_equipment"); + while(result && (row = mysql_fetch_row(result))){ + zone->AddNPCEquipment(atoul(row[0]), atoul(row[1])); + count++; + + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC Equipment List: %u, item: %u", atoul(row[0]), atoul(row[1])); + + } + return count; +} + +int8 WorldDatabase::GetAppearanceType(string type){ + int8 ret = 255; + if (type == "soga_hair_face_highlight_color") + ret = APPEARANCE_SOGA_HFHC; + else if (type == "soga_hair_type_highlight_color") + ret = APPEARANCE_SOGA_HTHC; + else if (type == "soga_hair_face_color") + ret = APPEARANCE_SOGA_HFC; + else if (type == "soga_hair_type_color") + ret = APPEARANCE_SOGA_HTC; + else if (type == "soga_hair_highlight") + ret = APPEARANCE_SOGA_HH; + else if (type == "soga_hair_color1") + ret = APPEARANCE_SOGA_HC1; + else if (type == "soga_hair_color2") + ret = APPEARANCE_SOGA_HC2; + else if (type == "hair_type_color") + ret = APPEARANCE_HTC; + else if (type == "soga_skin_color") + ret = APPEARANCE_SOGA_SC; + else if (type == "soga_eye_color") + ret = APPEARANCE_SOGA_EC; + else if (type == "hair_type_highlight_color") + ret = APPEARANCE_HTHC; + else if (type == "hair_face_highlight_color") + ret = APPEARANCE_HFHC; + else if (type == "hair_face_color") + ret = APPEARANCE_HFC; + else if (type == "hair_highlight") + ret = APPEARANCE_HH; + else if (type == "hair_color1") + ret = APPEARANCE_HC1; + else if (type == "wing_color1") + ret = APPEARANCE_WC1; + else if (type == "hair_color2") + ret = APPEARANCE_HC2; + else if (type == "wing_color2") + ret = APPEARANCE_WC2; + else if (type == "skin_color") + ret = APPEARANCE_SC; + else if (type == "eye_color") + ret = APPEARANCE_EC; + else if (type == "soga_eye_brow_type") + ret = APPEARANCE_SOGA_EBT; + else if (type == "soga_cheek_type") + ret = APPEARANCE_SOGA_CHEEKT; + else if (type == "soga_nose_type") + ret = APPEARANCE_SOGA_NT; + else if (type == "soga_chin_type") + ret = APPEARANCE_SOGA_CHINT; + else if (type == "soga_lip_type") + ret = APPEARANCE_SOGA_LT; + else if (type == "eye_brow_type") + ret = APPEARANCE_EBT; + else if (type == "soga_ear_type") + ret = APPEARANCE_SOGA_EART; + else if (type == "soga_eye_type") + ret = APPEARANCE_SOGA_EYET; + else if (type == "cheek_type") + ret = APPEARANCE_CHEEKT; + else if (type == "nose_type") + ret = APPEARANCE_NT; + else if (type == "chin_type") + ret = APPEARANCE_CHINT; + else if (type == "ear_type") + ret = APPEARANCE_EART; + else if (type == "eye_type") + ret = APPEARANCE_EYET; + else if (type == "lip_type") + ret = APPEARANCE_LT; + else if (type == "shirt_color") + ret = APPEARANCE_SHIRT; + else if (type == "unknown_chest_color") + ret = APPEARANCE_UCC; + else if (type == "pants_color") + ret = APPEARANCE_PANTS; + else if (type == "unknown_legs_color") + ret = APPEARANCE_ULC; + else if (type == "unknown9") + ret = APPEARANCE_U9; + else if (type == "body_size") + ret = APPEARANCE_BODY_SIZE; + else if (type == "soga_wing_color1") + ret = APPEARANCE_SOGA_WC1; + else if (type == "soga_wing_color2") + ret = APPEARANCE_SOGA_WC2; + else if (type == "soga_shirt_color") + ret = APPEARANCE_SOGA_SHIRT; + else if (type == "soga_unknown_chest_color") + ret = APPEARANCE_SOGA_UCC; + else if (type == "soga_pants_color") + ret = APPEARANCE_SOGA_PANTS; + else if (type == "soga_unknown_legs_color") + ret = APPEARANCE_SOGA_ULC; + else if (type == "soga_unknown13") + ret = APPEARANCE_SOGA_U13; + else if (type == "body_age") + ret = APPEARANCE_BODY_AGE; + else if (type == "model_color") + ret = APPEARANCE_MC; + else if (type == "soga_model_color") + ret = APPEARANCE_SMC; + else if (type == "soga_body_size") + ret = APPEARANCE_SBS; + else if (type == "soga_body_age") + ret = APPEARANCE_SBA; + return ret; +} + +int32 WorldDatabase::LoadAppearances(ZoneServer* zone, Client* client){ + Query query, query2; + MYSQL_ROW row; + int32 count = 0, spawn_id = 0, new_spawn_id = 0; + Entity* entity = 0; + if(client) + entity = client->GetPlayer(); + map appearance_types; + map > appearance_colors; + EQ2_Color color; + color.red = 0; + color.green = 0; + color.blue = 0; + string type; + MYSQL_RES* result = 0; + if(!client) + result = query.RunQuery2(Q_SELECT, "SELECT distinct `type` FROM npc_appearance where length(type) > 0"); + else + result = query.RunQuery2(Q_SELECT, "SELECT distinct `type` FROM char_colors where length(type) > 0 and char_id=%u", client->GetCharacterID()); + while(result && (row = mysql_fetch_row(result))){ + type = string(row[0]); + appearance_types[type] = GetAppearanceType(type); + if(appearance_types[type] == 255) + LogWrite(WORLD__ERROR, 0, "Appearance", "Unknown appearance type '%s' in LoadAppearances.", type.c_str()); + } + + MYSQL_RES* result2 = 0; + if(!client) + result2 = query2.RunQuery2(Q_SELECT, "SELECT `type`, spawn_id, signed_value, red, green, blue FROM npc_appearance where length(type) > 0 ORDER BY spawn_id"); + else + result2 = query2.RunQuery2(Q_SELECT, "SELECT `type`, char_id, signed_value, red, green, blue FROM char_colors where length(type) > 0 and char_id=%u", client->GetCharacterID()); + while(result2 && (row = mysql_fetch_row(result2))){ + if(!client){ + new_spawn_id = atoul(row[1]); + if(spawn_id != new_spawn_id){ + entity = zone->GetNPC(new_spawn_id, true); + if(!entity) + continue; + if(spawn_id > 0) + count++; + spawn_id = new_spawn_id; + } + } + if(appearance_types[row[0]] < APPEARANCE_SOGA_EBT){ + color.red = atoi(row[3]); + color.green = atoi(row[4]); + color.blue = atoi(row[5]); + } + switch(appearance_types[row[0]]){ + case APPEARANCE_SOGA_HFHC:{ + entity->features.soga_hair_face_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HTHC:{ + entity->features.soga_hair_type_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HFC:{ + entity->features.soga_hair_face_color = color; + break; + } + case APPEARANCE_SOGA_HTC:{ + entity->features.soga_hair_type_color = color; + break; + } + case APPEARANCE_SOGA_HH:{ + entity->features.soga_hair_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HC1:{ + entity->features.soga_hair_color1 = color; + break; + } + case APPEARANCE_SOGA_HC2:{ + entity->features.soga_hair_color2 = color; + break; + } + case APPEARANCE_SOGA_SC:{ + entity->features.soga_skin_color = color; + break; + } + case APPEARANCE_SOGA_EC:{ + entity->features.soga_eye_color = color; + break; + } + case APPEARANCE_HTHC:{ + entity->features.hair_type_highlight_color = color; + break; + } + case APPEARANCE_HFHC:{ + entity->features.hair_face_highlight_color = color; + break; + } + case APPEARANCE_HTC:{ + entity->features.hair_type_color = color; + break; + } + case APPEARANCE_HFC:{ + entity->features.hair_face_color = color; + break; + } + case APPEARANCE_HH:{ + entity->features.hair_highlight_color = color; + break; + } + case APPEARANCE_HC1:{ + entity->features.hair_color1 = color; + break; + } + case APPEARANCE_HC2:{ + entity->features.hair_color2 = color; + break; + } + case APPEARANCE_WC1:{ + entity->features.wing_color1 = color; + break; + } + case APPEARANCE_WC2:{ + entity->features.wing_color2 = color; + break; + } + case APPEARANCE_SC:{ + entity->features.skin_color = color; + break; + } + case APPEARANCE_EC:{ + entity->features.eye_color = color; + break; + } + case APPEARANCE_SOGA_EBT:{ + for(int i=0;i<3;i++) + entity->features.soga_eye_brow_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_CHEEKT:{ + for(int i=0;i<3;i++) + entity->features.soga_cheek_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_NT:{ + for(int i=0;i<3;i++) + entity->features.soga_nose_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_CHINT:{ + for(int i=0;i<3;i++) + entity->features.soga_chin_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_LT:{ + for(int i=0;i<3;i++) + entity->features.soga_lip_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_EART:{ + for(int i=0;i<3;i++) + entity->features.soga_ear_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SOGA_EYET:{ + for(int i=0;i<3;i++) + entity->features.soga_eye_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_EBT:{ + for(int i=0;i<3;i++) + entity->features.eye_brow_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_CHEEKT:{ + for(int i=0;i<3;i++) + entity->features.cheek_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_NT:{ + for(int i=0;i<3;i++) + entity->features.nose_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_CHINT:{ + for(int i=0;i<3;i++) + entity->features.chin_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_EART:{ + for(int i=0;i<3;i++) + entity->features.ear_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_EYET:{ + for(int i=0;i<3;i++) + entity->features.eye_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_LT:{ + for(int i=0;i<3;i++) + entity->features.lip_type[i] = atoi(row[3+i]); + break; + } + case APPEARANCE_SHIRT:{ + entity->features.shirt_color = color; + break; + } + case APPEARANCE_UCC:{ + break; + } + case APPEARANCE_PANTS:{ + entity->features.pants_color = color; + break; + } + case APPEARANCE_ULC:{ + break; + } + case APPEARANCE_U9:{ + break; + } + case APPEARANCE_BODY_SIZE:{ + entity->features.body_size = color.red; + break; + } + case APPEARANCE_SOGA_WC1:{ + break; + } + case APPEARANCE_SOGA_WC2:{ + break; + } + case APPEARANCE_SOGA_SHIRT:{ + break; + } + case APPEARANCE_SOGA_UCC:{ + break; + } + case APPEARANCE_SOGA_PANTS:{ + break; + } + case APPEARANCE_SOGA_ULC:{ + break; + } + case APPEARANCE_SOGA_U13:{ + break; + } + case APPEARANCE_BODY_AGE: { + entity->features.body_age = color.red; + break; + } + case APPEARANCE_MC:{ + entity->features.model_color = color; + break; + } + case APPEARANCE_SMC:{ + entity->features.soga_model_color = color; + break; + } + case APPEARANCE_SBS: { + entity->features.soga_body_size = color.red; + break; + } + case APPEARANCE_SBA: { + entity->features.soga_body_age = color.red; + break; + } + } + entity->info_changed = true; + } + return count; +} + +void WorldDatabase::LoadNPCs(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + NPC* npc = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str\n" + "FROM spawn s\n" + "INNER JOIN spawn_npcs npc\n" + "ON s.id = npc.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON npc.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n" + "GROUP BY s.id", + zone->GetZoneID(), zone->GetInstanceID()); + while(result && (row = mysql_fetch_row(result))){ + /*npc->SetAppearanceID(atoi(row[12])); + AppearanceData* appearance = world.GetNPCAppearance(npc->GetAppearanceID()); + if(appearance) + memcpy(&npc->appearance, appearance, sizeof(AppearanceData)); + */ + int32 npcXpackFlag = atoul(row[75]); + int32 npcHolidayFlag = atoul(row[76]); + + id = atoul(row[0]); + if(zone->GetNPC(id, true)) + continue; + npc = new NPC(); + + if (!CheckExpansionFlags(zone, npcXpackFlag) || !CheckHolidayFlags(zone, npcHolidayFlag)) + npc->SetOmittedByDBFlag(true); + + npc->SetDatabaseID(id); + strcpy(npc->appearance.name, row[1]); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[9])); + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[10])); + if(primary_command_list){ + npc->SetPrimaryCommands(primary_command_list); + npc->primary_command_list_id = atoul(row[9]); + } + if(secondary_command_list){ + npc->SetSecondaryCommands(secondary_command_list); + npc->secondary_command_list_id = atoul(row[10]); + } + npc->appearance.min_level = atoi(row[2]); + npc->appearance.max_level = atoi(row[3]); + npc->appearance.level = atoi(row[2]); + npc->appearance.difficulty = atoi(row[4]); + npc->appearance.race = atoi(row[5]); + npc->appearance.model_type = atoi(row[6]); + npc->appearance.soga_model_type = atoi(row[62]); + npc->appearance.adventure_class = atoi(row[7]); + npc->appearance.gender = atoi(row[8]); + npc->appearance.display_name = atoi(row[11]); + npc->features.hair_type = atoi(row[14]); + npc->features.hair_face_type = atoi(row[15]); + npc->features.wing_type = atoi(row[16]); + npc->features.chest_type = atoi(row[17]); + npc->features.legs_type = atoi(row[18]); + npc->features.soga_hair_type = atoi(row[19]); + npc->features.soga_hair_face_type = atoi(row[20]); + npc->appearance.attackable = atoi(row[21]); + npc->appearance.show_level = atoi(row[22]); + npc->appearance.targetable = atoi(row[23]); + npc->appearance.show_command_icon = atoi(row[24]); + npc->appearance.display_hand_icon = atoi(row[25]); + npc->appearance.hide_hood = atoi(row[70]); + npc->appearance.randomize = atoi(row[61]); + npc->SetTotalHP(atoul(row[26])); + npc->SetTotalHPBaseInstance(atoul(row[26])); + npc->SetTotalPower(atoul(row[27])); + npc->SetTotalPowerBaseInstance(atoul(row[27])); + npc->SetHP(npc->GetTotalHP()); + npc->SetPower(npc->GetTotalPower()); + if(npc->GetTotalHP() == 0){ + npc->SetTotalHP(15*npc->GetLevel() + 1); + npc->SetHP(15*npc->GetLevel() + 1); + } + if(npc->GetTotalPower() == 0){ + npc->SetTotalPower(15*npc->GetLevel() + 1); + npc->SetPower(15*npc->GetLevel() + 1); + } + npc->size = atoi(row[28]); + npc->appearance.pos.collision_radius = atoi(row[29]); + npc->appearance.action_state = atoi(row[30]); + npc->appearance.visual_state = atoi(row[31]); + npc->appearance.mood_state = atoi(row[32]); + npc->appearance.emote_state = atoi(row[71]); + npc->appearance.pos.state = atoi(row[33]); + npc->appearance.activity_status = atoi(row[34]); + npc->faction_id = atoul(row[35]); + if(row[36]){ + std::string sub_title = std::string(row[36]); + if(strncmp(row[36],"", 11) == 0) { + npc->SetCollector(true); + } + if(strlen(row[36]) < sizeof(npc->appearance.sub_title)) + strcpy(npc->appearance.sub_title, row[36]); + else + strncpy(npc->appearance.sub_title, row[36], sizeof(npc->appearance.sub_title)); + } + npc->SetMerchantID(atoul(row[37])); + npc->SetMerchantType(atoi(row[38])); + npc->SetSizeOffset(atoi(row[39])); + npc->SetAIStrategy(atoi(row[41])); + npc->SetPrimarySpellList(atoul(row[42])); + npc->SetSecondarySpellList(atoul(row[43])); + npc->SetPrimarySkillList(atoul(row[44])); + npc->SetSecondarySkillList(atoul(row[45])); + npc->SetEquipmentListID(atoul(row[46])); + + InfoStruct* info = npc->GetInfoStruct(); + info->set_attack_type(atoi(row[40])); + info->set_str_base(atoi(row[47])); + info->set_sta_base(atoi(row[48])); + info->set_wis_base(atoi(row[49])); + info->set_intel_base(atoi(row[50])); + info->set_agi_base(atoi(row[51])); + info->set_heat_base(atoi(row[52])); + info->set_cold_base(atoi(row[53])); + info->set_magic_base(atoi(row[54])); + info->set_mental_base(atoi(row[55])); + info->set_divine_base(atoi(row[56])); + info->set_disease_base(atoi(row[57])); + info->set_poison_base(atoi(row[58])); + info->set_alignment(atoi(row[64])); + + npc->SetAggroRadius(atof(row[59])); + npc->SetCastPercentage(atoi(row[60])); + npc->appearance.heroic_flag = atoi(row[63]); + + info->set_elemental_base(atoi(row[65])); + info->set_arcane_base(atoi(row[66])); + info->set_noxious_base(atoi(row[67])); + npc->SetTotalSavagery(atoul(row[68])); + npc->SetTotalDissonance(atoul(row[69])); + npc->SetSavagery(npc->GetTotalSavagery()); + npc->SetDissonance(npc->GetTotalDissonance()); + if(npc->GetTotalSavagery() == 0){ + npc->SetTotalSavagery(15*npc->GetLevel() + 1); + npc->SetSavagery(15*npc->GetLevel() + 1); + } + if(npc->GetTotalDissonance() == 0){ + npc->SetTotalDissonance(15*npc->GetLevel() + 1); + npc->SetDissonance(15*npc->GetLevel() + 1); + } + npc->SetPrefixTitle(row[72]); + npc->SetSuffixTitle(row[73]); + npc->SetLastName(row[74]); + + // xpack+holiday value handled at top at position 75+76 + + int8 disableSounds = atoul(row[77]); + npc->SetSoundsDisabled(disableSounds); + + npc->SetMerchantLevelRange(atoul(row[78]), atoul(row[79])); + + npc->SetAAXPRewards(atoul(row[80])); + + info->set_water_type(atoul(row[81])); + info->set_flying_type(atoul(row[82])); + + npc->SetLootTier(atoul(row[83])); + + npc->SetLootDropType(atoul(row[84])); + + npc->SetScaredByStrongPlayers(atoul(row[85])); + + if(row[86]){ + std::string action_state_str = std::string(row[86]); + npc->GetInfoStruct()->set_action_state(action_state_str); + } + + zone->AddNPC(id, npc); + total++; + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC: '%s' (%u)", npc->appearance.name, id); + } + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC(s).", total); + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Skill(s).", LoadNPCSkills(zone)); + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Equipment Piece(s).", LoadNPCEquipment(zone)); + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Appearance(s).", LoadAppearances(zone)); + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i NPC Equipment Appearance(s).", LoadNPCAppearanceEquipmentData(zone)); +} + + +void WorldDatabase::LoadSpiritShards(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT timestamp, name, level, race, gender, adventure_class, model_type, soga_model_type, hair_type, hair_face_type, wing_type, chest_type, legs_type, soga_hair_type, soga_hair_face_type, hide_hood, size, collision_radius, action_state, visual_state, mood_state, emote_state, pos_state, activity_status, sub_title, prefix_title, suffix_title, lastname, x, y, z, heading, gridid, id, charid\n" + "FROM character_spirit_shards\n" + "WHERE zoneid = %u and (instanceid = 0 or instanceid = %u)", + zone->GetZoneID(), zone->GetInstanceID()); + while(result && (row = mysql_fetch_row(result))){ + /*npc->SetAppearanceID(atoi(row[12])); + AppearanceData* appearance = world.GetNPCAppearance(npc->GetAppearanceID()); + if(appearance) + memcpy(&npc->appearance, appearance, sizeof(AppearanceData)); + */ + sint64 timestamp = 0; +#ifdef WIN32 + timestamp = _strtoui64(row[0], NULL, 10); +#else + timestamp = strtoull(row[0], 0, 10); +#endif + + if(!row[1]) + continue; + + NPC* shard = new NPC(); + + shard->SetShardCreatedTimestamp(timestamp); + strcpy(shard->appearance.name, row[1]); + + shard->appearance.level = atoul(row[2]); + shard->appearance.race = atoul(row[3]); + shard->appearance.gender = atoul(row[4]); + shard->appearance.adventure_class = atoul(row[5]); + shard->appearance.model_type = atoul(row[6]); + shard->appearance.soga_model_type = atoul(row[7]); + shard->appearance.display_name = 1; + shard->features.hair_type = atoul(row[8]); + shard->features.hair_face_type = atoul(row[9]); + shard->features.wing_type = atoul(row[10]); + shard->features.chest_type = atoul(row[11]); + shard->features.legs_type = atoul(row[12]); + shard->features.soga_hair_type = atoul(row[13]); + shard->features.soga_hair_face_type = atoul(row[14]); + shard->appearance.attackable = 0; + shard->appearance.show_level = 1; + shard->appearance.targetable = 1; + shard->appearance.show_command_icon = 1; + shard->appearance.display_hand_icon = 0; + shard->appearance.hide_hood = atoul(row[15]); + shard->size = atoul(row[16]); + shard->appearance.pos.collision_radius = atoul(row[17]); + shard->appearance.action_state = atoul(row[18]); + shard->appearance.visual_state = atoul(row[19]); // ghostly look + shard->appearance.mood_state = atoul(row[20]); + shard->appearance.emote_state = atoul(row[21]); + shard->appearance.pos.state = atoul(row[22]); + shard->appearance.activity_status = atoul(row[23]); + + if(row[24]) + strncpy(shard->appearance.sub_title, row[24], sizeof(shard->appearance.sub_title)); + + if(row[25]) + shard->SetPrefixTitle(row[25]); + + if(row[26]) + shard->SetSuffixTitle(row[26]); + + if(row[27]) + shard->SetLastName(row[27]); + + shard->SetX(atof(row[28])); + shard->SetY(atof(row[29])); + shard->SetZ(atof(row[30])); + shard->SetHeading(atof(row[31])); + shard->SetSpawnOrigX(shard->GetX()); + shard->SetSpawnOrigY(shard->GetY()); + shard->SetSpawnOrigZ(shard->GetZ()); + shard->SetSpawnOrigHeading(shard->GetHeading()); + shard->SetLocation(atoul(row[32])); + shard->SetShardID(atoul(row[33])); + shard->SetShardCharID(atoul(row[34])); + shard->SetAlive(false); + + const char* script = rule_manager.GetGlobalRule(R_Combat, SpiritShardSpawnScript)->GetString(); + + if(script) + { + shard->SetSpawnScript(script); + zone->CallSpawnScript(shard, SPAWN_SCRIPT_PRESPAWN); + } + + zone->AddSpawn(shard); + + if(script) + zone->CallSpawnScript(shard, SPAWN_SCRIPT_SPAWN); + + total++; + LogWrite(NPC__DEBUG, 5, "NPC", "---Loading Player Spirit Shard: '%s' (%u)", shard->appearance.name, id); + } + LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i Spirit Shard(s).", total); +} + +void WorldDatabase::LoadSigns(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + Sign* sign = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, ss.language\n" + "FROM spawn s\n" + "INNER JOIN spawn_signs ss\n" + "ON s.id = ss.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON ss.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u\n" + "GROUP BY s.id", + zone->GetZoneID()); + + while(result && (row = mysql_fetch_row(result))){ + int32 signXpackFlag = atoul(row[28]); + int32 signHolidayFlag = atoul(row[29]); + + id = atoul(row[0]); + if(zone->GetSign(id, true)) + continue; + sign = new Sign(); + + if (!CheckExpansionFlags(zone, signXpackFlag) || !CheckHolidayFlags(zone, signHolidayFlag)) + sign->SetOmittedByDBFlag(true); + + sign->SetDatabaseID(id); + strcpy(sign->appearance.name, row[1]); + sign->appearance.model_type = atoi(row[2]); + sign->SetSize(atoi(row[3])); + sign->appearance.show_command_icon = atoi(row[4]); + sign->SetWidgetID(atoul(row[5])); + sign->SetWidgetX(atof(row[6])); + sign->SetWidgetY(atof(row[7])); + sign->SetWidgetZ(atof(row[8])); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[9])); + if(primary_command_list){ + sign->SetPrimaryCommands(primary_command_list); + sign->primary_command_list_id = atoul(row[9]); + } + + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[10])); + if (secondary_command_list){ + sign->SetSecondaryCommands(secondary_command_list); + sign->secondary_command_list_id = atoul(row[10]); + } + + sign->appearance.pos.collision_radius = atoi(row[11]); + sign->SetSignIcon(atoi(row[12])); + if(strncasecmp(row[13],"Generic", 7) == 0) + sign->SetSignType(SIGN_TYPE_GENERIC); + else if(strncasecmp(row[13],"Zone", 4) == 0) + sign->SetSignType(SIGN_TYPE_ZONE); + sign->SetSignTitle(row[14]); + sign->SetSignDescription(row[15]); + sign->SetSignDistance(atof(row[16])); + sign->SetSignZoneID(atoul(row[17])); + sign->SetSignZoneX(atof(row[18])); + sign->SetSignZoneY(atof(row[19])); + sign->SetSignZoneZ(atof(row[20])); + sign->SetSignZoneHeading(atof(row[21])); + sign->SetIncludeHeading(atoi(row[22]) == 1); + sign->SetIncludeLocation(atoi(row[23]) == 1); + sign->SetTransporterID(atoul(row[24])); + sign->SetSizeOffset(atoi(row[25])); + sign->appearance.display_hand_icon = atoi(row[26]); + sign->SetVisualState(atoi(row[27])); + + // xpack+holiday value handled at top at position 28+29 + + int8 disableSounds = atoul(row[30]); + sign->SetSoundsDisabled(disableSounds); + + sign->SetMerchantLevelRange(atoul(row[31]), atoul(row[32])); + + sign->SetAAXPRewards(atoul(row[33])); + + sign->SetLootTier(atoul(row[34])); + + sign->SetLootDropType(atoul(row[35])); + + sign->SetLanguage(atoul(row[36])); + + zone->AddSign(id, sign); + total++; + + LogWrite(SIGN__DEBUG, 5, "Sign", "---Loading Sign: '%s' (%u).", sign->appearance.name, id); + + } + LogWrite(SIGN__DEBUG, 0, "Sign", "--Loaded %i Sign(s)", total); +} + +void WorldDatabase::LoadWidgets(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + Widget* widget = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_widgets sw\n" + "ON s.id = sw.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON sw.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u\n" + "GROUP BY s.id", + zone->GetZoneID()); + while(result && (row = mysql_fetch_row(result))){ + int32 widgetXpackFlag = atoul(row[33]); + int32 widgetHolidayFlag = atoul(row[34]); + + id = atoul(row[0]); + if(zone->GetWidget(id, true)) + continue; + widget = new Widget(); + + if (!CheckExpansionFlags(zone, widgetXpackFlag) || !CheckHolidayFlags(zone, widgetHolidayFlag)) + widget->SetOmittedByDBFlag(true); + + widget->SetDatabaseID(id); + strcpy(widget->appearance.name, row[1]); + widget->appearance.model_type = atoi(row[2]); + widget->SetSize(atoi(row[3])); + widget->appearance.show_command_icon = atoi(row[4]); + + if (row[5] == NULL) + widget->SetWidgetID(0xFFFFFFFF); + else + widget->SetWidgetID(atoul(row[5])); + + widget->SetWidgetX(atof(row[6])); + widget->SetWidgetY(atof(row[7])); + widget->SetWidgetZ(atof(row[8])); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[9])); + if(primary_command_list){ + widget->SetPrimaryCommands(primary_command_list); + widget->primary_command_list_id = atoul(row[9]); + } + + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[10])); + if (secondary_command_list) { + widget->SetSecondaryCommands(secondary_command_list); + widget->secondary_command_list_id = atoul(row[10]); + } + + widget->appearance.pos.collision_radius = atoi(row[11]); + widget->SetIncludeHeading(atoi(row[12]) == 1); + widget->SetIncludeLocation(atoi(row[13]) == 1); + widget->SetWidgetIcon(atoi(row[14])); + if (strncasecmp(row[15], "Generic", 7) == 0) + widget->SetWidgetType(WIDGET_TYPE_GENERIC); + else if (strncasecmp(row[15], "Door", 4) == 0) + widget->SetWidgetType(WIDGET_TYPE_DOOR); + else if (strncasecmp(row[15], "Lift", 4) == 0) + widget->SetWidgetType(WIDGET_TYPE_LIFT); + + widget->SetOpenHeading(atof(row[16])); + widget->SetOpenY(atof(row[17])); + widget->SetActionSpawnID(atoul(row[18])); + if(row[19] && strlen(row[19]) > 5) + widget->SetOpenSound(row[19]); + if(row[20] && strlen(row[20]) > 5) + widget->SetCloseSound(row[20]); + widget->SetOpenDuration(atoi(row[21])); + widget->SetClosedHeading(atof(row[22])); + widget->SetLinkedSpawnID(atoul(row[23])); + widget->SetCloseY(atof(row[24])); + widget->SetTransporterID(atoul(row[25])); + widget->SetSizeOffset(atoi(row[26])); + widget->SetHouseID(atoul(row[27])); + widget->SetOpenX(atof(row[28])); + widget->SetOpenZ(atof(row[29])); + widget->SetCloseX(atof(row[30])); + widget->SetCloseZ(atof(row[31])); + widget->appearance.display_hand_icon = atoi(row[32]); + + // xpack+holiday value handled at top at position 33+34 + + int8 disableSounds = atoul(row[35]); + widget->SetSoundsDisabled(disableSounds); + + widget->SetMerchantLevelRange(atoul(row[36]), atoul(row[37])); + + widget->SetAAXPRewards(atoul(row[38])); + + widget->SetLootTier(atoul(row[39])); + + widget->SetLootDropType(atoul(row[40])); + + zone->AddWidget(id, widget); + total++; + + LogWrite(WIDGET__DEBUG, 5, "Widget", "---Loading Widget: '%s' (%u).", widget->appearance.name, id); + + } + LogWrite(WIDGET__DEBUG, 0, "Widget", "--Loaded %i Widget(s)", total); +} + +void WorldDatabase::LoadObjects(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + Object* object = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_objects so\n" + "ON s.id = so.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON so.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n" + "GROUP BY s.id", + zone->GetZoneID(), zone->GetInstanceID()); + + while(result && (row = mysql_fetch_row(result))){ + + int32 objXpackFlag = atoul(row[19]); + int32 objHolidayFlag = atoul(row[20]); + + id = atoul(row[0]); + if(zone->GetObject(id, true)) + continue; + object = new Object(); + + if (!CheckExpansionFlags(zone, objXpackFlag) || !CheckHolidayFlags(zone, objHolidayFlag)) + object->SetOmittedByDBFlag(true); + + object->SetDatabaseID(id); + strcpy(object->appearance.name, row[1]); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[4])); + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[5])); + if(primary_command_list){ + object->SetPrimaryCommands(primary_command_list); + object->primary_command_list_id = atoul(row[4]); + } + if(secondary_command_list){ + object->SetSecondaryCommands(secondary_command_list); + object->secondary_command_list_id = atoul(row[5]); + } + object->appearance.race = atoi(row[2]); + object->appearance.model_type = atoi(row[3]); + object->appearance.targetable = atoi(row[6]); + object->size = atoi(row[7]); + object->appearance.display_name = atoi(row[8]); + object->appearance.visual_state = atoi(row[9]); + object->appearance.attackable = atoi(row[10]); + object->appearance.show_level = atoi(row[11]); + object->appearance.show_command_icon = atoi(row[12]); + object->appearance.display_hand_icon = atoi(row[13]); + object->faction_id = atoul(row[14]); + object->appearance.pos.collision_radius = atoi(row[15]); + object->SetTransporterID(atoul(row[16])); + object->SetSizeOffset(atoi(row[17])); + object->SetDeviceID(atoi(row[18])); + + // xpack value handled at top at position 19 + + int8 disableSounds = atoul(row[21]); + object->SetSoundsDisabled(disableSounds); + + object->SetMerchantLevelRange(atoul(row[22]), atoul(row[23])); + + object->SetAAXPRewards(atoul(row[24])); + + object->SetLootTier(atoul(row[25])); + + object->SetLootDropType(atoul(row[26])); + + zone->AddObject(id, object); + total++; + + LogWrite(OBJECT__DEBUG, 5, "Object", "---Loading Object: '%s' (%u)", object->appearance.name, id); + + } + LogWrite(OBJECT__DEBUG, 0, "Object", "--Loaded %i Object(s)", total); +} + +void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + GroundSpawn* spawn = 0; + int32 id = 0; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_ground sg\n" + "ON s.id = sg.spawn_id\n" + "INNER JOIN spawn_location_entry le\n" + "ON sg.spawn_id = le.spawn_id\n" + "INNER JOIN spawn_location_placement lp\n" + "ON le.spawn_location_id = lp.spawn_location_id\n" + "WHERE lp.zone_id = %u\n" + "GROUP BY s.id", + zone->GetZoneID()); + while(result && (row = mysql_fetch_row(result))){ + + int32 gsXpackFlag = atoul(row[21]); + int32 gsHolidayFlag = atoul(row[22]); + + id = atoul(row[0]); + if(zone->GetGroundSpawn(id, true)) + continue; + spawn = new GroundSpawn(); + + if (!CheckExpansionFlags(zone, gsXpackFlag) || !CheckHolidayFlags(zone, gsHolidayFlag)) + spawn->SetOmittedByDBFlag(true); + + spawn->SetDatabaseID(id); + spawn->forceMapCheck = true; + + strcpy(spawn->appearance.name, row[1]); + vector* primary_command_list = zone->GetEntityCommandList(atoul(row[4])); + vector* secondary_command_list = zone->GetEntityCommandList(atoul(row[5])); + if(primary_command_list){ + spawn->SetPrimaryCommands(primary_command_list); + spawn->primary_command_list_id = atoul(row[4]); + } + if(secondary_command_list){ + spawn->SetSecondaryCommands(secondary_command_list); + spawn->secondary_command_list_id = atoul(row[5]); + } + spawn->appearance.race = atoi(row[2]); + spawn->appearance.model_type = atoi(row[3]); + spawn->appearance.targetable = atoi(row[6]); + spawn->size = atoi(row[7]); + spawn->appearance.display_name = atoi(row[8]); + spawn->appearance.visual_state = atoi(row[9]); + spawn->appearance.attackable = atoi(row[10]); + spawn->appearance.show_level = atoi(row[11]); + spawn->appearance.show_command_icon = atoi(row[12]); + spawn->appearance.display_hand_icon = atoi(row[13]); + spawn->faction_id = atoul(row[14]); + spawn->appearance.pos.collision_radius = atoi(row[15]); + spawn->SetNumberHarvests(atoi(row[16])); + spawn->SetAttemptsPerHarvest(atoi(row[17])); + spawn->SetGroundSpawnEntryID(atoul(row[18])); + spawn->SetCollectionSkill(row[19]); + spawn->SetSizeOffset(atoi(row[20])); + + // xpack+holiday value handled at top at position 21+22 + + int8 disableSounds = atoul(row[23]); + spawn->SetSoundsDisabled(disableSounds); + + spawn->SetAAXPRewards(atoul(row[24])); + + spawn->SetLootTier(atoul(row[25])); + + spawn->SetLootDropType(atoul(row[26])); + + zone->AddGroundSpawn(id, spawn); + total++; + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn: '%s' (%u)", spawn->appearance.name, id); + } + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "--Loaded %i GroundSpawn(s)", total); +} + +void WorldDatabase::LoadGroundSpawnItems(ZoneServer* zone) { + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT groundspawn_id, item_id, is_rare, grid_id FROM groundspawn_items;"); + while(result && (row = mysql_fetch_row(result))) + { + zone->AddGroundSpawnItem(atoul(row[0]), atoul(row[1]), atoi(row[2]), atoul(row[3])); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn Items: ID: %u\n", atoul(row[0])); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---item: %ul, rare: %i, grid: %ul", atoul(row[1]), atoi(row[2]), atoul(row[3])); + total++; + } + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "--Loaded %i GroundSpawn Item%s.", total, total == 1 ? "" : "s"); +} + +void WorldDatabase::LoadGroundSpawnEntries(ZoneServer* zone) { + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT groundspawn_id, min_skill_level, min_adventure_level, bonus_table, harvest1, harvest3, harvest5, harvest_imbue, harvest_rare, harvest10, harvest_coin FROM groundspawns WHERE enabled = 1;"); + while(result && (row = mysql_fetch_row(result))) + { + // this is getting ridonkulous... + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn ID: %u\n" \ + "---min_skill_level: %i, min_adventure_level: %i, bonus_table: %i\n" \ + "---harvest1: %.2f, harvest3: %.2f, harvest5: %.2f\n" \ + "---harvest_imbue: %.2f, harvest_rare: %.2f, harvest10: %.2f\n" \ + "---harvest_coin: %u", atoul(row[0]), atoi(row[1]), atoi(row[2]), atoi(row[3]), atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atof(row[9]), atoul(row[10])); + + zone->AddGroundSpawnEntry(atoul(row[0]), atoi(row[1]), atoi(row[2]), atoi(row[3]), atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atof(row[9]), atoul(row[10])); + total++; + } + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "--Loaded %i GroundSpawn Entr%s.", total, total == 1 ? "y" : "ies"); + LoadGroundSpawnItems(zone); +} + +bool WorldDatabase::LoadCharacterStats(int32 id, int32 account_id, Client* client) +{ + DatabaseResult result; + + if( database_new.Select(&result, "SELECT * FROM character_details WHERE char_id = %i LIMIT 0, 1", id) ) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading character_details for '%s' (char_id: %u)", client->GetPlayer()->GetName(), id); + + while( result.Next() ) + { + InfoStruct* info = client->GetPlayer()->GetInfoStruct(); + + // we need stats assigned before we try to assign hp/power + info->set_str_base(result.GetInt16Str("str")); + info->set_sta_base(result.GetInt16Str("sta")); + info->set_agi_base(result.GetInt16Str("agi")); + info->set_wis_base(result.GetInt16Str("wis")); + info->set_intel_base(result.GetInt16Str("intel")); + info->set_sta(info->get_sta_base()); + info->set_agi(info->get_agi_base()); + info->set_str(info->get_str_base()); + info->set_wis(info->get_wis_base()); + info->set_intel(info->get_intel_base()); + + // must have totals up top before we set the current 'hp' / 'power' + client->GetPlayer()->CalculatePlayerHPPower(); + + client->GetPlayer()->SetHP(result.GetSInt32Str("hp")); + client->GetPlayer()->SetPower(result.GetSInt32Str("power")); + info->set_max_concentration_base(result.GetInt8Str("max_concentration")); + + if (info->get_max_concentration_base() == 0) + info->set_max_concentration_base(5); + + info->set_attack_base(result.GetInt16Str("attack")); + info->set_mitigation_base(result.GetInt16Str("mitigation")); + info->set_avoidance_base(result.GetInt16Str("avoidance")); + info->set_parry_base(result.GetInt16Str("parry")); + info->set_deflection_base(result.GetInt16Str("deflection")); + info->set_block_base(result.GetInt16Str("block")); + + // old resist types + info->set_heat_base(result.GetInt16Str("heat")); + info->set_cold_base(result.GetInt16Str("cold")); + info->set_magic_base(result.GetInt16Str("magic")); + info->set_mental_base(result.GetInt16Str("mental")); + info->set_divine_base(result.GetInt16Str("divine")); + info->set_disease_base(result.GetInt16Str("disease")); + info->set_poison_base(result.GetInt16Str("poison")); + + // + + info->set_coin_copper(result.GetInt32Str("coin_copper")); + info->set_coin_silver(result.GetInt32Str("coin_silver")); + info->set_coin_gold(result.GetInt32Str("coin_gold")); + info->set_coin_plat(result.GetInt32Str("coin_plat")); + const char* bio = result.GetStringStr("biography"); + if(bio && strlen(bio) > 0) + info->set_biography(std::string(bio)); + info->set_status_points(result.GetInt32Str("status_points")); + client->GetPlayer()->GetPlayerInfo()->SetBindZone(result.GetInt32Str("bind_zone_id")); + client->GetPlayer()->GetPlayerInfo()->SetBindX(result.GetFloatStr("bind_x")); + client->GetPlayer()->GetPlayerInfo()->SetBindY(result.GetFloatStr("bind_y")); + client->GetPlayer()->GetPlayerInfo()->SetBindZ(result.GetFloatStr("bind_z")); + client->GetPlayer()->GetPlayerInfo()->SetBindHeading(result.GetFloatStr("bind_heading")); + client->GetPlayer()->GetPlayerInfo()->SetHouseZone(result.GetInt32Str("house_zone_id")); + client->GetPlayer()->SetAssignedAA(result.GetInt16Str("assigned_aa")); + client->GetPlayer()->SetUnassignedAA(result.GetInt16Str("unassigned_aa")); + client->GetPlayer()->SetTradeskillAA(result.GetInt16Str("tradeskill_aa")); + client->GetPlayer()->SetUnassignedTradeskillAA(result.GetInt16Str("unassigned_tradeskill_aa")); + client->GetPlayer()->SetPrestigeAA(result.GetInt16Str("prestige_aa")); + client->GetPlayer()->SetUnassignedPrestigeAA(result.GetInt16Str("unassigned_prestige_aa")); + client->GetPlayer()->SetTradeskillPrestigeAA(result.GetInt16Str("tradeskill_prestige_aa")); + client->GetPlayer()->SetUnassignedTradeskillPrestigeAA(result.GetInt16Str("unassigned_tradeskill_prestige_aa")); + info->set_xp(result.GetInt32Str("xp")); + + info->set_xp_needed(result.GetInt32Str("xp_needed")); + + if(info->get_xp_needed()== 0) + client->GetPlayer()->SetNeededXP(); + + info->set_xp_debt(result.GetFloatStr("xp_debt")); + info->set_xp_vitality(result.GetFloatStr("xp_vitality")); + info->set_ts_xp(result.GetInt32Str("tradeskill_xp")); + info->set_ts_xp_needed(result.GetInt32Str("tradeskill_xp_needed")); + + if (info->get_ts_xp_needed() == 0) + client->GetPlayer()->SetNeededTSXP(); + + info->set_tradeskill_xp_vitality(result.GetFloatStr("tradeskill_xp_vitality")); + info->set_bank_coin_copper(result.GetInt32Str("bank_copper")); + info->set_bank_coin_silver(result.GetInt32Str("bank_silver")); + info->set_bank_coin_gold(result.GetInt32Str("bank_gold")); + info->set_bank_coin_plat(result.GetInt32Str("bank_plat")); + + client->GetPlayer()->SetCombatVoice(result.GetInt16Str("combat_voice")); + client->GetPlayer()->SetEmoteVoice(result.GetInt16Str("emote_voice")); + client->GetPlayer()->SetBiography(result.GetStringStr("biography")); + client->GetPlayer()->GetInfoStruct()->set_flags(result.GetInt32Str("flags")); + client->GetPlayer()->GetInfoStruct()->set_flags2(result.GetInt32Str("flags2")); + client->GetPlayer()->SetLastName(result.GetStringStr("last_name")); + + // new resist types + info->set_elemental_base(result.GetInt16Str("elemental")); + info->set_arcane_base(result.GetInt16Str("arcane")); + info->set_noxious_base(result.GetInt16Str("noxious")); + // new savagery and dissonance + client->GetPlayer()->SetSavagery(result.GetSInt16Str("savagery")); + client->GetPlayer()->SetDissonance(result.GetSInt16Str("dissonance")); + client->GetPlayer()->SetTotalSavageryBase(client->GetPlayer()->GetTotalSavagery()); + client->GetPlayer()->SetTotalDissonanceBase(client->GetPlayer()->GetTotalDissonance()); + + client->GetPlayer()->SetCurrentLanguage(result.GetInt32Str("current_language")); + + std::string petName = result.GetStringStr("pet_name"); + boost::algorithm::to_lower(petName); + + if(petName != "no pet") { // some reason we default to "no pet", the code handles an empty string as setting a random pet name when its summoned + client->GetPlayer()->GetInfoStruct()->set_pet_name(result.GetStringStr("pet_name")); + } + } + + return true; + } + else + { + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character_details for '%s' (char_id: %u)", client->GetPlayer()->GetName(), id); + return false; + } +} + +bool WorldDatabase::loadCharacter(const char* ch_name, int32 account_id, Client* client){ + Query query, query4; + MYSQL_ROW row, row4; + int32 id = 0; + query.escaped_name = getEscapeString(ch_name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, current_zone_id, x, y, z, heading, admin_status, race, model_type, class, deity, level, gender, tradeskill_class, tradeskill_level, wing_type, hair_type, chest_type, legs_type, soga_wing_type, soga_hair_type, soga_chest_type, soga_legs_type, 0xFFFFFFFF - crc32(name), facial_hair_type, soga_facial_hair_type, instance_id, group_id, last_saved, DATEDIFF(curdate(), created_date) as accage, alignment, first_world_login FROM characters where name='%s' and account_id=%i AND deleted = 0", query.escaped_name, account_id); + // no character found + if ( result == NULL ) { + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character for '%s'", ch_name); + return false; + } + + if (mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + id = strtoul(row[0], NULL, 0); + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading character for '%s' (char_id: %u)", ch_name, id); + + client->SetCharacterID(id); + client->GetPlayer()->SetCharacterID(id); + client->SetAccountID(account_id); + client->GetPlayer()->SetName(ch_name); + client->GetPlayer()->SetX(atof(row[2])); + client->GetPlayer()->SetY(atof(row[3])); + client->GetPlayer()->SetZ(atof(row[4])); + client->GetPlayer()->SetHeading(atof(row[5])); + client->SetAdminStatus(atoi(row[6])); + client->GetPlayer()->SetRace(atoi(row[7])); + client->GetPlayer()->SetModelType(atoi(row[8])); + client->GetPlayer()->SetAdventureClass(atoi(row[9])); + client->GetPlayer()->SetDeity(atoi(row[10])); + client->GetPlayer()->SetLevel(atoi(row[11])); + + client->GetPlayer()->SetGender(atoi(row[12])); + client->GetPlayer()->SetTradeskillClass(atoi(row[13])); + client->GetPlayer()->SetTSLevel(atoi(row[14])); + + client->GetPlayer()->features.wing_type = atoi(row[15]); + client->GetPlayer()->features.hair_type = atoi(row[16]); + client->GetPlayer()->features.chest_type = atoi(row[17]); + client->GetPlayer()->features.legs_type = atoi(row[18]); + + client->GetPlayer()->features.wing_type = atoi(row[19]); + client->GetPlayer()->features.soga_hair_type = atoi(row[20]); + client->GetPlayer()->features.soga_chest_type = atoi(row[21]); + client->GetPlayer()->features.soga_legs_type = atoi(row[22]); + client->SetNameCRC(atoul(row[23])); + client->GetPlayer()->features.hair_face_type = atoi(row[24]); + client->GetPlayer()->features.soga_hair_face_type = atoi(row[25]); + int32 instanceid = atoi(row[26]); + + int32 groupid = atoi(row[27]); + client->SetRejoinGroupID(groupid); + + int32 zoneid = atoul(row[1]); +/* +JA Notes on SOGA: I think there are many more settings to add than were commented out here, +because I can load a SOGA model player, but some features are missing (Barbarian WOAD, Skin tons, etc) +SOGA chars looked ok in LoginServer screen tho... odd. +*/ + + // load character instances here + if ( LoadCharacterInstances(client) ) + client->UpdateCharacterInstances(); + + if ( instanceid > 0 ) + client->SetCurrentZoneByInstanceID(instanceid, zoneid); + else + client->SetCurrentZone(zoneid); + + int32 lastsavedtime = atoi(row[28]); + client->SetLastSavedTimeStamp(lastsavedtime); + + if (row[29]) + client->GetPlayer()->GetPlayerInfo()->SetAccountAge(atoi(row[29])); + + client->GetPlayer()->GetInfoStruct()->set_alignment(atoi(row[30])); + + client->GetPlayer()->GetInfoStruct()->set_first_world_login(atoi(row[31])); + + LoadCharacterFriendsIgnoreList(client->GetPlayer()); + MYSQL_RES* result4 = query4.RunQuery2(Q_SELECT, "SELECT `guild_id` FROM `guild_members` WHERE `char_id`=%u", id); + if (result4 && (row4 = mysql_fetch_row(result4))) { + Guild* guild = guild_list.GetGuild(atoul(row4[0])); + if (guild) { + client->GetPlayer()->SetGuild(guild); + string subtitle; + subtitle.append("<").append(guild->GetName()).append(">"); + client->GetPlayer()->SetSubTitle(subtitle.c_str()); + } + } + + LoadCharacterHistory(id, client->GetPlayer()); + LoadCharacterLUAHistory(id, client->GetPlayer()); + LoadPlayerStatistics(client->GetPlayer(), id); + LoadPlayerCollections(client->GetPlayer()); + LoadPlayerRecipes(client->GetPlayer()); + //LoadPlayerAchievements(client->GetPlayer()); + LoadPlayerAchievementsUpdates(client->GetPlayer()); + LoadAppearances(client->GetCurrentZone(), client); + return LoadCharacterStats(id, account_id, client); + } + + // should not be here... + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character for '%s'", ch_name); + return false; +} + +void WorldDatabase::LoadCharacterQuestRewards(Client* client) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description FROM character_quest_rewards where char_id = %u ORDER BY indexed asc", client->GetCharacterID()); + int8 count = 0; + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + int32 index = atoul(row[0]); + int32 quest_id = atoul(row[1]); + + bool is_temporary = atoul(row[2]); + + bool is_collection = atoul(row[3]); + + bool has_displayed = atoul(row[4]); + + + int64 tmp_coin = 0; +#ifdef WIN32 + tmp_coin = _strtoui64(row[5], NULL, 10); +#else + tmp_coin = strtoull(row[5], 0, 10); +#endif + + + int32 tmp_status = atoul(row[6]); + + std::string description = std::string(""); + + if(row[7]) { + std::string description = std::string(row[7]); + } + + if(is_collection) { + map* collections = client->GetPlayer()->GetCollectionList()->GetCollections(); + map::iterator itr; + Collection* collection = 0; + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + if (collection->GetIsReadyToTurnIn()) { + client->GetPlayer()->SetPendingCollectionReward(collection); + break; + } + } + } + if(is_temporary) { + LoadCharacterQuestTemporaryRewards(client, quest_id); + } + client->QueueQuestReward(quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description, true, index); + count++; + } + } + + if(count) { + client->SetQuestUpdateState(true); + } +} + + +void WorldDatabase::LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, count FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", client->GetCharacterID(), quest_id); + int8 count = 0; + if(result) + { + while(result && (row = mysql_fetch_row(result))) + { + int32 item_id = atoul(row[0]); + int16 item_count = atoul(row[1]); + client->GetPlayer()->AddQuestTemporaryReward(quest_id, item_id, item_count); + } + } +} + +bool WorldDatabase::InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id){ + Query query1; + Query query2; + Query query3; + Query query4; + Query query5; + + /* Blank record */ + query1.RunQuery2(Q_INSERT, "INSERT INTO `character_details` (`char_id`) VALUES (%u)", character_id); + + /* Using the class id and race id */ + query2.RunQuery2(Q_UPDATE, "UPDATE character_details c, starting_details s SET c.max_hp = s.max_hp, c.hp = s.max_hp, c.max_power = s.max_power, c.power = s.max_power, c.str = s.str, c.sta = s.sta, c.agi = s.agi, c.wis = s.wis, c.intel = s.intel,c.heat = s.heat, c.cold = s.cold, c.magic = s.magic, c.mental = s.mental, c.divine = s.divine, c.disease = s.disease, c.poison = s.poison, c.coin_copper = s.coin_copper, c.coin_silver = s.coin_silver, c.coin_gold = s.coin_gold, c.coin_plat = s.coin_plat, c.status_points = s.status_points WHERE s.race_id = %d AND class_id = %d AND char_id = %u", race_id, class_id, character_id); + if (query2.GetAffectedRows() > 0) + return true; + + /* Using the class id and race id = 255 */ + query3.RunQuery2(Q_UPDATE, "UPDATE character_details c, starting_details s SET c.max_hp = s.max_hp, c.hp = s.max_hp, c.max_power = s.max_power, c.power = s.max_power, c.str = s.str, c.sta = s.sta, c.agi = s.agi, c.wis = s.wis, c.intel = s.intel,c.heat = s.heat, c.cold = s.cold, c.magic = s.magic, c.mental = s.mental, c.divine = s.divine, c.disease = s.disease, c.poison = s.poison, c.coin_copper = s.coin_copper, c.coin_silver = s.coin_silver, c.coin_gold = s.coin_gold, c.coin_plat = s.coin_plat, c.status_points = s.status_points WHERE s.race_id = 255 AND class_id = %d AND char_id = %u", class_id, character_id); + if (query3.GetAffectedRows() > 0) + return true; + + /* Using class id = 255 and the race id */ + query4.RunQuery2(Q_UPDATE, "UPDATE character_details c, starting_details s SET c.max_hp = s.max_hp, c.hp = s.max_hp, c.max_power = s.max_power, c.power = s.max_power, c.str = s.str, c.sta = s.sta, c.agi = s.agi, c.wis = s.wis, c.intel = s.intel,c.heat = s.heat, c.cold = s.cold, c.magic = s.magic, c.mental = s.mental, c.divine = s.divine, c.disease = s.disease, c.poison = s.poison, c.coin_copper = s.coin_copper, c.coin_silver = s.coin_silver, c.coin_gold = s.coin_gold, c.coin_plat = s.coin_plat, c.status_points = s.status_points WHERE s.race_id = %d AND class_id = 255 AND char_id = %u", race_id, character_id); + if (query4.GetAffectedRows() > 0) + return true; + + /* Using class id = 255 and race id = 255 */ + query5.RunQuery2(Q_UPDATE, "UPDATE character_details c, starting_details s SET c.max_hp = s.max_hp, c.hp = s.max_hp, c.max_power = s.max_power, c.power = s.max_power, c.str = s.str, c.sta = s.sta, c.agi = s.agi, c.wis = s.wis, c.intel = s.intel,c.heat = s.heat, c.cold = s.cold, c.magic = s.magic, c.mental = s.mental, c.divine = s.divine, c.disease = s.disease, c.poison = s.poison, c.coin_copper = s.coin_copper, c.coin_silver = s.coin_silver, c.coin_gold = s.coin_gold, c.coin_plat = s.coin_plat, c.status_points = s.status_points WHERE s.race_id = 255 AND class_id = 255 AND char_id = %u", character_id); + if (query5.GetAffectedRows() > 0) + return true; + + return false; +} + +int32 WorldDatabase::GetCharacterTimeStamp(int32 character_id, int32 account_id,bool* char_exists){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT unix_timestamp FROM characters where id=%i and account_id=%i",character_id,account_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + *char_exists = true; + return atoi(row[0]); // Return timestamp + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterTimeStamp query '%s': %s", query.GetQuery(), query.GetError()); + + *char_exists = false; + return 0; +} + +int32 WorldDatabase::GetCharacterTimeStamp(int32 character_id) { + Query query; + MYSQL_ROW row; + MYSQL_RES *result = query.RunQuery2(Q_SELECT, "SELECT unix_timestamp FROM characters WHERE id=%u", character_id); + int32 ret = 0; + + if (result && (row = mysql_fetch_row(result))) + ret = atoul(row[0]); + + return ret; +} + +bool WorldDatabase::UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update){ + Query query; + string update_charts = string("update characters set unix_timestamp=%i where id=%i and account_id=%i"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),timestamp_update,character_id,account_id); + if(!query.GetAffectedRows()) + { + LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateCharacterTimeStamp query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool WorldDatabase::insertCharacterProperty(Client* client, char* propName, char* propValue) { + Query query, query2; + + string update_status = string("update character_properties set propvalue='%s' where charid=%i and propname='%s'"); + query.RunQuery2(Q_UPDATE, update_status.c_str(), propValue, client->GetCharacterID(), propName); + if (!query.GetAffectedRows()) + { + query2.RunQuery2(Q_UPDATE, "insert into character_properties (charid, propname, propvalue) values(%i, '%s', '%s')", client->GetCharacterID(), propName, propValue); + if (query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) { + LogWrite(WORLD__ERROR, 0, "World", "Error in insertCharacterProperty query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + } + return true; +} + +bool WorldDatabase::loadCharacterProperties(Client* client) { + Query query; + MYSQL_ROW row; + int32 id = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT propname, propvalue FROM character_properties where charid = %i", client->GetCharacterID()); + // no character found + if (result == NULL) { + LogWrite(PLAYER__ERROR, 0, "Player", "Error loading character properties for '%s'", client->GetPlayer()->GetName()); + return false; + } + + while (result && (row = mysql_fetch_row(result))) { + char* prop_name = row[0]; + char* prop_value = row[1]; + + if (!prop_name || !prop_value) + continue; + + if (!stricmp(prop_name, CHAR_PROPERTY_SPEED)) + { + float new_speed = atof(prop_value); + client->GetPlayer()->SetSpeed(new_speed, true); + client->GetPlayer()->SetCharSheetChanged(true); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_FLYMODE)) + { + int8 flymode = atoul(prop_value); + if (flymode) // avoid fly mode notification unless enabled + ClientPacketFunctions::SendFlyMode(client, flymode, false); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_INVUL)) + { + int8 invul = atoul(prop_value); + client->GetPlayer()->SetInvulnerable(invul == 1); + if (client->GetPlayer()->GetInvulnerable()) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are now invulnerable!"); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GMVISION)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->SetGMVision(val == 1); + client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false); + if (val) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "GM Vision Enabled!"); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_REGIONDEBUG)) + { + int8 val = atoul(prop_value); + + client->SetRegionDebug(val == 1); + if (val) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Region Debug Enabled!"); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_LUADEBUG)) + { + int8 val = atoul(prop_value); + if (val) + { + client->SetLuaDebugClient(true); + if (lua_interface) + lua_interface->UpdateDebugClients(client); + + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You will now receive LUA error messages."); + } + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTMETHOD)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_loot_method(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOOTITEMRARITY)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_loot_items_rarity(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOSPLIT)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_auto_split(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPDEFAULTYELL)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_default_yell(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPAUTOLOCK)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_autolock(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPSOLOAUTOLOCK)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_solo_autolock(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_AUTOLOOTMETHOD)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_GROUPLOCKMETHOD)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_group_lock_method(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_ASSISTAUTOATTACK)) + { + int8 val = atoul(prop_value); + client->GetPlayer()->GetInfoStruct()->set_assist_auto_attack(val); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_SETACTIVEFOOD)) + { + int32 val = atoul(prop_value); + client->GetPlayer()->SetActiveFoodUniqueID(val, false); + } + else if (!stricmp(prop_name, CHAR_PROPERTY_SETACTIVEDRINK)) + { + int32 val = atoul(prop_value); + client->GetPlayer()->SetActiveDrinkUniqueID(val, false); + } + } + + return true; +} + +//gets the name FROM the db with the right letters in caps +string WorldDatabase::GetPlayerName(char* name){ + Query query; + string ret = ""; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM characters where name='%s'", getSafeEscapeString(name).c_str()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0]) + ret = string(row[0]); + } + return ret; +} + +int32 WorldDatabase::GetCharacterID(const char* name) { + int32 id = 0; + Query query; + if (name) { + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id` FROM `characters` WHERE `name`='%s'", name); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + id = atoul(row[0]); + } + } + return id; +} + +int32 WorldDatabase::GetCharacterCurrentZoneID(int32 character_id) { + int32 id = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `current_zone_id` FROM `characters` WHERE `id`=%u", character_id); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + id = atoul(row[0]); + } + return id; +} + +int32 WorldDatabase::GetCharacterAccountID(int32 character_id) { + int32 id = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `account_id` FROM `characters` WHERE `id`=%u", character_id); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + id = atoul(row[0]); + } + return id; +} + +sint16 WorldDatabase::GetHighestCharacterAdminStatus(int32 account_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(admin_status) FROM characters where account_id=%i ",account_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if ( row[0] != NULL ) + return atoi(row[0]); // Return characters status + else + return 0; + } + + return 0; +} + +sint16 WorldDatabase::GetLowestCharacterAdminStatus(int32 account_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT min(admin_status) FROM characters where account_id=%i ",account_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if ( row[0] != NULL ) + return atoi(row[0]); // Return characters status + else + return 0; + } + + return 0; +} + +sint16 WorldDatabase::GetCharacterAdminStatus(char* character_name){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT admin_status FROM characters where name='%s'", getSafeEscapeString(character_name).c_str()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters level + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterAdminStatus query '%s': %s", query.GetQuery(), query.GetError()); + + return -10; +} + +sint16 WorldDatabase::GetCharacterAdminStatus(int32 account_id , int32 char_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT admin_status FROM characters where account_id=%i and id=%i",account_id,char_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters status + } + else{ + Query query2; + result = query2.RunQuery2(Q_SELECT, "SELECT count(id) FROM characters where account_id=%i and id=%i",account_id,char_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(atoi(row[0]) == 0) //old character, needs to be deleted FROM login server + return -10; + return -8; + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterAdminStatus query '%s': %s", query.GetQuery(), query.GetError()); + } + return PLAY_ERROR_PROBLEM; +} + +bool WorldDatabase::UpdateAdminStatus(char* character_name, sint16 flag){ + Query query; + string update_status = string("update characters set admin_status=%i where name='%s'"); + query.RunQuery2(Q_UPDATE, update_status.c_str(),flag,character_name); + if(!query.GetAffectedRows()) + { + LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateAdminStatus query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +void WorldDatabase::SaveCharacterFloats(int32 char_id, const char* type, float float1, float float2, float float3, float multiplier){ + Query query; + string create_char = string("insert into char_colors (char_id, type, red, green, blue, signed_value) values(%i,'%s',%i,%i,%i, 1)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, (sint8)(float1*multiplier), (sint8)(float2*multiplier), (sint8)(float3*multiplier)); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterFloats query '%s': %s", query.GetQuery(), query.GetError()); + } +} + +void WorldDatabase::SaveCharacterColors(int32 char_id, const char* type, EQ2_Color color){ + Query query; + string create_char = string("insert into char_colors (char_id, type, red, green, blue) values(%i,'%s',%i,%i,%i)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, color.red, color.green, color.blue); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterColors query '%s': %s", query.GetQuery(), query.GetError()); + } +} + +void WorldDatabase::SaveNPCAppearanceEquipment(int32 spawn_id, int8 slot_id, int16 type, int8 red, int8 green, int8 blue, int8 hred, int8 hgreen, int8 hblue){ + Query query; + string appearance = string("INSERT INTO npc_appearance_equip (spawn_id, slot_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue) values (%i, %i, %i, %i, %i, %i, %i, %i, %i) ON DUPLICATE KEY UPDATE equip_type=%i, red=%i, green=%i, blue=%i, highlight_red=%i, highlight_green=%i, highlight_blue=%i"); + query.RunQuery2(Q_INSERT, appearance.c_str(), spawn_id, slot_id, type, red, green, blue, hred, hgreen, hblue, type, red, green, blue, hred, hgreen, hblue); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveNPCAppearanceEquipment query '%s': %s", query.GetQuery(), query.GetError()); + } +} + +int32 WorldDatabase::LoadNPCAppearanceEquipmentData(ZoneServer* zone){ + Query query; + MYSQL_ROW row; + + int32 spawn_id = 0, new_spawn_id = 0, count = 0; + NPC* npc = 0; + int8 slot = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_id, slot_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue FROM npc_appearance_equip ORDER BY spawn_id"); + while(result && (row = mysql_fetch_row(result))){ + new_spawn_id = atoul(row[0]); + if(new_spawn_id != spawn_id){ + npc = zone->GetNPC(new_spawn_id, true); + if(!npc) + continue; + if(spawn_id > 0) + count++; + spawn_id = new_spawn_id; + } + slot = atoul(row[1]); + if(slot < NUM_SLOTS){ + npc->SetEquipment(slot, atoul(row[2]), atoul(row[3]), atoul(row[4]), atoul(row[5]), atoul(row[6]), atoul(row[7]), atoul(row[8])); + } + } + if(query.GetError() && strlen(query.GetError()) > 0) + LogWrite(WORLD__ERROR, 0, "World", "Error in LoadNPCAppearanceEquipmentData query '%s': %s", query.GetQuery(), query.GetError()); + return count; +} + +int16 WorldDatabase::GetAppearanceID(string name){ + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT appearance_id FROM appearances where name='%s'", query.escaped_name); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +vector* WorldDatabase::GetAppearanceIDsLikeName(string name, bool filtered) { + vector* ids = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result; + if (filtered) + result = query.RunQuery2(Q_SELECT, "SELECT `appearance_id` FROM `appearances` WHERE `name` RLIKE '%s' AND `name` NOT RLIKE 'ghost' AND `name` NOT RLIKE 'headless' AND `name` NOT RLIKE 'elemental' AND `name` NOT RLIKE 'test' AND `name` NOT RLIKE 'zombie' AND `name` NOT RLIKE 'vampire'", query.escaped_name); + else + result = query.RunQuery2(Q_SELECT, "SELECT `appearance_id` FROM `appearances` WHERE `name` RLIKE '%s' AND `name` NOT RLIKE 'ghost' AND `name`", query.escaped_name); + while (result && (row = mysql_fetch_row(result))) { + if (!ids) + ids = new vector; + ids->push_back(atoi(row[0])); + } + return ids; +} + +string WorldDatabase::GetAppearanceName(int16 appearance_id) { + Query query; + MYSQL_ROW row; + string name; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `name` FROM `appearances` WHERE `appearance_id`=%u", appearance_id); + if (result && (row = mysql_fetch_row(result))) + name = string(row[0]); + return name; +} + +void WorldDatabase::UpdateRandomize(int32 spawn_id, sint32 value) { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `spawn_npcs` SET `randomize`=`randomize` + %i WHERE `spawn_id`=%u", value, spawn_id); +} + +int32 WorldDatabase::SaveCharacter(PacketStruct* create, int32 loginID){ + Query query; + int8 race_id = create->getType_int8_ByName("race"); + int8 orig_class_id = create->getType_int8_ByName("class");//Normal server + int8 class_id = orig_class_id; + if ( create->GetVersion() <= 546 ) { + class_id = 0; //Classic Server Only + } + + create->PrintPacket(); + + int8 gender_id = create->getType_int8_ByName("gender"); + sint16 auto_admin_status = 0; + + // fetch rules related to setting auto-admin status for server + bool auto_admin_players = rule_manager.GetGlobalRule(R_World, AutoAdminPlayers)->GetBool(); + bool auto_admin_gm = rule_manager.GetGlobalRule(R_World, AutoAdminGMs)->GetBool(); + + /* + The way I think this is supposed to work :) is if any of your chars are already GM, and AutoAdminGMs rule is true, + set the new character's admin_status to your highest status for any character active on your loginID. + - If status > 0, new character > 0 + - If status = 0, new character = 0 + - If status < 0, new character < 0, too... even if auto_admin_gm is true + + If we're not a GM (status = 0) but AutoAdminPlayers rule is true, + set the new character's admin_status to the default set in AutoAdminStatusValue rule. + + Else, if both rules are False, set everyone to 0 like normal. + + */ + + auto_admin_status = GetHighestCharacterAdminStatus(loginID); + + if( auto_admin_status > 0 && auto_admin_gm ) { + LogWrite(WORLD__WARNING, 0, "World", "New character '%s' granted GM status (%i) from accountID: %i", create->getType_EQ2_16BitString_ByName("name").data.c_str(), auto_admin_status, loginID); + } + else if( auto_admin_players ) + { + auto_admin_status = rule_manager.GetGlobalRule(R_World, AutoAdminStatusValue)->GetSInt16(); + LogWrite(WORLD__DEBUG, 0, "World", "New character '%s' granted AutoAdminPlayer status: %i", create->getType_EQ2_16BitString_ByName("name").data.c_str(), auto_admin_status); + } + else { + auto_admin_status = 0; + } + + string create_char = string("Insert into characters (account_id, server_id, name, race, class, gender, deity, body_size, body_age, soga_wing_type, soga_chest_type, soga_legs_type, soga_hair_type, soga_model_type, legs_type, chest_type, wing_type, hair_type, model_type, facial_hair_type, soga_facial_hair_type, created_date, last_saved, admin_status, first_world_login) values(%i, %i, '%s', %i, %i, %i, %i, %f, %f, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, now(), unix_timestamp(), %i, 1)"); + + query.RunQuery2(Q_INSERT, create_char.c_str(), + loginID, + create->getType_int32_ByName("server_id"), + create->getType_EQ2_16BitString_ByName("name").data.c_str(), + race_id, + class_id, + gender_id, + create->getType_int8_ByName("deity"), + create->getType_float_ByName("body_size"), + create->getType_float_ByName("body_age"), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_race_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("race_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("face_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_face_file").data), + auto_admin_status); + + if(query.GetError() && strlen(query.GetError()) > 0) + { + LogWrite(PLAYER__ERROR, 0, "Player", "Error in SaveCharacter query '%s': %s", query.GetQuery(), query.GetError()); + return 0; + } + + int32 last_insert_id = query.GetLastInsertedID(); + int32 char_id = last_insert_id; + UpdateStartingFactions(char_id, create->getType_int8_ByName("starting_zone")); + UpdateStartingZone(char_id, class_id, race_id, create); + UpdateStartingItems(char_id, class_id, race_id); + UpdateStartingSkills(char_id, class_id, race_id); + UpdateStartingSpells(char_id, class_id, race_id); + UpdateStartingSkillbar(char_id, class_id, race_id); + UpdateStartingTitles(char_id, class_id, race_id, gender_id); + InsertCharacterStats(char_id, class_id, race_id); + UpdateStartingLanguage(char_id, race_id, create->getType_int8_ByName("starting_zone")); + + LoadClaimItems(char_id); //insert claim items, from claim_items to the character_ version. + + AddNewPlayerToServerGuild(loginID, char_id); + + if (create->GetVersion() <= 561) { + float classic_multiplier = 250.0f; + SaveCharacterFloats(char_id, "skin_color", create->getType_float_ByName("skin_color", 0), create->getType_float_ByName("skin_color", 1), create->getType_float_ByName("skin_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "eye_color", create->getType_float_ByName("eye_color", 0), create->getType_float_ByName("eye_color", 1), create->getType_float_ByName("eye_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color1", create->getType_float_ByName("hair_color1", 0), create->getType_float_ByName("hair_color1", 1), create->getType_float_ByName("hair_color1", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color2", create->getType_float_ByName("hair_color2", 0), create->getType_float_ByName("hair_color2", 1), create->getType_float_ByName("hair_color2", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_highlight", create->getType_float_ByName("hair_highlight", 0), create->getType_float_ByName("hair_highlight", 1), create->getType_float_ByName("hair_highlight", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_color", create->getType_float_ByName("hair_face_color", 0), create->getType_float_ByName("hair_face_color", 1), create->getType_float_ByName("hair_face_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_highlight_color", create->getType_float_ByName("hair_face_highlight_color", 0), create->getType_float_ByName("hair_face_highlight_color", 1), create->getType_float_ByName("hair_face_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "shirt_color", create->getType_float_ByName("shirt_color", 0), create->getType_float_ByName("shirt_color", 1), create->getType_float_ByName("shirt_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_chest_color", create->getType_float_ByName("unknown_chest_color", 0), create->getType_float_ByName("unknown_chest_color", 1), create->getType_float_ByName("unknown_chest_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "pants_color", create->getType_float_ByName("pants_color", 0), create->getType_float_ByName("pants_color", 1), create->getType_float_ByName("pants_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_legs_color", create->getType_float_ByName("unknown_legs_color", 0), create->getType_float_ByName("unknown_legs_color", 1), create->getType_float_ByName("unknown_legs_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown9", create->getType_float_ByName("unknown9", 0), create->getType_float_ByName("unknown9", 1), create->getType_float_ByName("unknown9", 2), classic_multiplier); + } + else { + SaveCharacterColors(char_id, "skin_color", create->getType_EQ2_Color_ByName("skin_color")); + SaveCharacterColors(char_id, "model_color", create->getType_EQ2_Color_ByName("model_color")); + SaveCharacterColors(char_id, "eye_color", create->getType_EQ2_Color_ByName("eye_color")); + SaveCharacterColors(char_id, "hair_color1", create->getType_EQ2_Color_ByName("hair_color1")); + SaveCharacterColors(char_id, "hair_color2", create->getType_EQ2_Color_ByName("hair_color2")); + SaveCharacterColors(char_id, "hair_highlight", create->getType_EQ2_Color_ByName("hair_highlight")); + SaveCharacterColors(char_id, "hair_type_color", create->getType_EQ2_Color_ByName("hair_type_color")); + SaveCharacterColors(char_id, "hair_type_highlight_color", create->getType_EQ2_Color_ByName("hair_type_highlight_color")); + SaveCharacterColors(char_id, "hair_face_color", create->getType_EQ2_Color_ByName("hair_face_color")); + SaveCharacterColors(char_id, "hair_face_highlight_color", create->getType_EQ2_Color_ByName("hair_face_highlight_color")); + SaveCharacterColors(char_id, "wing_color1", create->getType_EQ2_Color_ByName("wing_color1")); + SaveCharacterColors(char_id, "wing_color2", create->getType_EQ2_Color_ByName("wing_color2")); + SaveCharacterColors(char_id, "shirt_color", create->getType_EQ2_Color_ByName("shirt_color")); + SaveCharacterColors(char_id, "unknown_chest_color", create->getType_EQ2_Color_ByName("unknown_chest_color")); + SaveCharacterColors(char_id, "pants_color", create->getType_EQ2_Color_ByName("pants_color")); + SaveCharacterColors(char_id, "unknown_legs_color", create->getType_EQ2_Color_ByName("unknown_legs_color")); + SaveCharacterColors(char_id, "unknown9", create->getType_EQ2_Color_ByName("unknown9")); + + SaveCharacterColors(char_id, "soga_skin_color", create->getType_EQ2_Color_ByName("soga_skin_color")); + SaveCharacterColors(char_id, "soga_model_color", create->getType_EQ2_Color_ByName("soga_model_color")); + SaveCharacterColors(char_id, "soga_eye_color", create->getType_EQ2_Color_ByName("soga_eye_color")); + SaveCharacterColors(char_id, "soga_hair_color1", create->getType_EQ2_Color_ByName("soga_hair_color1")); + SaveCharacterColors(char_id, "soga_hair_color2", create->getType_EQ2_Color_ByName("soga_hair_color2")); + SaveCharacterColors(char_id, "soga_hair_highlight", create->getType_EQ2_Color_ByName("soga_hair_highlight")); + SaveCharacterColors(char_id, "soga_hair_type_color", create->getType_EQ2_Color_ByName("soga_hair_type_color")); + SaveCharacterColors(char_id, "soga_hair_type_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_type_highlight_color")); + SaveCharacterColors(char_id, "soga_hair_face_color", create->getType_EQ2_Color_ByName("soga_hair_face_color")); + SaveCharacterColors(char_id, "soga_hair_face_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_face_highlight_color")); + SaveCharacterColors(char_id, "soga_wing_color1", create->getType_EQ2_Color_ByName("soga_wing_color1")); + SaveCharacterColors(char_id, "soga_wing_color2", create->getType_EQ2_Color_ByName("soga_wing_color2")); + SaveCharacterColors(char_id, "soga_shirt_color", create->getType_EQ2_Color_ByName("soga_shirt_color")); + SaveCharacterColors(char_id, "soga_unknown_chest_color", create->getType_EQ2_Color_ByName("soga_unknown_chest_color")); + SaveCharacterColors(char_id, "soga_pants_color", create->getType_EQ2_Color_ByName("soga_pants_color")); + SaveCharacterColors(char_id, "soga_unknown_legs_color", create->getType_EQ2_Color_ByName("soga_unknown_legs_color")); + SaveCharacterColors(char_id, "soga_unknown13", create->getType_EQ2_Color_ByName("soga_unknown13")); + SaveCharacterFloats(char_id, "soga_eye_type", create->getType_float_ByName("soga_eyes2", 0), create->getType_float_ByName("soga_eyes2", 1), create->getType_float_ByName("soga_eyes2", 2)); + SaveCharacterFloats(char_id, "soga_ear_type", create->getType_float_ByName("soga_ears", 0), create->getType_float_ByName("soga_ears", 1), create->getType_float_ByName("soga_ears", 2)); + SaveCharacterFloats(char_id, "soga_eye_brow_type", create->getType_float_ByName("soga_eye_brows", 0), create->getType_float_ByName("soga_eye_brows", 1), create->getType_float_ByName("soga_eye_brows", 2)); + SaveCharacterFloats(char_id, "soga_cheek_type", create->getType_float_ByName("soga_cheeks", 0), create->getType_float_ByName("soga_cheeks", 1), create->getType_float_ByName("soga_cheeks", 2)); + SaveCharacterFloats(char_id, "soga_lip_type", create->getType_float_ByName("soga_lips", 0), create->getType_float_ByName("soga_lips", 1), create->getType_float_ByName("soga_lips", 2)); + SaveCharacterFloats(char_id, "soga_chin_type", create->getType_float_ByName("soga_chin", 0), create->getType_float_ByName("soga_chin", 1), create->getType_float_ByName("soga_chin", 2)); + SaveCharacterFloats(char_id, "soga_nose_type", create->getType_float_ByName("soga_nose", 0), create->getType_float_ByName("soga_nose", 1), create->getType_float_ByName("soga_nose", 2)); + } + SaveCharacterFloats(char_id, "eye_type", create->getType_float_ByName("eyes2", 0), create->getType_float_ByName("eyes2", 1), create->getType_float_ByName("eyes2", 2)); + SaveCharacterFloats(char_id, "ear_type", create->getType_float_ByName("ears", 0), create->getType_float_ByName("ears", 1), create->getType_float_ByName("ears", 2)); + SaveCharacterFloats(char_id, "eye_brow_type", create->getType_float_ByName("eye_brows", 0), create->getType_float_ByName("eye_brows", 1), create->getType_float_ByName("eye_brows", 2)); + SaveCharacterFloats(char_id, "cheek_type", create->getType_float_ByName("cheeks", 0), create->getType_float_ByName("cheeks", 1), create->getType_float_ByName("cheeks", 2)); + SaveCharacterFloats(char_id, "lip_type", create->getType_float_ByName("lips", 0), create->getType_float_ByName("lips", 1), create->getType_float_ByName("lips", 2)); + SaveCharacterFloats(char_id, "chin_type", create->getType_float_ByName("chin", 0), create->getType_float_ByName("chin", 1), create->getType_float_ByName("chin", 2)); + SaveCharacterFloats(char_id, "nose_type", create->getType_float_ByName("nose", 0), create->getType_float_ByName("nose", 1), create->getType_float_ByName("nose", 2)); + SaveCharacterFloats(char_id, "body_size", create->getType_float_ByName("body_size", 0), 0, 0); + return char_id; +} + +int8 WorldDatabase::CheckNameFilter(const char* name, int8 min_length, int8 max_length) { + // the minimum 4 is enforced by the client too + if(!name || strlen(name) < min_length || strlen(name) > max_length) // Even 20 char length is long... + return BADNAMELENGTH_REPLY; + uchar* checkname = (uchar*)name; + for (int32 i = 0; i < strlen(name); i++) + { + if(!alpha_check(checkname[i])) + return NAMEINVALID_REPLY; + } + Query query; + LogWrite(WORLD__DEBUG, 0, "World", "Name check on: %s", name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT count(*) FROM characters WHERE name='%s'",name); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0] != 0 && atoi(row[0]) > 0) + return NAMETAKEN_REPLY; + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in CheckNameFilter (name exist check) (Name query '%s': %s", query.GetQuery(), query.GetError()); + + Query query3; + LogWrite(WORLD__DEBUG, 0, "World", "Name check on: %s (Bots table)", name); + MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT count(*) FROM bots WHERE name='%s'", name); + if (result3 && mysql_num_rows(result3) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result3); + if (row[0] != 0 && atoi(row[0]) > 0) + return NAMETAKEN_REPLY; + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in CheckNameFilter (name exist check, bot table) (Name query '%s': %s", query3.GetQuery(), query3.GetError()); + + + + Query query2; + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT count(*) FROM name_filter WHERE '%s' like name",name); + if(result2 && mysql_num_rows(result2) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result2); + if(row[0] != 0 && atoi(row[0]) > 0) + return NAMEFILTER_REPLY; + else if(row[0] != 0 && atoi(row[0]) == 0) + return CREATESUCCESS_REPLY; + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in CheckNameFilter (name_filter check) query '%s': %s", query.GetQuery(), query.GetError()); + + return UNKNOWNERROR_REPLY; +} + +char* WorldDatabase::GetCharacterName(int32 character_id){ + + LogWrite(WORLD__TRACE, 9, "World", "Enter: %s", __FUNCTION__); + + Query query; + char* name = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0] && strlen(row[0]) > 0) + { + name = new char[strlen(row[0])+1]; + memset(name,0, strlen(row[0])+1); + strcpy(name, row[0]); + } + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterName query '%s': %s", query.GetQuery(), query.GetError()); + + LogWrite(WORLD__TRACE, 9, "World", "Exit: %s", __FUNCTION__); + return name; +} + +int8 WorldDatabase::GetCharacterLevel(int32 character_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT level FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters level + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterLevel query '%s': %s", query.GetQuery(), query.GetError()); + + return 0; +} + +int16 WorldDatabase::GetCharacterModelType(int32 character_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT model_type FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters race + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterModelType query '%s': %s", query.GetQuery(), query.GetError()); + + return 0; +} + +int8 WorldDatabase::GetCharacterClass(int32 character_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT class FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters class + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterClass query '%s': %s", query.GetQuery(), query.GetError()); + + return 0; +} + +int8 WorldDatabase::GetCharacterGender(int32 character_id){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT gender FROM characters where id=%u",character_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return characters gender + } + else + LogWrite(WORLD__ERROR, 0, "World", "Error in GetCharacterGender query '%s': %s", query.GetQuery(), query.GetError()); + + return 0; +} + +void WorldDatabase::DeleteCharacterQuest(int32 quest_id, int32 char_id, bool repeated_quest) { + if (repeated_quest) { + if (!database_new.Query("UPDATE `character_quests` SET `given_date` = `completed_date` WHERE `char_id` = %u AND `quest_id` = %u", char_id, quest_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "Error (%u) in DeleteCharacterQuest query:\n%s", database_new.GetError(), database_new.GetErrorMsg()); + } + else { + if (!database_new.Query("DELETE FROM `character_quests` WHERE `char_id` = %u AND `quest_id` = %u", char_id, quest_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "Error (%u) in DeleteCharacterQuest query:\n%s", database_new.GetError(), database_new.GetErrorMsg()); + } + + if (!database_new.Query("DELETE FROM `character_quest_progress` WHERE `char_id` = %u AND `quest_id` = %u", char_id, quest_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "Error (%u) in DeleteCharacterQuest query:\n%s", database_new.GetError(), database_new.GetErrorMsg()); +} + +void WorldDatabase::SaveCharacterSkills(Client* client){ + vector* skills = client->GetPlayer()->GetSkills()->GetSaveNeededSkills(); + if(skills){ + Query query; + if(skills->size() > 0){ + Skill* skill = 0; + for(int32 i=0;isize();i++){ + skill = skills->at(i); + query.AddQueryAsync(client->GetCharacterID(),this,Q_REPLACE, "replace into character_skills (char_id, skill_id, current_val, max_val) values(%u, %u, %i, %i)", client->GetCharacterID(), skill->skill_id, skill->current_val, skill->max_val); + } + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterSkills query '%s': %s", query.GetQuery(), query.GetError()); + } + safe_delete(skills); + } +} + +void WorldDatabase::SaveCharacterQuests(Client* client){ + Query query; + map::iterator itr; + master_quest_list.LockQuests(); //prevent reloading until we are done + client->GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); //prevent all quest modifications until we are done + map* quests = client->GetPlayer()->GetPlayerQuests(); + for(itr = quests->begin(); itr != quests->end(); itr++){ + if(client->GetCurrentQuestID() == itr->first){ + query.AddQueryAsync(client->GetCharacterID(),this,Q_UPDATE, "update character_quests set current_quest = 0 where char_id = %u", client->GetCharacterID()); + query.AddQueryAsync(client->GetCharacterID(), this,Q_UPDATE, "update character_quests set current_quest = 1 where char_id = %u and quest_id = %u", client->GetCharacterID(), itr->first); + } + if(itr->second && itr->second->GetSaveNeeded()){ + query.AddQueryAsync(client->GetCharacterID(), this,Q_INSERT, "insert ignore into character_quests (char_id, quest_id, given_date, quest_giver) values(%u, %u, now(), %u)", client->GetCharacterID(), itr->first, itr->second->GetQuestGiver()); + query.AddQueryAsync(client->GetCharacterID(), this,Q_UPDATE, "update character_quests set tracked = %i, quest_flags = %u, hidden = %i, complete_count = %u where char_id = %u and quest_id = %u", itr->second->IsTracked() ? 1 : 0, itr->second->GetQuestFlags(), itr->second->IsHidden() ? 1 : 0, itr->second->GetCompleteCount(), client->GetCharacterID(), itr->first); + SaveCharacterQuestProgress(client, itr->second); + itr->second->SetSaveNeeded(false); + } + } + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterQuests query '%s': %s", query.GetQuery(), query.GetError()); + quests = client->GetPlayer()->GetCompletedPlayerQuests(); + for(itr = quests->begin(); itr != quests->end(); itr++){ + if(itr->second && itr->second->GetSaveNeeded()){ + query.AddQueryAsync(client->GetCharacterID(), this,Q_DELETE, "delete FROM character_quest_progress where char_id = %u and quest_id = %u", client->GetCharacterID(), itr->first); + + /* incase the quest is completed before the quest could be inserted in the PlayerQuests loop, we first try to insert it. If it already exists then we can just update + * the completed_date */ + query.AddQueryAsync(client->GetCharacterID(), this,Q_INSERT, "INSERT INTO character_quests (char_id, quest_id, quest_giver, current_quest, given_date, completed_date, complete_count) values (%u,%u,%u,0, now(),now(), %u) ON DUPLICATE KEY UPDATE completed_date = now(), complete_count = %u, current_quest = 0", client->GetCharacterID(), itr->first, itr->second->GetQuestGiver(), itr->second->GetCompleteCount(), itr->second->GetCompleteCount()); + itr->second->SetSaveNeeded(false); + } + } + client->GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + master_quest_list.UnlockQuests(); + +} + +void WorldDatabase::SaveCharRepeatableQuest(Client* client, int32 quest_id, int16 quest_complete_count) { + if (!database_new.Query("UPDATE `character_quests` SET `given_date` = now(), complete_count = %u WHERE `char_id` = %u AND `quest_id` = %u", quest_complete_count, client->GetCharacterID(), quest_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "DB Error %u\n%s", database_new.GetError(), database_new.GetErrorMsg()); +} + +void WorldDatabase::SaveCharacterQuestProgress(Client* client, Quest* quest){ + vector* steps = quest->GetQuestSteps(); + vector::iterator itr; + QuestStep* step = 0; + quest->MQuestSteps.readlock(__FUNCTION__, __LINE__); + if(steps){ + for(itr = steps->begin(); itr != steps->end(); itr++){ + step = *itr; + if(step && step->GetQuestCurrentQuantity() > 0) { + Query query; + query.RunQuery2(Q_REPLACE, "replace into character_quest_progress (char_id, quest_id, step_id, progress) values(%u, %u, %u, %i)", client->GetCharacterID(), quest->GetQuestID(), step->GetStepID(), step->GetQuestCurrentQuantity()); + + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterQuestProgress query '%s': %s", query.GetQuery(), query.GetError()); + } + } + } + quest->MQuestSteps.releasereadlock(__FUNCTION__, __LINE__); +} + +void WorldDatabase::LoadCharacterQuestProgress(Client* client){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT character_quest_progress.quest_id, step_id, progress FROM character_quest_progress, character_quests where character_quest_progress.char_id=%u and character_quest_progress.quest_id = character_quests.quest_id and character_quest_progress.char_id = character_quests.char_id ORDER BY character_quest_progress.quest_id",client->GetCharacterID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + Quest* quest = 0; + int32 quest_id = 0; + map* progress_map = new map(); + while(result && (row = mysql_fetch_row(result))){ + if(quest_id != atoul(row[0])){ + if(quest_id > 0){ + quest = client->GetPlayer()->GetQuest(quest_id); + if(quest) + client->SetPlayerQuest(quest, progress_map); + } + quest_id = atoul(row[0]); + progress_map->clear(); + } + (*progress_map)[atoul(row[1])] = atoul(row[2]); + } + if(progress_map->size() > 0){ + quest = client->GetPlayer()->GetQuest(quest_id); + if(quest) + client->SetPlayerQuest(quest, progress_map); + } + safe_delete(progress_map); + } + else if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(WORLD__ERROR, 0, "World", "Error in LoadCharacterQuestProgress query '%s': %s", query.GetQuery(), query.GetError()); +} + +void WorldDatabase::LoadCharacterQuests(Client* client){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading Character Quests..."); + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT quest_id, DAY(given_date), MONTH(given_date), YEAR(given_date), DAY(completed_date), MONTH(completed_date), YEAR(completed_date), quest_giver, tracked, quest_flags, hidden, UNIX_TIMESTAMP(given_date), UNIX_TIMESTAMP(completed_date), complete_count FROM character_quests WHERE char_id=%u ORDER BY current_quest", client->GetCharacterID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + Quest* quest = 0; + while(result && (row = mysql_fetch_row(result))){ + quest = master_quest_list.GetQuest(atoul(row[0])); + if(quest) { + + LogWrite(PLAYER__DEBUG, 5, "Player", "\tLoading quest_id: %u", atoul(row[0])); + bool addQuest = true; + + if(row[4] && atoi(row[4]) > 0){ + quest->SetTurnedIn(true); + if(row[4]) + quest->SetDay(atoi(row[4])); + if(row[5]) + quest->SetMonth(atoi(row[5])); + if(row[6] && atoi(row[6]) > 2000) + quest->SetYear(atoi(row[6]) - 2000); + client->GetPlayer()->AddCompletedQuest(quest); + + // Added timestamps to quickly compare given and completed dates + int32 given_timestamp = atoul(row[11]); + int32 completed_timestamp = atoul(row[12]); + + // If given timestamp is greater then completed then this is a repeatable quest we are working on + // so get a fresh quest object to add as an active quest + if (given_timestamp > completed_timestamp) + { + lua_interface->SetLuaUserDataStale(quest); + safe_delete(quest); + quest = master_quest_list.GetQuest(atoul(row[0])); + } + else + addQuest = false; + + quest->SetCompleteCount(atoi(row[13])); + } + + if (addQuest) { + if(row[1]) + quest->SetDay(atoi(row[1])); + if(row[2]) + quest->SetMonth(atoi(row[2])); + if(row[3] && atoi(row[3]) > 2000) + quest->SetYear(atoi(row[3]) - 2000); + quest->SetQuestGiver(atoul(row[7])); + quest->SetTracked(atoi(row[8]) == 1 ? true : false); + quest->SetQuestFlags(atoul(row[9])); + quest->SetHidden(atoi(row[10]) == 1 ? true : false); + client->AddPlayerQuest(quest, false, false); + } + quest->SetSaveNeeded(false); + + // Changed this to call reload with step = 0 for all quests and not + // just quests with quest flags to allow customized set up if needed + if (lua_interface) + lua_interface->CallQuestFunction(quest, "Reload", client->GetPlayer(), 0); + } + } + LoadCharacterQuestProgress(client); + } +} + +void WorldDatabase::LoadCharacterFriendsIgnoreList(Player* player) { + if (player) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `name`, `type` FROM `character_social` WHERE `char_id`=%u", player->GetCharacterID()); + while (result && (row = mysql_fetch_row(result))) { + if (strncmp(row[1], "FRIEND", 6) == 0) + player->AddFriend(row[0], false); + else + player->AddIgnore(row[0], false); + } + } +} + +void WorldDatabase::LoadZoneInfo(ZoneServer* zone){ + Query query; + int32 ruleset_id; + char* escaped = getEscapeString(zone->GetZoneName()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, file, description, underworld, safe_x, safe_y, safe_z, min_status, min_level, max_level, instance_type+0, shutdown_timer, zone_motd, default_reenter_time, default_reset_time, default_lockout_time, force_group_to_zone, safe_heading, xp_modifier, ruleset_id, expansion_id, weather_allowed, sky_file, can_bind, can_gate, city_zone, can_evac FROM zones where name='%s'",escaped); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + zone->SetZoneName(escaped); + zone->SetZoneID(strtoul(row[0], NULL, 0)); + zone->SetZoneFile(row[1]); + zone->SetZoneDescription(row[2]); + zone->SetUnderWorld(atof(row[3])); + zone->SetSafeX(atof(row[4])); + zone->SetSafeY(atof(row[5])); + zone->SetSafeZ(atof(row[6])); + zone->SetMinimumStatus(atoi(row[7])); + zone->SetMinimumLevel(atoi(row[8])); + zone->SetMaximumLevel(atoi(row[9])); + int8 type = (atoi(row[10]) == 0) ? 0 : atoi(row[10]) - 1; + zone->SetInstanceType(type); + zone->SetShutdownTimer(atoul(row[11])); + + char* zone_motd = row[12]; + if (zone_motd && strlen(zone_motd) > 0) + zone->SetZoneMOTD(string(zone_motd)); + + zone->SetDefaultReenterTime(atoi(row[13])); + zone->SetDefaultResetTime(atoi(row[14])); + zone->SetDefaultLockoutTime(atoi(row[15])); + zone->SetForceGroupZoneOption(atoi(row[16])); + zone->SetSafeHeading(atof(row[17])); + zone->SetXPModifier(atof(row[18])); + + if ((ruleset_id = atoul(row[19])) > 0 && !rule_manager.SetZoneRuleSet(zone->GetZoneID(), ruleset_id)) + LogWrite(ZONE__ERROR, 0, "Zones", "Error setting rule set for zone '%s' (%u). A rule set with ID %u does not exist.", zone->GetZoneName(), zone->GetZoneID(), ruleset_id); + + // check data_version to see if client has proper expansion to enter a zone + zone->SetMinimumVersion(GetMinimumClientVersion(atoi(row[20]))); + zone->SetWeatherAllowed(atoi(row[21]) == 0 ? false : true); + + zone->SetZoneSkyFile(row[22]); + + if (zone->IsInstanceZone()) + { + if ( zone->GetInstanceID() < 1 ) + zone->SetupInstance(CreateNewInstance(zone->GetZoneID())); + else + zone->SetupInstance(zone->GetInstanceID()); + } + zone->SetCanBind(atoul(row[23])); + zone->SetCanGate(atoul(row[24])); + zone->SetCityZone(atoi(row[25])); + zone->SetCanEvac(atoul(row[26])); + } + safe_delete_array(escaped); +} + +void WorldDatabase::LoadZoneInfo(ZoneInfo* zone_info) { + Query query; + int32 ruleset_id; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, file, description, underworld, safe_x, safe_y, safe_z, min_status, min_level, max_level, instance_type, shutdown_timer, zone_motd, default_reenter_time, default_reset_time, default_lockout_time, force_group_to_zone, lua_script, xp_modifier, ruleset_id, expansion_id, always_loaded, city_zone, start_zone, zone_type, weather_allowed, sky_file FROM zones WHERE id = %u", zone_info->id); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + strncpy(zone_info->name, row[0], sizeof(zone_info->name)); + strncpy(zone_info->file, row[1], sizeof(zone_info->file)); + strncpy(zone_info->description, row[2], sizeof(zone_info->description)); + zone_info->underworld = atof(row[3]); + zone_info->safe_x = atof(row[4]); + zone_info->safe_y = atof(row[5]); + zone_info->safe_z = atof(row[6]); + zone_info->min_status = atoi(row[7]); + zone_info->min_level = atoi(row[8]); + zone_info->max_level = atoi(row[9]); + zone_info->instance_type = (atoi(row[10]) == 0) ? 0 : atoi(row[10]) - 1; + zone_info->shutdown_timer = atoul(row[11]); + row[12] == NULL ? strncpy(zone_info->zone_motd, "", sizeof(zone_info->zone_motd)) : strncpy(zone_info->zone_motd, row[12], sizeof(zone_info->zone_motd)); + zone_info->default_reenter_time = atoi(row[13]); + zone_info->default_reset_time = atoi(row[14]); + zone_info->default_lockout_time = atoi(row[15]); + zone_info->force_group_to_zone = atoi(row[16]); + row[17] == NULL ? strncpy(zone_info->lua_script, "", sizeof(zone_info->lua_script)) : strncpy(zone_info->lua_script, row[17], sizeof(zone_info->lua_script)); + zone_info->xp_modifier = atof(row[18]); + zone_info->ruleset_id = atoul(row[19]); + if ((ruleset_id = atoul(row[19])) > 0 && !rule_manager.SetZoneRuleSet(zone_info->id, ruleset_id)) + LogWrite(ZONE__ERROR, 0, "Zones", "Error setting rule set for zone '%s' (%u). A rule set with ID %u does not exist.", zone_info->name, zone_info->id, ruleset_id); + + zone_info->expansion_id = atoi(row[20]); + zone_info->min_version = GetMinimumClientVersion(zone_info->expansion_id); + zone_info->always_loaded = atoi(row[21]); + zone_info->city_zone = atoi(row[22]); + zone_info->start_zone = atoi(row[23]); + row[24] == NULL ? strncpy(zone_info->zone_type, "", sizeof(zone_info->zone_type)) : strncpy(zone_info->zone_type, row[24], sizeof(zone_info->zone_type)); + zone_info->weather_allowed = atoi(row[25]); + + strncpy(zone_info->sky_file, row[26], sizeof(zone_info->sky_file)); + } +} + +void WorldDatabase::SaveZoneInfo(int32 zone_id, const char* field, sint32 value) { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `zones` SET `%s`=%i WHERE `id`=%u", field, value, zone_id); +} + +void WorldDatabase::SaveZoneInfo(int32 zone_id, const char* field, float value) { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `zones` SET `%s`=%f WHERE `id`=%u", field, value, zone_id); +} + +void WorldDatabase::SaveZoneInfo(int32 zone_id, const char* field, const char* value) { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE `zones` SET `%s`='%s' WHERE `id`=%u", field, const_cast(getEscapeString(value)), zone_id); +} + +int32 WorldDatabase::GetZoneID(const char* name) { + int32 zone_id = 0; + Query query; + char* escaped = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id` FROM zones WHERE `name`=\"%s\"", escaped); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + zone_id = atoi(row[0]); + } + safe_delete_array(escaped); + return zone_id; +} + +bool WorldDatabase::GetZoneRequirements(const char* zoneName, sint16* minStatus, int16* minLevel, int16* maxLevel, int16* minVersion) { + Query query; + char* escaped = getEscapeString(zoneName); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT min_status, min_level, max_level, expansion_id FROM zones where name='%s'",escaped); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + sint16 status = (sint16)atoi(row[0]); + int16 levelMin = (int16)atoi(row[1]); + int16 levelMax = (int16)atoi(row[2]); + int8 expansion_id = (int8)atoi(row[3]); + + *minStatus = status; + *minLevel = levelMin; + *maxLevel = levelMax; + + if( expansion_id >= 40 ) // lowest client we support is RoK - exp04 + *minVersion = GetMinimumClientVersion(expansion_id); + else + *minVersion = 0; + + safe_delete_array(escaped); + return true; + } + safe_delete_array(escaped); + + return false; +} + +int16 WorldDatabase::GetMinimumClientVersion(int8 expansion_id) +{ + /* + 1 n/a Classic Expansion + 2 adv01 Bloodline Chronicles + 3 adv02 Splitpaw Saga + 10 exp01 Desert of Flames + 20 exp02 Kingdom of Sky + 21 adv04 Fallen Dynasty + 30 exp03 Echoes of Faydwer + 40 exp04 Rise of Kunark + 50 exp05 The Shadow Odyssey + 60 exp06 Sentinel's Fate + 61 halas Halas Reborn + 70 exp07 Destiny of Velious + 80 exp08 Age of Discovery + */ + + int16 minVer = 0; + + // TODO: eventually replace this with reading values from eq2expansions table + switch(expansion_id) + { + case 40: // ROK + { + minVer = 843; + break; + } + case 50: // TSO + { + minVer = 908; + break; + } + case 60: // SF + { + minVer = 1008; + break; + } + case 61: // Halas + { + minVer = 1045; + break; + } + case 70: // DoV + { + minVer = 1096; + break; + } + case 80: // AoD + { + minVer = 1144; + break; + } + case 90: // CoE + { + minVer = 1188; + break; + } + } + + return minVer; +} + +// returns Expansion Name depending on the connected client's data version +string WorldDatabase::GetExpansionIDByVersion(int16 version) +{ + /* + 0 n/a Classic Expansion + 0 adv01 Bloodline Chronicles + 0 adv02 Splitpaw Saga + 0 exp01 Desert of Flames + 0 exp02 Kingdom of Sky + 0 adv04 Fallen Dynasty + 0 exp03 Echoes of Faydwer + 843 exp04 Rise of Kunark + 908 exp05 The Shadow Odyssey + 1008 exp06 Sentinel's Fate + 1045 halas Halas Reborn + 1096 exp07 Destiny of Velious + 1142 exp08 Age of Discovery + 1188 exp09 Chains of Eternity + 9999 (and beyond) + */ + string ret = ""; + + if( version >= 9999 ) + ret = "Unknown"; + else if( version >= 1188 ) + ret = "Chains of Eternity"; + else if( version >= 1142 ) + ret = "Age of Discovery"; + else if( version >= 1096 ) + ret = "Destiny of Velious"; + else if( version >= 1045 ) + ret = "Halas Reborn"; + else if( version >= 1008 ) + ret = "Sentinel's Fate"; + else if( version >= 908 ) + ret = "The Shadow Odyssey"; + else if( version >= 843 ) + ret = "Rise of Kunark"; + else + ret = "Any"; + + return ret; +} + + +void WorldDatabase::LoadSpecialZones(){ + Query query; + ZoneServer* zone = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, always_loaded FROM zones where always_loaded = 1"); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + zone = new ZoneServer(row[1]); + LoadZoneInfo(zone); + zone->Init(); + + zone->SetAlwaysLoaded(atoi(row[2]) == 1); + } + } +} + +bool WorldDatabase::SpawnGroupRemoveAssociation(int32 group1, int32 group2){ + Query query; + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_group_associations where (group_id1 = %u and group_id2 = %u) or (group_id1 = %u and group_id2 = %u)", group1, group2, group2, group1); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupRemoveSpawn query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +bool WorldDatabase::SpawnGroupAddAssociation(int32 group1, int32 group2){ + Query query; + query.RunQuery2(Q_INSERT, "insert ignore into spawn_location_group_associations (group_id1, group_id2) values(%u, %u)", group1, group2); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupAddAssociation query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +bool WorldDatabase::SpawnGroupAddSpawn(Spawn* spawn, int32 group_id){ + Query query; + query.RunQuery2(Q_INSERT, "insert ignore into spawn_location_group (group_id, placement_id) values(%u, %u)", group_id, spawn->GetSpawnLocationPlacementID()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupAddSpawn query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +bool WorldDatabase::SpawnGroupRemoveSpawn(Spawn* spawn, int32 group_id){ + Query query; + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_group where group_id = %u and placement_id = %u", group_id, spawn->GetSpawnLocationPlacementID()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupRemoveSpawn query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT count(group_id) FROM spawn_location_group where group_id = %u", group_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + if((row = mysql_fetch_row(result))){ + if(atoul(row[0]) == 0) + DeleteSpawnGroup(group_id); + } + } + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SpawnGroupRemoveSpawn query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::CreateSpawnGroup(Spawn* spawn, string name) +{ + int32 group_id = 0; + Query query; + + // JA: As of 0.7.1, DB Milestone 2, Content Team needs to use group_id's from Raw Data, so start any manual group_id's > 100,000 + query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_group (group_id, placement_id, name) SELECT IF(ISNULL(MAX(group_id))=1, 100000, MAX(group_id)+1), %u, '%s' FROM spawn_location_group", spawn->GetSpawnLocationPlacementID(), getSafeEscapeString(name.c_str()).c_str()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + return 0; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(group_id) FROM spawn_location_group"); + if(result && mysql_num_rows(result) > 0) + { + MYSQL_ROW row; + + if((row = mysql_fetch_row(result))) + { + if(row[0]) + group_id = atoul(row[0]); + } + } + + return group_id; +} + +void WorldDatabase::DeleteSpawnGroup(int32 id){ + Query query; + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_group where group_id = %u", id); +} + +bool WorldDatabase::SetGroupSpawnChance(int32 id, float chance){ + Query query; + query.RunQuery2(Q_UPDATE, "replace into spawn_location_group_chances (group_id, percentage) values(%u, %f)", id, chance); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in SetGroupSpawnChance query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::LoadSpawnGroupChances(ZoneServer* zone){ + Query query; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT slgc.group_id, slgc.percentage FROM spawn_location_group_chances slgc, spawn_location_group slg, spawn_location_placement slp where slgc.group_id = slg.group_id and slg.placement_id = slp.id and slp.zone_id = %u", zone->GetZoneID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + int32 group_id = 0; + float percent = 0; + while(result && (row = mysql_fetch_row(result))){ + group_id = atoul(row[0]); + percent = atof(row[1]); + zone->AddSpawnGroupChance(group_id, percent); + count++; + } + } + return count; +} + +int32 WorldDatabase::LoadSpawnLocationGroupAssociations(ZoneServer* zone){ + Query query; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT distinct slga.group_id1, slga.group_id2 FROM spawn_location_group_associations slga, spawn_location_group slg, spawn_location_placement slp where (slg.group_id = slga.group_id1 or slg.group_id = slga.group_id2) and slg.placement_id = slp.id and slp.zone_id = %u", zone->GetZoneID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + zone->AddSpawnGroupAssociation(atoul(row[0]), atoul(row[1])); + count++; + } + } + return count; +} + +int32 WorldDatabase::LoadSpawnLocationGroups(ZoneServer* zone){ + Query query; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT slg.group_id, slg.placement_id, slp.spawn_location_id FROM spawn_location_group slg, spawn_location_placement slp WHERE slg.placement_id = slp.id and slp.zone_id = %u", zone->GetZoneID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + int32 placement_id = 0; + int32 group_id = 0; + int32 spawn_location_id = 0; + while(result && (row = mysql_fetch_row(result))){ + group_id = atoul(row[0]); + placement_id = atoul(row[1]); + spawn_location_id = atoul(row[2]); + zone->AddSpawnGroupLocation(group_id, placement_id, spawn_location_id); + count++; + } + } + return count; +} + +int32 WorldDatabase::ProcessSpawnLocations(ZoneServer* zone, const char* sql_query, int8 type){ + int32 number = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, sql_query, zone->GetZoneID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + int32 spawn_location_id = 0xFFFFFFFF; + SpawnLocation* spawn_location = 0; + while(result && (row = mysql_fetch_row(result))){ + if((spawn_location_id == 0xFFFFFFFF) || atoul(row[0]) != spawn_location_id){ + if(spawn_location){ + zone->AddSpawnLocation(spawn_location_id, spawn_location); + number++; + } + spawn_location = new SpawnLocation(); + } + SpawnEntry* entry = new SpawnEntry; + + spawn_location_id = atoul(row[0]); + entry->spawn_location_id = spawn_location_id; + entry->spawn_entry_id = atoul(row[1]); + entry->spawn_type = type; + entry->spawn_id = atoul(row[9]); + entry->spawn_percentage = atof(row[10]); + entry->respawn = atoul(row[11]); + entry->expire_time = atoul(row[14]); + entry->expire_offset = atoul(row[15]); + //devn00b add stat overrides. Just a slight increase in size. Used in ZoneServer::AddNPCSpawn. + entry->lvl_override = atoul(row[19]); + entry->hp_override = atoul(row[20]); + entry->mp_override = atoul(row[21]); + entry->str_override = atoul(row[22]); + entry->sta_override = atoul(row[23]); + entry->wis_override = atoul(row[24]); + entry->int_override = atoul(row[25]); + entry->agi_override = atoul(row[26]); + entry->heat_override = atoul(row[27]); + entry->cold_override = atoul(row[28]); + entry->magic_override = atoul(row[29]); + entry->mental_override = atoul(row[30]); + entry->divine_override = atoul(row[31]); + entry->disease_override = atoul(row[32]); + entry->poison_override = atoul(row[33]); + entry->difficulty_override = atoul(row[34]); + spawn_location->x = atof(row[2]); + spawn_location->y = atof(row[3]); + spawn_location->z = atof(row[4]); + spawn_location->x_offset = atof(row[5]); + spawn_location->y_offset = atof(row[6]); + spawn_location->z_offset = atof(row[7]); + spawn_location->heading = atof(row[8]); + spawn_location->pitch = atof(row[16]); + spawn_location->roll = atof(row[17]); + spawn_location->conditional = atoi(row[18]); + spawn_location->total_percentage += entry->spawn_percentage; + spawn_location->grid_id = strtoul(row[12], NULL, 0); + spawn_location->placement_id = strtoul(row[13], NULL, 0); + spawn_location->AddSpawn(entry); + + } + if(spawn_location){ + zone->AddSpawnLocation(spawn_location_id, spawn_location); + number++; + } + } + return number; +} + +void WorldDatabase::ResetDatabase(){ + Query query; + query.RunQuery2("delete FROM table_versions where name != 'table_versions'", Q_DELETE); +} + +void WorldDatabase::EnableConstraints(){ + Query query; + query.RunQuery2("/*!40101 SET SQL_MODE=@OLD_SQL_MODE */", Q_DBMS); + query.RunQuery2("/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */", Q_DBMS); +} + +void WorldDatabase::DisableConstraints(){ + Query query; + query.RunQuery2("/*!40101 SET NAMES utf8 */", Q_DBMS); + query.RunQuery2("/*!40101 SET SQL_MODE=''*/", Q_DBMS); + query.RunQuery2("/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */", Q_DBMS); + query.RunQuery2("/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */", Q_DBMS); +} + +void WorldDatabase::LoadSpawns(ZoneServer* zone) +{ + Query query; + int32 npcs = 0, objects = 0, widgets = 0, signs = 0, ground_spawns = 0, spawn_groups = 0, spawn_group_associations = 0, spawn_group_chances = 0; + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter LoadSpawns"); + + npcs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_npcs sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC); + objects = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_objects so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT); + widgets = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_widgets sw where sw.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_WIDGET); + signs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_signs ss where ss.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_SIGN); + ground_spawns = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_ground sg where sg.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_GROUNDSPAWN); + spawn_groups = LoadSpawnLocationGroups(zone); + spawn_group_associations = LoadSpawnLocationGroupAssociations(zone); + spawn_group_chances = LoadSpawnGroupChances(zone); + LogWrite(SPAWN__INFO, 0, "Spawn", "Loaded for zone '%s' (%u):\n\t%u NPC(s), %u Object(s), %u Widget(s)\n\t%u Sign(s), %u Ground Spawn(s), %u Spawn Group(s)\n\t%u Spawn Group Association(s), %u Spawn Group Chance(s)", zone->GetZoneName(), zone->GetZoneID(), npcs, objects, widgets, signs, ground_spawns, spawn_groups, spawn_group_associations, spawn_group_chances); + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit LoadSpawns"); + +} + +bool WorldDatabase::UpdateSpawnLocationSpawns(Spawn* spawn) { + Query query; + query.RunQuery2(Q_UPDATE, "update spawn_location_placement set x=%f, y=%f, z=%f, heading=%f, x_offset=%f, y_offset=%f, z_offset=%f, respawn=%u, expire_timer=%u, expire_offset=%u, grid_id=%u, pitch=%f, roll=%f where id = %u", + spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetHeading(), spawn->GetXOffset(), spawn->GetYOffset(), spawn->GetZOffset(), spawn->GetRespawnTime(), spawn->GetExpireTime(), spawn->GetExpireOffsetTime(), spawn->GetLocation(), spawn->GetPitch(), spawn->GetRoll(), spawn->GetSpawnLocationPlacementID()); + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) { + LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnLocationSpawns query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +bool WorldDatabase::UpdateSpawnWidget(int32 widget_id, char* queryString) { + Query query; + query.RunQuery2(Q_UPDATE, "update spawn_widgets set %s where widget_id = %u", + queryString, widget_id); + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) { + LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnWidget query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +vector* WorldDatabase::GetSpawnNameList(const char* in_name){ + Query query; + string names = ""; + vector* ret = 0; + string name = getSafeEscapeString(in_name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT concat(spawn.id, ', ', name) FROM spawn where name like '%%%s%%'", name.c_str()); + if(result && mysql_num_rows(result) > 0){ + ret = new vector; + MYSQL_ROW row; + int8 num = 0; + while(result && (row = mysql_fetch_row(result))){ + if(num >= 10) + break; + ret->push_back(string(row[0])); + num++; + } + char total[60] = {0}; + if(mysql_num_rows(result) > 10) + sprintf(total, "Total number of results: %u (Limited to 10)", (int32)mysql_num_rows(result)); + else + sprintf(total, "Total number of results: %u", (int32)mysql_num_rows(result)); + ret->push_back(string(total)); + } + return ret; +} +string WorldDatabase::GetZoneName(char* zone_description){ + string ret; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM zones where description = '%s'", getSafeEscapeString(zone_description).c_str()); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row = mysql_fetch_row(result); + ret = string(row[0]); + } + return ret; +} + +void WorldDatabase::LoadRevivePoints(vector* revive_points, int32 zone_id){ + if(revive_points && revive_points->size() > 0){ + LogWrite(WORLD__ERROR, 0, "World", "Revive points have already been loaded for this zone!"); + return; + } + else if(!revive_points || zone_id == 0){ + LogWrite(WORLD__ERROR, 0, "World", "LoadRevivePoints called with null variables!"); + return; + } + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT respawn_zone_id, location_name, safe_x, safe_y, safe_z, heading, always_included FROM revive_points where zone_id=%u ORDER BY id asc", zone_id); + if(revive_points && result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + int32 id = 0; + RevivePoint* point = 0; + while(result && (row=mysql_fetch_row(result))){ + point = new RevivePoint; + point->id = id; + point->zone_id = atoul(row[0]); + point->location_name = string(row[1]); + point->x = atof(row[2]); + point->y = atof(row[3]); + point->z = atof(row[4]); + point->heading = atof(row[5]); + point->always_included = atoul(row[6]); + revive_points->push_back(point); + id++; + } + } +} + +int32 WorldDatabase::GetNextSpawnIDInZone(int32 zone_id) +{ + Query query; + int32 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT MAX(id) FROM spawn where id LIKE '%i____'", zone_id); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0]) + ret = atoi(row[0]) + 1; + } + if( ret == 0 ) + ret = zone_id * 10000; // there are no spawns for that zone yet, to start with the first ID + + LogWrite(WORLD__DEBUG, 0, "World", "Next Spawn ID for Zone %i: %u", zone_id, ret); + return ret; + +} + + +bool WorldDatabase::SaveSpawnInfo(Spawn* spawn){ + Query query; + string name = getSafeEscapeString(spawn->GetName()); + string suffix = getSafeEscapeString(spawn->GetSuffixTitle()); + string prefix = getSafeEscapeString(spawn->GetPrefixTitle()); + string last_name = getSafeEscapeString(spawn->GetLastName()); + if(spawn->GetDatabaseID() == 0){ + + int8 isInstanceType = (spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE); + + int32 new_spawn_id = GetNextSpawnIDInZone(spawn->GetZone()->GetZoneID()); + + query.RunQuery2(Q_INSERT, "insert into spawn (id, name, race, model_type, size, targetable, show_name, command_primary, command_secondary, visual_state, attackable, show_level, show_command_icon, display_hand_icon, faction_id, collision_radius, hp, power, prefix, suffix, last_name, is_instanced_spawn, merchant_min_level, merchant_max_level) values(%u, '%s', %i, %i, %i, %i, %i, %u, %u, %i, %i, %i, %i, %i, %u, %i, %u, %u, '%s', '%s', '%s', %u, %u, %u)", + new_spawn_id, name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->GetSize(), spawn->appearance.targetable, spawn->appearance.display_name, spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, 0, spawn->appearance.pos.collision_radius, spawn->GetTotalHP(), spawn->GetTotalPower(), prefix.c_str(), suffix.c_str(), last_name.c_str(), isInstanceType, spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel()); + + if( new_spawn_id > 0 ) + spawn->SetDatabaseID(new_spawn_id); // use the new zone_id range + else if( query.GetLastInsertedID() > 0 ) + spawn->SetDatabaseID(query.GetLastInsertedID()); // else fall back to last_inserted_id + else + return false; // else, hang your head in shame as you are an utter failure + + if(spawn->IsNPC()){ + query.RunQuery2(Q_INSERT, "insert into spawn_npcs (spawn_id, min_level, max_level, enc_level, class_, gender, min_group_size, max_group_size, hair_type_id, facial_hair_type_id, wing_type_id, chest_type_id, legs_type_id, soga_hair_type_id, soga_facial_hair_type_id, soga_model_type, heroic_flag, action_state, mood_state, initial_state, activity_status, hide_hood, emote_state) values(%u, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i)", + spawn->GetDatabaseID(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetAdventureClass(), spawn->GetGender(), 0, 0, ((NPC*)spawn)->features.hair_type, ((NPC*)spawn)->features.hair_face_type, + ((NPC*)spawn)->features.wing_type, ((NPC*)spawn)->features.chest_type, ((NPC*)spawn)->features.legs_type, ((NPC*)spawn)->features.soga_hair_type, ((NPC*)spawn)->features.soga_hair_face_type, spawn->appearance.soga_model_type, spawn->appearance.heroic_flag, spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(), spawn->GetActivityStatus(), spawn->appearance.hide_hood, spawn->appearance.emote_state); + } + else if(spawn->IsObject()){ + query.RunQuery2(Q_INSERT, "insert into spawn_objects (spawn_id) values(%u)", spawn->GetDatabaseID()); + } + else if(spawn->IsWidget()){ + Widget* widget = (Widget*)spawn; + query.RunQuery2(Q_INSERT, "insert into spawn_widgets (spawn_id, widget_id) values(%u, %u)", spawn->GetDatabaseID(), widget->GetWidgetID()); + } + else if(spawn->IsSign()){ + query.RunQuery2(Q_INSERT, "insert into spawn_signs (spawn_id, description) values(%u, 'change me')", spawn->GetDatabaseID()); + + } + else if (spawn->IsGroundSpawn()) { + query.RunQuery2(Q_INSERT, "insert into spawn_ground (spawn_id) values(%u)", spawn->GetDatabaseID()); + } + } + else{ + if(spawn->IsNPC()){ + query.RunQuery2(Q_UPDATE, "update spawn_npcs, spawn set name='%s', min_level=%i, max_level=%i, enc_level=%i, race=%i, model_type=%i, class_=%i, gender=%i, show_name=%i, attackable=%i, show_level=%i, targetable=%i, show_command_icon=%i, display_hand_icon=%i, hair_type_id=%i, facial_hair_type_id=%i, wing_type_id=%i, chest_type_id=%i, legs_type_id=%i, soga_hair_type_id=%i, soga_facial_hair_type_id=%i, soga_model_type=%i, size=%i, hp=%u, heroic_flag=%i, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, action_state=%i, mood_state=%i, initial_state=%i, activity_status=%i, alignment=%i, faction_id=%u, hide_hood=%i, emote_state=%i, suffix ='%s', prefix='%s', last_name='%s', merchant_min_level = %u, merchant_max_level = %u where spawn_npcs.spawn_id = spawn.id and spawn.id = %u", + name.c_str(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetRace(), spawn->GetModelType(), + spawn->GetAdventureClass(), spawn->GetGender(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.targetable, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, ((NPC*)spawn)->features.hair_type, + ((NPC*)spawn)->features.hair_face_type, ((NPC*)spawn)->features.wing_type, ((NPC*)spawn)->features.chest_type, ((NPC*)spawn)->features.legs_type, ((NPC*)spawn)->features.soga_hair_type, ((NPC*)spawn)->features.soga_hair_face_type, spawn->appearance.soga_model_type, spawn->GetSize(), + spawn->GetTotalHPBase(), spawn->appearance.heroic_flag, spawn->GetTotalPowerBase(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(), + spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(), + spawn->GetActivityStatus(), ((NPC*)spawn)->GetAlignment(), spawn->GetFactionID(), spawn->appearance.hide_hood, spawn->appearance.emote_state, + suffix.c_str(), prefix.c_str(), last_name.c_str(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID()); + } + else if(spawn->IsObject()){ + query.RunQuery2(Q_UPDATE, "update spawn_objects, spawn set name='%s', model_type=%i, show_name=%i, targetable=%i, size=%i, command_primary=%u, command_secondary=%u, visual_state=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, collision_radius=%i, hp = %u, power = %u, device_id = %i, merchant_min_level = %u, merchant_max_level = %u where spawn_objects.spawn_id = spawn.id and spawn.id = %u", + name.c_str(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.targetable, spawn->GetSize(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, + spawn->GetCollisionRadius(), spawn->GetTotalHP(), spawn->GetTotalPower(), ((Object*)spawn)->GetDeviceID(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID()); + } + else if(spawn->IsWidget()){ + Widget* widget = (Widget*)spawn; + char* openSound = 0; + char* closeSound = 0; + if (widget->GetOpenSound() != NULL) openSound = (char*)widget->GetOpenSound(); else openSound = (char*)string("0").c_str(); + if (widget->GetCloseSound() != NULL) closeSound = (char*)widget->GetCloseSound(); else closeSound = (char*)string("0").c_str(); + query.RunQuery2(Q_UPDATE, "update spawn_widgets, spawn set name='%s', race=%i, model_type=%i, show_name=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, size=%i, hp=%u, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, faction_id=%u, suffix ='%s', prefix='%s', last_name='%s',widget_id = %u,widget_x = %f,widget_y = %f,widget_z = %f,include_heading = %u,include_location = %u,icon = %u,type='%s',open_heading = %f,closed_heading = %f,open_x = %f,open_y = %f,open_z = %f,action_spawn_id = %u,open_sound_file='%s',close_sound_file='%s',open_duration = %u,close_x = %f,close_y=%f,close_z=%f,linked_spawn_id = %u,house_id = %u, merchant_min_level = %u, merchant_max_level = %u where spawn_widgets.spawn_id = spawn.id and spawn.id = %u", + name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, spawn->GetSize(), + spawn->GetTotalHP(), spawn->GetTotalPower(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetFactionID(), + suffix.c_str(), prefix.c_str(), last_name.c_str(), widget->GetWidgetID(), widget->GetX(), widget->GetY(), widget->GetZ(), widget->GetIncludeHeading(), widget->GetIncludeLocation(), widget->GetIconValue(), Widget::GetWidgetTypeNameByTypeID(widget->GetWidgetType()).c_str(), + widget->GetOpenHeading(), widget->GetClosedHeading(), widget->GetOpenX(), widget->GetOpenY(), widget->GetOpenZ(), + widget->GetActionSpawnID(), openSound, closeSound,widget->GetOpenDuration(), + widget->GetCloseX(),widget->GetCloseY(),widget->GetCloseZ(),widget->GetLinkedSpawnID(),widget->GetHouseID(), + spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID()); + } + else if(spawn->IsSign()){ + Sign* sign = (Sign*)spawn; + query.RunQuery2(Q_UPDATE, "update spawn_signs, spawn set name='%s', race=%i, model_type=%i, show_name=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, size=%i, hp=%u, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, faction_id=%u, suffix ='%s', prefix='%s', last_name='%s', `type`='%s', zone_id = %u, widget_id = %u, title='%s', widget_x = %f, widget_y = %f, widget_z = %f, icon = %u, description='%s', sign_distance = %f, zone_x = %f, zone_y = %f, zone_z = %f, zone_heading = %f, include_heading = %u, include_location = %u, merchant_min_level = %u, merchant_max_level = %u, language = %u where spawn_signs.spawn_id = spawn.id and spawn.id = %u", + name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, spawn->GetSize(), + spawn->GetTotalHP(), spawn->GetTotalPower(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetFactionID(), + suffix.c_str(), prefix.c_str(), last_name.c_str(), sign->GetSignType() == SIGN_TYPE_GENERIC ? "Generic" : "Zone", sign->GetSignZoneID(), + sign->GetWidgetID(), sign->GetSignTitle(), sign->GetWidgetX(), sign->GetWidgetY(), sign->GetWidgetZ(), + sign->GetIconValue(), sign->GetSignDescription(), sign->GetSignDistance(), sign->GetSignZoneX(), + sign->GetSignZoneY(), sign->GetSignZoneZ(), sign->GetSignZoneHeading(), sign->GetIncludeHeading(), + sign->GetIncludeLocation(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), sign->GetLanguage(), spawn->GetDatabaseID()); + } + } + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnInfo query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::SaveCombinedSpawnLocation(ZoneServer* zone, Spawn* in_spawn, const char* name){ + vector* spawns = in_spawn->GetSpawnGroup(); + uint32 spawnLocationID = 0; + if(spawns && spawns->size() > 0){ + vector::iterator itr; + map::iterator freq_itr; + Spawn* spawn = 0; + map database_spawns; + int32 total = 0; + float x_offset = GetSpawnLocationPlacementOffsetX(in_spawn->GetSpawnLocationID()); + float y_offset = GetSpawnLocationPlacementOffsetY(in_spawn->GetSpawnLocationID()); + float z_offset = GetSpawnLocationPlacementOffsetZ(in_spawn->GetSpawnLocationID()); + int32 spawn_location_id = GetNextSpawnLocation(); + spawnLocationID = spawn_location_id; + if(!name) + name = "Combine SpawnGroup Generated"; + if(!CreateNewSpawnLocation(spawn_location_id, name)){ + safe_delete(spawns); + return 0; + } + for(itr = spawns->begin();itr!=spawns->end();itr++){ + spawn = *itr; + if (spawn) { + RemoveSpawnFromSpawnLocation(spawn); + spawn->SetSpawnLocationID(spawn_location_id); + bool add = true; + for (freq_itr = database_spawns.begin(); freq_itr != database_spawns.end(); freq_itr++) { + if (spawn->GetDatabaseID() == freq_itr->first->GetDatabaseID()) { + freq_itr->second++; + total++; + add = false; + } + } + if (add) { + database_spawns[spawn] = 1; + total++; + } + } + } + for(freq_itr = database_spawns.begin(); freq_itr != database_spawns.end(); freq_itr++){ + int8 percent = (freq_itr->second*100)/total; + if(!SaveSpawnEntry(freq_itr->first, name, percent, x_offset, y_offset, z_offset, freq_itr->first == in_spawn, false)){ + safe_delete(spawns); + return 0; + } + } + for(itr=spawns->begin();itr!=spawns->end();itr++){ + spawn = *itr; + zone->RemoveSpawn(spawn, true, true, true, true, true); + } + safe_delete(spawns); + } + else{ + safe_delete(spawns); + return 0; + } + return spawnLocationID; +} + +bool WorldDatabase::SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name, int8 percent, float x_offset, float y_offset, float z_offset, bool save_zonespawn, bool create_spawnlocation){ + Query query; + Query query2; + int32 count = 0; + if(create_spawnlocation){ + count = GetSpawnLocationCount(spawn->GetSpawnLocationID()); + if(count == 0){ + if(!CreateNewSpawnLocation(spawn->GetSpawnLocationID(), spawn_location_name)) + return false; + } + } + query.RunQuery2(Q_INSERT, "insert into spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) values(%u, %u, %i)", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), percent); + + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + if(save_zonespawn){ + query2.RunQuery2(Q_INSERT, "insert into spawn_location_placement (zone_id, instance_id, spawn_location_id, x, y, z, x_offset, y_offset, z_offset, heading, grid_id) values(%u, %u, %u, %f, %f, %f, %f, %f, %f, %f, %u)", spawn->GetZone()->GetZoneID(), spawn->GetZone()->GetInstanceID(), spawn->GetSpawnLocationID(), spawn->GetX(), spawn->GetY(), spawn->GetZ(),x_offset, y_offset, z_offset, spawn->GetHeading(), spawn->GetLocation()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query2.GetQuery(), query2.GetError()); + return false; + } + spawn->SetSpawnLocationPlacementID(query2.GetLastInsertedID()); + } + return true; +} + +float WorldDatabase::GetSpawnLocationPlacementOffsetX(int32 location_id) { + Query query; + MYSQL_ROW row; + float ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `x_offset` FROM `spawn_location_placement` WHERE `spawn_location_id`=%u", location_id); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + ret = atof(row[0]); + } + return ret; +} + +float WorldDatabase::GetSpawnLocationPlacementOffsetY(int32 location_id) { + Query query; + MYSQL_ROW row; + float ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `y_offset` FROM `spawn_location_placement` WHERE `spawn_location_id`=%u", location_id); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + ret = atof(row[0]); + } + return ret; +} + +float WorldDatabase::GetSpawnLocationPlacementOffsetZ(int32 location_id) { + Query query; + MYSQL_ROW row; + float ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `z_offset` FROM `spawn_location_placement` WHERE `spawn_location_id`=%u", location_id); + if (result && (row = mysql_fetch_row(result))) { + if (row[0]) + ret = atof(row[0]); + } + return ret; +} + +bool WorldDatabase::CreateNewSpawnLocation(int32 id, const char* name){ + Query query; + if(!name) + name = "Unknown Spawn Location Name"; + string str_name = getSafeEscapeString(name); + query.RunQuery2(Q_INSERT, "insert into spawn_location_name (id, name) values(%u, '%s')", id, str_name.c_str()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in CreateNewSpawnLocation query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::GetSpawnLocationCount(int32 location, Spawn* spawn){ + Query query; + int32 ret = 0; + MYSQL_RES* result = 0; + if(spawn) + result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry where spawn_location_id=%u and spawn_id=%u", location, spawn->GetDatabaseID()); + else + result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry where spawn_location_id=%u", location); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result)) && row[0]){ + ret = strtoul(row[0], NULL, 0); + } + } + return ret; +} + +int32 WorldDatabase::GetNextSpawnLocation(){ + Query query; + int32 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM spawn_location_name"); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result)) && row[0]){ + ret = strtoul(row[0], NULL, 0); + } + } + ret++; + return ret; +} + +bool WorldDatabase::RemoveSpawnFromSpawnLocation(Spawn* spawn){ + Query query; + Query query2; + int32 count = GetSpawnLocationCount(spawn->GetSpawnLocationID(), spawn); + + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_placement where spawn_location_id=%u", spawn->GetSpawnLocationID()); + if(count == 1) + query.RunQuery2(Q_DELETE, "delete FROM spawn_location_name where id=%u", spawn->GetSpawnLocationID()); + + query2.RunQuery2(Q_DELETE, "delete FROM spawn_location_entry where spawn_id=%u and spawn_location_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID()); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in RemoveSpawnFromSpawnLocation query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + else if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in RemoveSpawnFromSpawnLocation query '%s': %s", query2.GetQuery(), query.GetError()); + return false; + } + return true; +} + +map* WorldDatabase::GetZoneList(const char* name, bool is_admin) +{ + Query query; + map* ret = 0; + string zone_name = getSafeEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `name` FROM zones WHERE `name` RLIKE '%s' %s", zone_name.c_str(), (is_admin)?"":" LIMIT 0,10"); + if(result && mysql_num_rows(result) > 0) + { + ret = new map; + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))) + { + zone_name = row[1]; + (*ret)[atoi(row[0])] = zone_name; + } + } + + return ret; +} + +void WorldDatabase::UpdateStartingFactions(int32 char_id, int8 choice){ + Query query; + query.RunQuery2(Q_INSERT, "insert into character_factions (char_id, faction_id, faction_level) select %u, faction_id, value FROM starting_factions where starting_city=%i", char_id, choice); +} + +string WorldDatabase::GetStartingZoneName(int8 choice){ + Query query; + string zone_name = ""; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM zones where start_zone = %u", choice); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + zone_name = string(row[0]); + } + } + return zone_name; +} + +void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_id, PacketStruct* create) +{ + Query query,query2; + + int32 packetVersion = create->GetVersion(); + int8 choice = create->getType_int8_ByName("starting_zone"); // 0 = far journey, 1 = isle of refuge + int8 deity = create->getType_int8_ByName("deity"); // aka 'alignment' for early DOF, 0 = evil, 1 = good + int32 startingZoneRuleFlag = rule_manager.GetGlobalRule(R_World, StartingZoneRuleFlag)->GetInt32(); + bool enforceRacialAlignment = rule_manager.GetGlobalRule(R_World, EnforceRacialAlignment)->GetBool(); + + if((startingZoneRuleFlag == 1 || startingZoneRuleFlag == 2) && packetVersion > 561) + { + LogWrite(PLAYER__INFO, 0, "Player", "Starting zone rule flag %u override choice %u to deity value of 0", startingZoneRuleFlag, choice); + choice = 0; + } + + LogWrite(PLAYER__INFO, 0, "Player", "Adding default zone for race: %i, class: %i for char_id: %u (choice: %i), deity(alignment): %u, version: %u.", race_id, class_id, char_id, choice, deity, packetVersion); + + // first, check to see if there is a starting_zones record for this race/class/choice combo (now using extended Archetype/BaseClass/Class combos + MYSQL_RES* result = 0; + + string whereRuleFlag(""); + if(startingZoneRuleFlag > 0) + whereRuleFlag = string(" AND ruleflag & " + std::to_string(startingZoneRuleFlag)); + + string syntaxSelect("SELECT z.name, sz.zone_id, z.safe_x, z.safe_y, z.safe_z, sz.x, sz.y, sz.z, sz.heading, sz.is_instance, z.city_zone, sz.start_alignment FROM"); + if ( class_id == 0 ) + result = query.RunQuery2(Q_SELECT, "%s starting_zones sz, zones z WHERE sz.zone_id = z.id AND class_id = 255 AND race_id IN (%i, 255) AND deity IN (%i, 255) AND choice = %u AND (min_version = 0 or min_version <= %u) AND (max_version = 0 or max_version >= %u)%s", + syntaxSelect.c_str(), race_id, deity, choice, packetVersion, packetVersion, whereRuleFlag.c_str()); + else + result = query.RunQuery2(Q_SELECT, "%s starting_zones sz, zones z WHERE sz.zone_id = z.id AND class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) AND deity IN (%i, 255) AND choice IN (%i, 255) AND (min_version = 0 or min_version <= %u) AND (max_version = 0 or max_version >= %u)%s", + syntaxSelect.c_str(), classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id, deity, choice, packetVersion, packetVersion, whereRuleFlag.c_str()); + + if(result && mysql_num_rows(result) > 0) + { + string zone_name = "ERROR"; + MYSQL_ROW row; + + bool zoneSet = false; + + float safeX = 0.0f, safeY = 0.0f, safeZ = 0.0f, x = 0.0f, y = 0.0f, z = 0.0f, heading = 0.0f; + int8 is_instance = 0; + int32 zone_id = 0; + int32 instance_id = 0; + int8 starting_city = 0; + sint8 start_alignment = 0; + + if( result && (row = mysql_fetch_row(result)) ) + { + int8 i=0; + + zoneSet = true; + + zone_name = string(row[i++]); + + zone_id = atoul(row[i++]); + + safeX = atof(row[i++]); + safeY = atof(row[i++]); + safeZ = atof(row[i++]); + + x = atof(row[i++]); + y = atof(row[i++]); + z = atof(row[i++]); + + if ( x == -999999.0f && y == -999999.0f && z == -999999.0f) + { + x = safeX; + y = safeY; + z = safeZ; + } + + heading = atof(row[i++]); + + if(heading == -999999.0f ) + heading = 0.0f; + + is_instance = atoul(row[i++]); + + starting_city = atoul(row[i++]); + + start_alignment = atoi(row[i++]); + } + + + // all EQ2 clients hard-code these restrictions in some form (later clients added Neutral instead of good/evil only) + // start with good races only + if(enforceRacialAlignment && (race_id == DWARF || race_id == FROGLOK || race_id == HALFLING || + race_id == HIGH_ELF || race_id == WOOD_ELF || race_id == FAE)) { + start_alignment = ALIGNMENT_GOOD; // always should good + } + // we drop into this case because it is a special check compared to the straigt good/evil checks on top and below + else if(start_alignment == ALIGNMENT_EVIL && deity == ALIGNMENT_GOOD) { + if (enforceRacialAlignment && (race_id == AERAKYN || race_id == RATONGA || race_id == BARBARIAN || race_id == ERUDITE || + race_id == HUMAN || race_id == VAMPIRE || race_id == HALF_ELF || race_id == GNOME || race_id == KERRA)) { + if(zone_id == 21 || zone_id == 25 || zone_id == 26 || zone_id == 27) { // far journey zones + start_alignment = deity; // inheriting the clients alignment of 'good' due to the fact we start in far journey and it is a shared instance right now (farjourneyfreeport) + } + // otherwise we use the starting zone alignment since these particular races can be evil which will be set in start_alignment of 0 within starting_zones (neriak, freeport, etc.) + } + else if(enforceRacialAlignment) { + LogWrite(WORLD__WARNING, 0, "World", "Starting alignment seems unexpected, zone id %u, race id %u, deity(alignment) choice: %u, start alignment: %i", zone_id, race_id, deity, start_alignment); + } + } + // anyone else is simply evil with the enforcement check + else if(enforceRacialAlignment && (race_id != DWARF && race_id != FROGLOK && race_id != HALFLING && + race_id != HIGH_ELF && race_id != WOOD_ELF && race_id != FAE)) { + start_alignment = ALIGNMENT_EVIL; + } + + if(is_instance) // should only be true if we get a result + { + // this will force a pre-load + ZoneServer* instance_zone = zone_list.GetByInstanceID(0, zone_id); + if (instance_zone) { + instance_id = CreateNewInstance(zone_id); + AddCharacterInstance(char_id, instance_id, string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); + // make sure we inherit the instance id setup in the AddCharacterInstance + instance_zone->SetupInstance(instance_id); + } + } + + query2.RunQuery2(Q_UPDATE, "UPDATE characters SET current_zone_id = %u, x = %f, y = %f, z = %f, heading = %f, starting_city = %i, instance_id = %u, alignment = %u WHERE id = %u", + zone_id, x, y, z, heading, starting_city, instance_id, start_alignment, char_id); + + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(PLAYER__ERROR, 0, "Player", "Error in UpdateStartingZone custom starting_zones, query: '%s': %s", query2.GetQuery(), query2.GetError()); + return; + } + + if(query2.GetAffectedRows() > 0) + { + LogWrite(PLAYER__INFO, 0, "Player", "Setting New Character Starting Zone to '%s' with location %f, %f, %f and heading %f FROM starting_zones table.", zone_name.c_str(), x, y, z, heading); + return; + } + } + else + { + // there was no matching starting_zone value, so use default 'choice' starting city + query2.RunQuery2(Q_UPDATE, "UPDATE characters c, zones z SET c.current_zone_id = z.id, c.x = z.safe_x, c.y = z.safe_y, c.z = z.safe_z, c.starting_city = %i WHERE z.start_zone = %i and c.id = %u", + choice, choice, char_id); + + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) + { + LogWrite(PLAYER__ERROR, 0, "Player", "Error in UpdateStartingZone player choice, query: '%s': %s", query2.GetQuery(), query2.GetError()); + return; + } + + if(query2.GetAffectedRows() > 0) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Setting New Character Starting Zone to '%s' FROM player choice.", GetStartingZoneName(choice).c_str()); + return; + } + } + + // if we are here, it's a bad thing. zone tables have no start_city values to match client 'choice', so throw the player into zone according to R_World::DefaultStartingZoneID rule. + // shout a few warnings so the admin fixes this asap! + int16 default_zone_id = rule_manager.GetGlobalRule(R_World, DefaultStartingZoneID)->GetInt16(); + + LogWrite(WORLD__WARNING, 0, "World", "No Starting City defined for player choice: %i! BAD! BAD! BAD! Defaulting player to zone %i.", choice, default_zone_id); + + query.RunQuery2(Q_UPDATE, "UPDATE characters c, zones z SET c.current_zone_id = z.id, c.x = z.safe_x, c.y = z.safe_y, c.z = z.safe_z, c.heading = z.safe_heading, c.starting_city = 1 WHERE z.id = %i and c.id = %u", default_zone_id, char_id); + + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + { + LogWrite(PLAYER__ERROR, 0, "Player", "Error in UpdateStartingZone default zone %i, query: '%s': %s", default_zone_id, query.GetQuery(), query.GetError()); + return; + } + + if (query.GetAffectedRows() > 0) { + string zone_name = GetZoneName(1); + if(zone_name.length() > 0) + LogWrite(PLAYER__DEBUG, 0, "Player", "Setting New Character Starting Zone to '%s' due to no valid options!", zone_name.c_str()); + else + LogWrite(PLAYER__DEBUG, 0, "Player", "Unable to set New Character Starting Zone due to no valid options!"); + } + + return; +} + +void WorldDatabase::UpdateStartingItems(int32 char_id, int8 class_id, int8 race_id, bool base_class){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default items for race: %i, class: %i for char_id: %u", race_id, class_id, char_id); + Query query; + Query query2; + vector items; + vector bags; + map total_slots; + map slots_left; + map equip_slots; + map item_list; + int32 item_id = 0; + Item* item = 0; + StartingItem* starting_item = 0; + //first get a list of the starting items for the character + MYSQL_RES* result = 0; + /*if(!base_class) + result = query2.RunQuery2(Q_SELECT, "SELECT `type`, item_id, creator, condition_, attuned, count FROM starting_items where (class_id=%i and race_id=%i) or (class_id=%i and race_id=255) or (class_id=255 and race_id=%i) or (class_id=255 and race_id=255) ORDER BY id", class_id, race_id, class_id, race_id); + else + result = query2.RunQuery2(Q_SELECT, "SELECT `type`, item_id, creator, condition_, attuned, count FROM starting_items where (class_id=%i and race_id=%i) or (class_id=%i and race_id=255) ORDER BY id", class_id, race_id, class_id);*/ + result = query2.RunQuery2(Q_SELECT, "SELECT `type`, item_id, creator, condition_, attuned, count FROM starting_items WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) ORDER BY id", classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + item_id = atoul(row[1]); + item = master_item_list.GetItem(item_id); + if(item){ + starting_item = &(item_list[item]); + starting_item->type = (row[0]) ? string(row[0]) : ""; + starting_item->item_id = atoul(row[1]); + starting_item->creator = (row[2]) ? string(row[2]) : ""; + starting_item->condition = atoi(row[3]); + starting_item->attuned = atoi(row[4]); + starting_item->count = atoi(row[5]); + item = master_item_list.GetItem(starting_item->item_id); + if(item){ + if(bags.size() < NUM_INV_SLOTS && item->IsBag() && item->details.num_slots > 0) + bags.push_back(item); + else + items.push_back(item); + } + } + } + } + slots_left[0] = NUM_INV_SLOTS; + //next create the bags in the inventory + for(int8 i=0;idetails.num_slots; + total_slots[query.GetLastInsertedID()] = item->details.num_slots; + slots_left[0]--; + } + map::iterator itr; + int32 inv_slot = 0; + int8 slot = 0; + //finally process the rest of the items, placing them in the first available slot + for(int32 x=0;xsecond > 0){ + if(itr->first == 0 && slots_left.size() > 1) //we want items to go into bags first, then inventory after bags are full + continue; + inv_slot = itr->first; + slot = total_slots[itr->first] - itr->second; + itr->second--; + if(itr->second == 0) + slots_left.erase(itr); + break; + } + } + query.RunQuery2(Q_INSERT, "insert into character_items (char_id, type, slot, item_id, creator, condition_, attuned, bag_id, count) values(%u, '%s', %i, %u, '%s', %i, %i, %u, %i)", + char_id, item_list[item].type.c_str(), slot, item_list[item].item_id, getSafeEscapeString(item_list[item].creator.c_str()).c_str(), item_list[item].condition, item_list[item].attuned, inv_slot, item_list[item].count); + } + else{ //EQUIPPED Items + for(int8 i=0;islot_data.size();i++){ + if(equip_slots.count(item->slot_data[i]) == 0){ + equip_slots[item->slot_data[i]] = true; + query.RunQuery2(Q_INSERT, "insert into character_items (char_id, type, slot, item_id, creator, condition_, attuned, bag_id, count) values(%u, '%s', %i, %u, '%s', %i, %i, %u, %i)", + char_id, item_list[item].type.c_str(), item->slot_data[i], item_list[item].item_id, getSafeEscapeString(item_list[item].creator.c_str()).c_str(), item_list[item].condition, item_list[item].attuned, 0, item_list[item].count); + break; + } + } + } + } +} + +void WorldDatabase::UpdateStartingSkills(int32 char_id, int8 class_id, int8 race_id) +{ + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default skills for race: %i, class: %i for char_id: %u", race_id, class_id, char_id); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_skills (char_id, skill_id, current_val, max_val) SELECT %u, skill_id, current_val, max_val FROM starting_skills WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255)", + char_id, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id); +} + +void WorldDatabase::UpdateStartingSpells(int32 char_id, int8 class_id, int8 race_id){ + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default spells for race: %i, class: %i for char_id: %u", race_id, class_id, char_id); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_spells (char_id, spell_id, tier, knowledge_slot) SELECT %u, spell_id, tier, knowledge_slot FROM starting_spells WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255)", + char_id, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id); +} + +void WorldDatabase::UpdateStartingSkillbar(int32 char_id, int8 class_id, int8 race_id){ + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default skillbar for race: %i, class: %i for char_id: %u", race_id, class_id, char_id); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_skillbar (char_id, type, hotbar, spell_id, slot, text_val) SELECT %u, type, hotbar, spell_id, slot, text_val FROM starting_skillbar WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255)", + char_id, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id); +} + +void WorldDatabase::UpdateStartingTitles(int32 char_id, int8 class_id, int8 race_id, int8 gender_id) { + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default titles for race: %i, class: %i, gender: %i for char_id: %u", race_id, class_id, gender_id, char_id); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_titles (char_id, title_id) SELECT %u, title_id FROM starting_titles WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) and gender_id IN (%i, 255)", + char_id, classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id, gender_id); +} + +string WorldDatabase::GetZoneDescription(int32 id){ + Query query; + string ret = ""; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT description FROM zones where id = %u", id); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + row = mysql_fetch_row(result); + ret = string(row[0]); + } + return ret; +} + +string WorldDatabase::GetZoneName(int32 id){ + if (zone_names.count(id) > 0){ + return zone_names[id]; + } + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `name` FROM zones where `id` = %u", id); + if(result && mysql_num_rows(result) > 0){ + MYSQL_ROW row; + row = mysql_fetch_row(result); + zone_names[id] = row[0]; + return zone_names[id]; + } + return string(""); +} + +bool WorldDatabase::VerifyZone(const char* name){ + Query query; + char* escaped = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM zones where name='%s'",escaped); + safe_delete_array(escaped); + if(result && mysql_num_rows(result) > 0) + return true; + else + return false; +} + +int8 WorldDatabase::GetInstanceTypeByZoneID(int32 zoneID) +{ + + std::map::iterator itr = zone_instance_types.find(zoneID); + if(itr != zone_instance_types.end()) + return itr->second; + + DatabaseResult result; + int8 ret = 0; + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Getting instances for zone_id %u", zoneID); + + if( !database_new.Select(&result, "SELECT instance_type+0 FROM zones WHERE id = %u", zoneID) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in GetInstanceTypeByZoneID() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return ret; + } + + if( result.GetNumRows() > 0 ) + { + result.Next(); + ret = (result.GetInt8Str("instance_type+0") == 0) ? 0 : result.GetInt8Str("instance_type+0") - 1; + zone_instance_types.insert(make_pair(zoneID, ret)); + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Found instance type %i for zone_id %u", ret, zoneID); + } + else + LogWrite(INSTANCE__DEBUG, 0, "Instance", "No instances found for zone_id %u", zoneID); + + return ret; +} + +void WorldDatabase::Save(Client* client){ + Query query; + Player* player = client->GetPlayer(); + if(!player->CheckPlayerInfo()) + return; + + int32 instance_id = 0; + if ( client->GetCurrentZone ( ) != NULL ) + instance_id = client->GetCurrentZone()->GetInstanceID(); + + int32 zone_id = 0; + if(client->GetCurrentZone()) + zone_id = client->GetCurrentZone()->GetZoneID(); + query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update characters set current_zone_id=%u, x=%f, y=%f, z=%f, heading=%f, level=%i,instance_id=%i,last_saved=%i, `class`=%i, `tradeskill_level`=%i, `tradeskill_class`=%i, `group_id`=%u, deity = %u, alignment = %u where id = %u", zone_id, player->GetX(), player->GetY(), player->GetZ(), player->GetHeading(), player->GetLevel(), instance_id, client->GetLastSavedTimeStamp(), client->GetPlayer()->GetAdventureClass(), client->GetPlayer()->GetTSLevel(), client->GetPlayer()->GetTradeskillClass(), client->GetPlayer()->GetGroupMemberInfo() ? client->GetPlayer()->GetGroupMemberInfo()->group_id : client->GetRejoinGroupID(), client->GetPlayer()->GetDeity(), client->GetPlayer()->GetInfoStruct()->get_alignment(), client->GetCharacterID()); + query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "update character_details set hp=%u, power=%u, str=%i, sta=%i, agi=%i, wis=%i, intel=%i, heat=%i, cold=%i, magic=%i, mental=%i, divine=%i, disease=%i, poison=%i, coin_copper=%u, coin_silver=%u, coin_gold=%u, coin_plat=%u, max_hp = %u, max_power=%u, xp = %u, xp_needed = %u, xp_debt = %f, xp_vitality = %f, tradeskill_xp = %u, tradeskill_xp_needed = %u, tradeskill_xp_vitality = %f, bank_copper = %u, bank_silver = %u, bank_gold = %u, bank_plat = %u, status_points = %u, bind_zone_id=%u, bind_x = %f, bind_y = %f, bind_z = %f, bind_heading = %f, house_zone_id=%u, combat_voice = %i, emote_voice = %i, biography='%s', flags=%u, flags2=%u, last_name='%s', assigned_aa = %i, unassigned_aa = %i, tradeskill_aa = %i, unassigned_tradeskill_aa = %i, prestige_aa = %i, unassigned_prestige_aa = %i, tradeskill_prestige_aa = %i, unassigned_tradeskill_prestige_aa = %i, pet_name = '%s' where char_id = %u", + player->GetHP(), player->GetPower(), player->GetStrBase(), player->GetStaBase(), player->GetAgiBase(), player->GetWisBase(), player->GetIntBase(), player->GetHeatResistanceBase(), player->GetColdResistanceBase(), player->GetMagicResistanceBase(), + player->GetMentalResistanceBase(), player->GetDivineResistanceBase(), player->GetDiseaseResistanceBase(), player->GetPoisonResistanceBase(), player->GetCoinsCopper(), player->GetCoinsSilver(), player->GetCoinsGold(), player->GetCoinsPlat(), player->GetTotalHPBase(), player->GetTotalPowerBase(), player->GetXP(), player->GetNeededXP(), player->GetXPDebt(), player->GetXPVitality(), player->GetTSXP(), player->GetNeededTSXP(), player->GetTSXPVitality(), player->GetBankCoinsCopper(), + player->GetBankCoinsSilver(), player->GetBankCoinsGold(), player->GetBankCoinsPlat(), player->GetStatusPoints(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneID(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneX(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneY(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneZ(), client->GetPlayer()->GetPlayerInfo()->GetBindZoneHeading(), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), + client->GetPlayer()->GetCombatVoice(), client->GetPlayer()->GetEmoteVoice(), getSafeEscapeString(client->GetPlayer()->GetBiography().c_str()).c_str(), player->GetFlags(), player->GetFlags2(), client->GetPlayer()->GetLastName(), + client->GetPlayer()->GetAssignedAA(), client->GetPlayer()->GetUnassignedAA(), client->GetPlayer()->GetTradeskillAA(), client->GetPlayer()->GetUnassignedTradeskillAA(), client->GetPlayer()->GetPrestigeAA(), + client->GetPlayer()->GetUnassignedPretigeAA(), client->GetPlayer()->GetTradeskillPrestigeAA(), client->GetPlayer()->GetUnassignedTradeskillPrestigeAA(), client->GetPlayer()->GetInfoStruct()->get_pet_name().c_str(), client->GetCharacterID()); + map::iterator itr; + map* friends = player->GetFriends(); + if(friends && friends->size() > 0){ + for(itr = friends->begin(); itr != friends->end(); itr++){ + if(itr->second == 1){ + query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "insert ignore into character_social (char_id, name, type) values(%u, '%s', 'FRIEND')", client->GetCharacterID(), getSafeEscapeString(itr->first.c_str()).c_str()); + itr->second = 0; + } + else if(itr->second == 2){ + query.AddQueryAsync(client->GetCharacterID(), this, Q_DELETE, "delete FROM character_social where char_id = %u and name = '%s'", client->GetCharacterID(), getSafeEscapeString(itr->first.c_str()).c_str()); + itr->second = 3; + } + } + } + map* ignored = player->GetIgnoredPlayers(); + if(ignored && ignored->size() > 0){ + for(itr = ignored->begin(); itr != ignored->end(); itr++){ + if(itr->second == 1){ + query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "insert ignore into character_social (char_id, name, type) values(%u, '%s', 'IGNORE')", client->GetCharacterID(), getSafeEscapeString(itr->first.c_str()).c_str()); + itr->second = 0; + } + else if(itr->second == 2){ + query.AddQueryAsync(client->GetCharacterID(), this, Q_DELETE, "delete FROM character_social where char_id = %u and name = '%s'", client->GetCharacterID(), getSafeEscapeString(itr->first.c_str()).c_str()); + itr->second = 3; + } + } + } + SavePlayerFactions(client); + SaveCharacterQuests(client); + SaveCharacterSkills(client); + SavePlayerSpells(client); + SavePlayerMail(client); + SavePlayerCollections(client); + client->SaveQuestRewardData(); + + LogWrite(PLAYER__INFO, 3, "Player", "Player '%s' (%u) data saved.", player->GetName(), player->GetCharacterID()); +} + +void WorldDatabase::LoadEntityCommands(ZoneServer* zone) { + int32 total = 0; + int32 id = 0; + EntityCommand* command = 0; + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT `command_list_id`, `command_text`, `distance`, `command`, `error_text`, `cast_time`, `spell_visual` FROM `entity_commands` ORDER BY `id`")) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + command = new EntityCommand; + + id = result.GetInt32(0); + command->name = result.GetString(1); + command->distance = result.GetFloat(2); + command->command = result.GetString(3); + command->error_text = result.GetString(4); + command->cast_time = result.GetInt16(5); + command->spell_visual = result.GetInt32(6); + command->default_allow_list = true; + + zone->SetEntityCommandList(id, command); + LogWrite(COMMAND__DEBUG, 5, "Command", "---Loading Command: '%s' (%s)", command->name.c_str(), command->command.c_str()); + + total++; + } + LogWrite(COMMAND__DEBUG, 0, "Command", "--Loaded %i entity command(s)", total); +} + +void WorldDatabase::LoadFactionAlliances() +{ + LogWrite(FACTION__DEBUG, 1, "Faction", "-Loading faction alliances..."); + int32 total = 0; + int32 fTotal = 0; + int32 hTotal = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT faction_id, friend_faction, hostile_faction FROM faction_alliances"); + + if(result && mysql_num_rows(result) > 0) + { + MYSQL_ROW row; + int32 faction_id = 0; + int32 friendly_id = 0; + int32 hostile_id = 0; + + while(result && (row = mysql_fetch_row(result))) + { + faction_id = atoul(row[0]); + friendly_id = atoul(row[1]); + hostile_id = atoul(row[2]); + + if(friendly_id > 0) + { + master_faction_list.AddFriendlyFaction(faction_id, friendly_id); + fTotal++; + LogWrite(FACTION__DEBUG, 5, "Faction", "---Faction %i is friendly towards %i", faction_id, friendly_id); + } + if(hostile_id > 0) + { + master_faction_list.AddHostileFaction(faction_id, hostile_id); + hTotal++; + LogWrite(FACTION__DEBUG, 5, "Faction", "---Faction %i is hostile towards %i", faction_id, hostile_id); + } + total++; + } + } + LogWrite(FACTION__DEBUG, 3, "Faction", "--Loaded %u Alliances: %i friendly, %i hostile", total, fTotal, hTotal); +} + +bool WorldDatabase::UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_id, int32 spawnentry_id, const char* name){ + bool ret = false; + if((spawn_id > 0 || spawn_location_id > 0 || spawnentry_id > 0) && name){ + Query query, query2; + int32 row_id = 0; + if(spawn_id > 0){ + query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawn_id=%u", spawn_id); + query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_id, lua_script) values(%u, '%s')", spawn_id, getSafeEscapeString(name).c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError()); + else{ + row_id = query2.GetLastInsertedID(); + if(row_id > 0) + world.AddSpawnScript(row_id, name); + ret = true; + } + } + else if(spawn_location_id > 0){ + query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawn_location_id=%u", spawn_location_id); + query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_location_id, lua_script) values(%u, '%s')", spawn_location_id, getSafeEscapeString(name).c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError()); + else{ + row_id = query2.GetLastInsertedID(); + if(row_id > 0) + world.AddSpawnLocationScript(row_id, name); + ret = true; + } + } + else if(spawnentry_id > 0){ + query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawnentry_id=%u", spawnentry_id); + query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawnentry_id, lua_script) values(%u, '%s')", spawnentry_id, getSafeEscapeString(name).c_str()); + if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError()); + else{ + row_id = query2.GetLastInsertedID(); + if(row_id > 0) + world.AddSpawnEntryScript(row_id, name); + ret = true; + } + } + } + return ret; +} + +void WorldDatabase::LoadSpawnScriptData() { + int32 total = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnentry_id, spawn_location_id, lua_script FROM spawn_scripts"); + if(result && mysql_num_rows(result) > 0) + { + MYSQL_ROW row; + int32 spawn_id = 0; + int32 spawnentry_id = 0; + int32 spawn_location_id = 0; + + while(result && (row = mysql_fetch_row(result))) + { + spawn_id = atoul(row[0]); + spawnentry_id = atoul(row[1]); + spawn_location_id = atoul(row[2]); + string spawn_script = string(row[3]); + + if(spawnentry_id > 0) + { + world.AddSpawnEntryScript(spawnentry_id, row[3]); + total++; + } + else if(spawn_location_id > 0) + { + world.AddSpawnLocationScript(spawn_location_id, row[3]); + total++; + } + else if(spawn_id > 0) + { + world.AddSpawnScript(spawn_id, row[3]); + total++; + } + else { + if(row[3]) + LogWrite(LUA__ERROR, 0, "LUA", "Invalid Entry in spawn_scripts table for lua_script '%s' (spawn_id, spawnentry_id and spawn_location_id are all 0)", row[3]); + else + LogWrite(LUA__ERROR, 0, "LUA", "Invalid Entry in spawn_scripts table."); + } + + spawn_id = 0; + spawnentry_id = 0; + spawn_location_id = 0; + } + } + LogWrite(LUA__DEBUG, 0, "LUA", "\tLoaded %u SpawnScript%s", total, total == 1 ? "" : "s"); +} + +void WorldDatabase::LoadZoneScriptData() { + Query query; + int32 total = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `lua_script` FROM `zones`"); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))) { + if (row[1]) { + int32 zone_id = atoul(row[0]); + string zone_script = string(row[1]); + if (zone_id > 0 && zone_script.length() > 0) { + + LogWrite(LUA__DEBUG, 5, "LUA", "ZoneScript: %s loaded.", zone_script.c_str()); + + world.AddZoneScript(zone_id, zone_script.c_str()); + total++; + } + else if (zone_id > 0) { + + string tmpScript; + tmpScript.append("ZoneScripts/"); + string zonename = GetZoneName(zone_id); + tmpScript.append(zonename); + tmpScript.append(".lua"); + struct stat buffer; + bool fileExists = (stat(tmpScript.c_str(), &buffer) == 0); + if (fileExists) + { + LogWrite(LUA__INFO, 0, "LUA", "No zonescript file described in the database, overriding with ZoneScript %s for Zone ID %u", (char*)tmpScript.c_str(), zone_id); + world.AddZoneScript(zone_id, tmpScript.c_str()); + } + } + } + } + } + LogWrite(LUA__DEBUG, 0, "LUA", "\tLoaded %u ZoneScript%s", total, total == 1 ? "" : "s"); +} + +int32 WorldDatabase::LoadSpellScriptData() { + Query query; + MYSQL_ROW row; + int32 count; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `lua_script` FROM `spells`"); // WHERE is_active = 1 + + while (result && (row = mysql_fetch_row(result))) { + if (row[0] && strlen(row[0]) > 0) { + if (lua_interface->LoadLuaSpell(row[0])) + LogWrite(SPELL__DEBUG, 5, "Spells", "SpellScript: %s loaded.", row[0]); + } + } + + count = mysql_num_rows(result); + LogWrite(SPELL__DEBUG, 0, "Spells", "\tLoaded %i SpellScript%s", count, count == 1 ? "" : "s"); + + return count; +} + +void WorldDatabase::LoadFactionList() { + int32 total = 0; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, type, description, default_level, negative_change, positive_change FROM factions"); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + Faction* faction = 0; + while(result && (row = mysql_fetch_row(result))){ + faction = new Faction; + faction->id = atoul(row[0]); + faction->name = string(row[1]); + faction->type = string(row[2]); + faction->description = string(row[3]); + faction->default_value = atoi(row[4]); + faction->negative_change = atoi(row[5]); + faction->positive_change = atoi(row[6]); + master_faction_list.AddFaction(faction); + total++; + LogWrite(FACTION__DEBUG, 5, "Faction", "---Loading Faction '%s' (%u)", faction->name.c_str(), faction->id); + } + } + LogWrite(FACTION__DEBUG, 3, "Faction", "--Loaded %u Faction%s", total, total == 1 ? "" : "s"); + LoadFactionAlliances(); +} + +void WorldDatabase::SavePlayerFactions(Client* client){ + LogWrite(PLAYER__DEBUG, 3, "Player", "Saving Player Factions..."); + + Query query; + map* factions = client->GetPlayer()->GetFactions()->GetFactionValues(); + map::iterator itr; + for(itr = factions->begin(); itr != factions->end(); itr++) + query.AddQueryAsync(client->GetCharacterID(), this,Q_INSERT, "insert into character_factions (char_id, faction_id, faction_level) values(%u, %u, %i) ON DUPLICATE KEY UPDATE faction_level=%i", client->GetCharacterID(), itr->first, itr->second, itr->second); +} + +bool WorldDatabase::LoadPlayerFactions(Client* client) { + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading Player Factions..."); + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT faction_id, faction_level FROM character_factions where char_id=%i", client->GetCharacterID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + while(result && (row = mysql_fetch_row(result))){ + client->GetPlayer()->GetFactions()->SetFactionValue(atoul(row[0]), atol(row[1])); + } + } + if(query.GetErrorNumber()) + return false; + + return true; +} + +void WorldDatabase::SavePlayerMail(Mail* mail) { + Query query_update; + Query query_insert; + if (mail) { + if(mail->mail_id > 0) + query_update.RunQuery2(Q_UPDATE, "UPDATE `character_mail` SET `already_read`=%u, `coin_copper`=%u, `coin_silver`=%u, `coin_gold`=%u, `coin_plat`=%u, `char_item_id`=%u, `stack`=%u WHERE `id`=%u", mail->already_read, mail->coin_copper, mail->coin_silver, mail->coin_gold, mail->coin_plat, mail->char_item_id, mail->stack, mail->mail_id); + else if (mail->mail_id == 0 || query_update.GetAffectedRows() == 0) + { + query_insert.RunQuery2(Q_INSERT, "INSERT INTO `character_mail` (`player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time`) VALUES (%u, '%s', '%s', '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u)", mail->player_to_id, mail->player_from.c_str(), getSafeEscapeString(mail->subject.c_str()).c_str(), getSafeEscapeString(mail->mail_body.c_str()).c_str(), mail->already_read, mail->mail_type, mail->coin_copper, mail->coin_silver, mail->coin_gold, mail->coin_plat, mail->stack, mail->postage_cost, mail->attachment_cost, mail->char_item_id, mail->time_sent, mail->expire_time); + + if(!mail->mail_id) + mail->mail_id = query_insert.GetLastInsertedID(); + } + mail->save_needed = false; + } +} + +void WorldDatabase::SavePlayerMail(Client* client) { + if (client) { + MutexMap* mail_list = client->GetPlayer()->GetMail(); + MutexMap::iterator itr = mail_list->begin(); + while (itr.Next()) { + Mail* mail = itr->second; + if (mail->save_needed) + SavePlayerMail(mail); + } + } +} + +void WorldDatabase::LoadPlayerMail(Client* client, bool new_only) { + LogWrite(PLAYER__DEBUG, 0, "Player", "Loading Player Mail..."); + if (client) { + Query query; + MYSQL_RES* result; + if (new_only) + result = query.RunQuery2(Q_SELECT, "SELECT `id`, `player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time` FROM `character_mail` WHERE `player_to_id`=%u AND `unread`=1", client->GetCharacterID()); + else + result = query.RunQuery2(Q_SELECT, "SELECT `id`, `player_to_id`, `player_from`, `subject`, `mail_body`, `already_read`, `mail_type`, `coin_copper`, `coin_silver`, `coin_gold`, `coin_plat`, `stack`, `postage_cost`, `attachment_cost`, `char_item_id`, `time_sent`, `expire_time` FROM `character_mail` WHERE `player_to_id`=%u", client->GetCharacterID()); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + bool hasMail = false; + while (result && (row = mysql_fetch_row(result))) { + + int32 time_sent = atoul(row[15]); + if ( time_sent > Timer::GetUnixTimeStamp() ) + continue; // should have not been received yet + + hasMail = true; + Mail* mail = new Mail; + mail->mail_id = atoul(row[0]); + mail->player_to_id = atoul(row[1]); + mail->player_from = string(row[2]); + mail->subject = string(row[3]); + if (row[4]) + mail->mail_body = string(row[4]); + mail->already_read = atoi(row[5]); + mail->mail_type = atoi(row[6]); + mail->coin_copper = atoul(row[7]); + mail->coin_silver = atoul(row[8]); + mail->coin_gold = atoul(row[9]); + mail->coin_plat = atoul(row[10]); + mail->stack = atoi(row[11]); + mail->postage_cost = atoul(row[12]); + mail->attachment_cost = atoul(row[13]); + mail->char_item_id = atoul(row[14]); + mail->time_sent = time_sent; + mail->expire_time = atoul(row[16]); + mail->save_needed = false; + client->GetPlayer()->AddMail(mail); + + LogWrite(PLAYER__DEBUG, 5, "Player", "Loaded Mail ID %i, to: %i, from: %s", atoul(row[0]), atoul(row[1]), string(row[2]).c_str()); + + } + + if(hasMail) + client->SimpleMessage(CHANNEL_NARRATIVE, "You've got mail! :)"); + } + } +} + +void WorldDatabase::DeletePlayerMail(Mail* mail) { + Query query; + if (mail) + query.RunQuery2(Q_DELETE, "DELETE FROM `character_mail` WHERE `id`=%u", mail->mail_id); + LogWrite(PLAYER__DEBUG, 0, "Player", "Delete Player Mail..."); +} + +vector* WorldDatabase::GetAllPlayerIDs() { + Query query; + vector* ids = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id` FROM `characters`"); + MYSQL_ROW row; + while (result && (row = mysql_fetch_row(result))) { + if (ids == 0) + ids = new vector; + ids->push_back(atoul(row[0])); + } + return ids; +} + +void WorldDatabase::GetPetNames(ZoneServer* zone) +{ + DatabaseResult result; + int32 total = 0; + + if( database_new.Select(&result, "SELECT pet_name FROM spawn_pet_names") ) + { + while(result.Next()) + { + zone->pet_names.push_back(result.GetStringStr("pet_name")); + total++; + LogWrite(PET__DEBUG, 5, "Pet", "---Loading Pet Name: '%s'", result.GetStringStr("pet_name")); + } + LogWrite(PET__DEBUG, 0, "Pet", "--Loaded %u Pet Names", total); + } +} + + +int32 WorldDatabase::GetMaxHotBarID(){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM character_skillbar"); + int32 ret = 0; + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row && row[0]) + ret = strtoul(row[0], NULL, 0); + } + return ret; +} + +void WorldDatabase::SaveQuickBar(int32 char_id, vector* quickbar_items){ + vector::iterator itr; + QuickBarItem* qbi = 0; + for(itr = quickbar_items->begin(); itr != quickbar_items->end(); itr++){ + qbi = *itr; + if(!qbi) + continue; + if(qbi->deleted == false){ + Query query; + if(qbi->text.size > 0){ + query.AddQueryAsync(char_id, this, Q_REPLACE, "replace into character_skillbar (id, hotbar, slot, char_id, spell_id, type, text_val, tier) values(%u, %u, %u, %u, %u, %i, '%s', %i)", + qbi->unique_id, qbi->hotbar, qbi->slot, char_id, qbi->id, qbi->type, getSafeEscapeString(qbi->text.data.c_str()).c_str(), qbi->tier); + } + else{ + query.AddQueryAsync(char_id, this, Q_REPLACE, "replace into character_skillbar (id, hotbar, slot, char_id, spell_id, type, text_val, tier) values(%u, %u, %u, %u, %u, %i, 'Unused', %i)", + qbi->unique_id, qbi->hotbar, qbi->slot, char_id, qbi->id, qbi->type, qbi->tier); + } + } + else{ + Query query; + query.AddQueryAsync(char_id, this, Q_DELETE, "delete FROM character_skillbar where hotbar=%u and slot=%u and char_id=%u", qbi->hotbar, qbi->slot, char_id); + } + } +} + +map >* WorldDatabase::LoadSpellClasses(){ + map >* ret = new map >(); + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spell_id, adventure_class_id, tradeskill_class_id, level FROM spell_classes"); + MYSQL_ROW row; + LevelArray* level = 0; + while(result && (row = mysql_fetch_row(result))){ + level = new LevelArray(); + level->adventure_class = atoi(row[1]); + level->tradeskill_class = atoi(row[2]); + level->spell_level = atoi(row[3]); + (*ret)[atoul(row[0])].push_back(level); + } + return ret; +} + +void WorldDatabase::LoadTraits(){ + Query query; + MYSQL_ROW row; + TraitData* trait; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`, `level`, `class_req`, `race_req`, `isTrait`,`isInate`, `isFocusEffect`, `isTraining`,`tier`, `group`, `item_id` FROM spell_traits where is_active = 1"); + while (result && (row = mysql_fetch_row(result))){ + trait = new TraitData; + int8 i = 0; + trait->spellID = strtoul(row[0], NULL, 0); + trait->level = atoi(row[(++i)]); + trait->classReq = atoi(row[(++i)]); + trait->raceReq = atoi(row[(++i)]); + trait->isTrait = (atoi(row[(++i)]) == 0) ? false : true; + trait->isInate = (atoi(row[(++i)]) == 0) ? false : true; + trait->isFocusEffect = (atoi(row[(++i)]) == 0) ? false : true; + trait->isTraining = (atoi(row[(++i)]) == 0) ? false : true; + trait->tier = atoi(row[(++i)]); + trait->group = atoi(row[(++i)]); + trait->item_id = atoul(row[(++i)]); + + master_trait_list.AddTrait(trait); + } + + LogWrite(SPELL__INFO, 0, "Traits", "Loaded %u Trait(s)", master_trait_list.Size()); +} + +void WorldDatabase::LoadSpells() +{ + DatabaseResult result; + Spell *spell; + SpellData *data; + int32 t_now = Timer::GetUnixTimeStamp(); + int32 total = 0; + map >* level_data = LoadSpellClasses(); + + if( !database_new.Select(&result, "SELECT s.`id`, ts.spell_id, ts.index, `name`, `description`, `type`, `class_skill`, `min_class_skill_req`, `mastery_skill`, `tier`, `is_aa`,`hp_req`, `power_req`,`power_by_level`, `cast_time`, `recast`, `radius`, `max_aoe_targets`, `req_concentration`, `range`, `duration1`, `duration2`, `resistibility`, `hp_upkeep`, `power_upkeep`, `duration_until_cancel`, `target_type`, `recovery`, `power_req_percent`, `hp_req_percent`, `icon`, `icon_heroic_op`, `icon_backdrop`, `success_message`, `fade_message`, `fade_message_others`, `cast_type`, `lua_script`, `call_frequency`, `interruptable`, `spell_visual`, `effect_message`, `min_range`, `can_effect_raid`, `affect_only_group_members`, `hit_bonus`, `display_spell_tier`, `friendly_spell`, `group_spell`, `spell_book_type`, spell_type+0, s.is_active, savagery_req, savagery_req_percent, savagery_upkeep, dissonance_req, dissonance_req_percent, dissonance_upkeep, linked_timer_id, det_type, incurable, control_effect_type, cast_while_moving, casting_flags, persist_through_death, not_maintained, savage_bar, savage_bar_slot, soe_spell_crc, 0xffffffff-CRC32(s.`name`) as 'spell_name_crc', type_group_spell_id, can_fizzle, given_by " + "FROM (spells s, spell_tiers st) " + "LEFT JOIN spell_ts_ability_index ts " + "ON s.`id` = ts.spell_id " + "WHERE s.id = st.spell_id AND s.is_active = 1 " + "ORDER BY s.`id`, `tier`") ) + { + // error + } + else + { + while( result.Next() ) + { + data = new SpellData; + int32 spell_id = result.GetInt32Str("id"); + string spell_name = result.GetStringStr("name"); + + /* General Spell info */ + data->id = spell_id; + data->inherited_spell_id = spell_id; + data->soe_spell_crc = result.GetInt32Str("soe_spell_crc"); + data->tier = result.GetInt8Str("tier"); + data->ts_loc_index = result.GetInt8Str("index"); + data->name.data = spell_name.c_str(); + data->name.size = data->name.data.length(); + data->description.data = result.GetStringStr("description"); + data->description.size = data->description.data.length(); + data->icon = result.GetSInt16Str("icon"); + data->icon_heroic_op = result.GetInt16Str("icon_heroic_op"); + data->icon_backdrop = result.GetInt16Str("icon_backdrop"); + data->spell_visual = result.GetInt32Str("spell_visual"); + data->type = result.GetInt16Str("type"); + data->target_type = result.GetInt8Str("target_type"); + data->cast_type = result.GetInt8Str("cast_type"); + data->spell_book_type = result.GetInt32Str("spell_book_type"); + data->det_type = result.GetInt8Str("det_type"); + data->incurable = (result.GetInt8Str("incurable") == 1); + data->control_effect_type = result.GetInt8Str("control_effect_type"); + data->casting_flags = result.GetInt32Str("casting_flags"); + data->savage_bar = result.GetInt8Str("savage_bar"); + data->savage_bar_slot = result.GetInt8Str("savage_bar_slot"); + data->spell_type = result.IsNullStr("spell_type+0") ? 0 : result.GetInt8Str("spell_type+0"); + + + /* Toggles */ + data->interruptable = ( result.GetInt8Str("interruptable") == 1); + data->duration_until_cancel = ( result.GetInt8Str("duration_until_cancel") == 1); + data->can_effect_raid = result.GetInt8Str("can_effect_raid"); + data->affect_only_group_members = result.GetInt8Str("affect_only_group_members"); + data->display_spell_tier = result.GetInt8Str("display_spell_tier"); + data->friendly_spell = result.GetInt8Str("friendly_spell"); + data->group_spell = result.GetInt8Str("group_spell"); + data->is_active = result.GetInt8Str("is_active"); + data->persist_through_death = ( result.GetInt8Str("persist_through_death") == 1); + data->cast_while_moving = ( result.GetInt8Str("cast_while_moving") == 1); + data->not_maintained = ( result.GetInt8Str("not_maintained") == 1); + data->is_aa = (result.GetInt8Str("is_aa") == 1); + + /* Skill Requirements */ + data->class_skill = result.GetInt32Str("class_skill"); + data->min_class_skill_req = result.GetInt16Str("min_class_skill_req"); + data->mastery_skill = result.GetInt32Str("mastery_skill"); + + /* Cost */ + data->req_concentration = result.GetInt16Str("req_concentration"); + data->hp_req = result.GetInt16Str("hp_req"); + data->hp_upkeep = result.GetInt16Str("hp_upkeep"); + data->hp_req_percent = result.GetInt8Str("hp_req_percent"); + data->power_req = result.GetFloatStr("power_req"); + + + + + + data->power_by_level = ( result.GetInt8Str("power_by_level") == 0)? false : true; + data->power_upkeep = result.GetInt16Str("power_upkeep"); + data->power_req_percent = result.GetInt8Str("power_req_percent"); + data->savagery_req = result.GetInt16Str("savagery_req"); + data->savagery_upkeep = result.GetInt16Str("savagery_upkeep"); + data->savagery_req_percent = result.GetInt8Str("savagery_req_percent"); + data->dissonance_req = result.GetInt16Str("dissonance_req"); + data->dissonance_upkeep = result.GetInt16Str("dissonance_upkeep"); + data->dissonance_req_percent = result.GetInt8Str("dissonance_req_percent"); + + /* Spell Parameters */ + data->call_frequency = result.GetInt32Str("call_frequency"); + data->orig_cast_time = result.GetInt16Str("cast_time"); + data->cast_time = result.GetInt16Str("cast_time"); + data->duration1 = result.GetInt32Str("duration1"); + data->duration2 = result.GetInt32Str("duration2"); + data->hit_bonus = result.GetFloatStr("hit_bonus"); + data->max_aoe_targets = result.GetInt16Str("max_aoe_targets"); + data->min_range = result.GetFloatStr("min_range"); + data->radius = result.GetFloatStr("radius"); + data->range = result.GetFloatStr("range"); + data->recast = result.GetFloatStr("recast"); + data->recovery = result.GetFloatStr("recovery"); + data->resistibility = result.GetFloatStr("resistibility"); + data->linked_timer = result.GetInt32Str("linked_timer_id"); + data->spell_name_crc = result.GetInt32Str("spell_name_crc"); + data->type_group_spell_id = result.GetSInt32Str("type_group_spell_id"); + data->can_fizzle = ( result.GetInt8Str("can_fizzle") == 1); + + string given_by = result.GetStringStr("given_by"); + data->given_by.data = given_by.c_str(); + data->given_by.size = data->given_by.data.length(); + + std::string givenType(given_by); + boost::algorithm::to_lower(givenType); + if(givenType == "unset" || givenType.length() < 1) { + data->given_by_type == GivenByType::GivenBy_Unset; + } + else if(givenType == "tradeskillclass") { + data->given_by_type = GivenByType::GivenBy_TradeskillClass; + } + else if(givenType == "spellscroll") { + data->given_by_type = GivenByType::GivenBy_SpellScroll; + } + else if(givenType == "alternateadvancement") { + data->given_by_type = GivenByType::GivenBy_AltAdvancement; + } + else if(givenType == "race") { + data->given_by_type = GivenByType::GivenBy_Race; + } + else if(givenType == "racialinnate") { + data->given_by_type = GivenByType::GivenBy_RacialInnate; + } + else if(givenType == "racialtradition") { + data->given_by_type = GivenByType::GivenBy_RacialTradition; + } + else if(givenType == "class") { + data->given_by_type = GivenByType::GivenBy_Class; + } + else if(givenType == "charactertrait") { + data->given_by_type = GivenByType::GivenBy_CharacterTrait; + } + else if(givenType == "focusabilities") { + data->given_by_type = GivenByType::GivenBy_FocusAbility; + } + else if(givenType == "classtraining") { + data->given_by_type = GivenByType::GivenBy_ClassTraining; + } + else if(givenType == "warderspell") { + data->given_by_type = GivenByType::GivenBy_WarderSpell; + } + else { + data->given_by_type = GivenByType::GivenBy_Unset; + } + + /* Cast Messaging */ + string message = result.GetStringStr("success_message"); + if( message.length() > 0 ) + data->success_message = message; + + message = result.GetStringStr("fade_message"); + if( message.length() > 0 ) + data->fade_message = string(message); + + message = result.GetStringStr("fade_message_others"); + if (message.length() > 0) + data->fade_message_others = string(message); + + message = result.GetStringStr("effect_message"); + if( message.length() > 0 ) + data->effect_message = string(message); + + string lua_script = result.GetStringStr("lua_script"); + if( lua_script.length() > 0 ) + data->lua_script = string(lua_script); + + + /* Load spell level data */ + spell = new Spell(data); + + if(level_data && level_data->count(data->id) > 0) + { + vector* level_array = &((*level_data)[data->id]); + + for(int8 i=0; isize(); i++) + { + spell->AddSpellLevel(level_array->at(i)->adventure_class, level_array->at(i)->tradeskill_class, level_array->at(i)->spell_level*10); + } + } + + + /* Add spell to master list */ + master_spell_list.AddSpell(data->id, data->tier, spell); + total++; + + if( lua_script.length() > 0 ) + LogWrite(SPELL__DEBUG, 5, "Spells", "\t%i. %s (Tier: %i) - '%s'", spell_id, spell_name.c_str(), data->tier, lua_script.c_str()); + else if(data->is_active) + LogWrite(SPELL__WARNING, 1, "Spells", "\tSpell %s (%u, Tier: %i) set 'Active', but missing LUAScript", spell_name.c_str(), spell_id, data->tier); + + } // end while + } // end else + + LogWrite(SPELL__DEBUG, 0, "Spells", "Loading Spell Effects..."); + LoadSpellEffects(); + + LogWrite(SPELL__DEBUG, 0, "Spells", "Loading Spell LUA Data..."); + LoadSpellLuaData(); + + if(lua_interface) + { + LogWrite(SPELL__DEBUG, 0, "Spells", "Loading Spells Scripts..."); + LoadSpellScriptData(); + } + + if (level_data) { + map >::iterator map_itr; + vector::iterator level_itr; + + for(map_itr = level_data->begin(); map_itr != level_data->end(); map_itr++) + { + for(level_itr = map_itr->second.begin(); level_itr != map_itr->second.end(); level_itr++) + { + safe_delete(*level_itr); + } + } + } + + safe_delete(level_data); + + LogWrite(SPELL__INFO, 0, "Spells", "Loaded %u Spell%s (took %u seconds)", total, total == 1 ? "" : "s", Timer::GetUnixTimeStamp() - t_now); +} + +void WorldDatabase::LoadSpellLuaData(){ + Spell *spell; + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES *result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`,`tier`,`value_type`,`value`,`value2`,`dynamic_helper` " + "FROM `spell_data` " + "ORDER BY `index_field`"); + + while (result && (row = mysql_fetch_row(result))) { + if ((spell = master_spell_list.GetSpell(atoul(row[0]), atoi(row[1]))) && row[2] && row[3] && row[4] && row[4]) { + + LogWrite(SPELL__DEBUG, 5, "Spells", "\tLoading Spell LUA Data for spell_id: %u", atoul(row[0])); + + if (!strcmp(row[2], "INT")) + spell->AddSpellLuaDataInt(atoi(row[3]), atoi(row[4]), string(row[5])); + else if (!strcmp(row[2], "FLOAT")) + spell->AddSpellLuaDataFloat(atof(row[3]), atof(row[4]),string(row[5])); + else if (!strcmp(row[2], "BOOL")) + spell->AddSpellLuaDataBool(!(strncasecmp(row[3], "true", 4)), string(row[5])); + else if (!strcmp(row[2], "STRING")) + spell->AddSpellLuaDataString(string(row[3]), string(row[4]), string(row[5])); + else + LogWrite(SPELL__ERROR, 0, "Spells", "Invalid Lua Spell data '%s' for Spell ID: %u", row[2], spell->GetSpellID()); + total++; + } + } + LogWrite(SPELL__DEBUG, 0, "Spells", "\tLoaded %i Spell LUA Data entr%s.", total, total == 1 ? "y" : "ies"); +} + +void WorldDatabase::LoadSpellEffects() { + Spell *spell; + Query query; + MYSQL_ROW row; + int32 total = 0; + MYSQL_RES *result = query.RunQuery2(Q_SELECT, "SELECT `spell_id`,`tier`,`percentage`,`bullet`,`description` " + "FROM `spell_display_effects` " + "ORDER BY `spell_id`,`id` ASC"); + + while (result && (row = mysql_fetch_row(result))) { + if ((spell = master_spell_list.GetSpell(atoul(row[0]), atoi(row[1]))) && row[4]) { + + LogWrite(SPELL__DEBUG, 5, "Spells", "\tLoading Spell Effects for spell_id: %u", atoul(row[0])); + + spell->AddSpellEffect(atoi(row[2]), atoi(row[3]), row[4]); + total++; + } + } + LogWrite(SPELL__DEBUG, 0, "Spells", "\tLoaded %u Spell Effect%s.", total, total == 1 ? "" : "s"); +} + +int32 WorldDatabase::LoadPlayerSkillbar(Client* client){ + client->GetPlayer()->ClearQuickbarItems(); + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, type, spell_id, slot, text_val, hotbar, tier FROM character_skillbar where char_id = %u", client->GetCharacterID()); + MYSQL_ROW row; + int32 count = 0; + while(result && (row = mysql_fetch_row(result))){ + count++; + int8 tier = atoi(row[6]); + Spell* spell = master_spell_list.GetSpell(atoul(row[2]), tier); + if(spell) + client->GetPlayer()->AddQuickbarItem(atoul(row[5]), atoul(row[3]), atoul(row[1]), spell->GetSpellIcon(), spell->GetSpellIconBackdrop(), spell->GetSpellID(), spell->GetSpellTier(), atoul(row[0]), row[4], false); + else if(atoul(row[1]) == QUICKBAR_MACRO) + client->GetPlayer()->AddQuickbarItem(atoul(row[5]), atoul(row[3]), atoul(row[1]), client->GetPlayer()->macro_icons[atoul(row[2])], 0xFFFF, atoul(row[2]), 0, atoul(row[0]), row[4], false); + else + client->GetPlayer()->AddQuickbarItem(atoul(row[5]), atoul(row[3]), atoul(row[1]), 0, 0, atoul(row[2]), 0, atoul(row[0]), row[4], false); + } + return count; +} + +bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){ + Guild *guild; + + if((guild = guild_list.GetGuild(GetGuildIDByCharacterID(character_id)))) + guild->RemoveGuildMember(character_id); + + Query query; + //devn00b: Update this whole thing we were missing many tables. This is ugly but swapped 99% of the delete to async to lighten server load. + + //Do character delete immediately. + query.RunQuery2(Q_DELETE, "DELETE FROM characters WHERE id=%u AND account_id=%u", character_id, account_id); + + //if no character then we shouldn't bother with the rest. + if (!query.GetAffectedRows()) + { + //No error just in case ppl try doing stupid stuff + return false; + } + + //Async + query.AddQueryAsync(character_id, this, Q_DELETE, "DELETE FROM character_languages WHERE char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "DELETE FROM character_quest_rewards WHERE char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "DELETE FROM character_quest_temporary_rewards WHERE char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "DELETE FROM character_claim_items where char_id=%u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from charactersproperties where charid = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_aa where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_achievements where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_achievements_items where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_buyback where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_collections where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_collection_items where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_details where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_factions where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_history where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_houses where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_instances where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_items where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_items_group_members where character_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_lua_history where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_macros where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_pictures where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_properties where charid = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_quests where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_quest_progress where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_recipes where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_recipe_books where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_skillbar where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_skills where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_social where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spells where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spell_effects where caster_char_id = %u or target_char_id = %u", character_id, character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spell_effect_targets where caster_char_id = %u or target_char_id = %u", character_id, character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_spirit_shards where charid = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_titles where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from char_colors where char_id = %u", character_id); + query.AddQueryAsync(character_id, this, Q_DELETE, "delete from statistics where char_id = %u", character_id); + + return true; +} + +void WorldDatabase::DeleteCharacterSpell(int32 character_id, int32 spell_id) { + if (character_id > 0 && spell_id > 0) { + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM character_spells WHERE char_id=%u AND spell_id=%u", character_id, spell_id); + } +} + +bool WorldDatabase::GetItemResultsToClient (Client* client, const char* varSearch, int maxResults) { + Query query; + MYSQL_ROW row; + int results = 0; + + if( maxResults > 10 && client->GetAdminStatus ( ) < 100 ) + maxResults = 10; + else if( maxResults > 20 ) + maxResults = 20; + + client->Message(CHANNEL_COLOR_YELLOW, "Item Search Results"); + client->Message(CHANNEL_COLOR_YELLOW, "ResultNum) [ItemID] ItemName"); + string itemsearch_query = string("SELECT id, name FROM items where name like '%%%s%%' limit %i"); + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, itemsearch_query.c_str(),getSafeEscapeString(varSearch).c_str(),maxResults); + while(result && (row = mysql_fetch_row(result))){ + results++; + client->Message(CHANNEL_COLOR_YELLOW, "%i) [%s] %s",results,row[0],row[1]); + } + + if(results == 0) + { + client->Message(CHANNEL_COLOR_YELLOW, "No Items Found."); + return false; + } + + client->Message(CHANNEL_COLOR_YELLOW, "%i Items Found.",results); + return true; +} + +void WorldDatabase::SaveWorldTime(WorldTime* time){ + Query query; + query.RunQuery2(Q_REPLACE, "replace into variables (variable_name, variable_value) values('gametime', '%i/%i/%i %i:%i')", time->month, time->day, time->year, time->hour, time->minute); +} + +void WorldDatabase::SaveBugReport(const char* category, const char* subcategory, const char* causes_crash, const char* reproducible, const char* summary, const char* description, const char* version, const char* player, int32 account_id, const char* spawn_name, int32 spawn_id, int32 zone_id){ + Query query; + + int32 dbVersion = rule_manager.GetGlobalRule(R_World, DatabaseVersion)->GetInt32(); + + string bug_report = string("insert into bugs (category, subcategory, causes_crash, reproducible, summary, description, version, player, account_id, spawn_name, spawn_id, zone_id, dbversion, worldversion) values('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %u, '%s', %u, %u, %u, '%s')"); + query.RunQuery2(Q_INSERT, bug_report.c_str(), getSafeEscapeString(category).c_str(), getSafeEscapeString(subcategory).c_str(), + getSafeEscapeString(causes_crash).c_str(), getSafeEscapeString(reproducible).c_str(), getSafeEscapeString(summary).c_str(), + getSafeEscapeString(description).c_str(), getSafeEscapeString(version).c_str(), getSafeEscapeString(player).c_str(), account_id, + getSafeEscapeString(spawn_name).c_str(), spawn_id, zone_id, dbVersion, CURRENT_VERSION); + FixBugReport(); + FixBugReport(); + FixBugReport(); +} + +void WorldDatabase::FixBugReport(){ + Query query; + string bug_report = string("update bugs set description = REPLACE(description,SUBSTRING(description,INSTR(description,'%'), 3),char(CONV(SUBSTRING(description,INSTR(description,'%')+1, 2), 16, 10))), summary = REPLACE(summary,SUBSTRING(summary,INSTR(summary,'%'), 3),char(CONV(SUBSTRING(summary,INSTR(summary,'%')+1, 2), 16, 10)))"); + query.RunQuery2(bug_report.c_str(), Q_UPDATE); +} + +int32 WorldDatabase::LoadQuests(){ + Query query; + MYSQL_ROW row; + std::string querystr = std::string("SELECT `quest_id`, `name`, `type`, `zone`, `level`, `enc_level`, `description`, `lua_script`, `completed_text`, `spawn_id`, `shareable_flag`, `deleteable` FROM `quests`"); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, querystr.c_str()); + Quest* quest = 0; + char* name = 0; + char* type = 0; + char* zone = 0; + int8 level = 0; + int8 enc_level = 0; + char* description = 0; + char* script = 0; + int32 total = 0; + int32 id = 0; + char* completed_description = 0; + int32 return_npc_id = 0; + if(result){ + while(result && (row = mysql_fetch_row(result))){ + id = atoul(row[0]); + name = row[1]; + type = row[2]; + zone = row[3]; + level = atoul(row[4]); + enc_level = atoul(row[5]); + description = row[6]; + script = row[7]; + completed_description = row[8]; + return_npc_id = atoi(row[9]); + if(lua_interface) { + quest = lua_interface->LoadQuest(id, name, type, zone, level, description, script); + } + if(quest){ + + LogWrite(QUEST__DEBUG, 5, "Quests", "\tLoading Quest: '%s' (%u)", name, id); + + LoadQuestDetails(quest); + string compDescription; + if (completed_description == NULL) + { + compDescription = string("Missing! Notify Developer"); + LogWrite(QUEST__WARNING, 5, "Quests", "\tLoading Quest MISSING completed_text in quests table for: '%s' (%u)", name, id); + } + else + compDescription = string(completed_description); + + quest->SetCompletedDescription(string(compDescription)); + quest->SetQuestReturnNPC(return_npc_id); + quest->SetEncounterLevel(enc_level); + quest->SetQuestShareableFlag(atoul(row[10])); + quest->SetCanDeleteQuest(atoul(row[11])); + total++; + master_quest_list.AddQuest(id, quest); + } + else + { + LogWrite(QUEST__ERROR, 5, "Quests", "\tFAILED LOADING QUEST: '%s' (%u), check that script file exists/permissions correct: %s", name, id, (script && strlen(script)) ? script : "Not Set, Missing!"); + } + } + } + LogWrite(QUEST__DEBUG, 0, "Quest", "\tLoaded %i Quest(s)", total); + return total; +} + +void WorldDatabase::LoadQuestDetails(Quest* quest) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `type`, `subtype`, `value`, `faction_id`, `quantity` FROM `quest_details` WHERE `quest_id`=%u", quest->GetQuestID()); + string type; + string subtype; + sint64 value; + int32 faction_id; + int32 quantity; + while (result && (row = mysql_fetch_row(result))) { + type = string(row[0]); + subtype = string(row[1]); + value = atoi(row[2]); + faction_id = atoi(row[3]); + quantity = atoi(row[4]); + + + LogWrite(QUEST__DEBUG, 5, "Quests", "\t- Type: %s, SubType: %s, Val: %u, Faction: %u, Qty: %u", type.c_str(), subtype.c_str(), value, faction_id, quantity); + + + if (type == "Prereq") { + if (subtype == "Class") + quest->AddPrereqClass(value); + else if (subtype == "Faction") + quest->AddPrereqFaction(faction_id, value); + else if (subtype == "Item") { + Item* master_item = master_item_list.GetItem(value); + if (master_item) { + Item* item = new Item(master_item); + quest->AddPrereqItem(item); + } + } + else if (subtype == "AdvLevel") + quest->SetPrereqLevel(value); + else if (subtype == "Quest") + quest->AddPrereqQuest(value); + else if (subtype == "Race") + quest->AddPrereqRace(value); + else if (subtype == "TSClass") + quest->AddPrereqTradeskillClass(value); + else if (subtype == "TSLevel") + quest->SetPrereqTSLevel(value); + else if (subtype == "MaxTSLevel") + quest->SetPrereqMaxTSLevel(value); + else if (subtype == "MaxAdvLevel") + quest->SetPrereqMaxLevel(value); + } + else if (type == "Reward") { + if (subtype == "Item") { + Item* master_item = master_item_list.GetItem(value); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddRewardItem(item); + } + } + else if (subtype == "Selectable") { + Item* master_item = master_item_list.GetItem(value); + if (master_item) { + Item* item = new Item(master_item); + item->details.count = quantity; + quest->AddSelectableRewardItem(item); + } + } + else if (subtype == "Coin") { + int32 copper = 0; + int32 silver = 0; + int32 gold = 0; + int32 plat = 0; + if (value >= 1000000) { + plat = value / 1000000; + value -= 1000000 * plat; + } + if (value >= 10000) { + gold = value / 10000; + value -= 10000 * gold; + } + if (value >= 100) { + silver = value / 100; + value -= 100 * silver; + } + if (value > 0) + copper = value; + quest->AddRewardCoins(copper, silver, gold, plat); + } + else if (subtype == "MaxCoin") { + quest->AddRewardCoinsMax(value); + } + else if (subtype == "Faction") + quest->AddRewardFaction(faction_id, value); + else if (subtype == "Experience") + quest->SetRewardXP(value); + else if (subtype == "TSExperience") + quest->SetRewardTSXP(value); + } + } +} + +void WorldDatabase::LoadMerchantInformation() { + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "\tClearing Merchant Inventory..."); + world.DeleteMerchantItems(); + + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "\tLoading Merchant Inventory..."); + LoadMerchantInventory(); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT merchant_id, inventory_id FROM merchants ORDER BY merchant_id"); + int32 total = 0; + int32 last_merchant_id = 0; + int32 id = 0; + MerchantInfo* merchant = 0; + if(result) { + while(result && (row = mysql_fetch_row(result))) { + id = atoul(row[0]); + + LogWrite(MERCHANT__DEBUG, 5, "Merchant", "\tMerchantID: %u, InventoryID: %u", id, atoul(row[1])); + + if(id != last_merchant_id) { + if(merchant) + world.AddMerchantInfo(last_merchant_id, merchant); + merchant = new MerchantInfo; + last_merchant_id = id; + total++; + } + merchant->inventory_ids.push_back(atoul(row[1])); + } + if(merchant) + world.AddMerchantInfo(last_merchant_id, merchant); + } + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "\tLoaded %i Merchant List(s)", total); +} + +void WorldDatabase::LoadMerchantInventory(){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT inventory_id, item_id, quantity, price_item_id, price_item_qty, price_item2_id, price_item2_qty, price_status, price_coins, price_stationcash FROM merchant_inventory ORDER BY inventory_id"); + int32 total = 0; + int32 id; + + if(result) { + while(result && (row = mysql_fetch_row(result))) { + MerchantItemInfo ItemInfo; + id = atoul(row[0]); + ItemInfo.item_id = atoul(row[1]); + ItemInfo.quantity = atoi(row[2]); + ItemInfo.price_item_id = atoul(row[3]); + ItemInfo.price_item_qty = atoi(row[4]); + ItemInfo.price_item2_id = atoul(row[5]); + ItemInfo.price_item2_qty = atoi(row[6]); + ItemInfo.price_status = atoul(row[7]); + ItemInfo.price_coins = atoul(row[8]); + ItemInfo.price_stationcash = atoul(row[9]); + LogWrite(MERCHANT__DEBUG, 5, "Merchant", "\tInventoryID: %u, ItemID: %u, Qty: %u", id, ItemInfo.item_id, ItemInfo.quantity); + world.AddMerchantItem(id, ItemInfo); + total++; + } + } + LogWrite(MERCHANT__DEBUG, 0, "Merchant", "\tLoaded %i Merchant Inventory Item(s)", total); +} + +string WorldDatabase::GetMerchantDescription(int32 merchant_id) { + Query query; + MYSQL_ROW row; + string description; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `description` FROM `merchants` WHERE `merchant_id`=%u", merchant_id); + if (result && (row = mysql_fetch_row(result))) + description = string(row[0]); + return description; +} + +void WorldDatabase::LoadTransporters(ZoneServer* zone){ + int32 total = 0; + zone->DeleteGlobalTransporters(); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT transport_id, transport_type, display_name, message, destination_zone_id, destination_x, destination_y, destination_z, destination_heading, trigger_location_zone_id, trigger_location_x, trigger_location_y, trigger_location_z, trigger_radius, cost, id, min_level, max_level, quest_req, quest_step_req, quest_completed, map_x, map_y, expansion_flag, holiday_flag, min_client_version, max_client_version, flight_path_id, mount_id, mount_red_color, mount_green_color, mount_blue_color FROM transporters ORDER BY transport_id"); + if(result){ + while(result && (row = mysql_fetch_row(result))){ + LogWrite(TRANSPORT__DEBUG, 5, "Transport", "---Loading Transporter ID: %u, transport_type: %s", row[0], row[1]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---display_name: %s, message: %s", row[2], row[3]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---destination_zone_id: %s", row[4]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---destination_x: %s, destination_y: %s, destination_z: %s, destination_heading: %s", row[5], row[6], row[7], row[8]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---trigger_location_zone_id: %s", row[9]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---trigger_location_x: %s, trigger_location_y: %s, trigger_location_z: %s", row[10], row[11], row[12], row[13]); + LogWrite(TRANSPORT__DEBUG, 7, "Transport", "---trigger_radius: %s, cost: %s, id: %s", row[14], row[15]); + string name = ""; + if(row[2]) + name = string(row[2]); + string message = ""; + if(row[3]) + message = string(row[3]); + + if(row[1] && strcmp(row[1], "Zone") == 0) + zone->AddTransporter(atoul(row[0]), TRANSPORT_TYPE_ZONE, name, message, atoul(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atoul(row[14]), atoul(row[15]), atoi(row[16]), atoi(row[17]), atoul(row[18]), atoi(row[19]), atoul(row[20]), atoul(row[21]), atoul(row[22]), atoul(row[23]), atoul(row[24]), atoul(row[25]), atoul(row[26]), atoul(row[27]), atoul(row[28]), atoul(row[29]), atoul(row[30]), atoul(row[31])); + else if (row[1] && strcmp(row[1], "Flight") == 0) + zone->AddTransporter(atoul(row[0]), TRANSPORT_TYPE_FLIGHT, name, message, atoul(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atoul(row[14]), atoul(row[15]), atoi(row[16]), atoi(row[17]), atoul(row[18]), atoi(row[19]), atoul(row[20]), atoul(row[21]), atoul(row[22]), atoul(row[23]), atoul(row[24]), atoul(row[25]), atoul(row[26]), atoul(row[27]), atoul(row[28]), atoul(row[29]), atoul(row[30]), atoul(row[31])); + else if(row[1] && strcmp(row[1], "Location") == 0) + zone->AddLocationTransporter(atoul(row[9]), message, atof(row[10]), atof(row[11]), atof(row[12]), atof(row[13]), atoul(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atoul(row[14]), atoul(row[15])); + else + zone->AddTransporter(atoul(row[0]), TRANSPORT_TYPE_GENERIC, "", message, atoul(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atof(row[8]), atoul(row[14]), atoul(row[15]), atoi(row[16]), atoi(row[17]), atoul(row[18]), atoi(row[19]), atoul(row[20]), atoul(row[21]), atoul(row[22]), atoul(row[23]), atoul(row[24]), atoul(row[25]), atoul(row[26]), atoul(row[27]), atoul(row[28]), atoul(row[29]), atoul(row[30]), atoul(row[31])); + total++; + } + } + LogWrite(TRANSPORT__DEBUG, 0, "Transport", "--Loaded %i Transporter(s)", total); + LoadTransportMaps(zone); +} + +void WorldDatabase::LoadFogInit(string zone, PacketStruct* packet) +{ + LogWrite(WORLD__TRACE, 9, "World", "Enter: %s", __FUNCTION__); + + if(!packet || zone.length() == 0) + return; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT highest, lowest, zone_name, explored_map_name, unexplored_map_name, bounds1_x, bounds1_z, bounds2_x, bounds2_z, bounds3_x, bounds3_z, bounds4_x, bounds4_z, explored_key, unexplored_key, map_id FROM map_data where zone_name like '%s%%'", getSafeEscapeString(&zone).c_str()); + if(result){ + int count = mysql_num_rows(result); + int i=0; + int64 explored_key; + int64 unexplored_key; + packet->setArrayLengthByName("num_maps", count); + while(result && (row = mysql_fetch_row(result))){ + packet->setDataByName("highest_z", atof(row[0])); + packet->setDataByName("lowest_z", atof(row[1])); + packet->setDataByName("map_id", atoul(row[15])); + packet->setArrayDataByName("unknown7", 1600, i); + packet->setArrayDataByName("unknown8", 1200, i); + packet->setArrayDataByName("zone_name", row[2], i); + packet->setArrayDataByName("explored_map_name", row[3], i); + packet->setArrayDataByName("unexplored_map_name", row[4], i); + packet->setArrayDataByName("map_bounds1_x", atof(row[5]), i); + packet->setArrayDataByName("map_bounds1_z", atof(row[6]), i); + packet->setArrayDataByName("map_bounds2_x", atof(row[7]), i); + packet->setArrayDataByName("map_bounds2_z", atof(row[8]), i); + packet->setArrayDataByName("map_bounds3_x", atof(row[9]), i); + packet->setArrayDataByName("map_bounds3_z", atof(row[10]), i); + packet->setArrayDataByName("map_bounds4_x", atof(row[11]), i); + packet->setArrayDataByName("map_bounds4_z", atof(row[12]), i); +#ifdef WIN32 + explored_key = _strtoui64(row[13], NULL, 10); + unexplored_key = _strtoui64(row[14], NULL, 10); +#else + explored_key = strtoull(row[13], 0, 10); + unexplored_key = strtoull(row[14], 0, 10); +#endif + packet->setArrayDataByName("explored_key", explored_key, i); + packet->setArrayDataByName("unexplored_key", unexplored_key, i); + i++; + } + } + LogWrite(WORLD__TRACE, 9, "World", "Exit: %s", __FUNCTION__); +} + +string WorldDatabase::GetColumnNames(char* name){ + Query query; + MYSQL_ROW row; + string columns = ""; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "show columns FROM %s", name); + if(result && mysql_num_rows(result) > 0){ + int16 i = 0; + while((row = mysql_fetch_row(result))){ + if(strcmp(row[0], "table_data_version") != 0){ + if(i>0) + columns.append(","); + columns.append(row[0]); + i++; + } + } + } + columns.append(""); + return columns; +} + +void WorldDatabase::ToggleCharacterOnline() { + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE characters SET is_online = 0;"); +} + +void WorldDatabase::ToggleCharacterOnline(Client* client, int8 toggle) { + if (client) { + Query query; + Player* player = client->GetPlayer(); + //if(!player->CheckPlayerInfo()) + // return; + if (player) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "Toggling Character %s", toggle ? "ONLINE!" : "OFFLINE!"); + query.RunQuery2(Q_UPDATE, "UPDATE characters SET is_online=%i WHERE id = %u;", toggle, client->GetCharacterID()); + } + } +} + +void WorldDatabase::LoadPlayerStatistics(Player* player, int32 char_id) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT stat_id, stat_value, stat_date FROM statistics WHERE char_id=%i", char_id); + while (result && (row = mysql_fetch_row(result))) { + int32 stat_id = atoi(row[0]); + sint32 stat_value = atoi(row[1]); + int32 stat_date = atoi(row[2]); + player->AddPlayerStatistic(stat_id, stat_value, stat_date); + } +} + +void WorldDatabase::WritePlayerStatistic(Player *player, Statistic* stat) { + if (player && player->GetCharacterID() > 0 && stat) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO statistics (char_id, guild_id, stat_id, stat_value, stat_date) VALUES(%i, %i, %i, %i, %i) ON DUPLICATE KEY UPDATE stat_value = %i, stat_date = %i;", + player->GetCharacterID(), 0, stat->stat_id, stat->stat_value, stat->stat_date, + stat->stat_value, stat->stat_date); + } +} + +void WorldDatabase::LoadServerStatistics() +{ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT stat_id, stat_value, stat_date FROM statistics WHERE char_id=0 AND guild_id=0"); + while (result && (row = mysql_fetch_row(result))) { + int32 stat_id = atoi(row[0]); + sint32 stat_value = atoi(row[1]); + int32 stat_date = atoi(row[2]); + world.AddServerStatistic(stat_id, stat_value, stat_date); + LogWrite(INIT__DEBUG, 5, "Stats", "Loading Stat ID %i, value: %i", stat_id, stat_value); + } +} + +void WorldDatabase::WriteServerStatistic(Statistic* stat) { + if (stat) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO statistics (char_id, guild_id, stat_id, stat_value, stat_date) VALUES(0, 0, %i, %i, %i) ON DUPLICATE KEY UPDATE stat_value = %i, stat_date = %i;", + stat->stat_id, stat->stat_value, stat->stat_date, + stat->stat_value, stat->stat_date); + } +} + +void WorldDatabase::WriteServerStatistic(int32 stat_id, sint32 stat_value) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO statistics (char_id, guild_id, stat_id, stat_value, stat_date) VALUES(0, 0, %i, %i, %i) ON DUPLICATE KEY UPDATE stat_value = %i, stat_date = %i;", + stat_id, stat_value, Timer::GetUnixTimeStamp(), + stat_value, Timer::GetUnixTimeStamp()); +} + +void WorldDatabase::WriteServerStatisticsNeededQueries() { + Query query1, query2, query3; + MYSQL_ROW row1, row2, row3; + + // Number of unique accounts + MYSQL_RES* result1 = query1.RunQuery2(Q_SELECT, "SELECT COUNT(DISTINCT account_id) FROM characters"); + if (result1 && (row1 = mysql_fetch_row(result1)) && row1[0] != NULL) + WriteServerStatistic(STAT_SERVER_NUM_ACCOUNTS, atoi(row1[0])); + else + WriteServerStatistic(STAT_SERVER_NUM_ACCOUNTS, 0); + + // Number of characters + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT COUNT(id) FROM characters"); + if (result2 && (row2 = mysql_fetch_row(result2)) && row2[0] != NULL) + WriteServerStatistic(STAT_SERVER_NUM_CHARACTERS, atoi(row2[0])); + else + WriteServerStatistic(STAT_SERVER_NUM_CHARACTERS, 0); + + // Average character level + MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT ROUND(AVG(level)) FROM characters"); + if (result3 && (row3 = mysql_fetch_row(result3)) && row3[0] != NULL) + WriteServerStatistic(STAT_SERVER_AVG_CHAR_LEVEL, atoi(row3[0])); + else + WriteServerStatistic(STAT_SERVER_AVG_CHAR_LEVEL, 0); +} + +map* WorldDatabase::GetInstanceRemovedSpawns(int32 instance_id, int8 type) +{ + DatabaseResult result; + map* ret = NULL; + + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Loading removed spawns for instance_id: %u, spawn_type: %i", instance_id, type); + + if( !database_new.Select(&result, "SELECT spawn_location_entry_id, respawn_time FROM instance_spawns_removed WHERE instance_id = %i AND spawn_type = %i", instance_id, type) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in GetInstanceRemovedSpawns() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return ret; + } + else + { + if( result.GetNumRows() > 0 ) + { + ret = new map; + + while( result.Next() ) + { + int32 spawn_location_entry_id = result.GetInt32Str("spawn_location_entry_id"); + /* + respawnTime == 0 - never respawn + respawnTime = 1 - spawn now + respawnTime > 1 (continue timer) + */ + int32 respawntime = result.GetInt32Str("respawn_time"); + + LogWrite(INSTANCE__DEBUG, 5, "Instance", "Found spawn point: %u, respawn time: %i", spawn_location_entry_id, respawntime); + + ret->insert(make_pair(spawn_location_entry_id, respawntime)); + } + } + else + LogWrite(INSTANCE__DEBUG, 0, "Instance", "No removed spawns found for instance_id: %u, spawn_type: %i", instance_id, type); + + } + + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + + return ret; +} + +bool WorldDatabase::CheckVectorForValue(vector* vector, int32 value) { + if ( vector != NULL ) + { + for(int32 i=0;isize();i++) + { + int32 compare = vector->at(i); + if ( compare == value ) + return true; + } + } + + return false; +} + +int32 WorldDatabase::CheckSpawnRemoveInfo(map* inmap, int32 spawn_location_entry_id) +{ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + map::iterator iter; + + if ( inmap != NULL ) + { + for(iter=inmap->begin();iter!=inmap->end();iter++) + { + if ( iter->first == spawn_location_entry_id ) + return (int32)iter->second; + } + } + + return 1; +} + +int32 WorldDatabase::AddCharacterInstance(int32 char_id, int32 instance_id, string zone_name, int8 instance_type, int32 last_success, int32 last_failure, int32 success_lockout, int32 failure_lockout) +{ + int32 ret = 0; + if( !database_new.Query("INSERT INTO character_instances (char_id, instance_id, instance_zone_name, instance_type, last_success_timestamp, last_failure_timestamp, success_lockout_time, failure_lockout_time) VALUES (%u, %u, '%s', %i, %u, %u, %u, %u) ", char_id, instance_id, database_new.EscapeStr(zone_name).c_str(), instance_type, last_success, last_failure, success_lockout, failure_lockout) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in AddCharacterInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return 0; + } + ret = database_new.LastInsertID(); + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Adding character %u to instance: %u", char_id, instance_id); + //LogWrite(INSTANCE__DEBUG, 1, "Instance", "-- Reenter: %u, Reset: %u, Lockout: %u", grant_reenter_time_left, grant_reset_time_left, lockout_time); + + return ret; +} + +bool WorldDatabase::UpdateCharacterInstanceTimers(int32 char_id, int32 instance_id, int32 lockout_time, int32 reset_time, int32 reenter_time ) +{ + if ( lockout_time > 0 && reset_time > 0 && reenter_time > 0 ) + database_new.Query("UPDATE character_instances SET lockout_time = %i, grant_reset_time_left = %i, grant_reenter_time_left = %i WHERE char_id = %i AND instance_id = %i", lockout_time, reset_time, reenter_time, char_id, instance_id); + else if ( lockout_time > 0 && reset_time > 0 ) + database_new.Query("UPDATE character_instances SET lockout_time = %i, grant_reset_time_left = %i WHERE char_id = %i AND instance_id = %i", lockout_time, reset_time, char_id, instance_id); + else if ( reset_time > 0 && reenter_time > 0 ) + database_new.Query("UPDATE character_instances SET grant_reset_time_left = %i, grant_reenter_time_left = %i WHERE char_id = %i AND instance_id = %i",reset_time, reenter_time, char_id, instance_id); + else if ( lockout_time > 0 && reenter_time > 0 ) + database_new.Query("UPDATE character_instances SET lockout_time = %i, grant_reenter_time_left = %i WHERE char_id = %i AND instance_id = %i", lockout_time, reenter_time, char_id, instance_id); + else if ( lockout_time > 0 ) + database_new.Query("UPDATE character_instances SET lockout_time = %i WHERE char_id = %i AND instance_id = %i", lockout_time, char_id, instance_id); + else if ( reset_time > 0 ) + database_new.Query("UPDATE character_instances SET grant_reset_time_left = %i WHERE char_id = %i AND instance_id = %i", reset_time, char_id, instance_id); + else if ( reenter_time > 0 ) + database_new.Query("UPDATE character_instances SET grant_reenter_time_left = %i WHERE char_id = %i AND instance_id = %i", reenter_time, char_id, instance_id); + + if( database_new.GetError() ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in UpdateCharacterInstanceTimers() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + else + { + if ( database_new.AffectedRows() > 0 ) + { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Updating instance timers for character %u to instance: %u", char_id, instance_id); + LogWrite(INSTANCE__DEBUG, 1, "Instance", "-- Reenter: %u, Reset: %u, Lockout: %u", reenter_time, reset_time, lockout_time); + return true; + } + else + return false; + } +} + +bool WorldDatabase::UpdateCharacterInstance(int32 char_id, string zone_name, int32 instance_id, int8 type, int32 timestamp) { + // type = 1 = success timestamp + // type = 2 = failure timestamp + if (instance_id > 0) { + if (type == 1) { + database_new.Query("UPDATE character_instances SET instance_id = %u, last_success_timestamp = %u WHERE char_id = %u AND instance_zone_name = '%s'", instance_id, timestamp, char_id, database_new.EscapeStr(zone_name).c_str()); + } + else if (type == 2) { + database_new.Query("UPDATE character_instances SET instance_id = %u, last_failure_timestamp = %u WHERE char_id = %u AND instance_zone_name = '%s'", instance_id, timestamp, char_id, database_new.EscapeStr(zone_name).c_str()); + } + else { + database_new.Query("UPDATE character_instances SET instance_id = %u WHERE char_id = %u AND instance_zone_name = '%s'", instance_id, char_id, database_new.EscapeStr(zone_name).c_str()); + } + } + else { + if (type == 1) { + database_new.Query("UPDATE character_instances SET last_success_timestamp = %u WHERE char_id = %u AND instance_zone_name = '%s'", timestamp, char_id, database_new.EscapeStr(zone_name).c_str()); + } + else if (type == 2) { + database_new.Query("UPDATE character_instances SET last_failure_timestamp = %u WHERE char_id = %u AND instance_zone_name = '%s'", timestamp, char_id, database_new.EscapeStr(zone_name).c_str()); + } + } + + if (database_new.GetError()) { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in UpdateCharacterInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + return true; +} + +bool WorldDatabase::VerifyInstanceID(int32 char_id, int32 instance_id) { + DatabaseResult result; + database_new.Select(&result, "SELECT COUNT(id) as num_instances FROM instances WHERE id = %u", instance_id); + + if (result.Next() && result.GetInt32Str("num_instances") == 0) { + DeleteCharacterFromInstance(char_id, instance_id); + return false; + } + + return true; +} + +bool WorldDatabase::UpdateInstancedSpawnRemoved(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 instance_id ) +{ + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Updating removed spawns for instance_id: %u", instance_id); + LogWrite(INSTANCE__DEBUG, 1, "Instance", "-- Spawn Location Entry ID: %u, Type: %u, Respawn: %u", spawn_location_entry_id, spawn_type, respawn_time); + + if( !database_new.Query("UPDATE instance_spawns_removed SET respawn_time = %i WHERE spawn_location_entry_id = %i AND spawn_type = %i AND instance_id = %i", respawn_time, spawn_location_entry_id, spawn_type, instance_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in UpdateInstancedSpawnRemoved() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + if ( database_new.AffectedRows() > 0 ) + { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Updated removed spawns for instance_id: %u", instance_id); + return true; + } + else + return false; +} + +int32 WorldDatabase::CreateNewInstance(int32 zone_id) +{ + int32 ret = 0; + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Creating new instance for zone: %u ", zone_id); + + if( !database_new.Query("INSERT INTO instances (zone_id) VALUES (%u)", zone_id) ) + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in CreateNewInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + else + ret = database_new.LastInsertID(); + + if( ret > 0 ) + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Created new instance_id %u for zone: %u ", ret, zone_id); + + return ret; +} + +int32 WorldDatabase::CreateInstanceSpawnRemoved(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 instance_id ) +{ + int32 ret = 0; + + LogWrite(INSTANCE__DEBUG, 3, "Instance", "Creating new instance spawn removed entries for instance_id: %u ", instance_id); + LogWrite(INSTANCE__DEBUG, 5, "Instance", "-- Spawn Location Entry ID: %u, Type: %u, Respawn: %u", spawn_location_entry_id, spawn_type, respawn_time); + + if( !database_new.Query("INSERT INTO instance_spawns_removed (spawn_location_entry_id, spawn_type, instance_id, respawn_time) values(%u, %u, %u, %u)", spawn_location_entry_id, spawn_type, instance_id, respawn_time) ) + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in CreateInstanceSpawnRemoved() query '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + else + ret = database_new.LastInsertID(); + + // potentially spammy, if it calls for every spawn added. Set to level 3 or 5? + if( ret > 0 ) + LogWrite(INSTANCE__DEBUG, 5, "Instance", "Created new spawn removed entry: %u for instance_id %u", ret, instance_id); + + return ret; +} + +bool WorldDatabase::DeleteInstance(int32 instance_id) +{ + if( !database_new.Query("DELETE FROM instances WHERE id = %u", instance_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeleteInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + /* JA: should not need this delete with FK/Constraints + if( !database_new.Query("DELETE FROM instance_spawns_removed WHERE instance_id = %u", instance_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeleteInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + */ + + // Remove the instance from the character_instances table + database_new.Query("UPDATE `character_instances` SET `instance_id` = 0 WHERE `instance_id` = %u", instance_id); + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Deleted instance_id %u", instance_id); + + return true; +} + +bool WorldDatabase::DeleteInstanceSpawnRemoved(int32 instance_id, int32 spawn_location_entry_id) +{ + if( !database_new.Query("DELETE FROM instance_spawns_removed WHERE instance_id = %u AND spawn_location_entry_id = %u", instance_id, spawn_location_entry_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeleteInstanceSpawnRemoved() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Deleted removed spawn: %u for instance_id %u", spawn_location_entry_id, instance_id); + + return true; +} + +bool WorldDatabase::DeleteCharacterFromInstance(int32 char_id, int32 instance_id) +{ + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Delete character %u from instance_id %u.", char_id, instance_id); + + if( !database_new.Query("UPDATE `character_instances` SET `instance_id` = 0 WHERE `instance_id` = %u AND `char_id` = %u", instance_id, char_id) ) + { + LogWrite(INSTANCE__ERROR, 0, "Instance", "Error in DeleteCharacterFromInstance() '%s': %i", database_new.GetErrorMsg(), database_new.GetError()); + return false; + } + + if ( database_new.AffectedRows() == 0 ) // didn't find an instance to delete + { + LogWrite(INSTANCE__DEBUG, 1, "Instance", "No instance_id %u for character %u to delete.", instance_id, char_id); + return false; + } + else + { + // delete entire instance if the last player has left + DatabaseResult result; + database_new.Select(&result, "SELECT count(id) as num_instances FROM character_instances where instance_id = %u",instance_id); + + if(result.Next() && result.GetInt32Str("num_instances") == 0) + { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "No characters in instance: Delete instance_id %u.", instance_id); + DeleteInstance(instance_id); + } + } + + return true; +} + +bool WorldDatabase::LoadCharacterInstances(Client* client) +{ + DatabaseResult result; + DatabaseResult result2; + + bool addedInstance = false; + + database_new.Select(&result, "SELECT `id`, `instance_id`, `instance_zone_name`, `instance_type`, `last_success_timestamp`, `last_failure_timestamp`, `success_lockout_time`, `failure_lockout_time` FROM `character_instances` WHERE `char_id` = %u", client->GetCharacterID()); + + if( result.GetNumRows() > 0 ) + { + while( result.Next() ) + { + int32 zone_id = 0; + int32 instance_id = result.GetInt32Str("instance_id"); + // If `instance_id` is greater then 0 then get the zone id with it, else get the zone id from the zone name + if (instance_id != 0) { + if (database_new.Select(&result2, "SELECT `zone_id` FROM `instances` WHERE `id` = %u", instance_id)) { + if (result2.Next()) + zone_id = result2.GetInt32Str("zone_id"); + } + } + if (zone_id == 0) + zone_id = GetZoneID(result.GetStringStr("instance_zone_name")); + + client->GetPlayer()->GetCharacterInstances()->AddInstance( + result.GetInt32Str("id"), + instance_id, + result.GetInt32Str("last_success_timestamp"), + result.GetInt32Str("last_failure_timestamp"), + result.GetInt32Str("success_lockout_time"), + result.GetInt32Str("failure_lockout_time"), + zone_id, + result.GetInt8Str("instance_type"), + string(result.GetStringStr("instance_zone_name")) + ); + addedInstance = true; + } + } + + return addedInstance; +} + +void WorldDatabase::UpdateLoginEquipment() +{ + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Updating `character_items` CRC in %s", __FUNCTION__); + + database_new.Query("UPDATE character_items SET login_checksum = CRC32(CRC32(type) + CRC32(slot) + CRC32(item_id)) WHERE `type` = 'EQUIPPED' AND ( slot <= 8 OR slot = 19 )"); +} + +MutexMap* WorldDatabase::GetEquipmentUpdates() +{ + DatabaseResult result; + MutexMap* ret = 0; + LoginEquipmentUpdate update; + int32 count = 0; + + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Looking for Login Appearance Updates..."); + + // TODO: Someday store the equipment colors in character_items, for custom colorization of gear (?) + if( database_new.Select(&result, "SELECT ci.id, ci.char_id, ia.equip_type, ia.red, ia.green, ia.blue, ia.highlight_red, ia.highlight_green, ia.highlight_blue, ci.slot FROM characters c JOIN character_items ci ON c.id = ci.char_id JOIN item_appearances ia ON ci.item_id = ia.item_id WHERE c.deleted = 0 AND ci.type = 'EQUIPPED' AND ( ci.slot <= 8 OR ci.slot = 19 ) AND ci.login_checksum <> CRC32(CRC32(`ci`.`type`) + CRC32(ci.slot) + CRC32(ci.item_id)) ORDER BY ci.char_id, ci.slot") ) + { + while( result.Next() ) + { + LogWrite(INIT__LOGIN_DEBUG, 5, "Login", "Found update for char_id %i!", result.GetInt32Str("char_id")); + + if(!ret) + ret = new MutexMap(); + + update.world_char_id = result.GetInt32Str("char_id"); + update.equip_type = result.GetInt16Str("equip_type"); + update.red = result.GetInt8Str("red"); + update.green = result.GetInt8Str("green"); + update.blue = result.GetInt8Str("blue"); + update.highlight_red = result.GetInt8Str("highlight_red"); + update.highlight_green = result.GetInt8Str("highlight_green"); + update.highlight_blue = result.GetInt8Str("highlight_blue"); + update.slot = result.GetInt8Str("slot"); + ret->Put(result.GetInt32Str("id"), update); + count++; + + } + } + + if(count) + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Found %i Login Appearance Update%s...", count, count == 1 ? "" : "s"); + + return ret; +} + + +MutexMap* WorldDatabase::GetEquipmentUpdates(int32 char_id) +{ + DatabaseResult result; + MutexMap* ret = 0; + LoginEquipmentUpdate update; + int32 count = 0; + + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Looking for Login Appearance Updates for char_id: %u", char_id); + + // TODO: Someday store the equipment colors in character_items, for custom colorization of gear (?) + if( database_new.Select(&result, "SELECT ci.id, ci.char_id, ia.equip_type, ia.red, green, ia.blue, ia.highlight_red, ia.highlight_green, ia.highlight_blue, ci.slot FROM characters c JOIN character_items ci ON c.id = ci.char_id JOIN item_appearances ia ON ci.item_id = ia.item_id WHERE c.deleted = 0 AND ci.type = 'EQUIPPED' AND ( ci.slot <= 8 OR ci.slot = 19 ) AND ci.login_checksum <> CRC32(CRC32(ci.type) + CRC32(ci.slot) + CRC32(ci.item_id)) AND ci.char_id = %u ORDER BY ci.slot", char_id) ) + { + while( result.Next() ) + { + LogWrite(INIT__LOGIN_DEBUG, 5, "Login", "Found update for char_id %i!", result.GetInt32Str("char_id")); + + if(!ret) + ret = new MutexMap(); + + update.world_char_id = char_id; + update.equip_type = result.GetInt16Str("equip_type"); + update.red = result.GetInt8Str("red"); + update.green = result.GetInt8Str("green"); + update.blue = result.GetInt8Str("blue"); + update.highlight_red = result.GetInt8Str("highlight_red"); + update.highlight_green = result.GetInt8Str("highlight_green"); + update.highlight_blue = result.GetInt8Str("highlight_blue"); + update.slot = result.GetInt8Str("slot"); + ret->Put(result.GetInt32Str("id"), update); + count++; + } + } + + if(count) + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Found %i Login Appearance Update%s...", count, count == 1 ? "" : "s"); + + return ret; +} + + +void WorldDatabase::UpdateLoginZones() { + Query query; + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Updating `zones` CRC in %s", __FUNCTION__); + query.RunQuery2("UPDATE zones SET login_checksum = CRC32(CRC32(id) + CRC32(`name`) + CRC32(`file`) + CRC32(description))", Q_UPDATE); +} + +MutexMap* WorldDatabase::GetZoneUpdates() { + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Looking for Login Zone Updates..."); + MutexMap* ret = 0; + LoginZoneUpdate update; + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name, description FROM zones where login_checksum != crc32(crc32(id) + crc32(name) + crc32(file) + crc32(description))"); + while(result && (row = mysql_fetch_row(result))) { + if(row[0] && row[1]) { + + LogWrite(INIT__LOGIN_DEBUG, 5, "Login", "Found update for zone_id %i!", atoi(row[0])); + + if(!ret) + ret = new MutexMap(); + update.name = string(row[1]); + if(row[2]) + update.description = string(row[2]); + else + update.description = ""; + ret->Put(atoi(row[0]), update); + count++; + } + } + if(count) + LogWrite(INIT__LOGIN_DEBUG, 0, "Login", "Found %i Login Zone Update%s...", count, count == 1 ? "" : "s"); + return ret; +} + +void WorldDatabase::LoadLocationGrids(ZoneServer* zone) { + if (zone) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `grid_id`, `name`, `include_y`, `discovery` FROM `locations` WHERE `zone_id`=%u", zone->GetZoneID()); + while (result && (row = mysql_fetch_row(result))) { + LocationGrid* grid = new LocationGrid; + grid->id = atoul(row[0]); + grid->grid_id = atoul(row[1]); + grid->name = string(row[2]); + grid->include_y = (atoi(row[3]) == 1); + grid->discovery = (atoi(row[4]) == 1); + if (LoadLocationGridLocations(grid)) + zone->AddLocationGrid(grid); + else + safe_delete(grid); + } + } +} + +bool WorldDatabase::LoadLocationGridLocations(LocationGrid* grid) { + bool ret = false; + if (grid) { + Query query; + int row_count = 0; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `x`, `y`, `z` FROM `location_details` WHERE `location_id`=%u", grid->id); + if (result) { + while (result && (row = mysql_fetch_row(result))) { + row_count++; + Location* location = new Location; + location->id = atoul(row[0]); + location->x = atof(row[1]); + location->y = atof(row[2]); + location->z = atof(row[3]); + grid->locations.Add(location); + } + ret = true; + } + if(row_count > 0 && row_count < 3) + LogWrite(WORLD__WARNING, 0, "World", "Grid '%s' only has %u location(s). A minimum of 3 is needed for a proper location based grid.", grid->name.c_str(), row_count); + } + return ret; +} + +int32 WorldDatabase::CreateLocation(int32 zone_id, int32 grid_id, const char* name, bool include_y) { + int32 ret = 0; + if (name && strlen(name) > 0) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `locations` (`zone_id`, `grid_id`, `name`, `include_y`) VALUES (%u, %u, '%s', %u)", zone_id, grid_id, name, include_y == true ? 1 : 0); + ret = query.GetLastInsertedID(); + } + return ret; +} + +bool WorldDatabase::AddLocationPoint(int32 location_id, float x, float y, float z) { + bool ret = false; + if (LocationExists(location_id)) { + Query query; + query.RunQuery2(Q_INSERT, "INSERT INTO `location_details` (`location_id`, `x`, `y`, `z`) VALUES (%u, %f, %f, %f)", location_id, x, y, z); + ret = true; + } + return ret; +} + +bool WorldDatabase::DeleteLocation(int32 location_id) { + bool ret = false; + if (LocationExists(location_id)) { + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `locations` WHERE `id`=%u", location_id); + ret = true; + } + return ret; +} + +bool WorldDatabase::DeleteLocationPoint(int32 location_point_id) { + Query query; + query.RunQuery2(Q_DELETE, "DELETE FROM `location_details` WHERE `id`=%u", location_point_id); + return true; +} + +void WorldDatabase::ListLocations(Client* client) { + if (client) { + Query query; + MYSQL_ROW row; + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Listing all locations:"); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `zone_id`, `grid_id`, `name` FROM `locations`"); + while (result && (row = mysql_fetch_row(result))) { + int32 id = atoul(row[0]); + int32 zone_id = atoul(row[1]); + int32 grid_id = atoul(row[2]); + const char* name = row[3]; + client->Message(CHANNEL_COLOR_YELLOW, "%u) Zone ID: %u Grid ID:%u Name: '%s'", id, zone_id, grid_id, name); + } + } +} + +void WorldDatabase::ListLocationPoints(Client* client, int32 location_id) { + if (client) { + if (LocationExists(location_id)) { + Query query; + client->Message(CHANNEL_COLOR_YELLOW, "Listing all points for location ID %u:", location_id); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `x`, `y`, `z` FROM `location_details` WHERE `location_id`=%u", location_id); + MYSQL_ROW row; + while (result && (row = mysql_fetch_row(result))) { + int32 id = atoul(row[0]); + float x = atof(row[1]); + float y = atof(row[2]); + float z = atof(row[3]); + client->Message(CHANNEL_COLOR_YELLOW, "%u) (%f, %f, %f)", id, x, y, z); + } + } + else + client->Message(CHANNEL_COLOR_YELLOW, "A location with ID %u does not exist", location_id); + } +} + +bool WorldDatabase::LocationExists(int32 location_id) { + bool ret = false; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT COUNT(id) FROM `locations` WHERE `id`=%u", location_id); + MYSQL_ROW row; + if (result && (row = mysql_fetch_row(result))) { + if (atoul(row[0]) > 0) + ret = true; + } + return ret; +} + +bool WorldDatabase::QueriesFromFile(const char * file) { + return database_new.QueriesFromFile(file); +} + +bool WorldDatabase::CheckBannedIPs(const char* loginIP) +{ + // til you build the table, all IPs are allowed + return false; +} + +sint32 WorldDatabase::AddMasterTitle(const char* titleName, int8 isPrefix) +{ + if(titleName == nullptr || strlen(titleName) < 1) + { + LogWrite(DATABASE__ERROR, 0, "DBNew", "AddMasterTitle called with missing titleName"); + return -1; + } + + Query query; + Title* title = master_titles_list.GetTitleByName(titleName); + + if(title) + return title->GetID(); + + query.RunQuery2(Q_INSERT, "INSERT INTO titles set title='%s', prefix=%u", titleName, isPrefix); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(DATABASE__ERROR, 0, "Database", "Error in AddMasterTitle query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + int32 last_insert_id = query.GetLastInsertedID(); + if(last_insert_id > 0) + { + title = new Title; + title->SetID(last_insert_id); + title->SetName(titleName); + title->SetPrefix(isPrefix); + master_titles_list.AddTitle(title); + return (sint32)last_insert_id; + } + + + return -1; +} + +void WorldDatabase::LoadTitles(){ + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, title, prefix FROM titles"); + if(result && mysql_num_rows(result) > 0){ + Title* title = 0; + while(result && (row = mysql_fetch_row(result))){ + sint32 idx = atoi(row[0]); + LogWrite(WORLD__DEBUG, 5, "World", "\tLoading Title '%s' (%u), Prefix: %i, Index: %u", row[1], idx, atoi(row[2]), count); + title = new Title; + title->SetID(idx); + title->SetName(row[1]); + title->SetPrefix(atoi(row[2])); + master_titles_list.AddTitle(title); + count++; + } + } + LogWrite(WORLD__DEBUG, 0, "World", "\tLoaded %u Title%s", count, count == 1 ? "" : "s"); +} + +sint32 WorldDatabase::LoadCharacterTitles(int32 char_id, Player *player){ + LogWrite(WORLD__DEBUG, 0, "World", "Loading Titles for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + sint32 index = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT title_id, title, prefix FROM character_titles, titles WHERE character_titles.title_id = titles.id AND character_titles.char_id = %u", char_id); + if(result && mysql_num_rows(result) > 0){ + while(result && (row = mysql_fetch_row(result))){ + LogWrite(WORLD__DEBUG, 5, "World", "\tLoading Title ID: %u, Title: '%s' Index: %u", atoul(row[0]), row[1], index); + player->AddTitle(index, row[1], atoi(row[2])); + index++; + } + } + return index; +} + +sint32 WorldDatabase::GetCharPrefixIndex(int32 char_id, Player *player){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Getting current title index for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + sint32 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT prefix_title FROM character_details WHERE char_id = %u", char_id); + if(result && mysql_num_rows(result) > 0) + while(result && (row = mysql_fetch_row(result))){ + ret = atoi(row[0]); + LogWrite(PLAYER__DEBUG, 5, "Player", "\tPrefix Index: %i", ret); + } + return ret; +} + +sint32 WorldDatabase::GetCharSuffixIndex(int32 char_id, Player *player){ + LogWrite(PLAYER__DEBUG, 0, "Player", "Getting current title index for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + sint32 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT suffix_title FROM character_details WHERE char_id = %u", char_id); + if(result && mysql_num_rows(result) > 0) + while(result && (row = mysql_fetch_row(result))){ + ret = atoi(row[0]); + LogWrite(PLAYER__DEBUG, 5, "Player", "\tSuffix Index: %i", ret); + } + return ret; +} + +void WorldDatabase::SaveCharPrefixIndex(sint32 index, int32 char_id){ + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE character_details SET prefix_title = %i WHERE char_id = %u", index, char_id); + LogWrite(PLAYER__DEBUG, 0, "Player", "Saving Prefix Index %i for character id '%u'...", index, char_id); +} + +void WorldDatabase::SaveCharSuffixIndex(sint32 index, int32 char_id){ + Query query; + query.RunQuery2(Q_SELECT, "UPDATE character_details SET suffix_title = %i WHERE char_id = %u", index, char_id); + LogWrite(PLAYER__DEBUG, 0, "Player", "Saving Suffix Index %i for character id %u...", index, char_id); +} + +sint32 WorldDatabase::AddCharacterTitle(sint32 index, int32 char_id, Spawn* player) { + if(!player || !player->IsPlayer()) + { + LogWrite(DATABASE__ERROR, 0, "DBNew", "AddCharacterTitle spawn is not a player: %s", player ? player->GetName() : "Unset"); + return -1; + } + + Title* title = master_titles_list.GetTitle(index); + if(!title) + { + LogWrite(DATABASE__ERROR, 0, "DBNew", "AddCharacterTitle title index %u missing from master_titles_list for player: %s (%u)", index, player ? player->GetName() : "Unset", char_id); + return -1; + } + + Query query; + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding titles for char_id: %u, index: %i", char_id, index); + query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_titles (char_id, title_id) VALUES (%u, %i)", char_id, index); + sint32 curIndex = (sint32)((Player*)player)->GetPlayerTitles()->Size(); + ((Player*)player)->AddTitle(curIndex++, title->GetName(), title->GetPrefix(), title->GetSaveNeeded()); + + return curIndex; +} + +void WorldDatabase::LoadLanguages() +{ + int32 count = 0; + Query query; + MYSQL_ROW row; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, language FROM languages"); + + if(result && mysql_num_rows(result) > 0) + { + Language* language = 0; + + while(result && (row = mysql_fetch_row(result))) + { + LogWrite(WORLD__DEBUG, 5, "World", "\tLoading language '%s' , ID: %u", row[1], atoul(row[0])); + language = new Language; + language->SetID(atoul(row[0])); + language->SetName(row[1]); + master_languages_list.AddLanguage(language); + count++; + } + } + LogWrite(WORLD__DEBUG, 0, "World", "\tLoaded %u Language%s", count, count == 1 ? "" : "s"); +} + +int32 WorldDatabase::LoadCharacterLanguages(int32 char_id, Player *player) +{ + LogWrite(WORLD__DEBUG, 0, "World", "Loading Languages for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + int32 count = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT language_id, language FROM character_languages, languages WHERE character_languages.language_id = languages.id AND character_languages.char_id = %u", char_id); + + if(result && mysql_num_rows(result) > 0) + { + while(result && (row = mysql_fetch_row(result))) + { + LogWrite(WORLD__DEBUG, 5, "World", "\tLoading Language ID: %u, Language: '%s'", atoul(row[0]), row[1]); + player->AddLanguage(atoul(row[0]), row[1]); + count++; + } + } + return count; +} + +int16 WorldDatabase::GetCharacterCurrentLang(int32 char_id, Player *player) +{ + LogWrite(PLAYER__DEBUG, 0, "Player", "Getting current language for player '%s'...", player->GetName()); + Query query; + MYSQL_ROW row; + int16 ret = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT current_language FROM character_details WHERE char_id = %u", char_id); + + if(result && mysql_num_rows(result) > 0) + while(result && (row = mysql_fetch_row(result))) + { + ret = atoi(row[0]); + LogWrite(PLAYER__DEBUG, 5, "Player", "\tLanguage ID: %i", ret); + } + return ret; +} + +void WorldDatabase::SaveCharacterCurrentLang(int32 id, int32 char_id, Client *client) +{ + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE character_details SET current_language = %i WHERE char_id = %u", id, char_id); + LogWrite(PLAYER__DEBUG, 3, "Player", "Saving current language ID %i for player '%s'...", id, client->GetPlayer()->GetName()); +} + +void WorldDatabase::SaveCharacterLang(int32 char_id, int32 lang_id) { + if (!database_new.Query("INSERT INTO character_languages (char_id, language_id) VALUES (%u, %u)", char_id, lang_id)) + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); +} + +// JA - this is not used yet, lots more to consider for storing player history +void WorldDatabase::LoadCharacterHistory(int32 char_id, Player *player) +{ + DatabaseResult result; + + // Use -1 on type and subtype to turn the enum into an int and make it a 0 index + if (!database_new.Select(&result, "SELECT type-1, subtype-1, value, value2, location, event_id, event_date FROM character_history WHERE char_id = %u", char_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + + int8 type = result.GetInt8(0); + int8 subtype = result.GetInt8(1); + + HistoryData* hd = new HistoryData; + hd->Value = result.GetInt32(2); + hd->Value2 = result.GetInt32(3); + strcpy(hd->Location, result.GetString(4)); + // skipped event id as use for it has not been determined yet + hd->EventDate = result.GetInt32(6); + hd->needs_save = false; + + player->LoadPlayerHistory(type, subtype, hd); + } +} + +void WorldDatabase::LoadSpellErrors() { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `version`, `error_index`, `value` FROM `spell_error_versions`"); + + if (result && mysql_num_rows(result) > 0) { + while ((row = mysql_fetch_row(result))) { + master_spell_list.AddSpellError(atoi(row[0]), atoi(row[1]), atoi(row[2])); + } + } +} + +void WorldDatabase::SaveCharacterHistory(Player* player, int8 type, int8 subtype, int32 value, int32 value2, char* location, int32 event_date) { + string str_type(""); + string str_subtype(""); + switch (type) { + case HISTORY_TYPE_NONE: + str_type = "None"; + break; + case HISTORY_TYPE_DEATH: + str_type = "Death"; + break; + case HISTORY_TYPE_DISCOVERY: + str_type = "Discovery"; + break; + case HISTORY_TYPE_XP: + str_type = "XP"; + break; + default: + LogWrite(PLAYER__ERROR, 0, "Player", "WorldDatabase::SaveCharacterHistory() - Invalid history type given (%i) with subtype (%i), character history NOT saved.", type, subtype); + return; + } + switch (subtype) { + case HISTORY_SUBTYPE_NONE: + str_subtype = "None"; + break; + case HISTORY_SUBTYPE_ADVENTURE: + str_subtype = "Adventure"; + break; + case HISTORY_SUBTYPE_TRADESKILL: + str_subtype = "Tradeskill"; + break; + case HISTORY_SUBTYPE_QUEST: + str_subtype = "Quest"; + break; + case HISTORY_SUBTYPE_AA: + str_subtype = "AA"; + break; + case HISTORY_SUBTYPE_ITEM: + str_subtype = "Item"; + break; + case HISTORY_SUBTYPE_LOCATION: + str_subtype = "Location"; + break; + default: + LogWrite(PLAYER__ERROR, 0, "Player", "WorldDatabase::SaveCharacterHistory() - Invalid history sub type given (%i) with type (%i), character history NOT saved.", subtype, type); + return; + } + + LogWrite(PLAYER__DEBUG, 1, "Player", "Saving character history, type = %s (%i) subtype = %s (%i)", (char*)str_type.c_str(), type, (char*)str_subtype.c_str(), subtype); + + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_REPLACE, "replace into character_history (char_id, type, subtype, value, value2, location, event_date) values (%u, '%s', '%s', %i, %i, '%s', %u)", + player->GetCharacterID(), str_type.c_str(), str_subtype.c_str(), value, value2, getSafeEscapeString(location).c_str(), event_date); +} + +void WorldDatabase::LoadTransportMaps(ZoneServer* zone) { + int32 total = 0; + + LogWrite(TRANSPORT__DEBUG, 0, "Transport", "-Loading Transporter Maps..."); + zone->DeleteTransporterMaps(); + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `transport_id`, `map_name` FROM `transport_maps`"); + if(result) { + while(result && (row = mysql_fetch_row(result))){ + zone->AddTransportMap(atoul(row[0]), string(row[1])); + total++; + } + } + LogWrite(TRANSPORT__DEBUG, 0, "Transport", "--Loaded %i Transporter Maps", total); +} + +bool WorldDatabase::LoadSign(ZoneServer* zone, int32 spawn_id) { + Sign* sign = 0; + int32 id = 0; + DatabaseResult result; + database_new.Select(&result, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, ss.language\n" + "FROM spawn s\n" + "INNER JOIN spawn_signs ss\n" + "ON ss.spawn_id = s.id\n" + "WHERE s.id = %u\n", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + sign = new Sign(); + sign->SetDatabaseID(id); + strcpy(sign->appearance.name, result.GetString(1)); + sign->appearance.model_type = result.GetInt16(2); + sign->SetSize(result.GetInt16(3)); + sign->appearance.show_command_icon = result.GetInt8(4); + sign->SetWidgetID(result.GetInt32(5)); + sign->SetWidgetX(result.GetFloat(6)); + sign->SetWidgetY(result.GetFloat(7)); + sign->SetWidgetZ(result.GetFloat(8)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(9)); + if(primary_command_list){ + sign->SetPrimaryCommands(primary_command_list); + sign->primary_command_list_id = result.GetInt32(9); + } + + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(10)); + if (secondary_command_list) { + sign->SetSecondaryCommands(secondary_command_list); + sign->secondary_command_list_id = result.GetInt32(10); + } + + sign->appearance.pos.collision_radius = result.GetInt16(11); + sign->SetSignIcon(result.GetInt8(12)); + if(strncasecmp(result.GetString(13), "Generic", 7) == 0) + sign->SetSignType(SIGN_TYPE_GENERIC); + else if(strncasecmp(result.GetString(13), "Zone", 4) == 0) + sign->SetSignType(SIGN_TYPE_ZONE); + sign->SetSignTitle(result.GetString(14)); + sign->SetSignDescription(result.GetString(15)); + sign->SetSignDistance(result.GetFloat(16)); + sign->SetSignZoneID(result.GetInt32(17)); + sign->SetSignZoneX(result.GetFloat(18)); + sign->SetSignZoneY(result.GetFloat(19)); + sign->SetSignZoneZ(result.GetFloat(20)); + sign->SetSignZoneHeading(result.GetFloat(21)); + sign->SetIncludeHeading(result.GetInt8(22) == 1); + sign->SetIncludeLocation(result.GetInt8(23) == 1); + sign->SetTransporterID(result.GetInt32(24)); + sign->SetSizeOffset(result.GetInt8(25)); + sign->appearance.display_hand_icon = result.GetInt8(26); + sign->SetVisualState(result.GetInt16(27)); + + sign->SetSoundsDisabled(result.GetInt8(28)); + + sign->SetMerchantLevelRange(result.GetInt32(29), result.GetInt32(30)); + + sign->SetAAXPRewards(result.GetInt32(31)); + + sign->SetLootTier(result.GetInt32(32)); + + sign->SetLootDropType(result.GetInt32(33)); + + sign->SetLanguage(result.GetInt8(34)); + + zone->AddSign(id, sign); + + + LogWrite(SIGN__DEBUG, 0, "Sign", "Loaded Sign: '%s' (%u).", sign->appearance.name, spawn_id); + return true; + } + LogWrite(SIGN__DEBUG, 0, "Sign", "Unable to find a sign for spawn id of %u", spawn_id); + return false; +} + +bool WorldDatabase::LoadWidget(ZoneServer* zone, int32 spawn_id) { + Widget* widget = 0; + int32 id = 0; + DatabaseResult result; + + database_new.Select(&result, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_widgets sw\n" + "ON sw.spawn_id = s.id\n" + "WHERE s.id = %u", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + widget = new Widget(); + widget->SetDatabaseID(id); + strcpy(widget->appearance.name, result.GetString(1)); + widget->appearance.model_type = result.GetInt16(2); + widget->SetSize(result.GetInt16(3)); + widget->appearance.show_command_icon = result.GetInt8(4); + widget->SetWidgetID(result.GetInt32(5)); + widget->SetWidgetX(result.GetFloat(6)); + widget->SetWidgetY(result.GetFloat(7)); + widget->SetWidgetZ(result.GetFloat(8)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(9)); + if(primary_command_list){ + widget->SetPrimaryCommands(primary_command_list); + widget->primary_command_list_id = result.GetInt32(9); + } + + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(10)); + if (secondary_command_list) { + widget->SetSecondaryCommands(secondary_command_list); + widget->secondary_command_list_id = result.GetInt32(10); + } + + widget->appearance.pos.collision_radius = result.GetInt16(11); + widget->SetIncludeHeading(result.GetInt8(12) == 1); + widget->SetIncludeLocation(result.GetInt8(13) == 1); + widget->SetWidgetIcon(result.GetInt8(14)); + if(strncasecmp(result.GetString(15),"Generic", 7) == 0) + widget->SetWidgetType(WIDGET_TYPE_GENERIC); + else if(strncasecmp(result.GetString(15),"Door", 4) == 0) + widget->SetWidgetType(WIDGET_TYPE_DOOR); + widget->SetOpenHeading(result.GetFloat(16)); + widget->SetOpenY(result.GetFloat(17)); + widget->SetActionSpawnID(result.GetInt32(18)); + if(!result.IsNull(19) && strlen(result.GetString(19)) > 5) + widget->SetOpenSound(result.GetString(19)); + if(!result.IsNull(20) && strlen(result.GetString(20)) > 5) + widget->SetCloseSound(result.GetString(20)); + widget->SetOpenDuration(result.GetInt16(21)); + widget->SetClosedHeading(result.GetFloat(22)); + widget->SetLinkedSpawnID(result.GetInt32(23)); + widget->SetCloseY(result.GetFloat(24)); + widget->SetTransporterID(result.GetInt32(25)); + widget->SetSizeOffset(result.GetInt8(26)); + widget->SetHouseID(result.GetInt32(27)); + widget->SetOpenX(result.GetFloat(28)); + widget->SetOpenZ(result.GetFloat(29)); + widget->SetCloseX(result.GetFloat(30)); + widget->SetCloseZ(result.GetFloat(31)); + widget->appearance.display_hand_icon = result.GetInt8(32); + + widget->SetSoundsDisabled(result.GetInt8(33)); + + widget->SetMerchantLevelRange(result.GetInt32(34), result.GetInt32(35)); + + widget->SetAAXPRewards(result.GetInt32(36)); + + widget->SetLootTier(result.GetInt32(37)); + + widget->SetLootDropType(result.GetInt32(38)); + + zone->AddWidget(id, widget); + + LogWrite(WIDGET__DEBUG, 0, "Widget", "Loaded Widget: '%s' (%u).", widget->appearance.name, spawn_id); + return true; + } + + LogWrite(WIDGET__DEBUG, 0, "Widget", "Unable to find a widget for spawn id of %u", spawn_id); + return false; +} + +bool WorldDatabase::LoadObject(ZoneServer* zone, int32 spawn_id) { + Object* object = 0; + int32 id = 0; + DatabaseResult result; + + database_new.Select(&result, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + "FROM spawn s\n" + "INNER JOIN spawn_objects so\n" + "ON so.spawn_id = s.id\n" + "WHERE s.id = %u", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + object = new Object(); + object->SetDatabaseID(id); + strcpy(object->appearance.name, result.GetString(1)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(4)); + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(5)); + if(primary_command_list){ + object->SetPrimaryCommands(primary_command_list); + object->primary_command_list_id = result.GetInt32(4); + } + if(secondary_command_list){ + object->SetSecondaryCommands(secondary_command_list); + object->secondary_command_list_id = result.GetInt32(5); + } + object->appearance.race = result.GetInt8(2); + object->appearance.model_type = result.GetInt16(3); + object->appearance.targetable = result.GetInt8(6); + object->size = result.GetInt16(7); + object->appearance.display_name = result.GetInt8(8); + object->appearance.visual_state = result.GetInt16(9); + object->appearance.attackable = result.GetInt8(10); + object->appearance.show_level = result.GetInt8(11); + object->appearance.show_command_icon = result.GetInt8(12); + object->appearance.display_hand_icon = result.GetInt8(13); + object->faction_id = result.GetInt32(14); + object->appearance.pos.collision_radius = result.GetInt16(15); + object->SetTransporterID(result.GetInt32(16)); + object->SetSizeOffset(result.GetInt8(17)); + object->SetDeviceID(result.GetInt8(18)); + object->SetSoundsDisabled(result.GetInt8(19)); + + object->SetMerchantLevelRange(result.GetInt32(20), result.GetInt32(21)); + + object->SetAAXPRewards(result.GetInt32(22)); + + object->SetLootTier(result.GetInt32(23)); + + object->SetLootDropType(result.GetInt32(24)); + + zone->AddObject(id, object); + + LogWrite(OBJECT__DEBUG, 0, "Object", "Loaded Object: '%s' (%u).", object->appearance.name, spawn_id); + return true; + } + + LogWrite(OBJECT__DEBUG, 0, "Object", "Unable to find an object for spawn id of %u", spawn_id); + return false; +} + +bool WorldDatabase::LoadGroundSpawn(ZoneServer* zone, int32 spawn_id) { + GroundSpawn* spawn = 0; + int32 id = 0; + DatabaseResult result; + + database_new.Select(&result, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.disable_sounds, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, sg.randomize_heading\n" + "FROM spawn s\n" + "INNER JOIN spawn_ground sg\n" + "ON sg.spawn_id = s.id\n" + "WHERE s.id = %u", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + spawn = new GroundSpawn(); + spawn->SetDatabaseID(id); + strcpy(spawn->appearance.name, result.GetString(1)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(4)); + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(5)); + if(primary_command_list){ + spawn->SetPrimaryCommands(primary_command_list); + spawn->primary_command_list_id = result.GetInt32(4); + } + if(secondary_command_list){ + spawn->SetSecondaryCommands(secondary_command_list); + spawn->secondary_command_list_id = result.GetInt32(5); + } + spawn->appearance.race = result.GetInt8(2); + spawn->appearance.model_type = result.GetInt16(3); + spawn->appearance.targetable = result.GetInt8(6); + spawn->size = result.GetInt16(7); + spawn->appearance.display_name = result.GetInt8(8); + spawn->appearance.visual_state = result.GetInt16(9); + spawn->appearance.attackable = result.GetInt8(10); + spawn->appearance.show_level = result.GetInt8(11); + spawn->appearance.show_command_icon = result.GetInt8(12); + spawn->appearance.display_hand_icon = result.GetInt8(13); + spawn->faction_id = result.GetInt32(14); + spawn->appearance.pos.collision_radius = result.GetInt16(15); + spawn->SetNumberHarvests(result.GetInt8(16)); + spawn->SetAttemptsPerHarvest(result.GetInt8(17)); + spawn->SetGroundSpawnEntryID(result.GetInt32(18)); + spawn->SetCollectionSkill(result.GetString(19)); + spawn->SetSizeOffset(result.GetInt8(20)); + + spawn->SetSoundsDisabled(result.GetInt8(21)); + spawn->SetAAXPRewards(result.GetInt32(22)); + + spawn->SetLootTier(result.GetInt32(23)); + + spawn->SetLootDropType(result.GetInt32(24)); + + spawn->SetRandomizeHeading(result.GetInt32(25)); + + zone->AddGroundSpawn(id, spawn); + + if (!zone->GetGroundSpawnEntries(spawn->GetGroundSpawnEntryID())) + LoadGroundSpawnEntry(zone, spawn->GetGroundSpawnEntryID()); + + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "Loaded Ground Spawn: '%s' (%u).", spawn->appearance.name, spawn_id); + return true; + } + + LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "Unable to find a ground spawn for spawn id of %u", spawn_id); + return false; +} + +void WorldDatabase::LoadGroundSpawnItems(ZoneServer* zone, int32 entry_id) { + DatabaseResult result; + + database_new.Select(&result, "SELECT item_id, is_rare, grid_id\n" + "FROM groundspawn_items\n" + "WHERE groundspawn_id = %u", + entry_id); + + if (result.GetNumRows() > 0 && result.Next()) { + zone->AddGroundSpawnItem(entry_id, result.GetInt32(0), result.GetInt8(1), result.GetInt32(2)); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn Items: ID: %u\n", entry_id); + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---item: %ul, rare: %i, grid: %ul", result.GetInt32(0), result.GetInt8(1), result.GetInt32(2)); + } +} + +void WorldDatabase::LoadGroundSpawnEntry(ZoneServer* zone, int32 entry_id) { + DatabaseResult result; + + database_new.Select(&result, "SELECT min_skill_level, min_adventure_level, bonus_table, harvest1, harvest3, harvest5, harvest_imbue, harvest_rare, harvest10, harvest_coin\n" + "FROM groundspawns\n" + "WHERE enabled = 1 AND groundspawn_id = %u", + entry_id); + + if (result.GetNumRows() > 0 && result.Next()) { + // this is getting ridonkulous... + LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn ID: %u\n" \ + "---min_skill_level: %i, min_adventure_level: %i, bonus_table: %i\n" \ + "---harvest1: %.2f, harvest3: %.2f, harvest5: %.2f\n" \ + "---harvest_imbue: %.2f, harvest_rare: %.2f, harvest10: %.2f\n" \ + "---harvest_coin: %u", entry_id, result.GetInt16(0), result.GetInt16(1), result.GetInt8(2), result.GetFloat(3), result.GetFloat(4), result.GetFloat(5), result.GetFloat(6), result.GetFloat(7), result.GetFloat(8), result.GetInt32(9)); + + zone->AddGroundSpawnEntry(entry_id, result.GetInt16(0), result.GetInt16(1), result.GetInt8(2), result.GetFloat(3), result.GetFloat(4), result.GetFloat(5), result.GetFloat(6), result.GetFloat(7), result.GetFloat(8), result.GetInt32(9)); + LoadGroundSpawnItems(zone, entry_id); + } +} + +bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) { + NPC* npc = nullptr; + int32 id = 0; + DatabaseResult result; + + database_new.Select(&result, "SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str\n" + "FROM spawn s\n" + "INNER JOIN spawn_npcs npc\n" + "ON npc.spawn_id = s.id\n" + "WHERE s.id = %u", + spawn_id); + + if (result.GetNumRows() > 0 && result.Next()) { + id = result.GetInt32(0); + npc = new NPC(); + npc->SetDatabaseID(id); + strcpy(npc->appearance.name, result.GetString(1)); + vector* primary_command_list = zone->GetEntityCommandList(result.GetInt32(9)); + vector* secondary_command_list = zone->GetEntityCommandList(result.GetInt32(10)); + if(primary_command_list){ + npc->SetPrimaryCommands(primary_command_list); + npc->primary_command_list_id = result.GetInt32(9); + } + if(secondary_command_list){ + npc->SetSecondaryCommands(secondary_command_list); + npc->secondary_command_list_id = result.GetInt32(10); + } + npc->appearance.min_level = result.GetInt8(2); + npc->appearance.max_level = result.GetInt8(3); + npc->appearance.level = result.GetInt8(2); + npc->appearance.difficulty = result.GetInt8(4); + npc->appearance.race = result.GetInt8(5); + npc->appearance.model_type = result.GetInt16(6); + npc->appearance.soga_model_type = result.GetInt16(62); + npc->appearance.adventure_class = result.GetInt8(7); + npc->appearance.gender = result.GetInt8(8); + npc->appearance.display_name = result.GetInt8(11); + npc->features.hair_type = result.GetInt16(14); + npc->features.hair_face_type = result.GetInt16(15); + npc->features.wing_type = result.GetInt16(16); + npc->features.chest_type = result.GetInt16(17); + npc->features.legs_type = result.GetInt16(18); + npc->features.soga_hair_type = result.GetInt16(19); + npc->features.soga_hair_face_type = result.GetInt16(20); + npc->appearance.attackable = result.GetInt8(21); + npc->appearance.show_level = result.GetInt8(22); + npc->appearance.targetable = result.GetInt8(23); + npc->appearance.show_command_icon = result.GetInt8(24); + npc->appearance.display_hand_icon = result.GetInt8(25); + npc->appearance.hide_hood = result.GetInt8(70); + npc->appearance.randomize = result.GetInt32(61); + npc->SetTotalHP(result.GetInt32(26)); + npc->SetTotalPower(result.GetInt32(27)); + npc->SetHP(npc->GetTotalHP()); + npc->SetPower(npc->GetTotalPower()); + if(npc->GetTotalHP() == 0){ + npc->SetTotalHP(15*npc->GetLevel() + 1); + npc->SetHP(15*npc->GetLevel() + 1); + } + if(npc->GetTotalPower() == 0){ + npc->SetTotalPower(15*npc->GetLevel() + 1); + npc->SetPower(15*npc->GetLevel() + 1); + } + npc->size = result.GetInt16(28); + npc->appearance.pos.collision_radius = result.GetInt16(29); + npc->appearance.action_state = result.GetInt16(30); + npc->appearance.visual_state = result.GetInt16(31); + npc->appearance.mood_state = result.GetInt16(32); + npc->appearance.emote_state = result.GetInt16(71); + npc->appearance.pos.state = result.GetInt16(33); + npc->appearance.activity_status = result.GetInt16(34); + npc->faction_id = result.GetInt32(35); + if(!result.IsNull(36)){ + std::string sub_title = std::string(result.GetString(36)); + if(sub_title.find("Collector") != std::string::npos) { + npc->SetCollector(true); + } + if(strlen(result.GetString(36)) < sizeof(npc->appearance.sub_title)) + strcpy(npc->appearance.sub_title, result.GetString(36)); + else + strncpy(npc->appearance.sub_title, result.GetString(36), sizeof(npc->appearance.sub_title)); + } + npc->SetMerchantID(result.GetInt32(37)); + npc->SetMerchantType(result.GetInt8(38)); + npc->SetSizeOffset(result.GetInt8(39)); + npc->SetAIStrategy(result.GetInt8(41)); + npc->SetPrimarySpellList(result.GetInt32(42)); + npc->SetSecondarySpellList(result.GetInt32(43)); + npc->SetPrimarySkillList(result.GetInt32(44)); + npc->SetSecondarySkillList(result.GetInt32(45)); + npc->SetEquipmentListID(result.GetInt32(46)); + + InfoStruct* info = npc->GetInfoStruct(); + info->set_attack_type(result.GetInt8(40)); + info->set_str_base(result.GetInt16(47)); + info->set_sta_base(result.GetInt16(48)); + info->set_wis_base(result.GetInt16(49)); + info->set_intel_base(result.GetInt16(50)); + info->set_agi_base(result.GetInt16(51)); + info->set_heat_base(result.GetInt16(52)); + info->set_cold_base(result.GetInt16(53)); + info->set_magic_base(result.GetInt16(54)); + info->set_mental_base(result.GetInt16(55)); + info->set_divine_base(result.GetInt16(56)); + info->set_disease_base(result.GetInt16(57)); + info->set_poison_base(result.GetInt16(58)); + info->set_alignment(result.GetSInt8(64)); + + npc->SetAggroRadius(result.GetFloat(59)); + npc->SetCastPercentage(result.GetInt8(60)); + npc->appearance.heroic_flag = result.GetInt8(63); + + info->set_elemental_base(result.GetInt16(65)); + info->set_arcane_base(result.GetInt16(66)); + info->set_noxious_base(result.GetInt16(67)); + npc->SetTotalSavagery(result.GetInt32(68)); + npc->SetTotalDissonance(result.GetInt32(69)); + npc->SetSavagery(npc->GetTotalSavagery()); + npc->SetDissonance(npc->GetTotalDissonance()); + if(npc->GetTotalSavagery() == 0){ + npc->SetTotalSavagery(15*npc->GetLevel() + 1); + npc->SetSavagery(15*npc->GetLevel() + 1); + } + if(npc->GetTotalDissonance() == 0){ + npc->SetTotalDissonance(15*npc->GetLevel() + 1); + npc->SetDissonance(15*npc->GetLevel() + 1); + } + npc->SetPrefixTitle(result.GetString(72)); + npc->SetSuffixTitle(result.GetString(73)); + npc->SetLastName(result.GetString(74)); + + npc->SetSoundsDisabled(result.GetInt8(75)); + + npc->SetMerchantLevelRange(result.GetInt32(76), result.GetInt32(77)); + + npc->SetAAXPRewards(result.GetInt32(78)); + + npc->SetLootTier(result.GetInt32(79)); + + npc->SetLootDropType(result.GetInt32(80)); + + npc->SetScaredByStrongPlayers(result.GetInt32(81)); + + if(!result.IsNull(82)){ + std::string action_state_str = std::string(result.GetString(82)); + npc->GetInfoStruct()->set_action_state(action_state_str); + } + + zone->AddNPC(id, npc); + + //skipped spells/skills/equipment as it is all loaded, the following rely on a spawn to load + LoadAppearance(zone, spawn_id); + LoadNPCAppearanceEquipmentData(zone, spawn_id); + + LogWrite(NPC__DEBUG, 0, "NPC", "Loaded NPC: '%s' (%u).", npc->appearance.name, spawn_id); + return true; + } + + LogWrite(NPC__DEBUG, 0, "NPC", "Unable to find a npc for spawn id of %u", spawn_id); + return false; +} + +void WorldDatabase::LoadAppearance(ZoneServer* zone, int32 spawn_id) { + Entity* entity = zone->GetNPC(spawn_id); + if (!entity) + return; + + DatabaseResult result, result2; + map appearance_types; + map > appearance_colors; + EQ2_Color color; + color.red = 0; + color.green = 0; + color.blue = 0; + string type; + + database_new.Select(&result2, "SELECT distinct `type`\n" + "FROM npc_appearance\n" + "WHERE length(`type`) > 0 AND `spawn_id` = %u", + spawn_id); + + while(result2.Next()) { + type = string(result2.GetString(0)); + appearance_types[type] = GetAppearanceType(type); + if(appearance_types[type] == 255) + LogWrite(WORLD__ERROR, 0, "Appearance", "Unknown appearance type '%s' in LoadAppearances.", type.c_str()); + } + + database_new.Select(&result, "SELECT `type`, `signed_value`, `red`, `green`, `blue`\n" + "FROM npc_appearance\n" + "WHERE length(`type`) > 0 AND `spawn_id` = %u", + spawn_id); + + while(result.Next()) { + if(appearance_types[result.GetString(0)] < APPEARANCE_SOGA_EBT){ + color.red = result.GetInt8(2); + color.green = result.GetInt8(3); + color.blue = result.GetInt8(4); + } + switch(appearance_types[result.GetString(0)]){ + case APPEARANCE_SOGA_HFHC:{ + entity->features.soga_hair_face_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HTHC:{ + entity->features.soga_hair_type_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HFC:{ + entity->features.soga_hair_face_color = color; + break; + } + case APPEARANCE_SOGA_HTC:{ + entity->features.soga_hair_type_color = color; + break; + } + case APPEARANCE_SOGA_HH:{ + entity->features.soga_hair_highlight_color = color; + break; + } + case APPEARANCE_SOGA_HC1:{ + entity->features.soga_hair_color1 = color; + break; + } + case APPEARANCE_SOGA_HC2:{ + entity->features.soga_hair_color2 = color; + break; + } + case APPEARANCE_SOGA_SC:{ + entity->features.soga_skin_color = color; + break; + } + case APPEARANCE_SOGA_EC:{ + entity->features.soga_eye_color = color; + break; + } + case APPEARANCE_HTHC:{ + entity->features.hair_type_highlight_color = color; + break; + } + case APPEARANCE_HFHC:{ + entity->features.hair_face_highlight_color = color; + break; + } + case APPEARANCE_HTC:{ + entity->features.hair_type_color = color; + break; + } + case APPEARANCE_HFC:{ + entity->features.hair_face_color = color; + break; + } + case APPEARANCE_HH:{ + entity->features.hair_highlight_color = color; + break; + } + case APPEARANCE_HC1:{ + entity->features.hair_color1 = color; + break; + } + case APPEARANCE_HC2:{ + entity->features.hair_color2 = color; + break; + } + case APPEARANCE_WC1:{ + entity->features.wing_color1 = color; + break; + } + case APPEARANCE_WC2:{ + entity->features.wing_color2 = color; + break; + } + case APPEARANCE_SC:{ + entity->features.skin_color = color; + break; + } + case APPEARANCE_EC:{ + entity->features.eye_color = color; + break; + } + case APPEARANCE_SOGA_EBT:{ + for(int i=0;i<3;i++) + entity->features.soga_eye_brow_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_CHEEKT:{ + for(int i=0;i<3;i++) + entity->features.soga_cheek_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_NT:{ + for(int i=0;i<3;i++) + entity->features.soga_nose_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_CHINT:{ + for(int i=0;i<3;i++) + entity->features.soga_chin_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_LT:{ + for(int i=0;i<3;i++) + entity->features.soga_lip_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_EART:{ + for(int i=0;i<3;i++) + entity->features.soga_ear_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SOGA_EYET:{ + for(int i=0;i<3;i++) + entity->features.soga_eye_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_EBT:{ + for(int i=0;i<3;i++) + entity->features.eye_brow_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_CHEEKT:{ + for(int i=0;i<3;i++) + entity->features.cheek_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_NT:{ + for(int i=0;i<3;i++) + entity->features.nose_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_CHINT:{ + for(int i=0;i<3;i++) + entity->features.chin_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_EART:{ + for(int i=0;i<3;i++) + entity->features.ear_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_EYET:{ + for(int i=0;i<3;i++) + entity->features.eye_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_LT:{ + for(int i=0;i<3;i++) + entity->features.lip_type[i] = result.GetInt8(2+i); + break; + } + case APPEARANCE_SHIRT:{ + entity->features.shirt_color = color; + break; + } + case APPEARANCE_UCC:{ + break; + } + case APPEARANCE_PANTS:{ + entity->features.pants_color = color; + break; + } + case APPEARANCE_ULC:{ + break; + } + case APPEARANCE_U9:{ + break; + } + case APPEARANCE_BODY_SIZE:{ + entity->features.body_size = color.red; + break; + } + case APPEARANCE_SOGA_WC1:{ + break; + } + case APPEARANCE_SOGA_WC2:{ + break; + } + case APPEARANCE_SOGA_SHIRT:{ + break; + } + case APPEARANCE_SOGA_UCC:{ + break; + } + case APPEARANCE_SOGA_PANTS:{ + break; + } + case APPEARANCE_SOGA_ULC:{ + break; + } + case APPEARANCE_SOGA_U13:{ + break; + } + case APPEARANCE_BODY_AGE: { + entity->features.body_age = color.red; + break; + } + case APPEARANCE_MC:{ + entity->features.model_color = color; + break; + } + case APPEARANCE_SMC:{ + entity->features.soga_model_color = color; + break; + } + case APPEARANCE_SBS: { + entity->features.soga_body_size = color.red; + break; + } + case APPEARANCE_SBA: { + entity->features.soga_body_age = color.red; + break; + } + } + } + + entity->info_changed = true; +} + +void WorldDatabase::LoadNPCAppearanceEquipmentData(ZoneServer* zone, int32 spawn_id) { + NPC* npc = zone->GetNPC(spawn_id); + if(!npc) { + LogWrite(NPC__ERROR, 0, "NPC", "Unable to get a valid npc (%u) in %s", spawn_id, __FUNCTION__); + return; + } + + DatabaseResult result; + int8 slot = 0; + + if (!database_new.Select(&result, "SELECT slot_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue\n" + "FROM npc_appearance_equip\n" + "WHERE spawn_id = %u\n", + spawn_id)) + { + + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + slot = result.GetInt8(0); + if(slot < NUM_SLOTS) { + npc->SetEquipment(slot, result.GetInt16(1), result.GetInt8(2), result.GetInt8(3), result.GetInt8(4), result.GetInt8(5), result.GetInt8(6), result.GetInt8(7)); + } + } +} + +void WorldDatabase::SaveCharacterPicture(int32 characterID, int8 type, uchar* picture, int32 picture_size) { + stringstream ss_hex; + stringstream ss_query; + ss_hex.flags(ios::hex); + for (int32 i = 0; i < picture_size; i++) + ss_hex << setfill('0') << setw(2) << (int32)picture[i]; + + ss_query << "INSERT INTO `character_pictures` (`char_id`, `pic_type`, `picture`) VALUES (" << characterID << ", " << (int32)type << ", '" << ss_hex.str() << "') ON DUPLICATE KEY UPDATE `picture` = '" << ss_hex.str() << "'"; + + Query query; + query.RunQuery2(ss_query.str(), Q_REPLACE); + + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error: in SaveCharacterPicture! Error Message: ", query.GetError()); +} + +void WorldDatabase::LoadZoneFlightPaths(ZoneServer* zone) { + DatabaseResult result; + int32 total = 0; + + if (!database_new.Select(&result, "SELECT id, speed, flying, early_dismount FROM flight_paths WHERE zone_id = %u", zone->GetZoneID())) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + FlightPathInfo* info = new FlightPathInfo; + int32 id = result.GetInt32(0); + info->speed = result.GetFloat(1); + info->flying = result.GetInt8(2) == 1 ? true : false; + info->dismount = result.GetInt8(3) == 1 ? true : false; + + zone->AddFlightPath(id, info); + total++; + } + + LogWrite(ZONE__DEBUG, 0, "Zone", "Loaded %u flight paths for %s", total, zone->GetZoneDescription()); + LoadZoneFlightPathLocations(zone); +} + +void WorldDatabase::LoadZoneFlightPathLocations(ZoneServer* zone) { + DatabaseResult result; + int32 total = 0; + + if (!database_new.Select(&result, "SELECT loc.flight_path, loc.x, loc.y, loc.z FROM flight_paths_locations loc\n" + "INNER JOIN flight_paths path\n" + "ON loc.flight_path = path.id\n" + "WHERE path.zone_id = %u\n" + "ORDER BY loc.id", + zone->GetZoneID())) + { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + FlightPathLocation* loc = new FlightPathLocation; + int32 id = result.GetInt32(0); + loc->X = result.GetFloat(1); + loc->Y = result.GetFloat(2); + loc->Z = result.GetFloat(3); + + zone->AddFlightPathLocation(id, loc); + total++; + } + + LogWrite(ZONE__DEBUG, 0, "Zone", "Loaded %u flight path locations for %s", total, zone->GetZoneDescription()); +} + +void WorldDatabase::SaveCharacterLUAHistory(Player* player, int32 event_id, int32 value, int32 value2) { + Query query; + query.AddQueryAsync(player->GetCharacterID(), this, Q_REPLACE, "REPLACE INTO character_lua_history(char_id, event_id, value, value2) VALUES(% u, % u, % u, % u)", player->GetCharacterID(), event_id, value, value2); + +// if (!database_new.Query("REPLACE INTO character_lua_history (char_id, event_id, value, value2) VALUES (%u, %u, %u, %u)", player->GetCharacterID(), event_id, value, value2)) +// LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); +} + +void WorldDatabase::LoadCharacterLUAHistory(int32 char_id, Player* player) { + DatabaseResult result; + int32 total = 0; + + if (!database_new.Select(&result, "SELECT event_id, value, value2 FROM character_lua_history WHERE char_id = %u", char_id)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + + while (result.Next()) { + int32 id = result.GetInt32(0); + LUAHistory* hd = new LUAHistory; + hd->Value = result.GetInt32(1); + hd->Value2 = result.GetInt32(2); + hd->SaveNeeded = false; + player->LoadLUAHistory(id, hd); + total++; + } + + LogWrite(PLAYER__DEBUG, 0, "Player", "Loaded %u LUA history for %s", total, player->GetName()); +} + +void WorldDatabase::FindSpell(Client* client, char* findString) +{ + + char* find_escaped = getEscapeString(findString); + + DatabaseResult result; + if (!database_new.Select(&result, "SELECT s.`id`, ts.spell_id, ts.index, `name`, `tier` " + "FROM (spells s, spell_tiers st) " + "LEFT JOIN spell_ts_ability_index ts " + "ON s.`id` = ts.spell_id " + "WHERE s.id = st.spell_id and s.name like '%%%s%%' AND s.is_active = 1 " + "ORDER BY s.`id`, `tier` limit 50", find_escaped)) + { + // error + } + else + { + client->Message(CHANNEL_COLOR_YELLOW, "SpellID (SpellTier): SpellName for %s", findString); + while (result.Next()) + { + int32 spell_id = result.GetInt32Str("id"); + string spell_name = result.GetStringStr("name"); + int8 tier = result.GetInt8Str("tier"); + client->Message(CHANNEL_COLOR_YELLOW, "%i (%i): %s", spell_id, tier, spell_name.c_str()); + } + client->Message(CHANNEL_COLOR_YELLOW, "End Spell Results for %s", find_escaped); + } + + safe_delete_array(find_escaped); +} + +void WorldDatabase::LoadChestTraps() { + chest_trap_list.Clear(); + int32 index = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, applicable_zone_id, chest_min_difficulty, chest_max_difficulty, spell_id, spell_tier FROM chest_traps"); + if (result && mysql_num_rows(result) > 0) { + Title* title = 0; + while (result && (row = mysql_fetch_row(result))) { + int32 dbid = atoul(row[0]); + sint32 applicable_zone_id = atoi(row[1]); + int32 mindifficulty = atoul(row[2]); + int32 maxdifficulty = atoul(row[3]); + int32 spellid = atoul(row[4]); + int32 tier = atoul(row[5]); + ChestTrap* trap = new ChestTrap(dbid,applicable_zone_id,mindifficulty,maxdifficulty,spellid,tier); + chest_trap_list.AddChestTrap(trap); + } + } +} + +bool WorldDatabase::CheckExpansionFlags(ZoneServer* zone, int32 spawnXpackFlag) +{ + if (spawnXpackFlag == 0) + return true; + + int32 globalXpackFlag = rule_manager.GetGlobalRule(R_Expansion, GlobalExpansionFlag)->GetInt32(); + int32 zoneXpackFlag = zone->GetExpansionFlag(); + // zone expansion flag takes priority + if (zoneXpackFlag > 0 && (spawnXpackFlag & zoneXpackFlag) == 0) + return false; + // zone expansion flag fails, then if global expansion flag set, we see if that bit operand doesn't match, skip mob then + else if (zoneXpackFlag == 0 && globalXpackFlag > 0 && (spawnXpackFlag & globalXpackFlag) == 0) + return false; + + return true; +} + +bool WorldDatabase::CheckHolidayFlags(ZoneServer* zone, int32 spawnHolidayFlag) +{ + if (spawnHolidayFlag == 0) + return true; + + int32 globalHolidayFlag = rule_manager.GetGlobalRule(R_Expansion, GlobalHolidayFlag)->GetInt32(); + int32 zoneHolidayFlag = zone->GetHolidayFlag(); + // zone holiday flag takes priority + if (zoneHolidayFlag > 0 && (spawnHolidayFlag & zoneHolidayFlag) == 0) + return false; + // zone holiday flag fails, then if global expansion flag set, we see if that bit operand doesn't match, skip mob then + else if (zoneHolidayFlag == 0 && globalHolidayFlag > 0 && (spawnHolidayFlag & globalHolidayFlag) == 0) + return false; + + return true; +} + +void WorldDatabase::GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn) +{ + if (!spawn) + return; + + if (zone->house_object_database_lookup.count(spawn->GetModelType()) < 1) + zone->house_object_database_lookup.Put(spawn->GetModelType(), spawn->GetDatabaseID()); + + DatabaseResult result; + + database_new.Select(&result, "SELECT pickup_item_id, pickup_unique_item_id\n" + " FROM spawn_instance_data\n" + " WHERE spawn_id = %u and spawn_location_id = %u", + spawn->GetDatabaseID(),spawn->GetSpawnLocationID()); + + if (result.GetNumRows() > 0 && result.Next()) { + spawn->SetPickupItemID(result.GetInt32(0)); + spawn->SetPickupUniqueItemID(result.GetInt32(1)); + + if (spawn->GetZone() != nullptr && spawn->GetMap() != nullptr && spawn->GetMap()->IsMapLoaded()) + { + auto loc = glm::vec3(spawn->GetX(),spawn->GetZ(),spawn->GetY()); + uint32 GridID = 0; + float new_z = spawn->FindBestZ(loc, nullptr, &GridID); + spawn->SetPos(&(spawn->appearance.pos.grid_id), GridID); + } + } +} + +int32 WorldDatabase::FindHouseInstanceSpawn(Spawn* spawn) +{ + DatabaseResult result; + + database_new.Select(&result, "SELECT id\n" + " FROM spawn\n" + " WHERE model_type = %u and is_instanced_spawn=1 limit 1", + spawn->GetModelType()); + + if (result.GetNumRows() > 0 && result.Next()) { + return result.GetInt32(0); + } + + return 0; +} + + +void WorldDatabase::LoadStartingSkills(World* world) +{ + world->MStartingLists.writelock(); + + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_id, class_id, skill_id, current_val, max_val, progress FROM starting_skills"); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + Skill* skill = 0; + + while (result && (row = mysql_fetch_row(result))) + { + + StartingSkill skill; + skill.header.race_id = atoul(row[0]); + skill.header.class_id = atoul(row[1]); + skill.skill_id = atoul(row[2]); + skill.current_val = atoul(row[3]); + skill.max_val = atoul(row[4]); + + if (!world->starting_skills.count(skill.header.race_id)) + { + multimap* skills = new multimap(); + skills->insert(make_pair(skill.header.class_id, skill)); + world->starting_skills.insert(make_pair(skill.header.race_id, skills)); + } + else + { + multimap*>::const_iterator skills = world->starting_skills.find(skill.header.race_id); + skills->second->insert(make_pair(skill.header.class_id, skill)); + } + total++; + } + } + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Starting Skill(s)", total); + + world->MStartingLists.releasewritelock(); +} + + +void WorldDatabase::LoadVoiceOvers(World* world) +{ + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type_id, id, indexed, mp3_string, text_string, emote_string, key1, key2, garbled, garble_link_id FROM voiceovers"); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + Skill* skill = 0; + + while (result && (row = mysql_fetch_row(result))) + { + + VoiceOverStruct vos; + vos.mp3_string = std::string(row[3]); + vos.text_string = std::string(row[4]); + vos.emote_string = std::string(row[5]); + vos.key1 = atoul(row[6]); + vos.key2 = atoul(row[7]); + vos.is_garbled = atoul(row[8]); + vos.garble_link_id = atoul(row[9]); + int8 type = atoul(row[0]); + int32 id = atoul(row[1]); + int16 index = atoul(row[2]); + world->AddVoiceOver(type, id, index, &vos); + total++; + } + } + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Voiceover(s)", total); +} + + +void WorldDatabase::LoadStartingSpells(World* world) +{ + world->MStartingLists.writelock(); + + int32 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_id, class_id, spell_id, tier, knowledge_slot FROM starting_spells"); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + Skill* skill = 0; + + while (result && (row = mysql_fetch_row(result))) + { + + StartingSpell spell; + spell.header.race_id = atoul(row[0]); + spell.header.class_id = atoul(row[1]); + spell.spell_id = atoul(row[2]); + spell.tier = atoul(row[3]); + spell.knowledge_slot = atoul(row[4]); + + if (!world->starting_spells.count(spell.header.race_id)) + { + multimap* spells = new multimap(); + spells->insert(make_pair(spell.header.class_id, spell)); + world->starting_spells.insert(make_pair(spell.header.race_id, spells)); + } + else + { + multimap*>::iterator spells = world->starting_spells.find(spell.header.race_id); + spells->second->insert(make_pair(spell.header.class_id, spell)); + } + total++; + } + } + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Starting Spell(s)", total); + + world->MStartingLists.releasewritelock(); +} + + +bool WorldDatabase::DeleteSpiritShard(int32 id){ + Query query; + query.RunQuery2(Q_DELETE, "delete FROM character_spirit_shards where id=%u",id); + if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){ + LogWrite(WORLD__ERROR, 0, "World", "Error in DeleteSpiritShard query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + return true; +} + +int32 WorldDatabase::CreateSpiritShard(const char* name, int32 level, int8 race, int8 gender, int8 adventure_class, + int16 model_type, int16 soga_model_type, int16 hair_type, int16 hair_face_type, int16 wing_type, + int16 chest_type, int16 legs_type, int16 soga_hair_type, int16 soga_hair_face_type, int8 hide_hood, + int16 size, int16 collision_radius, int16 action_state, int16 visual_state, int16 mood_state, int16 emote_state, + int16 pos_state, int16 activity_status, char* sub_title, char* prefix_title, char* suffix_title, char* lastname, + float x, float y, float z, float heading, int32 gridid, int32 charid, int32 zoneid, int32 instanceid) +{ + LogWrite(WORLD__INFO, 3, "World", "Saving Spirit Shard %s %u", name, charid); + + Query query; + char* name_escaped = getEscapeString(name); + + if(!sub_title) + sub_title = ""; + char* subtitle_escaped = getEscapeString(sub_title); + char* prefix_escaped = getEscapeString(prefix_title); + char* suffix_escaped = getEscapeString(suffix_title); + char* lastname_escaped = getEscapeString(lastname); + string insert = string("INSERT INTO character_spirit_shards (name, level, race, gender, adventure_class, model_type, soga_model_type, hair_type, hair_face_type, wing_type, chest_type, legs_type, soga_hair_type, soga_hair_face_type, hide_hood, size, collision_radius, action_state, visual_state, mood_state, emote_state, pos_state, activity_status, sub_title, prefix_title, suffix_title, lastname, x, y, z, heading, gridid, charid, zoneid, instanceid) VALUES ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s', '%s', '%s', '%s', %f, %f, %f, %f, %u, %u, %u, %u)"); + query.RunQuery2(Q_INSERT, insert.c_str(), name_escaped, level, race, gender, adventure_class, model_type, soga_model_type, + hair_type, hair_face_type, wing_type, chest_type, legs_type, soga_hair_type, + soga_hair_face_type, hide_hood, size, collision_radius, action_state, visual_state, + mood_state, emote_state, pos_state, activity_status, subtitle_escaped, prefix_escaped, suffix_escaped, + lastname_escaped, x, y, z, heading, gridid, charid, zoneid, instanceid); + + + safe_delete_array(name_escaped); + safe_delete_array(subtitle_escaped); + safe_delete_array(prefix_escaped); + safe_delete_array(suffix_escaped); + safe_delete_array(lastname_escaped); + + return query.GetLastInsertedID(); +} + +void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int8 db_spell_type) +{ + SpellProcess* spellProcess = client->GetCurrentZone()->GetSpellProcess(); + Player* player = client->GetPlayer(); + + if(!spellProcess) + return; + DatabaseResult result; + + multimap restoreSpells; + // Use -1 on type and subtype to turn the enum into an int and make it a 0 index + if (!database_new.Select(&result, "SELECT name, caster_char_id, target_char_id, target_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function FROM character_spell_effects WHERE charid = %u and db_effect_type = %u", char_id, db_spell_type)) { + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg()); + return; + } + InfoStruct* info = player->GetInfoStruct(); + while (result.Next()) { +//result.GetInt8Str + char spell_name[60]; + strncpy(spell_name, result.GetStringStr("name"), 60); + + int32 caster_char_id = result.GetInt32Str("caster_char_id"); + int32 target_char_id = result.GetInt32Str("target_char_id"); + int8 target_type = result.GetInt8Str("target_type"); + int32 spell_id = result.GetInt32Str("spell_id"); + int32 effect_slot = result.GetInt32Str("effect_slot"); + int32 slot_pos = result.GetInt32Str("slot_pos"); + int16 icon = result.GetInt32Str("icon"); + int16 icon_backdrop = result.GetInt32Str("icon_backdrop"); + int8 conc_used = result.GetInt32Str("conc_used"); + int8 tier = result.GetInt32Str("tier"); + float total_time = result.GetFloatStr("total_time"); + int32 expire_timestamp = result.GetInt32Str("expire_timestamp"); + + string lua_file (result.GetStringStr("lua_file")); + + int8 custom_spell = result.GetInt32Str("custom_spell"); + + int32 damage_remaining = result.GetInt32Str("damage_remaining"); + int32 effect_bitmask = result.GetInt32Str("effect_bitmask"); + int16 num_triggers = result.GetInt32Str("num_triggers"); + int8 had_triggers = result.GetInt32Str("had_triggers"); + int8 cancel_after_triggers = result.GetInt32Str("cancel_after_triggers"); + int8 crit = result.GetInt32Str("crit"); + int8 last_spellattack_hit = result.GetInt32Str("last_spellattack_hit"); + int8 interrupted = result.GetInt32Str("interrupted"); + int8 resisted = result.GetInt32Str("resisted"); + int8 has_damaged = result.GetInt32Str("has_damaged"); + std::string custom_function = std::string(result.GetStringStr("custom_function")); + LuaSpell* lua_spell = 0; + if(custom_spell) + { + if((lua_spell = lua_interface->GetSpell(lua_file.c_str())) == nullptr) + { + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), custom lua script not loaded, when attempting to load.", spell_id, tier, lua_file.c_str()); + lua_interface->LoadLuaSpell(lua_file); + } + } + + Spell* spell = master_spell_list.GetSpell(spell_id, tier); + + if(!spell) + { + LogWrite(LUA__ERROR, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u), spell could not be found!", spell_id, tier); + spell = master_spell_list.GetSpell(spell_id, 0); + if(spell) + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u), identified tier 0 as replacement since the GetSpell failed!", spell_id, tier); + else + continue; + } + + bool isMaintained = false; + bool isExistingLuaSpell = false; + MaintainedEffects* effect = nullptr; + Client* tmpCaster = nullptr; + if(caster_char_id == player->GetCharacterID() && (target_char_id == 0xFFFFFFFF || target_char_id == player->GetCharacterID()) && (effect = player->GetMaintainedSpell(spell_id)) != nullptr) + { + safe_delete(lua_spell); + lua_spell = effect->spell; + if(lua_spell) + spell = lua_spell->spell; + isMaintained = true; + isExistingLuaSpell = true; + } + else if ( caster_char_id != player->GetCharacterID() && (tmpCaster = zone_list.GetClientByCharID(caster_char_id)) != nullptr + && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(spell_id)) != nullptr) + { + if(effect->spell && effect->spell_id == spell_id) + { + safe_delete(lua_spell); + if(tmpCaster->GetCurrentZone() == player->GetZone()) + spellProcess->AddLuaSpellTarget(effect->spell, client->GetPlayer()->GetID()); + lua_spell = effect->spell; + spell = effect->spell->spell; + isExistingLuaSpell = true; + isMaintained = true; + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), effect spell id %u maintained spell recovered from %s", spell_id, tier, spell_name, effect ? effect->spell_id : 0, (tmpCaster && tmpCaster->GetPlayer()) ? tmpCaster->GetPlayer()->GetName() : "?"); + } + else + { + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), something went wrong loading another characters maintained spell. Effect has spell id %u", spell_id, tier, lua_file.c_str(), effect ? effect->spell_id : 0); + safe_delete(lua_spell); + continue; + } + } + else if(custom_spell && lua_spell) + { + lua_spell->spell = new Spell(spell); + + lua_interface->AddCustomSpell(lua_spell); + } + else if(db_spell_type == DB_TYPE_MAINTAINEDEFFECTS) + { + safe_delete(lua_spell); + if(!target_char_id) + continue; + + lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str()); + if(lua_spell) + lua_spell->spell = spell; + } + + if(!lua_spell) + { + LogWrite(LUA__ERROR, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), lua_spell FAILED, when attempting to load.", spell_id, tier, lua_file.c_str()); + continue; + } + + SpellScriptTimer* timer = nullptr; + if(!isExistingLuaSpell && expire_timestamp != 0xFFFFFFFF && custom_function.size() > 0) + { + timer = new SpellScriptTimer; + + timer->caster = 0; + timer->deleteWhenDone = false; + timer->target = 0; + + timer->time = expire_timestamp; + timer->customFunction = string(custom_function); // TODO + timer->spell = lua_spell; + timer->caster = (caster_char_id == player->GetCharacterID()) ? player->GetID() : 0; + + if(target_char_id == 0xFFFFFFFF && player->HasPet()) + timer->target = player->GetPet()->GetID(); + else + timer->target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0; + + if(!timer->target && target_char_id) + { + Client* tmpClient = zone_list.GetClientByCharID(target_char_id); + if(tmpClient && tmpClient->GetPlayer() && tmpClient->GetPlayer()->GetZone() == player->GetZone()) + timer->target = tmpClient->GetPlayer()->GetID(); + } + } + + if(!isExistingLuaSpell) + { + lua_spell->crit = crit; + lua_spell->damage_remaining = damage_remaining; + lua_spell->effect_bitmask = effect_bitmask; + lua_spell->had_dmg_remaining = (damage_remaining>0) ? true : false; + lua_spell->had_triggers = had_triggers; + lua_spell->initial_caster_char_id = caster_char_id; + lua_spell->initial_target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0; + lua_spell->initial_target_char_id = target_char_id; + lua_spell->interrupted = interrupted; + lua_spell->last_spellattack_hit = last_spellattack_hit; + lua_spell->num_triggers = num_triggers; + lua_spell->has_damaged = has_damaged; + lua_spell->is_damage_spell = has_damaged; + } + + if(lua_spell->initial_target == 0 && target_char_id == 0xFFFFFFFF && player->HasPet()) + { + lua_spell->initial_target = player->GetPet()->GetID(); + lua_spell->initial_target_char_id = target_char_id; + } + //lua_spell->num_calls ?? + //if(target_char_id == player->GetCharacterID()) + // lua_spell->targets.push_back(player->GetID()); + + if(db_spell_type == DB_TYPE_SPELLEFFECTS) + { + if (caster_char_id != player->GetCharacterID() && lua_spell->spell->GetSpellData()->group_spell && lua_spell->spell->GetSpellData()->friendly_spell) + { + if(!isExistingLuaSpell) + safe_delete(lua_spell); + continue; + } + + player->MSpellEffects.writelock(); + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id); + + if(lua_spell->caster && (rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() || (!rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() && lua_spell->caster->GetZone() == player->GetZone()))) + info->spell_effects[effect_slot].caster = lua_spell->caster; + else if(caster_char_id != player->GetCharacterID()) + { + Client* tmpCaster = zone_list.GetClientByCharID(caster_char_id); + if(tmpCaster) + { + if((rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() || (!rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() && lua_spell->caster->GetZone() == player->GetZone()))) + { + info->spell_effects[effect_slot].caster = tmpCaster->GetPlayer(); + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, found player %s.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, tmpCaster->GetPlayer()->GetName()); + } + else + { + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, found player %s, SKIPPED due to R_Spells, EnableCrossZoneTargetBuffs.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, tmpCaster->GetPlayer()->GetName()); + if(!isExistingLuaSpell) + { + safe_delete(lua_spell); + } + else + { + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + lua_spell->char_id_targets.insert(make_pair(player->GetCharacterID(),0)); + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + player->MSpellEffects.releasewritelock(); + continue; + } + + } + } + else if(caster_char_id == player->GetCharacterID()) + info->spell_effects[effect_slot].caster = player; + else + { + LogWrite(LUA__ERROR, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, failed to find caster will delete: %u", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, isExistingLuaSpell); + if(!isExistingLuaSpell) + safe_delete(lua_spell); + continue; + } + if(spell->GetSpellData()->duration_until_cancel) + info->spell_effects[effect_slot].expire_timestamp = 0xFFFFFFFF; + else + info->spell_effects[effect_slot].expire_timestamp = Timer::GetCurrentTime2() + expire_timestamp; + info->spell_effects[effect_slot].icon = icon; + info->spell_effects[effect_slot].icon_backdrop = icon_backdrop; + info->spell_effects[effect_slot].spell_id = spell_id; + info->spell_effects[effect_slot].tier = tier; + info->spell_effects[effect_slot].total_time = total_time; + info->spell_effects[effect_slot].spell = lua_spell; + + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + multimap::iterator entries; + while((entries = lua_spell->char_id_targets.find(player->GetCharacterID())) != lua_spell->char_id_targets.end()) + { + lua_spell->char_id_targets.erase(entries); + } + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + + lua_spell->slot_pos = slot_pos; + if(!isExistingLuaSpell) + lua_spell->caster = player; // TODO: get actual player + + player->MSpellEffects.releasewritelock(); + + if(!isMaintained) + spellProcess->ProcessSpell(lua_spell, true, "cast", timer); + else + { + // track target id when caster isnt in zone somehow + } + } + else if ( db_spell_type == DB_TYPE_MAINTAINEDEFFECTS ) + { + player->MMaintainedSpells.writelock(); + + DatabaseResult targets; + // Use -1 on type and subtype to turn the enum into an int and make it a 0 index + if (database_new.Select(&targets, "SELECT target_char_id, target_type, db_effect_type, spell_id from character_spell_effect_targets where caster_char_id = %u and effect_slot = %u and slot_pos = %u", char_id, effect_slot, slot_pos)) { + while (targets.Next()) { + int32 target_char = targets.GetInt32Str("target_char_id"); + int8 maintained_target_type = targets.GetInt32Str("target_type"); + int32 in_spell_id = targets.GetInt32Str("spell_id"); + if(spell_id != in_spell_id) + continue; + + int32 idToAdd = 0; + + if(target_char == 0xFFFFFFFF) + { + if( player->HasPet() ) + { + restoreSpells.insert(make_pair(lua_spell, player->GetPet())); + // target added via restoreSpells + } + } + else + { + Client* client2 = zone_list.GetClientByCharID(target_char); + if(client2 && client2->GetPlayer() && client2->GetCurrentZone() == client->GetCurrentZone()) + { + if(maintained_target_type > 0) + { + if(client != client2) + { + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + + if(client2->GetPlayer()->GetPet() && maintained_target_type == PET_TYPE_COMBAT) + { + restoreSpells.insert(make_pair(lua_spell, client2->GetPlayer()->GetPet())); + // target added via restoreSpells + } + if(client2->GetPlayer()->GetCharmedPet() && maintained_target_type == PET_TYPE_CHARMED) + { + restoreSpells.insert(make_pair(lua_spell, client2->GetPlayer()->GetCharmedPet())); + // target added via restoreSpells + } + + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + } // end of pet clause + else if(client != client2) // maintained type must be 0, so client + restoreSpells.insert(make_pair(lua_spell, client2->GetPlayer())); + + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + multimap::iterator entries; + for(entries = lua_spell->char_id_targets.begin(); entries != lua_spell->char_id_targets.end(); entries++) + { + int32 ent_char_id = entries->first; + int8 ent_target_type = entries->second; + if(ent_char_id == target_char && ent_target_type == maintained_target_type) + entries = lua_spell->char_id_targets.erase(entries); + } + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + else + { + lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__); + lua_spell->char_id_targets.insert(make_pair(target_char,maintained_target_type)); + lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__); + } + } + } + } + + Client* tmpClient = 0; + int32 targetID = 0; + if(target_char_id == 0xFFFFFFFF && player->HasPet()) + targetID = player->GetPet()->GetID(); + else if(target_char_id == player->GetCharacterID()) + { + targetID = player->GetID(); + tmpClient = player->GetClient(); + } + else if((tmpClient = zone_list.GetClientByCharID(target_char_id)) != nullptr && tmpClient->GetPlayer()) + targetID = tmpClient->GetPlayer()->GetID(); + + info->maintained_effects[effect_slot].conc_used = conc_used; + strncpy(info->maintained_effects[effect_slot].name, spell_name, 60); + info->maintained_effects[effect_slot].slot_pos = slot_pos; + info->maintained_effects[effect_slot].target = targetID; + info->maintained_effects[effect_slot].target_type = target_type; + if(spell->GetSpellData()->duration_until_cancel) + info->maintained_effects[effect_slot].expire_timestamp = 0xFFFFFFFF; + else + info->maintained_effects[effect_slot].expire_timestamp = Timer::GetCurrentTime2() + expire_timestamp; + info->maintained_effects[effect_slot].icon = icon; + info->maintained_effects[effect_slot].icon_backdrop = icon_backdrop; + info->maintained_effects[effect_slot].spell_id = spell_id; + info->maintained_effects[effect_slot].tier = tier; + info->maintained_effects[effect_slot].total_time = total_time; + info->maintained_effects[effect_slot].spell = lua_spell; + if(!isExistingLuaSpell) + lua_spell->caster = player; + player->MMaintainedSpells.releasewritelock(); + + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s process spell caster %s (%u), caster char id: %u, target id %u (%s).", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", + lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, targetID, tmpClient ? tmpClient->GetPlayer()->GetName() : ""); + if(tmpClient && lua_spell->initial_target_char_id == tmpClient->GetCharacterID()) + { + lua_spell->initial_target = tmpClient->GetPlayer()->GetID(); + spellProcess->AddLuaSpellTarget(lua_spell, lua_spell->initial_target, false); + } + + spellProcess->ProcessSpell(lua_spell, true, "cast", timer); + } + if(!isExistingLuaSpell && expire_timestamp != 0xFFFFFFFF && !isMaintained) + { + lua_spell->timer.SetTimer(expire_timestamp); + lua_spell->timer.SetAtTrigger(lua_spell->spell->GetSpellData()->duration1 * 100); + lua_spell->timer.Start(); + } + + if(target_char_id == player->GetCharacterID() && lua_spell->spell->GetSpellData()->det_type) + player->AddDetrimentalSpell(lua_spell, expire_timestamp); + + if(!isExistingLuaSpell) + { + if(timer) + spellProcess->AddSpellScriptTimer(timer); + + lua_spell->num_calls = 1; + lua_spell->restored = true; + } + + if(!lua_spell->resisted && (lua_spell->spell->GetSpellDuration() > 0 || lua_spell->spell->GetSpellData()->duration_until_cancel)) + spellProcess->AddActiveSpell(lua_spell); + + if ( db_spell_type == DB_TYPE_MAINTAINEDEFFECTS ) + { + // restore concentration on zoning/reloading characters maintained effects + spellProcess->AddConcentration(lua_spell); + if (num_triggers > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, num_triggers, 0); + if (damage_remaining > 0) + ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, damage_remaining, 1); + } + } + + if(db_spell_type == DB_TYPE_SPELLEFFECTS) + { + // if the cross_zone_target_buff option is disabled then we check for all possible targets within the current zone + // if cross_zone_target_buff is enabled, we only check to restore pets, the players get restored by their own spell effects (we don't directly track the pets, indirectly we do through the player casting and their targets) + int8 cross_zone_target_buff = rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8(); + + DatabaseResult targets; + if ( + (!cross_zone_target_buff && database_new.Select(&targets, "SELECT caster_char_id, target_type, spell_id from character_spell_effect_targets where target_char_id = %u", player->GetCharacterID())) || + (database_new.Select(&targets, "SELECT caster_char_id, target_type, spell_id from character_spell_effect_targets where target_char_id = %u and target_type > 0", player->GetCharacterID()))) + { + while (targets.Next()) { + int32 caster_char_id = targets.GetInt32Str("caster_char_id"); + int8 prev_target_type = targets.GetInt32Str("target_type"); + int32 in_spell_id = targets.GetInt32Str("spell_id"); + Client* tmpCaster = nullptr; + MaintainedEffects* effect = nullptr; + if (caster_char_id != player->GetCharacterID() && (tmpCaster = zone_list.GetClientByCharID(caster_char_id)) != nullptr && (cross_zone_target_buff || + tmpCaster->GetCurrentZone() == player->GetZone()) && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(in_spell_id)) != nullptr) + { + if(prev_target_type > 0) + { + if(player->HasPet()) + { + if(player->GetPet() && prev_target_type == PET_TYPE_COMBAT) + restoreSpells.insert(make_pair(effect->spell, player->GetPet())); + if(player->GetCharmedPet() && prev_target_type == PET_TYPE_CHARMED) + restoreSpells.insert(make_pair(effect->spell, player->GetCharmedPet())); + } + } + else if(!player->GetSpellEffect(effect->spell_id, tmpCaster->GetPlayer())) + { + if(effect->spell->initial_target_char_id == player->GetCharacterID()) + effect->spell->initial_target = player->GetID(); + restoreSpells.insert(make_pair(effect->spell, player)); + } + } + } + } + } + + multimap::const_iterator itr; + + for (itr = restoreSpells.begin(); itr != restoreSpells.end(); itr++) + { + LuaSpell* tmpSpell = itr->first; + Entity* target = itr->second; + if(!target) + { + target = client->GetPlayer()->GetPet(); + if(!target) + continue; + } + + Entity* caster = tmpSpell->caster; + if(!caster) + caster = client->GetPlayer(); + + int32 group_id_target = target->group_id; + if(target->IsPet() && target->GetOwner()) + group_id_target = target->GetOwner()->group_id; + + if(caster != target && caster->GetPet() != target && + tmpSpell->spell->GetSpellData()->group_spell && tmpSpell->spell->GetSpellData()->friendly_spell && (caster->group_id == 0 || group_id_target == 0 || caster->group_id != group_id_target)) + { + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s player no longer grouped with %s to reload bonuses for spell %s (target_groupid: %u, caster_groupid: %u).", target->GetName(), caster ? caster->GetName() : "?", tmpSpell->spell->GetName(), group_id_target, caster->group_id); + continue; + } + + LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s using caster %s to reload bonuses for spell %s.", player->GetName(), caster ? caster->GetName() : "?", tmpSpell->spell->GetName()); + + spellProcess->AddLuaSpellTarget(tmpSpell, target->GetID()); + + target->AddSpellEffect(tmpSpell, tmpSpell->timer.GetRemainingTime() != 0 ? tmpSpell->timer.GetRemainingTime() : 0); + vector* sb_list = caster->GetAllSpellBonuses(tmpSpell); + for (int32 x = 0; x < sb_list->size(); x++) { + BonusValues* bv = sb_list->at(x); + target->AddSpellBonus(tmpSpell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req); + } + sb_list->clear(); + safe_delete(sb_list); + // look for a skill bonus on the caster's spell + if(caster->IsPlayer()) + { + SkillBonus* sb = ((Player*)caster)->GetSkillBonus(tmpSpell->spell->GetSpellID()); + if (sb) { + map::iterator itr_skills; + for (itr_skills = sb->skills.begin(); itr_skills != sb->skills.end(); itr_skills++) + target->AddSkillBonus(sb->spell_id, (*itr_skills).second->skill_id, (*itr_skills).second->value); + } + } + + } + +} + +//devn00b: We need to handle non-found factions so the subtraction/addition works on 1st kill. Need to find a better way to handle this, but for now.. +bool WorldDatabase::VerifyFactionID(int32 char_id, int32 faction_id) { + DatabaseResult result; + database_new.Select(&result, "SELECT COUNT(id) as faction_exists from character_factions where faction_id=%u and char_id=%u", faction_id, char_id); + + if (result.Next() && result.GetInt32Str("faction_exists") == 0) + return false; + + return true; +} + +//using int/32 for starting city, should be large enough to support future zones (LOL). TODO: Will probably need more support bolted on, to support PVP and such. +void WorldDatabase::UpdateStartingLanguage(int32 char_id, uint8 race_id, int32 starting_city) +{ + int8 rule = rule_manager.GetGlobalRule(R_World, StartingZoneLanguages)->GetInt8(); + //this should never need to be done but lets make sure we got all the stuff we need. char_id at min since used for both. + if(!char_id || (rule == 0 && !race_id) || (rule == 1 && !starting_city)) + return; + + std::string query_string = "SELECT language_id FROM starting_languages "; + //check the rule, and that they gave us a race, if so use default entries to match live. + if(rule == 0 && race_id >= 0) { + query_string.append("WHERE race=" + std::to_string(race_id)); + } + //if we have a starting city supplied, and the rule is set to use it, deal with it + else if(rule == 1) { + query_string.append("WHERE starting_city=" + std::to_string(starting_city) + " or (starting_city=0 and race_id=" + std::to_string(race_id) + ")"); + } + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT,query_string.c_str()); + LogWrite(PLAYER__DEBUG, 0, "Player", "Adding Languages for starting_city: %i based on rule R_World:StartingZoneLanguages value %u", starting_city, rule); + if (result) { + if (mysql_num_rows(result) > 0) { + while (result && (row = mysql_fetch_row(result))){ + //add custom languages to the character_languages db. + Query query2; + query2.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%u,%u)",char_id, atoul(row[0])); + } + } + } +} + + +//devn00b: load the items the server has into a character_ db for easy access. Should be done on char create. +void WorldDatabase::LoadClaimItems(int32 char_id) +{ + if (!char_id) { + LogWrite(WORLD__DEBUG, 3, "World", "-- There was an error in LoadClaimItems (missing char_id)"); + return; + } + + int16 total = 0; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, item_id, max_claim, one_per_char, veteran_reward_time from claim_items"); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + while (result && (row = mysql_fetch_row(result))) + { + int32 item_id = atoul(row[1]); + int16 max_claim = atoul(row[2]); + int16 curr_claim = max_claim; + int8 one_per_char = atoul(row[3]); + int64 vet_reward_time = atoi64(row[4]); + int32 acct_id = GetCharacterAccountID(char_id); + + if (one_per_char == 1) { + max_claim = 1; + curr_claim = 1; + } + Query query2; + MYSQL_RES* res = query2.RunQuery2(Q_INSERT, "insert ignore into character_claim_items (char_id, item_id, max_claim, curr_claim, one_per_char, veteran_reward_time, account_id) values (%i, %i, %i, %i, %i, %I64i, %i)", char_id, item_id, max_claim, curr_claim, one_per_char, vet_reward_time, acct_id); + total++; + } + } + } + LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Claim Item(s)", total); +} + +int16 WorldDatabase::CountCharClaimItems(int32 char_id) { + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT count(*) from character_claim_items where char_id=%i and curr_claim >0", char_id); + + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + return atoi(row[0]); // Return count + } + + return 0; +} + +vector WorldDatabase::LoadCharacterClaimItems(int32 char_id) { + vector claim; + + //make sure we have a charID shouldn't need this but adding anyway. + if (!char_id) + return claim; + + int32 acct_id = GetCharacterAccountID(char_id); + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, max_claim, curr_claim, one_per_char, last_claim, veteran_reward_time from character_claim_items where char_id=%u and curr_claim > 0", char_id); + int16 i = 0; + + if (result) + { + if (mysql_num_rows(result) > 0) + { + + while (result && (row = mysql_fetch_row(result))) + { + ClaimItems c; + int8 max_claim = atoi(row[1]); + int8 curr_claim = atoi(row[2]); + + //if one per char is set set max/cur to 1. + if (atoi(row[3]) == 1) { + max_claim = 1; + curr_claim = 1; + } + if (atoi(row[3]) == 0) { + + //handle getting lowest claim amt from table, to prevent new chars skewing. + Query query2; + MYSQL_ROW row2; + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT min(curr_claim) AS min_claim FROM character_claim_items WHERE account_id=%i and item_id=%i", acct_id, atoi(row[0])); + + //our current claim is now the lowest. + if (result2 && (row2 = mysql_fetch_row(result2)) && row2[0] != NULL) { + int8 curr_claim = atoi(row2[0]); + } + + } + + c.item_id = atoi(row[0]); + c.max_claim = max_claim; + c.curr_claim = curr_claim; + c.one_per_char = atoi(row[3]); + c.vet_reward_time = atoi(row[5]); + claim.push_back(c); + i++; + } + return claim; + } + } + return claim; +} + +void WorldDatabase::ClaimItem(int32 char_id, int32 item_id, Client* client) { + + if (!client || !item_id || !char_id) { + return; + } + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, max_claim, curr_claim, one_per_char, last_claim, veteran_reward_time, one_per_char from character_claim_items where char_id=%u and item_id=%u", char_id, item_id); + + if (result) + { + if (mysql_num_rows(result) > 0) + { + while (result && (row = mysql_fetch_row(result))) + { + Item* item = master_item_list.GetItem(item_id); + Player* player = client->GetPlayer(); + InfoStruct* info = player->GetInfoStruct(); + + int32 item_id = atoi(row[0]); + int8 max_claim = atoi(row[1]); + int8 min_claim = 0; + int8 curr_claim = atoi(row[2]); + int32 vet_reward_req = atoi(row[5]); + int8 one_per_char = atoi(row[6]); + int32 acct_id = GetCharacterAccountID(char_id); + char claim_msg[512] = { 0 }; + + //no claims left. + if (curr_claim == 0) { + client->Message(CHANNEL_COLOR_RED, "You have nothing to claim."); + return; + } + + //On live, characters must wait 6 seconds between claims. So..Lots of stuff here. + uint32 last_claim = info->get_last_claim_time(); //load our last claim + time_t now = time(0); //get the current epoc time + uint32 curr_time = now; // current time. + uint32 total_time = curr_time - last_claim; //Time since last claim (Current time - last claim) + + if (total_time < 6) { + uint32 ttw = 6 - total_time; + char tmp[64] = { 0 }; + sprintf(tmp, "You must wait %u more seconds.", ttw); + client->Message(CHANNEL_COLOR_RED, tmp); + return; + } + //handle the 2 different messages found from live (11/2022) + if (item->generic_info.item_type == 18) { + sprintf(claim_msg, "You have consumed the item\nYou have chosen to give this character a %s", item->name.c_str()); + } + else { + sprintf(claim_msg, "You have consumed the item"); + } + + + //not one per char, so remove 1 from account. + if (one_per_char == 0) { + Query query3; + MYSQL_ROW row3; + MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT min(curr_claim) AS min_claim FROM character_claim_items WHERE account_id=%i and item_id=%i", acct_id, item_id); + + if (result3 && (row3 = mysql_fetch_row(result3)) && row3[0] != NULL) { + int16 min_claim = atoi(row3[0]); + + //remove 1 from claim on one per char + int32 acct_id = GetCharacterAccountID(char_id); + Query query2; + query2.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET curr_claim=(SELECT min(curr_claim) AS min_claim FROM character_claim_items WHERE account_id=%i and item_id=%i) - 1 , `last_claim`=%u WHERE account_id=%i and item_id=%i AND curr_claim > 0", acct_id, item_id, curr_time, acct_id, item_id); + //give the item to the player, and update last claim time. + bool item_added = client->AddItem(item_id); + if (item_added == 0) { + return; + } + info->set_last_claim_time(curr_time); + client->Message(CHANNEL_COLOR_YELLOW, claim_msg); + //reload the window to show new count. + client->ShowClaimWindow(); + return; + } + } + + Query query4; + query4.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET `curr_claim`=0 , `last_claim`=%u WHERE char_id=%i and item_id=%i", curr_time, char_id, item_id); + //give the item to the player, and update last claim time. + //client->AddItem(item_id); + bool item_added = client->AddItem(item_id); + if (item_added == 0) { + return; + } + info->set_last_claim_time(curr_time); + client->Message(CHANNEL_COLOR_YELLOW, claim_msg); + //reload the window to show new count. + client->ShowClaimWindow(); + return; + } + } + } + return; +} + +//returns account age in seconds +int32 WorldDatabase::GetAccountAge(int32 account_id) { + if (!account_id) + return 0; + + int32 acct_age = 0; + int32 acct_created = 0; + time_t now = time(0); //get the current epoc time + uint32 curr_time = now; // current time. + + Query query; + //order by created date and grab the oldest one. + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT UNIX_TIMESTAMP(created_date) FROM characters WHERE account_id=%i ORDER BY created_date LIMIT 1", account_id); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row = mysql_fetch_row(result); + acct_created = atoi(row[0]); + } + if (!curr_time || !acct_created) + return 0; + + acct_age = curr_time - acct_created; + return acct_age; +} + +void WorldDatabase::SaveSignMark(int32 char_id, int32 sign_id, char* char_name, Client* client) { + + if (!database_new.Query("update spawn_signs set char_id=%u, char_name='%s' where widget_id=%u", char_id, char_name, sign_id)) { + LogWrite(SIGN__DEBUG, 0, "Sign", "ERROR in WorldDatabase::SaveSignMark"); + return; + } + +} + +string WorldDatabase::GetSignMark(int32 char_id, int32 sign_id, char* char_name) { + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT char_name from spawn_signs where widget_id=%u", sign_id); + char* charname = 0; + + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row = mysql_fetch_row(result); + + charname = new char[strlen(row[0]) + 1]; + memset(charname, 0, strlen(row[0]) + 1); + strcpy(charname, row[0]); + } + + return charname; +} + +int32 WorldDatabase::GetMysqlExpCurve(int level) { + Query query; + MYSQL_ROW row; + //this should never happen but just in case. + if(!level){ + level = 95; + } + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT exp_needed from exp_per_level where level=%u", level); + if (result && mysql_num_rows(result) > 0) { + MYSQL_ROW row = mysql_fetch_row(result); + return atoi(row[0]); + } + //return 1 so we dont break shit later divide by 0 and all that. + return 1; +} \ No newline at end of file diff --git a/source/WorldServer/WorldDatabase.h b/source/WorldServer/WorldDatabase.h new file mode 100644 index 0000000..30e98cb --- /dev/null +++ b/source/WorldServer/WorldDatabase.h @@ -0,0 +1,664 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQ2WORLD_EMU_DATABASE_H +#define EQ2WORLD_EMU_DATABASE_H + +#ifdef WIN32 + #include + #include +#endif +#include +#include +#include +#include +#include + +#include "../common/database.h" +#include "../common/types.h" +#include "../common/MiscFunctions.h" +#include "../common/Mutex.h" +#include "../common/DatabaseNew.h" +#include "client.h" +#include "Object.h" +#include "Widget.h" +#include "Sign.h" +#include "NPC.h" +#include "zoneserver.h" +#include "Collections/Collections.h" +#include "Achievements/Achievements.h" +#include "Recipes/Recipe.h" +#include "../common/PacketStruct.h" +#include "Spells.h" +#include "Titles.h" +#include "Rules/Rules.h" +#include "Languages.h" +#include "World.h" + +using namespace std; + +#define APPEARANCE_SOGA_HFHC 0 +#define APPEARANCE_SOGA_HTHC 1 +#define APPEARANCE_SOGA_HFC 2 +#define APPEARANCE_SOGA_HTC 3 +#define APPEARANCE_SOGA_HH 4 +#define APPEARANCE_SOGA_HC1 5 +#define APPEARANCE_SOGA_HC2 6 +#define APPEARANCE_SOGA_SC 7 +#define APPEARANCE_SOGA_EC 8 +#define APPEARANCE_HTHC 9 +#define APPEARANCE_HFHC 10 +#define APPEARANCE_HTC 11 +#define APPEARANCE_HFC 12 +#define APPEARANCE_HH 13 +#define APPEARANCE_HC1 14 +#define APPEARANCE_HC2 15 +#define APPEARANCE_WC1 16 +#define APPEARANCE_WC2 17 +#define APPEARANCE_SC 18 +#define APPEARANCE_EC 19 +#define APPEARANCE_SHIRT 20 +#define APPEARANCE_UCC 21 +#define APPEARANCE_PANTS 22 +#define APPEARANCE_ULC 23 +#define APPEARANCE_U9 24 +#define APPEARANCE_BODY_SIZE 25 +#define APPEARANCE_SOGA_WC1 26 +#define APPEARANCE_SOGA_WC2 27 +#define APPEARANCE_SOGA_SHIRT 28 +#define APPEARANCE_SOGA_UCC 29 +#define APPEARANCE_SOGA_PANTS 30 +#define APPEARANCE_SOGA_ULC 31 +#define APPEARANCE_SOGA_U13 32 +#define APPEARANCE_SOGA_EBT 33 +#define APPEARANCE_SOGA_CHEEKT 34 +#define APPEARANCE_SOGA_NT 35 +#define APPEARANCE_SOGA_CHINT 36 +#define APPEARANCE_SOGA_LT 37 +#define APPEARANCE_SOGA_EART 38 +#define APPEARANCE_SOGA_EYET 39 +#define APPEARANCE_EBT 40 +#define APPEARANCE_CHEEKT 41 +#define APPEARANCE_NT 42 +#define APPEARANCE_CHINT 43 +#define APPEARANCE_EART 44 +#define APPEARANCE_EYET 45 +#define APPEARANCE_LT 46 +#define APPEARANCE_BODY_AGE 47 +#define APPEARANCE_MC 48 +#define APPEARANCE_SMC 49 +#define APPEARANCE_SBS 50 +#define APPEARANCE_SBA 51 + +#define CHAR_PROPERTY_SPEED "modify_speed" +#define CHAR_PROPERTY_FLYMODE "modify_flymode" +#define CHAR_PROPERTY_INVUL "modify_invul" +#define CHAR_PROPERTY_REGIONDEBUG "modify_regiondebug" +#define CHAR_PROPERTY_GMVISION "modify_gmvision" +#define CHAR_PROPERTY_LUADEBUG "modify_luadebug" + +#define CHAR_PROPERTY_GROUPLOOTMETHOD "group_loot_method" +#define CHAR_PROPERTY_GROUPLOOTITEMRARITY "group_loot_item_rarity" +#define CHAR_PROPERTY_GROUPAUTOSPLIT "group_auto_split" +#define CHAR_PROPERTY_GROUPDEFAULTYELL "group_default_yell" +#define CHAR_PROPERTY_GROUPAUTOLOCK "group_autolock" +#define CHAR_PROPERTY_GROUPLOCKMETHOD "group_lock_method" +#define CHAR_PROPERTY_GROUPSOLOAUTOLOCK "group_solo_autolock" +#define CHAR_PROPERTY_AUTOLOOTMETHOD "group_auto_loot_method" + +#define CHAR_PROPERTY_ASSISTAUTOATTACK "assist_auto_attack" + +#define CHAR_PROPERTY_SETACTIVEFOOD "set_active_food" +#define CHAR_PROPERTY_SETACTIVEDRINK "set_active_drink" + +#define DB_TYPE_SPELLEFFECTS 1 +#define DB_TYPE_MAINTAINEDEFFECTS 2 + +struct StartingItem{ + string type; + int32 item_id; + string creator; + int8 condition; + int8 attuned; + int16 count; +}; + +struct ClaimItems { + int32 char_id; + int32 item_id; + int8 max_claim; + int8 curr_claim; + int8 one_per_char; + int32 vet_reward_time; +}; + +class Bot; + +class WorldDatabase : public Database { +public: + WorldDatabase(); + ~WorldDatabase(); + + bool ConnectNewDatabase(); + + void PingNewDB(); + + string GetZoneName(int32 id); + string GetZoneDescription(int32 id); + int32 LoadCharacterSkills(int32 char_id, Player* player); + void DeleteCharacterSkill(int32 char_id, Skill* skill); + void DeleteCharacterSpell(int32 character_id, int32 spell_id); + int32 LoadCharacterSpells(int32 char_id, Player* player); + int32 LoadItemBlueStats(); + void SaveQuickBar(int32 char_id, vector* quickbar_items); + void SavePlayerSpells(Client* client); + int32 LoadSkills(); + void LoadCommandList(); + map >* LoadCharacterMacros(int32 char_id); + void UpdateCharacterMacro(int32 char_id, int8 number, const char* name, int16 icon, vector* updates); + void SaveWorldTime(WorldTime* time); + + bool SaveSpawnInfo(Spawn* spawn); + int32 GetNextSpawnIDInZone(int32 zone_id); + bool SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name, int8 percent, float x_offset, float y_offset, float z_offset, bool save_zonespawn = true, bool create_spawnlocation = true); + float GetSpawnLocationPlacementOffsetX(int32 location_id); + float GetSpawnLocationPlacementOffsetY(int32 location_id); + float GetSpawnLocationPlacementOffsetZ(int32 location_id); + int32 GetNextSpawnLocation(); + bool CreateNewSpawnLocation(int32 id, const char* name); + bool RemoveSpawnFromSpawnLocation(Spawn* spawn); + int32 GetSpawnLocationCount(int32 location, Spawn* spawn = 0); + vector* GetSpawnNameList(const char* in_name); + void LoadSubCommandList(); + void LoadGlobalVariables(); + void UpdateVitality(int32 timestamp, float amount); + void SaveVariable(const char* name, const char* value, const char* comment); + void LoadVisualStates(); + void LoadAppearanceMasterList(); + void Save(Client* client); + void SaveItems(Client* client); + void SaveItem(int32 account_id, int32 char_id, Item* item, const char* type); + void DeleteBuyBack(int32 char_id, int32 item_id, int16 quantity, int32 price); + void LoadBuyBacks(Client* client); + void SaveBuyBacks(Client* client); + void SaveBuyBack(int32 char_id, int32 item_id, int16 quantity, int32 price); + void DeleteItem(int32 char_id, Item* item, const char* type); + void SaveCharacterColors(int32 char_id, const char* type, EQ2_Color color); + void SaveCharacterFloats(int32 char_id, const char* type, float float1, float float2, float float3, float multiplier = 100.0f); + int16 GetAppearanceID(string name); + vector* GetAppearanceIDsLikeName(string name, bool filtered = true); + string GetAppearanceName(int16 appearance_id); + void UpdateRandomize(int32 spawn_id, sint32 value); + int32 SaveCharacter(PacketStruct* create, int32 loginID); + int32 LoadNPCAppearanceEquipmentData(ZoneServer* zone); + void SaveNPCAppearanceEquipment(int32 spawn_id, int8 slot_id, int16 type, int8 red=0, int8 green=0, int8 blue=0, int8 hred=0, int8 hgreen=0, int8 hblue=0); + void LoadSpecialZones(); + void SaveCharacterSkills(Client* client); + void SaveCharacterQuests(Client* client); + void SaveCharacterQuestProgress(Client* client, Quest* quest); + void DeleteCharacterQuest(int32 quest_id, int32 char_id, bool repeated_quest = false); + void LoadCharacterQuests(Client* client); + void LoadPlayerAA(Player *player); + void LoadCharacterQuestProgress(Client* client); + void LoadCharacterFriendsIgnoreList(Player* player); + void LoadZoneInfo(ZoneServer* zone); + void LoadZoneInfo(ZoneInfo* zone_info); + int32 GetZoneID(const char* name); + void SaveZoneInfo(int32 zone_id, const char* field, sint32 value); + void SaveZoneInfo(int32 zone_id, const char* field, float value); + void SaveZoneInfo(int32 zone_id, const char* field, const char* value); + bool GetZoneRequirements(const char* zoneName,sint16* minStatus, int16* minLevel, int16* maxLevel, int16* minVersion); + int16 GetMinimumClientVersion(int8 expansion_id); + string GetExpansionIDByVersion(int16 version); + int32 CheckTableVersions(char* tablename); + bool RunDatabaseQueries(TableQuery* queries, bool output_result = true, bool data = false); + void UpdateTableVersion(char* name, int32 version); + void UpdateDataTableVersion(char* name, int32 version); + void UpdateStartingFactions(int32 char_id, int8 choice); + string GetStartingZoneName(int8 choice); + void UpdateStartingZone(int32 char_id, int8 class_id, int8 race_id, PacketStruct* create); + void UpdateStartingItems(int32 char_id, int8 class_id, int8 race_id, bool base_class = false); + void UpdateStartingSkills(int32 char_id, int8 class_id, int8 race_id); + void UpdateStartingSpells(int32 char_id, int8 class_id, int8 race_id); + void UpdateStartingSkillbar(int32 char_id, int8 class_id, int8 race_id); + void UpdateStartingTitles(int32 char_id, int8 class_id, int8 race_id, int8 gender_id); + bool UpdateSpawnLocationSpawns(Spawn* spawn); + bool UpdateSpawnWidget(int32 widget_id, char* query); + bool CheckVersionTable(); + void LoadFactionAlliances(); + void LoadFactionList(); + bool LoadPlayerFactions(Client* client); + void SavePlayerFactions(Client* client); + bool VerifyFactionID(int32 char_id, int32 faction_id); + void LoadSpawnScriptData(); + void LoadZoneScriptData(); + int32 LoadSpellScriptData(); + bool UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_id, int32 spawnentry_id, const char* name); + map* GetZoneList(const char* name, bool is_admin = false); + bool VerifyZone(const char* name); + int8 GetInstanceTypeByZoneID(int32 zoneID); + /*void loadNPCAppearance(int32 appearance_id); + void LoadNPCAppearances();*/ + void ResetDatabase(); + void EnableConstraints(); + void DisableConstraints(); + int32 SaveCombinedSpawnLocation(ZoneServer* zone, Spawn* spawn, const char* name); + int32 ProcessSpawnLocations(ZoneServer* zone, const char* sql_query, int8 type); + int32 LoadSpawnLocationGroupAssociations(ZoneServer* zone); + int32 LoadSpawnLocationGroups(ZoneServer* zone); + int32 LoadSpawnGroupChances(ZoneServer* zone); + bool SpawnGroupAddAssociation(int32 group1, int32 group2); + bool SpawnGroupRemoveAssociation(int32 group1, int32 group2); + bool SpawnGroupAddSpawn(Spawn* spawn, int32 group_id); + bool SpawnGroupRemoveSpawn(Spawn* spawn, int32 group_id); + int32 CreateSpawnGroup(Spawn* spawn, string name); + void DeleteSpawnGroup(int32 id); + bool SetGroupSpawnChance(int32 id, float chance); + void LoadGroundSpawnEntries(ZoneServer* zone); + void LoadGroundSpawnItems(ZoneServer* zone); + void LoadSpawns(ZoneServer* zone); + int8 GetAppearanceType(string type); + void LoadNPCs(ZoneServer* zone); + void LoadSpiritShards(ZoneServer* zone); + int32 LoadAppearances(ZoneServer* zone, Client* client = 0); + int32 LoadNPCSpells(); + int32 LoadNPCSkills(ZoneServer* zone); + int32 LoadNPCEquipment(ZoneServer* zone); + void LoadObjects(ZoneServer* zone); + void LoadGroundSpawns(ZoneServer* zone); + void LoadWidgets(ZoneServer* zone); + void LoadSigns(ZoneServer* zone); + void ReloadItemList(int32 item_id = 0); + void LoadItemList(int32 item_id = 0); + int32 LoadItemStats(int32 item_id = 0); + int32 LoadItemModStrings(int32 item_id = 0); + int32 LoadItemAppearances(int32 item_id = 0); + int32 LoadItemLevelOverride(int32 item_id = 0); + int32 LoadItemEffects(int32 item_id = 0); + int32 LoadBookPages(int32 item_id = 0); + int32 LoadNextUniqueItemID(); + int32 LoadSkillItems(int32 item_id = 0); + int32 LoadRangeWeapons(int32 item_id = 0); + int32 LoadThrownWeapons(int32 item_id = 0); + int32 LoadBaubles(int32 item_id = 0); + int32 LoadBooks(int32 item_id = 0); + int32 LoadItemsets(int32 item_id = 0); + int32 LoadHouseItem(int32 item_id = 0); + int32 LoadRecipeBookItems(int32 item_id = 0); + int32 LoadArmor(int32 item_id = 0); + int32 LoadAdornments(int32 item_id = 0); + int32 LoadClassifications(); + int32 LoadShields(int32 item_id = 0); + int32 LoadBags(int32 item_id = 0); + int32 LoadFoods(int32 item_id = 0); + int32 LoadWeapons(int32 item_id = 0); + int32 LoadRanged(); + int32 LoadHouseContainers(int32 item_id = 0); + void LoadBrokerItemStats(); + + void SaveSignMark(int32 char_id, int32 sign_id, char* char_name, Client* client); + string GetSignMark(int32 char_id, int32 sign_id, char* char_name); // returns the string containing the character name + + + map >* LoadSpellClasses(); + void LoadTransporters(ZoneServer* zone); + void LoadTransportMaps(ZoneServer* zone); + void LoadDataFromRow(DatabaseResult *result, Item* item); + void LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16); + bool loadCharacter(const char* name, int32 account_id, Client* client); + bool LoadCharacterStats(int32 id, int32 account_id, Client* client); + void LoadCharacterQuestRewards(Client* client); + void LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id); + bool InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id); + bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp); + bool insertCharacterProperty(Client* client, char* propName, char* propValue); + bool loadCharacterProperties(Client* client); + string GetPlayerName(char* name); + int32 GetCharacterTimeStamp(int32 character_id, int32 account_id,bool* char_exists); + int32 GetCharacterTimeStamp(int32 character_id); + sint32 GetLatestDataTableVersion(char* name); + sint16 GetLowestCharacterAdminStatus(int32 account_id); + sint16 GetHighestCharacterAdminStatus(int32 account_id); + sint16 GetCharacterAdminStatus(char* character_name); + sint16 GetCharacterAdminStatus(int32 account_id , int32 char_id); + bool UpdateAdminStatus(char* character_name, sint16 flag); + void LoadMerchantInformation(); + void LoadMerchantInventory(); + string GetMerchantDescription(int32 merchant_id); + void LoadPlayerStatistics(Player* player, int32 char_id); + void WritePlayerStatistic(Player* player, Statistic* stat); + void LoadServerStatistics(); + void WriteServerStatistic(Statistic* stat); + void WriteServerStatistic(int32 stat_id, sint32 stat_value); + void WriteServerStatisticsNeededQueries(); + void SavePlayerMail(Mail* mail); + void SavePlayerMail(Client* client); + void LoadPlayerMail(Client* client, bool new_only = false); + void DeletePlayerMail(Mail* mail); + vector* GetAllPlayerIDs(); + void GetPetNames(ZoneServer* zone); + //void LoadMerchantMultipliers(); + + char* GetCharacterName(int32 character_id); + int8 GetCharacterLevel(int32 character_id); + int16 GetCharacterModelType(int32 character_id); + int8 GetCharacterClass(int32 character_id); + int8 GetCharacterGender(int32 character_id); + int32 GetCharacterID(const char* name); + int32 GetCharacterCurrentZoneID(int32 character_id); + int32 GetCharacterAccountID(int32 character_id); + void LoadEntityCommands(ZoneServer* zone); + void LoadSpells(); + void LoadSpellEffects(); + void LoadSpellLuaData(); + void LoadTraits(); + int32 LoadPlayerSkillbar(Client* client); + string GetColumnNames(char* name); + + string GetZoneName(char* zone_description); + bool GetItemResultsToClient (Client* client, const char* varSearch, int maxResults=20); + void LoadRevivePoints(vector* revive_points, int32 zone_id); + void SaveBugReport(const char* category, const char* subcategory, const char* causes_crash, const char* reproducible, const char* summary, const char* description, const char* version, const char* player, int32 account_id, const char* spawn_name, int32 spawn_id, int32 zone_id); + void FixBugReport(); + + int32 LoadQuests(); + void LoadQuestDetails(Quest* quest); + + bool DeleteCharacter(int32 account_id, int32 character_id); + + int32 GetMaxHotBarID(); + + int8 CheckNameFilter(const char* name, int8 min_length = 4, int8 max_length = 15); + static int32 NextUniqueHotbarID(){ + next_id++; + return next_id; + } + void LoadFogInit(string zone, PacketStruct* packet); + static int32 next_id; + + void ToggleCharacterOnline(); + void ToggleCharacterOnline(Client* client, int8 toggle); + + // Zone Instance DB Functions + map* GetInstanceRemovedSpawns(int32 instance_id, int8 type); + int32 CreateNewInstance(int32 zone_id); + //int32 AddCharacterInstance(int32 char_id, int32 instance_id, int32 grant_reenter_time_left=0, int32 grant_reset_time_left=0, int32 lockout_time=0); + int32 AddCharacterInstance(int32 char_id, int32 instance_id, string zone_name, int8 instance_type, int32 last_success, int32 last_failure, int32 success_lockout, int32 failure_lockout); + bool UpdateCharacterInstanceTimers(int32 char_id, int32 instance_id, int32 lockout_time=0, int32 reset_time=0, int32 reenter_time=0 ); + bool UpdateCharacterInstance(int32 char_id, string zone_name, int32 instance_id, int8 type = 0, int32 timestamp = 0); + bool VerifyInstanceID(int32 char_id, int32 instance_id); + bool CheckVectorForValue(vector* vector, int32 value); + int32 CheckSpawnRemoveInfo(map* inmap, int32 spawn_location_entry_id); + bool UpdateInstancedSpawnRemoved(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 instance_id ); + int32 CreateInstanceSpawnRemoved(int32 spawn_location_entry_id, int32 spawn_type, int32 respawn_time, int32 instance_id ); + bool DeleteInstance(int32 instance_id); + bool DeleteInstanceSpawnRemoved(int32 instance_id, int32 spawn_location_entry_id); + bool DeleteCharacterFromInstance(int32 char_id, int32 instance_id); + bool LoadCharacterInstances(Client* client); + // + + MutexMap* GetEquipmentUpdates(); + MutexMap* GetEquipmentUpdates(int32 char_id); + void UpdateLoginEquipment(); + MutexMap* GetZoneUpdates(); + void UpdateLoginZones(); + void LoadLocationGrids(ZoneServer* zone); + bool LoadLocationGridLocations(LocationGrid* grid); + int32 CreateLocation(int32 zone_id, int32 grid_id, const char* name, bool include_y); + bool AddLocationPoint(int32 location_id, float x, float y, float z); + bool DeleteLocation(int32 location_id); + bool DeleteLocationPoint(int32 location_point_id); + void ListLocations(Client* client); + void ListLocationPoints(Client* client, int32 location_id); + bool LocationExists(int32 location_id); + + + bool GetTableVersions(vector *table_versions); + bool QueriesFromFile(const char *file); + + + /* Achievements */ + void LoadAchievements(); + int32 LoadAchievementRequirements(Achievement *achievement); + int32 LoadAchievementRewards(Achievement *achievement); + void LoadPlayerAchievements(Player *player); + int32 LoadPlayerAchievementsUpdates(Player *player); + int32 LoadPlayerAchievementsUpdateItems(AchievementUpdate *update, int32 player_id); + + /* Alternate Advancement */ + void LoadAltAdvancements(); + void LoadTreeNodes(); + + /* Collections */ + void LoadCollections(); + int32 LoadCollectionItems(Collection *collection); + int32 LoadCollectionRewards(Collection *collection); + void LoadPlayerCollections(Player *player); + void LoadPlayerCollectionItems(Player *player, Collection *collection); + void SavePlayerCollections(Client *client); + void SavePlayerCollection(Client *client, Collection *collection); + void SavePlayerCollectionItems(Client *client, Collection *collection); + void SavePlayerCollectionItem(Client *client, Collection *collection, int32 item_id); + + /* Commands */ + map* GetSpawnTemplateListByName(const char* name); + map* GetSpawnTemplateListByID(int32 location_id); + int32 SaveSpawnTemplate(int32 placement_id, const char* template_name); + bool RemoveSpawnTemplate(int32 template_id); + int32 CreateSpawnFromTemplateByID(Client* client, int32 template_id); + int32 CreateSpawnFromTemplateByName(Client* client, const char* template_name); + bool SaveZoneSafeCoords(int32 zone_id, float x, float y, float z, float heading); + bool SaveSignZoneToCoords(int32 spawn_id, float x, float y, float z, float heading); + + /* Guilds */ + void LoadGuilds(); + int32 LoadGuildMembers(Guild* guild); + void LoadGuildEvents(Guild* guild); + void LoadGuildRanks(Guild* guild); + void LoadGuildEventFilters(Guild* guild); + void LoadGuildPointsHistory(Guild* guild, GuildMember* guild_member); + void LoadGuildRecruiting(Guild* guild); + void SaveGuild(Guild* guild, bool new_guild = false); + void SaveGuildMembers(Guild* guild); + void SaveGuildEvents(Guild* guild); + void SaveGuildRanks(Guild* guild); + void SaveGuildEventFilters(Guild* guild); + void SaveGuildPointsHistory(Guild* guild); + void SaveGuildRecruiting(Guild* guild); + void DeleteGuild(Guild* guild); + void DeleteGuildMember(Guild* guild, int32 character_id); + void DeleteGuildEvent(Guild* guild, int64 event_id); + void DeleteGuildPointHistory(Guild* guild, int32 character_id, PointHistory* point_history); + void ArchiveGuildEvent(Guild* guild, GuildEvent* guild_event); + void SaveHiddenGuildEvent(Guild* guild, GuildEvent* guild_event); + void LoadGuildDefaultRanks(Guild* guild); + void LoadGuildDefaultEventFilters(Guild* guild); + bool AddNewPlayerToServerGuild(int32 account_id, int32 char_id); + int32 GetGuildIDByCharacterID(int32 char_id); + + /* Chat */ + void LoadChannels(); + + /* Recipes */ + void LoadRecipes(); + void LoadRecipeBooks(); + void LoadPlayerRecipes(Player *player); + int32 LoadPlayerRecipeBooks(int32 char_id, Player *player); + void SavePlayerRecipeBook(Player* player, int32 recipebook_id); + void LoadRecipeComponents(); + void UpdatePlayerRecipe(Player* player, int32 recipe_id, int8 highest_rank); + void SavePlayerRecipe(Player* player, int32 recipe_id); + + /* Tradeskills */ + void LoadTradeskillEvents(); + + /* Rules */ + void LoadGlobalRuleSet(); + void LoadRuleSets(bool reload=false); + void LoadRuleSetDetails(RuleSet *rule_set); + + /* Titles */ + sint32 AddMasterTitle(const char* titleName, int8 isPrefix = 0); + void LoadTitles(); + sint32 LoadCharacterTitles(int32 char_id, Player *player); + sint32 GetCharPrefixIndex(int32 char_id, Player *player); + sint32 GetCharSuffixIndex(int32 char_id, Player *player); + void SaveCharPrefixIndex(sint32 index, int32 char_id); + void SaveCharSuffixIndex(sint32 index, int32 char_id); + sint32 AddCharacterTitle(sint32 index, int32 char_id, Spawn* player); + + /* Languages */ + void LoadLanguages(); + int32 LoadCharacterLanguages(int32 char_id, Player *player); + int16 GetCharacterCurrentLang(int32 char_id, Player *player); + void SaveCharacterCurrentLang(int32 id, int32 char_id, Client *client); + void UpdateStartingLanguage(int32 char_id, uint8 race_id, int32 starting_city=0); + + /// Saves the given language for the given player + /// Character ID to save the language to + /// Language ID to save to the character + void SaveCharacterLang(int32 char_id, int32 lang_id); + + /* Tradeskills */ + + /* Character History */ + void SaveCharacterHistory(Player* player, int8 type, int8 subtype, int32 value, int32 value2, char* location, int32 event_date); + + /* Housing */ + void LoadHouseZones(); + int64 AddPlayerHouse(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due); + void SetHouseUpkeepDue(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due); + void RemovePlayerHouse(int32 char_id, int32 house_id); + void UpdateHouseEscrow(int32 house_id, int32 instance_id, int64 amount_coins, int32 amount_status); + void LoadPlayerHouses(); + void LoadDeposits(PlayerHouse* house); + void LoadHistory(PlayerHouse* house); + void AddHistory(PlayerHouse* house, char* name, char* reason, int32 timestamp, int64 amount = 0, int32 status = 0, int8 pos_flag = 0); + + /* World */ + bool CheckBannedIPs(const char* loginIP); + + /* Heroic OP */ + void LoadHOStarters(); + void LoadHOWheel(); + + /* Claim Items */ + void LoadClaimItems(int32 char_id); + int16 CountCharClaimItems(int32 char_id); + vector LoadCharacterClaimItems(int32 char_id); + void ClaimItem(int32 char_id, int32 item_id, Client* client); + int32 GetAccountAge(int32 account_id); + + /* Race Types */ + void LoadRaceTypes(); + + /* Loot */ + void LoadLoot(ZoneServer* zone); + void LoadGlobalLoot(ZoneServer* zone); + bool LoadSpawnLoot(ZoneServer* zone, Spawn* spawn); + void AddLootTableToSpawn(Spawn* spawn, int32 loottable_id); + bool RemoveSpawnLootTable(Spawn* spawn, int32 loottable_id); + + void LoadCharacterHistory(int32 char_id, Player *player); + void LoadSpellErrors(); + + /* Load single spawns */ + bool LoadSign(ZoneServer* zone, int32 spawn_id); + bool LoadWidget(ZoneServer* zone, int32 spawn_id); + bool LoadObject(ZoneServer* zone, int32 spawn_id); + bool LoadGroundSpawn(ZoneServer* zone, int32 spawn_id); + void LoadGroundSpawnEntry(ZoneServer* zone, int32 entry_id); + void LoadGroundSpawnItems(ZoneServer* zone, int32 entry_id); + bool LoadNPC(ZoneServer* zone, int32 spawn_id); + void LoadAppearance(ZoneServer* zone, int32 spawn_id); + void LoadNPCAppearanceEquipmentData(ZoneServer* zone, int32 spawn_id); + + /* Save character Pictures */ + /// Saves the pictures that clients send to the server + /// The ID of the character + /// The type of image this is, 0 = paperdoll, 1 = headshot + /// The raw png data + void SaveCharacterPicture(int32 characterID, int8 type, uchar* picture, int32 picture_size); + + /* Quests */ + /// Updates the given date for the quest in the DB for a repeatable quest + /// Client to save the quest for + /// ID of the quest to save + /// Number of times the quest has already been completed + void SaveCharRepeatableQuest(Client* client, int32 quest_id, int16 quest_complete_count); + + + /* Zone Flight Paths */ + void LoadZoneFlightPaths(ZoneServer* zone); + void LoadZoneFlightPathLocations(ZoneServer* zone); + + /* Character LUA History */ + void SaveCharacterLUAHistory(Player* player, int32 event_id, int32 value, int32 value2); + void LoadCharacterLUAHistory(int32 char_id, Player* player); + + /* Bots - BotDB.cpp */ + int32 CreateNewBot(int32 char_id, string name, int8 race, int8 advClass, int8 gender, int16 model_id, int32& index); + void SaveBotAppearance(Bot* bot); + void SaveBotColors(int32 bot_id, const char* type, EQ2_Color color); + void SaveBotFloats(int32 bot_id, const char* type, float float1, float float2, float float3); + bool LoadBot(int32 char_id, int32 bot_index, Bot* bot); + void LoadBotAppearance(Bot* bot); + void SaveBotItem(int32 bot_id, int32 item_id, int8 slot); + void LoadBotEquipment(Bot* bot); + string GetBotList(int32 char_id); + void DeleteBot(int32 char_id, int32 bot_index); + void SetBotStartingItems(Bot* bot, int8 class_id, int8 race_id); + void LoadTransmuting(); + + void FindSpell(Client* client, char* findString); + + void LoadChestTraps(); + + bool CheckExpansionFlags(ZoneServer* zone, int32 spawnXpackFlag); + bool CheckHolidayFlags(ZoneServer* zone, int32 spawnHolidayFlag); + void GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn); + int32 FindHouseInstanceSpawn(Spawn* spawn); + + /* Starting Character Abilities */ + + void LoadStartingSkills(World* world); + void LoadStartingSpells(World* world); + void LoadVoiceOvers(World* world); + + int32 CreateSpiritShard(const char* name, int32 level, int8 race, int8 gender, int8 adventure_class, + int16 model_type, int16 soga_model_type, int16 hair_type, int16 hair_face_type, int16 wing_type, + int16 chest_type, int16 legs_type, int16 soga_hair_type, int16 soga_hair_face_type, int8 hide_hood, + int16 size, int16 collision_radius, int16 action_state, int16 visual_state, int16 mood_state, int16 emote_state, + int16 pos_state, int16 activity_status, char* sub_title, char* prefix_title, char* suffix_title, char* lastname, + float x, float y, float z, float heading, int32 gridid, int32 charid, int32 zoneid, int32 instanceid); + bool DeleteSpiritShard(int32 id); + + void LoadCharacterSpellEffects(int32 char_id, Client *client, int8 db_spell_type); + + int32 GetMysqlExpCurve(int level); +private: + DatabaseNew database_new; + std::map zone_names; + string skills; + int32 max_zonename; + char** zonename_array; + std::map zone_instance_types; +}; +#endif + diff --git a/source/WorldServer/WorldTCPConnection.h b/source/WorldServer/WorldTCPConnection.h new file mode 100644 index 0000000..545cd9b --- /dev/null +++ b/source/WorldServer/WorldTCPConnection.h @@ -0,0 +1,33 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef WorldTCPCONNECTION_H +#define WorldTCPCONNECTION_H +class WorldTCPConnection +{ +public: + WorldTCPConnection() { } + virtual ~WorldTCPConnection() { } + virtual void SendEmoteMessage(const char* to, int32 to_guilddbid, sint16 to_minstatus, int32 type, const char* message, ...) { } + virtual void SendEmoteMessageRaw(const char* to, int32 to_guilddbid, sint16 to_minstatus, int32 type, const char* message) { } + + virtual inline bool IsConsole() { return false; } +}; + +#endif diff --git a/source/WorldServer/Zone/ChestTrap.cpp b/source/WorldServer/Zone/ChestTrap.cpp new file mode 100644 index 0000000..260d3b9 --- /dev/null +++ b/source/WorldServer/Zone/ChestTrap.cpp @@ -0,0 +1,285 @@ +#include "ChestTrap.h" +#include +#include + +//required for c++17 compat (random_shuffle removed, replaced with shuffle) +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#include +#else +#include // std::random_shuffle +#endif + +int32 ChestTrapList::Size() { + + MChestTrapList.readlock(__FUNCTION__, __LINE__); + int32 size = chesttrap_list.size(); + MChestTrapList.releasereadlock(__FUNCTION__, __LINE__); + return size; +} + +void ChestTrapList::AddChestTrap(ChestTrap* trap) { + if (trap->GetDBID() < 1) + return; + + MChestTrapList.writelock(__FUNCTION__, __LINE__); + if (chesttrap_list.count(trap->GetDBID()) > 0) + { + ChestTrap* tmpTrap = chesttrap_list[trap->GetDBID()]; + chesttrap_list.erase(trap->GetDBID()); + safe_delete(tmpTrap); + } + + chesttrap_list[trap->GetDBID()] = trap; + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); +} + +bool ChestTrapList::GetChestTrap(int32 id, ChestTrap::ChestTrapInfo* cti) { + ChestTrap* res = 0; + MChestTrapList.readlock(__FUNCTION__, __LINE__); + if (chesttrap_list.count(id) > 0) + res = chesttrap_list[id]; + + memset(cti, 0, sizeof(ChestTrap::ChestTrapInfo)); + if (res) + memcpy(cti, res->GetChestTrapInfo(), sizeof(ChestTrap::ChestTrapInfo)); + MChestTrapList.releasereadlock(__FUNCTION__, __LINE__); + + return cti; +} + +bool ChestTrapList::GetNextTrap(int32 zoneid, int32 chest_difficulty, ChestTrap::ChestTrapInfo* cti) +{ + MChestListsInUse.writelock(__FUNCTION__, __LINE__); + ChestTrapList* zoneTrapList = GetChestListByZone(zoneid); + ChestTrapList* zoneDifficultyTrapList = zoneTrapList->GetChestListByDifficulty(chest_difficulty); + + bool ret = zoneTrapList->GetNextChestTrap(cti); + MChestListsInUse.releasewritelock(__FUNCTION__, __LINE__); + + return ret; +} + +void ChestTrapList::Clear() { + MChestListsInUse.writelock(__FUNCTION__, __LINE__); + ClearTraps(); + ClearTrapList(); + MChestListsInUse.releasewritelock(__FUNCTION__, __LINE__); +} + +bool ChestTrapList::GetNextChestTrap(ChestTrap::ChestTrapInfo* cti) { + MChestTrapList.readlock(__FUNCTION__, __LINE__); + if (cycleItr == chesttrap_list.end()) + { + MChestTrapList.releasereadlock(__FUNCTION__, __LINE__); + //re-shuffle the map, we reached the end + shuffleMap(this); + } + else + MChestTrapList.releasereadlock(__FUNCTION__, __LINE__); + + if (cycleItr == chesttrap_list.end()) + return false; + + MChestTrapList.writelock(__FUNCTION__, __LINE__); + ChestTrap* trap = cycleItr->second; + + memset(cti, 0, sizeof(ChestTrap::ChestTrapInfo)); + if (trap) + memcpy(cti, trap->GetChestTrapInfo(), sizeof(ChestTrap::ChestTrapInfo)); + + cycleItr++; + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); + + return true; +} + +ChestTrapList* ChestTrapList::GetChestListByDifficulty(int32 difficulty) { + ChestTrapList* usedList = 0; + + int32 id = 0; + if (ChestTrapParent) + { + usedList = GetChestTrapList(ChestTrapBaseList::DIFFICULTY); + id = ChestTrapBaseList::DIFFICULTY; + } + else + { + usedList = GetChestTrapListByID(difficulty); + id = difficulty; + } + + if (usedList && usedList->IsListLoaded()) + return usedList; + else if (!usedList) + { + usedList = new ChestTrapList(); + AddChestTrapList(usedList, id); + } + + MChestTrapList.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for (itr = chesttrap_list.begin(); itr != chesttrap_list.end(); itr++) { + ChestTrap* curTrap = itr->second; + if ((curTrap->GetMinChestDifficulty() <= difficulty && difficulty <= curTrap->GetMaxChestDifficulty()) || + (curTrap->GetMinChestDifficulty() == 0 && curTrap->GetMaxChestDifficulty() == 0)) + usedList->AddChestTrap(curTrap); + } + + shuffleMap(usedList); + usedList->SetListLoaded(true); + + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); + + return usedList; +} + +ChestTrapList* ChestTrapList::GetChestListByZone(int32 zoneid) { + ChestTrapList* usedList = 0; + + int32 id = 0; + if (ChestTrapParent) + { + usedList = GetChestTrapList(ChestTrapBaseList::ZONE); + id = ChestTrapBaseList::ZONE; + } + else + { + usedList = GetChestTrapListByID(zoneid); + id = zoneid; + } + + if (usedList && usedList->IsListLoaded()) + return usedList; + else if (!usedList) + { + usedList = new ChestTrapList(); + AddChestTrapList(usedList, id); + } + + MChestTrapList.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for (itr = chesttrap_list.begin(); itr != chesttrap_list.end(); itr++) { + ChestTrap* curTrap = itr->second; + if (curTrap->GetApplicableZoneID() == zoneid || curTrap->GetApplicableZoneID() == -1) + usedList->AddChestTrap(curTrap); + } + + shuffleMap(usedList); + usedList->SetListLoaded(true); + + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); + + return usedList; +} + +map* ChestTrapList::GetAllChestTraps() { return &chesttrap_list; } +bool ChestTrapList::IsListLoaded() { return ListLoaded; } +void ChestTrapList::SetListLoaded(bool val) { ListLoaded = val; } + +void ChestTrapList::AddChestTrapList(ChestTrapList* traplist, int32 id) { + if (chesttrap_innerlist.count(id) > 0) + { + ChestTrapList* tmpTrapList = chesttrap_innerlist[id]; + chesttrap_innerlist.erase(id); + safe_delete(tmpTrapList); + } + + chesttrap_innerlist[id] = traplist; +} + + +ChestTrapList* ChestTrapList::GetChestTrapList(ChestTrapBaseList listName) { + ChestTrapList* ctl = 0; + MChestTrapInnerList.readlock(__FUNCTION__, __LINE__); + if (chesttrap_innerlist.count(listName) > 0) + ctl = chesttrap_innerlist[listName]; + MChestTrapInnerList.releasereadlock(__FUNCTION__, __LINE__); + + return ctl; +} + +ChestTrapList* ChestTrapList::GetChestTrapListByID(int32 id) { + ChestTrapList* ctl = 0; + MChestTrapInnerList.readlock(__FUNCTION__, __LINE__); + if (chesttrap_innerlist.count(id) > 0) + ctl = chesttrap_innerlist[id]; + MChestTrapInnerList.releasereadlock(__FUNCTION__, __LINE__); + + return ctl; +} + +void ChestTrapList::ClearTraps() { + MChestTrapList.writelock(__FUNCTION__, __LINE__); + map::iterator itr; + for (itr = chesttrap_list.begin(); itr != chesttrap_list.end(); itr++) + safe_delete(itr->second); + chesttrap_list.clear(); + MChestTrapList.releasewritelock(__FUNCTION__, __LINE__); +} + +void ChestTrapList::ClearTrapList() { + MChestTrapInnerList.writelock(__FUNCTION__, __LINE__); + map::iterator itr2; + for (itr2 = chesttrap_innerlist.begin(); itr2 != chesttrap_innerlist.end(); itr2++) + safe_delete(itr2->second); + chesttrap_innerlist.clear(); + MChestTrapInnerList.releasewritelock(__FUNCTION__, __LINE__); + + // reinstantiate the base lists (zone/difficulty/etc) + InstantiateLists(ChestTrapParent); +} + +void ChestTrapList::SetupMutexes() +{ + MChestTrapList.SetName("ChestTrapList"); + MChestTrapInnerList.SetName("MChestTrapInnerList"); + MChestListsInUse.SetName("MChestListsInUse"); +} + +void ChestTrapList::InstantiateLists(bool parent) +{ + if (parent) + { + difficultyList = new ChestTrapList(false); + zoneList = new ChestTrapList(false); + MChestTrapInnerList.writelock(__FUNCTION__, __LINE__); + chesttrap_innerlist[ChestTrapBaseList::DIFFICULTY] = difficultyList; + chesttrap_innerlist[ChestTrapBaseList::ZONE] = zoneList; + MChestTrapInnerList.releasewritelock(__FUNCTION__, __LINE__); + } +} + +void ChestTrapList::shuffleMap(ChestTrapList* list) { + std::vector tmp_chests; + + map::iterator itr; + for (itr = chesttrap_list.begin(); itr != chesttrap_list.end(); itr++) { + ChestTrap* curTrap = itr->second; + tmp_chests.push_back(curTrap); + } + +#ifdef WIN32 + //c++17/windows removed random_shuffle replaced with this ugly bullshit 9/22/22 + //taken right from their example. + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(tmp_chests.begin(), tmp_chests.end(),g); +#else + //let linux continue on, with the function as is since it still works. + std::random_shuffle(tmp_chests.begin(), tmp_chests.end()); +#endif + + chesttrap_list.clear(); + + + + int count = 0; + + for (std::vector::iterator it = tmp_chests.begin(); it != tmp_chests.end(); ++it) + { + chesttrap_list[count] = *it; + count++; + } + + cycleItr = chesttrap_list.begin(); +} diff --git a/source/WorldServer/Zone/ChestTrap.h b/source/WorldServer/Zone/ChestTrap.h new file mode 100644 index 0000000..9d4c9d3 --- /dev/null +++ b/source/WorldServer/Zone/ChestTrap.h @@ -0,0 +1,134 @@ +#include +#include +#include +#include "../../common/Mutex.h" +#include "../../common/types.h" +#pragma once + +using namespace std; + +enum ChestTrapBaseList { + DIFFICULTY = 0, + ZONE = 1 +}; + +class ChestTrap { +public: + struct ChestTrapInfo { + int32 id; + int32 applicable_zone_id; + int32 min_chest_difficulty; + int32 max_chest_difficulty; + int32 spell_id; + int32 spell_tier; + }; + + //Constructors **must** always set all ChestTrapInfo as we don't memset so a data value will be wack if not set! + ChestTrap(int32 dbid, sint32 zoneid, int32 mindifficulty, int32 maxdifficulty, int32 spellid, int32 tier) + { + s_ChestTrapInfo.id = dbid; + s_ChestTrapInfo.applicable_zone_id = zoneid; + s_ChestTrapInfo.min_chest_difficulty = mindifficulty; + s_ChestTrapInfo.max_chest_difficulty = maxdifficulty; + s_ChestTrapInfo.spell_id = spellid; + s_ChestTrapInfo.spell_tier = tier; + } + + ChestTrap(ChestTrap* parent) + { + s_ChestTrapInfo.id = parent->GetDBID(); + s_ChestTrapInfo.applicable_zone_id = parent->GetApplicableZoneID(); + s_ChestTrapInfo.min_chest_difficulty = parent->GetMinChestDifficulty(); + s_ChestTrapInfo.max_chest_difficulty = parent->GetMaxChestDifficulty(); + s_ChestTrapInfo.spell_id = parent->GetSpellID(); + s_ChestTrapInfo.spell_tier = parent->GetSpellTier(); + } + + int32 GetDBID() { return s_ChestTrapInfo.id; } + sint32 GetApplicableZoneID() { return s_ChestTrapInfo.applicable_zone_id; } + int32 GetMinChestDifficulty() { return s_ChestTrapInfo.min_chest_difficulty; } + int32 GetMaxChestDifficulty() { return s_ChestTrapInfo.max_chest_difficulty; } + int32 GetSpellID() { return s_ChestTrapInfo.spell_id; } + int32 GetSpellTier() { return s_ChestTrapInfo.spell_tier; } + + ChestTrapInfo* GetChestTrapInfo() { return &s_ChestTrapInfo; } +private: + ChestTrapInfo s_ChestTrapInfo; +}; + +class ChestTrapList { +public: + ChestTrapList() { + SetupMutexes(); + + ChestTrapParent = true; + // instantiate the parent lists for zone/difficulty/etc, later on we will do the inverse of each map, zone->difficulty and difficulty->zone + InstantiateLists(true); + ListLoaded = true; + } + + // not to be called externally from ChestTrapList/ChestTrap + ChestTrapList(bool parentClass) { + SetupMutexes(); + + ChestTrapParent = parentClass; + + ListLoaded = false; + } + + ~ChestTrapList() { + Clear(); + } + + int32 Size(); + + void AddChestTrap(ChestTrap* trap); + + bool GetChestTrap(int32 id, ChestTrap::ChestTrapInfo* cti); + + bool GetNextTrap(int32 zoneid, int32 chest_difficulty, ChestTrap::ChestTrapInfo* cti); + + void Clear(); +private: + // randomized maps so we just iterate the map for our next 'random' result + bool GetNextChestTrap(ChestTrap::ChestTrapInfo* cti); + + ChestTrapList* GetChestListByDifficulty(int32 difficulty); + + ChestTrapList* GetChestListByZone(int32 zoneid); + + map* GetAllChestTraps(); + bool IsListLoaded(); + void SetListLoaded(bool val); + + void AddChestTrapList(ChestTrapList* trap, int32 id); + + void SetCycleIterator(map::iterator itr); + + ChestTrapList* GetChestTrapList(ChestTrapBaseList listName); + ChestTrapList* GetChestTrapListByID(int32 id); + + void ClearTraps(); + void ClearTrapList(); + + void SetupMutexes(); + + void InstantiateLists(bool parent); + + void shuffleMap(ChestTrapList* list); + + bool ChestTrapParent; + bool ListLoaded; + map chesttrap_list; + map chesttrap_innerlist; + + ChestTrapList* difficultyList; + ChestTrapList* zoneList; + + map::iterator cycleItr; + + Mutex MChestTrapList; + Mutex MChestTrapInnerList; + + Mutex MChestListsInUse; +}; diff --git a/source/WorldServer/Zone/map.cpp b/source/WorldServer/Zone/map.cpp new file mode 100644 index 0000000..cd9be04 --- /dev/null +++ b/source/WorldServer/Zone/map.cpp @@ -0,0 +1,983 @@ +#include "map.h" +#include "raycast_mesh.h" +#include "../../common/Log.h" + +#ifdef WIN32 +#define _snprintf snprintf +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct Map::impl +{ + RaycastMesh *rm; +}; + + +inline bool file_exists(const std::string& name) { + std::ifstream f(name.c_str()); + return f.good(); +} + +ThreadReturnType LoadMapAsync(void* mapToLoad) +{ + Map* map = (Map*)mapToLoad; + map->SetMapLoaded(false); + + + std::string filename = "Maps/"; + filename += map->GetFileName(); + + std::string deflatedFileName = filename + ".EQ2MapDeflated"; + + filename += ".EQ2Map"; + + if(file_exists(deflatedFileName)) + filename = deflatedFileName; + + map->SetFileName(filename); + + if (map->Load(filename)) + map->SetMapLoaded(true); + + map->SetMapLoading(false); + THREAD_RETURN(NULL); +} + +Map::Map(string zonename, string file) { + CheckMapMutex.SetName(file + "MapMutex"); + SetMapLoaded(false); + m_ZoneName = zonename; + m_ZoneFile = file; + imp = nullptr; + m_MinY = 9999999.0f; + m_MaxY = -9999999.0f; +} + +Map::~Map() { + SetMapLoaded(false); + if(imp) { + imp->rm->release(); + safe_delete(imp); + } + + std::map::iterator itr; + for(itr = grid_map_border.begin(); itr != grid_map_border.end(); itr++) { + safe_delete(itr->second); + } + grid_map_border.clear(); +} + +float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result, std::map* ignored_widgets, uint32* GridID, uint32* WidgetID) +{ + if (!IsMapLoaded()) + return BEST_Z_INVALID; + if (!imp) + return BEST_Z_INVALID; + + glm::vec3 tmp; + if(!result) + result = &tmp; + + start.z += 1.0f;//RuleI(Map, FindBestZHeightAdjust); + glm::vec3 from(start.x, start.y, start.z); + glm::vec3 to(start.x, start.y, BEST_Z_INVALID); + float hit_distance; + bool hit = false; + + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID, (RmMap*)ignored_widgets); + if(hit) { + return result->z; + } + + // Find nearest Z above us + + to.z = -BEST_Z_INVALID; + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID, (RmMap*)ignored_widgets); + if (hit) + { + return result->z; + } + + return BEST_Z_INVALID; +} + +float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result, std::map* ignored_widgets, uint32 *GridID, uint32* WidgetID) { + if (!IsMapLoaded()) + return false; + // Unlike FindBestZ, this method finds the closest Z value above or below the specified point. + // + if (!imp) + return false; + + float ClosestZ = BEST_Z_INVALID; + + glm::vec3 tmp; + if (!result) + result = &tmp; + + glm::vec3 from(start.x, start.y, start.z); + glm::vec3 to(start.x, start.y, BEST_Z_INVALID); + float hit_distance; + bool hit = false; + // first check is below us + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID, (RmMap*)ignored_widgets); + if (hit) { + ClosestZ = result->z; + + } + + // Find nearest Z above us + to.z = -BEST_Z_INVALID; + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance, (RmUint32*)GridID, (RmUint32*)WidgetID, (RmMap*)ignored_widgets); + if (hit) { + if (std::abs(from.z - result->z) < std::abs(ClosestZ - from.z)) + return result->z; + } + + return ClosestZ; +} + +bool Map::LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, std::map* ignored_widgets, glm::vec3 *result) { + if (!IsMapLoaded()) + return false; + if(!imp) + return false; + return imp->rm->raycast((const RmReal*)&start, (const RmReal*)&end, (RmReal*)result, nullptr, nullptr, nullptr, nullptr, (RmMap*)ignored_widgets); +} + +bool Map::LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, std::map* ignored_widgets, glm::vec3 *result) { + if (!IsMapLoaded()) + return false; + if (!imp) + return false; + + float z = BEST_Z_INVALID; + glm::vec3 step; + glm::vec3 cur; + cur.x = start.x; + cur.y = start.y; + cur.z = start.z; + + step.x = end.x - start.x; + step.y = end.y - start.y; + step.z = end.z - start.z; + float factor = step_mag / sqrt(step.x*step.x + step.y*step.y + step.z*step.z); + + step.x *= factor; + step.y *= factor; + step.z *= factor; + + int steps = 0; + + if (step.x > 0 && step.x < 0.001f) + step.x = 0.001f; + if (step.y > 0 && step.y < 0.001f) + step.y = 0.001f; + if (step.z > 0 && step.z < 0.001f) + step.z = 0.001f; + if (step.x < 0 && step.x > -0.001f) + step.x = -0.001f; + if (step.y < 0 && step.y > -0.001f) + step.y = -0.001f; + if (step.z < 0 && step.z > -0.001f) + step.z = -0.001f; + + //while we are not past end + //always do this once, even if start == end. + while(cur.x != end.x || cur.y != end.y || cur.z != end.z) + { + steps++; + glm::vec3 me; + me.x = cur.x; + me.y = cur.y; + me.z = cur.z; + glm::vec3 hit; + + float best_z = FindBestZ(me, &hit, ignored_widgets); + float diff = best_z - z; + diff = diff < 0 ? -diff : diff; + + if (z <= BEST_Z_INVALID || best_z <= BEST_Z_INVALID || diff < 12.0) + z = best_z; + else + return true; + + //look at current location + if(LineIntersectsZone(start, end, step_mag, ignored_widgets, result)) + { + return true; + } + + //move 1 step + if (cur.x != end.x) + cur.x += step.x; + if (cur.y != end.y) + cur.y += step.y; + if (cur.z != end.z) + cur.z += step.z; + + //watch for end conditions + if ( (cur.x > end.x && end.x >= start.x) || (cur.x < end.x && end.x <= start.x) || (step.x == 0) ) { + cur.x = end.x; + } + if ( (cur.y > end.y && end.y >= start.y) || (cur.y < end.y && end.y <= start.y) || (step.y == 0) ) { + cur.y = end.y; + } + if ( (cur.z > end.z && end.z >= start.z) || (cur.z < end.z && end.z < start.z) || (step.z == 0) ) { + cur.z = end.z; + } + } + + //walked entire line and didnt run into anything... + return false; +} + +bool Map::CheckLoS(glm::vec3 myloc, glm::vec3 oloc, std::map* ignored_widgets) +{ + if (!IsMapLoaded()) + return false; + if(!imp) + return false; + + return !imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, nullptr, nullptr, nullptr, nullptr, (RmMap*)ignored_widgets); +} + +// returns true if a collision happens +bool Map::DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, std::map* ignored_widgets, glm::vec3 &outnorm, float &distance) { + if (!IsMapLoaded()) + return false; + if(!imp) + return false; + + return imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, (RmReal *)&outnorm, (RmReal *)&distance, nullptr, nullptr, (RmMap*)ignored_widgets); +} + +Map *Map::LoadMapFile(std::string zonename, std::string file) { + + std::string filename = "Maps/"; + filename += file; + + std::string deflatedFileName = filename + ".EQ2MapDeflated"; + + filename += ".EQ2Map"; + + if(file_exists(deflatedFileName)) + filename = deflatedFileName; + + LogWrite(MAP__INFO, 7, "Map", "Attempting to load Map File [{%s}]", filename.c_str()); + + auto m = new Map(zonename, file); + m->SetMapLoading(true); + m->SetFileName(filename); +#ifdef WIN32 + _beginthread(LoadMapAsync, 0, (void*)m); +#else + pthread_t t1; + pthread_create(&t1, NULL, LoadMapAsync, (void*)m); + pthread_detach(t1); +#endif + + return m; +} + +/** + * @param filename + * @return + */ +bool Map::Load(const std::string &filename) +{ + FILE *map_file = fopen(filename.c_str(), "rb"); + if (map_file) { + LogWrite(MAP__INFO, 7, "Map", "Loading Map File [{%s}]", filename.c_str()); + bool loaded_map_file = LoadV2(map_file); + fclose(map_file); + + if (loaded_map_file) { + LogWrite(MAP__INFO, 7, "Map", "Loaded Map File [{%s}]", filename.c_str()); + } + else { + LogWrite(MAP__ERROR, 7, "Map", "FAILED Loading Map File [{%s}]", filename.c_str()); + } + return loaded_map_file; + } + else { + return false; + } + + return false; +} + +struct ModelEntry +{ + struct Poly + { + uint32 v1, v2, v3; + uint8 vis; + }; + std::vector verts; + std::vector polys; +}; + + +bool Map::LoadV2(FILE* f) { + + std::size_t foundDeflated = m_FileName.find(".EQ2MapDeflated"); + if(foundDeflated != std::string::npos) + return LoadV2Deflated(f); + + // Read the string for the zone file name this was created for + int8 strSize; + char name[256]; + fread(&strSize, sizeof(int8), 1, f); + LogWrite(MAP__DEBUG, 0, "Map", "strSize = %u", strSize); + + size_t len = fread(&name, sizeof(char), strSize, f); + name[len] = '\0'; + LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name); + + string fileName(name); + std::size_t found = fileName.find(m_ZoneName); + // Make sure file contents are for the correct zone + if (found == std::string::npos) { + fclose(f); + LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV2() map contents (%s) do not match its name (%s).", &name, m_ZoneName.c_str()); + return false; + } + // Read the min bounds + fread(&m_MinX, sizeof(float), 1, f); + fread(&m_MinZ, sizeof(float), 1, f); + + // Read the max bounds + fread(&m_MaxX, sizeof(float), 1, f); + fread(&m_MaxZ, sizeof(float), 1, f); + + // Read the number of grids + int32 NumGrids; + fread(&NumGrids, sizeof(int32), 1, f); + + std::vector verts; + std::vector indices; + std::vector grids; + std::vector widgets; + + uint32 face_count = 0; + // Loop through the grids loading the face list + for (int32 i = 0; i < NumGrids; i++) { + // Read the grid id + int32 GridID; + fread(&GridID, sizeof(int32), 1, f); + + // Read the number of vertices + int32 NumFaces; + fread(&NumFaces, sizeof(int32), 1, f); + + face_count += NumFaces; + // Loop through the vertices list reading + // 3 at a time to creat a triangle (face) + GridMapBorder* border = GetMapGridBorder(GridID); + + for (int32 y = 0; y < NumFaces; ) { + // Each vertex need an x,y,z coordinate and +// we will be reading 3 to create the face + float x1, x2, x3; + float y1, y2, y3; + float z1, z2, z3; + + // Read the first vertex + fread(&x1, sizeof(float), 1, f); + fread(&y1, sizeof(float), 1, f); + fread(&z1, sizeof(float), 1, f); + y++; + + // Read the second vertex + fread(&x2, sizeof(float), 1, f); + fread(&y2, sizeof(float), 1, f); + fread(&z2, sizeof(float), 1, f); + y++; + + // Read the third (final) vertex + fread(&x3, sizeof(float), 1, f); + fread(&y3, sizeof(float), 1, f); + fread(&z3, sizeof(float), 1, f); + y++; + + glm::vec3 a(x1, z1, y1); + glm::vec3 b(x2, z2, y2); + glm::vec3 c(x3, z3, y3); + + MapMinMaxY(y1); + MapMinMaxY(y2); + MapMinMaxY(y3); + + size_t sz = verts.size(); + verts.push_back(a); + indices.push_back((uint32)sz); + + verts.push_back(b); + indices.push_back((uint32)sz + 1); + + verts.push_back(c); + indices.push_back((uint32)sz + 2); + + grids.push_back((uint32)GridID); + widgets.push_back((uint32)0); + MapGridMinMaxBorderArray(border, a, b, c); + } + } + face_count = face_count / 3; + + if (imp) { + imp->rm->release(); + imp->rm = nullptr; + } + else { + imp = new impl; + } + + imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]); + + if (!imp->rm) { + delete imp; + imp = nullptr; + return false; + } + + return true; +} + +bool Map::LoadV3Deflated(std::ifstream* file, std::streambuf * const srcbuf) { + + std::vector verts; + std::vector indices; + std::vector grids; + std::vector widgets; + + int8 strSize = 0; + char* buf = new char[1024]; + + int32 mapVersion = 0; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&mapVersion,&buf[0],sizeof(int32)); + LogWrite(MAP__DEBUG, 0, "Map", "MapVersion = %u", mapVersion); + + srcbuf->sgetn(buf,sizeof(int8)); + memcpy(&strSize,&buf[0],sizeof(int8)); + LogWrite(MAP__DEBUG, 0, "Map", "strSize = %u", strSize); + + char name[256]; + srcbuf->sgetn(&name[0],strSize); + name[strSize] = '\0'; + LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name); + string fileName(name); + std::size_t found = fileName.find(m_ZoneName); + // Make sure file contents are for the correct zone + if (found == std::string::npos) { + file->close(); + safe_delete_array(buf); + LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV3Deflated() map contents (%s) do not match its name (%s).", &name, m_ZoneFile.c_str()); + return false; + } + // Read the min bounds + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinX,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinY,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinZ,&buf[0],sizeof(float)); + + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxX,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxY,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxZ,&buf[0],sizeof(float)); + + // Read the number of grids + int32 NumGrids; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&NumGrids,&buf[0],sizeof(int32)); + + uint32 face_count = 0; + // Loop through the grids loading the face list + for (int32 i = 0; i < NumGrids; i++) { + // Read the grid id + int32 GridID; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&GridID,&buf[0],sizeof(int32)); + + // Read the number of vertices + int32 vertex_map_count; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&vertex_map_count,&buf[0],sizeof(int32)); + + GridMapBorder* border = GetMapGridBorder(GridID); + for(int32 m = 0; m < vertex_map_count; m++) { + int32 WidgetID; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&WidgetID,&buf[0],sizeof(int32)); + + float w_x1, w_y1, w_z1; + + // read widget coords + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&w_x1,&buf[0],sizeof(float)); + memcpy(&w_y1,&buf[4],sizeof(float)); + memcpy(&w_z1,&buf[8],sizeof(float)); + + glm::vec3 a(w_x1, w_y1, w_z1); + widget_map.insert(make_pair(WidgetID, a)); + + int32 NumFaces; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&NumFaces,&buf[0],sizeof(int32)); + + face_count += NumFaces; + + for (int32 y = 0; y < NumFaces; ) { + // Each vertex need an x,y,z coordinate and + // we will be reading 3 to create the face + float x1, x2, x3; + float y1, y2, y3; + float z1, z2, z3; + + // Read the first vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x1,&buf[0],sizeof(float)); + memcpy(&y1,&buf[4],sizeof(float)); + memcpy(&z1,&buf[8],sizeof(float)); + y++; + + // Read the second vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x2,&buf[0],sizeof(float)); + memcpy(&y2,&buf[4],sizeof(float)); + memcpy(&z2,&buf[8],sizeof(float)); + y++; + + // Read the third (final) vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x3,&buf[0],sizeof(float)); + memcpy(&y3,&buf[4],sizeof(float)); + memcpy(&z3,&buf[8],sizeof(float)); + y++; + + glm::vec3 a(x1, z1, y1); + glm::vec3 b(x2, z2, y2); + glm::vec3 c(x3, z3, y3); + + size_t sz = verts.size(); + verts.push_back(a); + indices.push_back((uint32)sz); + + verts.push_back(b); + indices.push_back((uint32)sz + 1); + + verts.push_back(c); + indices.push_back((uint32)sz + 2); + + grids.push_back(GridID); + widgets.push_back(WidgetID); + MapGridMinMaxBorderArray(border, a, b, c); + } + + } + // Loop through the vertices list reading + // 3 at a time to creat a triangle (face) + } + face_count = face_count / 3; + + if (imp) { + imp->rm->release(); + imp->rm = nullptr; + } + else { + imp = new impl; + } + + imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]); + + file->close(); + safe_delete_array(buf); + if (!imp->rm) { + delete imp; + imp = nullptr; + return false; + } + + return true; +} + +bool Map::LoadV2Deflated(FILE* f) { + std::ifstream file(m_FileName.c_str(), ios_base::in | ios_base::binary); + boost::iostreams::filtering_streambuf inbuf; + inbuf.push(boost::iostreams::gzip_decompressor()); + inbuf.push(file); + ostream out(&inbuf); + std::streambuf * const srcbuf = out.rdbuf(); + std::streamsize size = srcbuf->in_avail(); + if(size == -1) + { + file.close(); + LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV2Deflated() unable to deflate (%s).", m_ZoneFile.c_str()); + return false; + } + // Read the string for the zone file name this was created for + int8 strSize; + char* buf = new char[1024]; + srcbuf->sgetn(buf,sizeof(int8)); + memcpy(&strSize,&buf[0],sizeof(int8)); + LogWrite(MAP__DEBUG, 0, "Map", "strSize = %u", strSize); + + char name[256]; + srcbuf->sgetn(&name[0],strSize); + name[strSize] = '\0'; + LogWrite(MAP__DEBUG, 0, "Map", "name = %s", name); + string fileName(name); + + if(fileName.find("EQ2EmuMapTool") != std::string::npos) { + safe_delete_array(buf); + return(LoadV3Deflated(&file, srcbuf)); + } + + + std::size_t found = fileName.find(m_ZoneName); + // Make sure file contents are for the correct zone + if (found == std::string::npos) { + file.close(); + safe_delete_array(buf); + LogWrite(MAP__ERROR, 0, "Map", "Map::LoadV2Deflated() map contents (%s) do not match its name (%s).", &name, m_ZoneFile.c_str()); + return false; + } + // Read the min bounds + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinX,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MinZ,&buf[0],sizeof(float)); + + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxX,&buf[0],sizeof(float)); + srcbuf->sgetn(buf,sizeof(float)); + memcpy(&m_MaxZ,&buf[0],sizeof(float)); + + // Read the number of grids + int32 NumGrids; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&NumGrids,&buf[0],sizeof(int32)); + + std::vector verts; + std::vector indices; + std::vector grids; + std::vector widgets; + + uint32 face_count = 0; + // Loop through the grids loading the face list + for (int32 i = 0; i < NumGrids; i++) { + // Read the grid id + int32 GridID; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&GridID,&buf[0],sizeof(int32)); + + // Read the number of vertices + int32 NumFaces; + srcbuf->sgetn(buf,sizeof(int32)); + memcpy(&NumFaces,&buf[0],sizeof(int32)); + + face_count += NumFaces; + // Loop through the vertices list reading + // 3 at a time to creat a triangle (face) + GridMapBorder* border = GetMapGridBorder(GridID); + + for (int32 y = 0; y < NumFaces; ) { + // Each vertex need an x,y,z coordinate and +// we will be reading 3 to create the face + float x1, x2, x3; + float y1, y2, y3; + float z1, z2, z3; + + // Read the first vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x1,&buf[0],sizeof(float)); + memcpy(&y1,&buf[4],sizeof(float)); + memcpy(&z1,&buf[8],sizeof(float)); + y++; + + // Read the second vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x2,&buf[0],sizeof(float)); + memcpy(&y2,&buf[4],sizeof(float)); + memcpy(&z2,&buf[8],sizeof(float)); + y++; + + // Read the third (final) vertex + srcbuf->sgetn(buf,sizeof(float)*3); + memcpy(&x3,&buf[0],sizeof(float)); + memcpy(&y3,&buf[4],sizeof(float)); + memcpy(&z3,&buf[8],sizeof(float)); + y++; + + glm::vec3 a(x1, z1, y1); + glm::vec3 b(x2, z2, y2); + glm::vec3 c(x3, z3, y3); + + MapMinMaxY(y1); + MapMinMaxY(y2); + MapMinMaxY(y3); + + size_t sz = verts.size(); + verts.push_back(a); + indices.push_back((uint32)sz); + + verts.push_back(b); + indices.push_back((uint32)sz + 1); + + verts.push_back(c); + indices.push_back((uint32)sz + 2); + + grids.push_back(GridID); + widgets.push_back((uint32)0); + MapGridMinMaxBorderArray(border, a, b, c); + } + } + face_count = face_count / 3; + + if (imp) { + imp->rm->release(); + imp->rm = nullptr; + } + else { + imp = new impl; + } + + imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0], &grids[0], &widgets[0]); + + file.close(); + safe_delete_array(buf); + if (!imp->rm) { + delete imp; + imp = nullptr; + return false; + } + + return true; +} + + +void Map::RotateVertex(glm::vec3 &v, float rx, float ry, float rz) { + glm::vec3 nv = v; + + nv.y = (std::cos(rx) * v.y) - (std::sin(rx) * v.z); + nv.z = (std::sin(rx) * v.y) + (std::cos(rx) * v.z); + + v = nv; + + nv.x = (std::cos(ry) * v.x) + (std::sin(ry) * v.z); + nv.z = -(std::sin(ry) * v.x) + (std::cos(ry) * v.z); + + v = nv; + + nv.x = (std::cos(rz) * v.x) - (std::sin(rz) * v.y); + nv.y = (std::sin(rz) * v.x) + (std::cos(rz) * v.y); + + v = nv; +} + +void Map::ScaleVertex(glm::vec3 &v, float sx, float sy, float sz) { + v.x = v.x * sx; + v.y = v.y * sy; + v.z = v.z * sz; +} + +void Map::TranslateVertex(glm::vec3 &v, float tx, float ty, float tz) { + v.x = v.x + tx; + v.y = v.y + ty; + v.z = v.z + tz; +} + +void Map::MapMinMaxY(float y) { + if(y < m_MinY) + m_MinY = y; + if(y > m_MaxY) + m_MaxY = y; +} + +void Map::MapGridMinMaxBorderArray(GridMapBorder* border, glm::vec3 a, glm::vec3 b, glm::vec3 c) { + if(!border) + return; + + MapGridMinMaxBorder(border, a); + MapGridMinMaxBorder(border, b); + MapGridMinMaxBorder(border, c); +} + +void Map::MapGridMinMaxBorder(GridMapBorder* border, glm::vec3 a) { + if(!border) + return; + + if(a.x < border->m_MinX) + border->m_MinX = a.x; + if(a.x > border->m_MaxX) + border->m_MaxX = a.x; + if(a.y < border->m_MinY) + border->m_MinY = a.y; + if(a.y > border->m_MaxY) + border->m_MaxY = a.y; + if(a.z < border->m_MinZ) + border->m_MinZ = a.z; + if(a.z > border->m_MaxZ) + border->m_MaxZ = a.z; +} + +bool Map::IsPointInGrid(GridMapBorder* border, glm::vec3 a, float radius) { + return border != nullptr && (a.x >= (border->m_MinX - radius) && a.x <= (border->m_MaxX + radius) && a.y >= (border->m_MinY - radius) && a.y <= (border->m_MaxY + radius) && a.z >= (border->m_MinZ - radius) && a.z <= (border->m_MaxZ + radius)); +} + +std::vector Map::GetGridsByPoint(glm::vec3 a, float radius) { + std::vector grids; + std::map::iterator itr; + for(itr = grid_map_border.begin(); itr != grid_map_border.end(); itr++) { + if(IsPointInGrid(itr->second, a, radius)) { + grids.push_back(itr->first); + } + } + return grids; +} + +GridMapBorder* Map::GetMapGridBorder(int32 grid_id, bool instantiate_border) { + std::map::iterator itr = grid_map_border.find(grid_id); + GridMapBorder* border = nullptr; + if(itr != grid_map_border.end()) { + border = itr->second; + } + else if(instantiate_border) { + border = new GridMapBorder; + border->m_MinX = 999999.0f; + border->m_MaxX = -999999.0f; + border->m_MinY = 999999.0f; + border->m_MaxY = -999999.0f; + border->m_MinZ = 999999.0f; + border->m_MaxZ = -999999.0f; + grid_map_border.insert(make_pair(grid_id, border)); + } + return border; +} + +void MapRange::AddVersionRange(std::string zoneName) { + boost::filesystem::path targetDir("Maps/"); + + // crash fix since the dir isn't present + if(!boost::filesystem::is_directory(targetDir)) + { + LogWrite(MAP__ERROR, 7, "Map", "Unable to find directory %s", targetDir.c_str()); + return; + } + + boost::filesystem::recursive_directory_iterator iter(targetDir), eod; + boost::smatch base_match; + std::string formula = "(.*\\/|.*\\\\)((" + zoneName + ")(\\-([0-9]+)\\-([0-9]+))?)(\\.EQ2Map|\\.EQ2MapDeflated)$"; + boost::regex re(formula.c_str()); + LogWrite(MAP__INFO, 0, "Map", "Map Formula to match: %s", formula.c_str()); + BOOST_FOREACH(boost::filesystem::path + const & i, make_pair(iter, eod)) { + if (is_regular_file(i)) { + std::string fileName(i.string()); + + if (boost::regex_match(fileName, base_match, re)) { + boost::ssub_match base_sub_match = base_match[2]; + boost::ssub_match base_sub_match2 = base_match[5]; + boost::ssub_match base_sub_match3 = base_match[6]; + std::string baseMatch(base_sub_match.str().c_str()); + std::string baseMatch2(base_sub_match2.str().c_str()); + std::string baseMatch3(base_sub_match3.str().c_str()); + LogWrite(MAP__INFO, 0, "Map", "Map To Load: %s, size: %i, string: %s, min: %s, max: %s\n", i.string().c_str(), base_match.size(), baseMatch.c_str(), baseMatch2.c_str(), baseMatch3.c_str()); + + Map * zonemap = Map::LoadMapFile(zoneName, base_sub_match.str().c_str()); + + int32 min_version = 0, max_version = 0; + if (strlen(base_sub_match2.str().c_str()) > 0) + min_version = atoul(base_sub_match2.str().c_str()); + + if (strlen(base_sub_match2.str().c_str()) > 0) + max_version = atoul(base_sub_match3.str().c_str()); + version_map.insert(std::make_pair(new VersionRange(min_version, max_version), zonemap)); + } + } + } +} + +MapRange::MapRange() +{ + +} + +MapRange::~MapRange() +{ + Clear(); +} + +void MapRange::Clear() +{ + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + Map* map = itr->second; + delete range; + delete map; + } + + version_map.clear(); +} + +map::iterator MapRange::FindVersionRange(int32 min_version, int32 max_version) +{ + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion()) + return itr; + // if the min version is in range, but max range is 0 + else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0) + return itr; + // if min version is 0 and max_version has a cap + else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion()) + return itr; + } + + return version_map.end(); +} + +map::iterator MapRange::FindMapByVersion(int32 version) +{ + map::iterator enditr = version_map.end(); + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0) + enditr = itr; + else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion()) + return itr; + } + + return enditr; +} \ No newline at end of file diff --git a/source/WorldServer/Zone/map.h b/source/WorldServer/Zone/map.h new file mode 100644 index 0000000..b6cdd92 --- /dev/null +++ b/source/WorldServer/Zone/map.h @@ -0,0 +1,151 @@ +/* + EQEMu: Everquest Server Emulator + + Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#ifndef ZONE_MAP_H +#define ZONE_MAP_H + +#include "../../common/types.h" +#include "../../common/MiscFunctions.h" +#include "../../common/Mutex.h" +#include "position.h" +#include + +#define BEST_Z_INVALID -99999 + +struct GridMapBorder { + float m_MinX; + float m_MaxX; + float m_MinY; + float m_MaxY; + float m_MinZ; + float m_MaxZ; +}; +class Map +{ +public: + Map(string zonename, string filename); + ~Map(); + + float FindBestZ(glm::vec3 &start, glm::vec3 *result, std::map* ignored_widgets, uint32 *GridID = 0, uint32* WidgetID = 0); + float FindClosestZ(glm::vec3 &start, glm::vec3 *result, std::map* ignored_widgets, uint32 *GridID = 0, uint32* WidgetID = 0); + bool LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, std::map* ignored_widgets, glm::vec3 *result); + bool LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, std::map* ignored_widgets, glm::vec3 *result); + bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc, std::map* ignored_widgets); + bool DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, std::map* ignored_widgets, glm::vec3 &outnorm, float &distance); + + bool Load(const std::string& filename); + + static Map *LoadMapFile(std::string zonename, std::string file); + + std::string GetFileName() { return m_ZoneFile; } + void SetMapLoaded(bool val) { + CheckMapMutex.writelock(); + mapLoaded = val; + CheckMapMutex.releasewritelock(); + } + bool IsMapLoaded() { + bool isMapLoaded = false; + CheckMapMutex.readlock(); + isMapLoaded = mapLoaded; + CheckMapMutex.releasereadlock(); + return isMapLoaded; + } + + void SetMapLoading(bool val) { + CheckMapMutex.writelock(); + mapLoading = val; + CheckMapMutex.releasewritelock(); + } + bool IsMapLoading() { + bool isMapLoading = false; + CheckMapMutex.readlock(); + isMapLoading = mapLoading; + CheckMapMutex.releasereadlock(); + return isMapLoading; + } + float GetMinX() { return m_MinX; } + float GetMaxX() { return m_MaxX; } + float GetMinY() { return m_MinY; } + float GetMaxY() { return m_MaxY; } + float GetMinZ() { return m_MinZ; } + float GetMaxZ() { return m_MaxZ; } + + bool isPointWithinMap(double x, double y, double z, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + return (x >= m_MinX && x <= m_MaxX && y >= m_MinY && y <= m_MaxY && z >= m_MinZ && z <= m_MaxZ); + } + + void SetFileName(std::string newfile) { m_FileName = string(newfile); } + + void MapMinMaxY(float y); + void MapGridMinMaxBorderArray(GridMapBorder* border, glm::vec3 a, glm::vec3 b, glm::vec3 c); + void MapGridMinMaxBorder(GridMapBorder* border, glm::vec3 a); + bool IsPointInGrid(GridMapBorder* border, glm::vec3 a, float radius); + std::vector GetGridsByPoint(glm::vec3 a, float radius); + GridMapBorder* GetMapGridBorder(int32 grid_id, bool instantiate_border = true); + + std::map widget_map; + std::map grid_map_border; +private: + void RotateVertex(glm::vec3 &v, float rx, float ry, float rz); + void ScaleVertex(glm::vec3 &v, float sx, float sy, float sz); + void TranslateVertex(glm::vec3 &v, float tx, float ty, float tz); + bool LoadV2(FILE *f); + bool LoadV2Deflated(FILE *f); + bool LoadV3Deflated(std::ifstream* file, std::streambuf * const srcbuf); + + string m_FileName; + string m_ZoneFile; + string m_ZoneName; + int32 m_CellSize; + + float m_MinX; + float m_MinY; + float m_MinZ; + float m_MaxX; + float m_MaxY; + float m_MaxZ; + + struct impl; + impl *imp; + bool mapLoaded; + bool mapLoading; + Mutex CheckMapMutex; +}; + +class MapRange { +public: + MapRange(); + + ~MapRange(); + + void Clear(); + + void AddVersionRange(std::string zoneName); + + map::iterator FindVersionRange(int32 min_version, int32 max_version); + map::iterator FindMapByVersion(int32 version); + map::iterator GetRangeEnd() { return version_map.end(); } +private: + std::map version_map; + string name; +}; + +#endif diff --git a/source/WorldServer/Zone/mob_movement_manager.cpp b/source/WorldServer/Zone/mob_movement_manager.cpp new file mode 100644 index 0000000..5f9a43e --- /dev/null +++ b/source/WorldServer/Zone/mob_movement_manager.cpp @@ -0,0 +1,1279 @@ +#include "mob_movement_manager.h" +#include "../Entity.h" +#include "../zoneserver.h" +#include "region_map.h" +#include "map.h" +#include "../../common/timer.h" +#include "pathfinder_interface.h" +#include "position.h" +#include "../../common/Log.h" + +#include +#include +#include +#include + +#ifdef WIN32 +#include +#endif + +extern double frame_time; + +class IMovementCommand { +public: + IMovementCommand() = default; + virtual ~IMovementCommand() = default; + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) = 0; + virtual bool Started() const = 0; +}; + +class RotateToCommand : public IMovementCommand { +public: + RotateToCommand(double rotate_to, double dir, MobMovementMode mob_movement_mode) + { + m_rotate_to = rotate_to; + m_rotate_to_dir = dir; + m_rotate_to_mode = mob_movement_mode; + m_started = false; + } + + virtual ~RotateToCommand() + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + auto rotate_to_speed = m_rotate_to_mode == MovementRunning ? 200.0 : 16.0; //todo: get this from mob + + auto from = mob_movement_manager->FixHeading(mob->GetHeading()); + auto to = mob_movement_manager->FixHeading(m_rotate_to); + auto diff = to - from; + + while (diff < -256.0) { + diff += 512.0; + } + + while (diff > 256) { + diff -= 512.0; + } + + auto dist = std::abs(diff); + + if (!m_started) { + m_started = true; + //mob->SetMoving(true); + + /*if (dist > 15.0f && rotate_to_speed > 0.0 && rotate_to_speed <= 25.0) { //send basic rotation + mob_movement_manager->SendCommandToClients( + mob, + 0.0, + 0.0, + 0.0, + m_rotate_to_dir * rotate_to_speed, + 0, + ClientRangeClose + ); + }*/ + } + + auto td = rotate_to_speed * 19.0 * frame_time; + + if (td >= dist) { + mob->SetHeading(to); + //mob->SetMoving(false); + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, 0, ClientRangeCloseMedium); + return true; + } + + from += td * m_rotate_to_dir; + mob->SetHeading(mob_movement_manager->FixHeading(from)); + return false; + } + + virtual bool Started() const + { + return m_started; + } + +private: + double m_rotate_to; + double m_rotate_to_dir; + MobMovementMode m_rotate_to_mode; + bool m_started; +}; + +class MoveToCommand : public IMovementCommand { +public: + MoveToCommand(float x, float y, float z, MobMovementMode mob_movement_mode) + { + m_distance_moved_since_correction = 0.0; + m_move_to_x = x; + m_move_to_y = y; + m_move_to_z = z; + m_move_to_mode = mob_movement_mode; + m_last_sent_time = 0.0; + m_last_sent_speed = 0; + m_started = false; + m_total_h_dist = 0.0; + m_total_v_dist = 0.0; + } + + virtual ~MoveToCommand() + { + + } + + /** + * @param mob_movement_manager + * @param mob + * @return + */ + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + //Send a movement packet when you start moving + double current_time = static_cast(Timer::GetCurrentTime2()) / 1000.0; + int current_speed = 0; + + if (m_move_to_mode == MovementRunning) { + current_speed = ((Spawn*)mob)->GetSpeed(); + } + + if (!m_started) { + m_started = true; + //rotate to the point + //mob->SetMoving(true); + mob->SetHeading(mob->GetFaceTarget(m_move_to_x, m_move_to_z)); + + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + // Z/Y are flipped due to EverQuest 2 using Y as up/down + m_total_h_dist = DistanceNoZ(glm::vec4(mob->GetX(),mob->GetZ(),mob->GetY(),mob->GetHeading()), glm::vec4(m_move_to_x, m_move_to_z, 0.0f, 0.0f)); + m_total_v_dist = m_move_to_y - mob->GetY(); + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + //When speed changes + if (current_speed != m_last_sent_speed) { + //if (RuleB(Map, FixZWhenPathing)) { + // mob->FixZ(); + //} + + m_distance_moved_since_correction = 0.0; + + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + //If x seconds have passed without sending an update. + if (current_time - m_last_sent_time >= 5.0) { + //if (RuleB(Map, FixZWhenPathing)) { + //mob->FixZ(); + //} + + m_distance_moved_since_correction = 0.0; + + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + //mob_movemesnt_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + glm::vec3 p = glm::vec3(mob->GetX(), mob->GetY(), mob->GetZ()); + // our X/Z versus the mobs X/Z + glm::vec2 tar(m_move_to_x, m_move_to_z); + glm::vec2 pos(p.x, p.z); + double len = glm::distance(pos, tar); + if (len < .01) { + return true; + } + + //mob->SetMoved(true); + + glm::vec2 dir = tar - pos; + glm::vec2 ndir = glm::normalize(dir); + double distance_moved = frame_time * current_speed * 0.4f * 1.45f; + + //mob->SetX(m_move_to_x); + //mob->SetY(m_move_to_z); + //mob->SetZ(m_move_to_y); + + mob->ClearRunningLocations(); + + if (distance_moved > len) { + //if (RuleB(Map, FixZWhenPathing)) { + //mob->FixZ(); + //} + // we use npos.y because higher up that is the equilvaent Z + mob->AddRunningLocation(m_move_to_x, m_move_to_y, m_move_to_z, current_speed, distance_moved, true, true, "", true); + return false; + } + else { + glm::vec2 npos = pos + (ndir * static_cast(distance_moved)); + + len -= distance_moved; + double total_distance_traveled = m_total_h_dist - len; + double start_y = m_move_to_y - m_total_v_dist; + double y_at_pos = start_y + (m_total_v_dist * (total_distance_traveled / m_total_h_dist)); + + // we use npos.y because higher up that is the equilvaent Z + mob->AddRunningLocation(m_move_to_x, m_move_to_y, m_move_to_z, current_speed, distance_moved, true, true, "", true); + + // mob->SetX(npos.x); + // mob->SetY(z_at_pos); + // mob->SetZ(npos.y); + + //if (RuleB(Map, FixZWhenPathing)) { + // m_distance_moved_since_correction += distance_moved; + // if (m_distance_moved_since_correction > 10.0f /*RuleR(Map, DistanceCanTravelBeforeAdjustment)*/) { + // m_distance_moved_since_correction = 0.0; + //mob->FixZ(); + //} + // } + } + + return false; + } + + virtual bool Started() const + { + return m_started; + } + +protected: + double m_distance_moved_since_correction; + double m_move_to_x; + double m_move_to_y; + double m_move_to_z; + MobMovementMode m_move_to_mode; + bool m_started; + + double m_last_sent_time; + int m_last_sent_speed; + double m_total_h_dist; + double m_total_v_dist; +}; + +class SwimToCommand : public MoveToCommand { +public: + SwimToCommand(float x, float y, float z, MobMovementMode mob_movement_mode) : MoveToCommand(x, y, z, mob_movement_mode) + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + //Send a movement packet when you start moving + double current_time = static_cast(Timer::GetCurrentTime2()) / 1000.0; + int current_speed = 0; + + if (m_move_to_mode == MovementRunning) { + if (mob->IsFeared()) { + current_speed = mob->GetBaseSpeed(); + } + else { + //runback overrides + if (mob->GetSpeed() > mob->GetMaxSpeed()) + current_speed = mob->GetSpeed(); + else + current_speed = mob->GetMaxSpeed(); + } + } + else { + current_speed = mob->GetBaseSpeed(); + } + + if (!m_started) { + m_started = true; + //rotate to the point + //mob->SetMoving(true); + mob->SetHeading(mob->GetFaceTarget(m_move_to_x, m_move_to_z)); + + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + m_total_h_dist = DistanceNoZ(glm::vec4(mob->GetX(),mob->GetZ(),mob->GetY(),mob->GetHeading()), glm::vec4(m_move_to_x, m_move_to_z, 0.0f, 0.0f)); + m_total_v_dist = m_move_to_y - mob->GetY(); + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + //When speed changes + if (current_speed != m_last_sent_speed) { + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + //If x seconds have passed without sending an update. + if (current_time - m_last_sent_time >= 1.5) { + m_last_sent_speed = current_speed; + m_last_sent_time = current_time; + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, current_speed, ClientRangeCloseMedium); + } + + glm::vec4 p = glm::vec4(mob->GetX(), mob->GetZ(), mob->GetY(), mob->GetHeading()); + glm::vec2 tar(m_move_to_x, m_move_to_y); + glm::vec2 pos(p.x, p.y); + double len = glm::distance(pos, tar); + if (len == 0) { + return true; + } + + //mob->SetMoved(true); + + glm::vec2 dir = tar - pos; + glm::vec2 ndir = glm::normalize(dir); + double distance_moved = frame_time * current_speed * 0.4f * 1.45f; + + mob->SetX(m_move_to_x); + mob->SetZ(m_move_to_z); + mob->SetY(m_move_to_y); + if (distance_moved > len) { + return true; + } + else { + glm::vec2 npos = pos + (ndir * static_cast(distance_moved)); + + len -= distance_moved; + double total_distance_traveled = m_total_h_dist - len; + double start_y = m_move_to_y - m_total_v_dist; + double y_at_pos = start_y + (m_total_v_dist * (total_distance_traveled / m_total_h_dist)); + + mob->SetX(npos.x); + mob->SetZ(npos.y); + mob->SetY(y_at_pos); + } + + return false; + } +}; + +class TeleportToCommand : public IMovementCommand { +public: + TeleportToCommand(float x, float y, float z, float heading) + { + m_teleport_to_x = x; + m_teleport_to_y = y; + m_teleport_to_z = z; + m_teleport_to_heading = heading; + } + + virtual ~TeleportToCommand() + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + mob->SetX(m_teleport_to_x); + mob->SetZ(m_teleport_to_z); + mob->SetY(m_teleport_to_y); + mob->SetHeading(mob_movement_manager->FixHeading(m_teleport_to_heading)); + //mob_movement_manager->SendCommandToClients(mob, 0.0, 0.0, 0.0, 0.0, 0, ClientRangeAny); + + return true; + } + + virtual bool Started() const + { + return false; + } + +private: + + double m_teleport_to_x; + double m_teleport_to_y; + double m_teleport_to_z; + double m_teleport_to_heading; +}; + +class StopMovingCommand : public IMovementCommand { +public: + StopMovingCommand() + { + } + + virtual ~StopMovingCommand() + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + mob->ClearRunningLocations(); + return true; + } + + virtual bool Started() const + { + return false; + } +}; + +class EvadeCombatCommand : public IMovementCommand { +public: + EvadeCombatCommand() + { + } + + virtual ~EvadeCombatCommand() + { + + } + + virtual bool Process(MobMovementManager* mob_movement_manager, Entity* mob) + { + return true; + } + + virtual bool Started() const + { + return false; + } +}; + +struct MovementStats { + MovementStats() + { + LastResetTime = static_cast(Timer::GetCurrentTime2()) / 1000.0; + TotalSent = 0ULL; + TotalSentMovement = 0ULL; + TotalSentPosition = 0ULL; + TotalSentHeading = 0ULL; + } + + double LastResetTime; + uint64_t TotalSent; + uint64_t TotalSentMovement; + uint64_t TotalSentPosition; + uint64_t TotalSentHeading; +}; + +struct NavigateTo { + NavigateTo() + { + navigate_to_x = 0.0; + navigate_to_y = 0.0; + navigate_to_z = 0.0; + navigate_to_heading = 0.0; + last_set_time = 0.0; + } + + double navigate_to_x; + double navigate_to_y; + double navigate_to_z; + double navigate_to_heading; + double last_set_time; +}; + +struct MobMovementEntry { + std::deque> Commands; + NavigateTo NavTo; +}; + +void AdjustRoute(std::list &nodes, Entity *who) +{ + if (who->GetZone() == nullptr || !who->GetMap() /*|| !zone->HasWaterMap()*/) { + return; + } + + auto offset = who->GetYOffset(); + + for (auto &node : nodes) { + //if (!zone->watermap->InLiquid(node.pos)) { + auto best_z = who->FindBestZ(node.pos, nullptr); + if (best_z != BEST_Z_INVALID) { + node.pos.z = best_z + offset; + } + //} // todo: floating logic? + } +} + +struct MobMovementManager::Implementation { + std::map Entries; + std::vector Clients; + MovementStats Stats; +}; + +MobMovementManager::MobMovementManager() +{ + MobListMutex.SetName("MobMovementManager"); + _impl.reset(new Implementation()); +} + +MobMovementManager::~MobMovementManager() +{ +} + +void MobMovementManager::Process() +{ + MobListMutex.readlock(); + for (auto &iter : _impl->Entries) { + auto &ent = iter.second; + auto &commands = ent.Commands; + + if (commands.size() < 1) + continue; + + iter.first->MCommandMutex.writelock(); + while (true != commands.empty()) { + auto &cmd = commands.front(); + auto r = cmd->Process(this, iter.first); + + if (true != r) { + break; + } + + commands.pop_front(); + } + iter.first->MCommandMutex.releasewritelock(); + } + MobListMutex.releasereadlock(); +} + +/** + * @param mob + */ +void MobMovementManager::AddMob(Entity *mob) +{ + MobListMutex.writelock(); + _impl->Entries.insert(std::make_pair(mob, MobMovementEntry())); + MobListMutex.releasewritelock(); +} + +/** + * @param mob + */ +void MobMovementManager::RemoveMob(Entity *mob) +{ + MobListMutex.writelock(); + auto iter = _impl->Entries.find(mob); + if(iter != _impl->Entries.end()) + _impl->Entries.erase(iter); + MobListMutex.releasewritelock(); +} + +/** + * @param client + */ +void MobMovementManager::AddClient(Client *client) +{ + _impl->Clients.push_back(client); +} + +/** + * @param client + */ +void MobMovementManager::RemoveClient(Client *client) +{ + auto iter = _impl->Clients.begin(); + while (iter != _impl->Clients.end()) { + if (client == *iter) { + _impl->Clients.erase(iter); + return; + } + + ++iter; + } +} + +/** + * @param who + * @param to + * @param mob_movement_mode + */ +void MobMovementManager::RotateTo(Entity *who, float to, MobMovementMode mob_movement_mode) +{ + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + + if (true != ent.second.Commands.empty()) { + MobListMutex.releasereadlock(); + return; + } + + PushRotateTo(ent.second, who, to, mob_movement_mode); + MobListMutex.releasereadlock(); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param heading + */ +void MobMovementManager::Teleport(Entity *who, float x, float y, float z, float heading) +{ + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + + ent.second.Commands.clear(); + + PushTeleportTo(ent.second, x, y, z, heading); + MobListMutex.releasereadlock(); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param mode + */ +void MobMovementManager::NavigateTo(Entity *who, float x, float y, float z, MobMovementMode mode, bool overrideDistance) +{ + glm::vec3 targPos(x, z, y); + glm::vec3 origPos(who->GetX(), who->GetZ(), who->GetY()); + + if (IsPositionEqualWithinCertainZ(targPos, origPos, 6.0f)) { + return; + } + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + auto &nav = ent.second.NavTo; + + double current_time = static_cast(Timer::GetCurrentTime2()) / 1000.0; + if ((current_time - nav.last_set_time) > 0.5) { + //Can potentially recalc + + auto within = IsPositionWithinSimpleCylinder( + glm::vec3(who->GetX(), who->GetZ(), who->GetY()), + glm::vec3(nav.navigate_to_x, nav.navigate_to_z, nav.navigate_to_y), + 1.5f, + 6.0f + ); + + who->MCommandMutex.writelock(); + + if (within && ent.second.Commands.size() > 0 && nav.last_set_time != 0) + { + //who->ClearRunningLocations(); + //StopNavigation((Entity*)who); + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); + return; + } + else if (!within && ent.second.Commands.size() > 0 && nav.last_set_time != 0) + { + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); + return; + } + + LogWrite(MAP__DEBUG, 0, "Map", "%s %f %f %f: within: %i, commands: %i, lastnav: %f %f %f", who->GetName(), + who->GetX(),who->GetY(),who->GetZ(),within, + ent.second.Commands.size(), nav.navigate_to_x, nav.navigate_to_y, nav.navigate_to_z); + //auto heading_match = IsHeadingEqual(0.0, nav.navigate_to_heading); + + //if (/*false == within ||*/ false == heading_match || ent.second.Commands.size() == 0) { + ent.second.Commands.clear(); + + //Path is no longer valid, calculate a new path + UpdatePath(who, x, y, z, mode); + nav.navigate_to_x = x; + nav.navigate_to_y = y; + nav.navigate_to_z = z; + nav.navigate_to_heading = 0.0; + nav.last_set_time = current_time; + + who->MCommandMutex.releasewritelock(); + //} + } + MobListMutex.releasereadlock(); +} + +/** + * @param who + */ +void MobMovementManager::StopNavigation(Entity *who) +{ + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + auto &nav = ent.second.NavTo; + + nav.navigate_to_x = 0.0; + nav.navigate_to_y = 0.0; + nav.navigate_to_z = 0.0; + nav.navigate_to_heading = 0.0; + nav.last_set_time = 0.0; + + who->MCommandMutex.writelock(); + if (true == ent.second.Commands.empty()) { + PushStopMoving(ent.second); + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); + return; + } + + if (!who->IsRunning()) { + ent.second.Commands.clear(); + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); + return; + } + + ent.second.Commands.clear(); + PushStopMoving(ent.second); + + who->MCommandMutex.releasewritelock(); + MobListMutex.releasereadlock(); +} + +void MobMovementManager::DisruptNavigation(Entity* who) +{ + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto& ent = (*iter); + auto& nav = ent.second.NavTo; + + nav.navigate_to_x = 0.0; + nav.navigate_to_y = 0.0; + nav.navigate_to_z = 0.0; + nav.navigate_to_heading = 0.0; + nav.last_set_time = 0.0; + + if (!who->IsRunning()) { + who->MCommandMutex.writelock(); + ent.second.Commands.clear(); + who->MCommandMutex.releasewritelock(); + } + MobListMutex.releasereadlock(); +} + +/** + * @param in + * @return + */ +float MobMovementManager::FixHeading(float in) +{ + auto h = in; + while (h > 512.0) { + h -= 512.0; + } + + while (h < 0.0) { + h += 512.0; + } + + return h; +} + +void MobMovementManager::ClearStats() +{ + _impl->Stats.LastResetTime = static_cast(Timer::GetCurrentTime2()) / 1000.0; + _impl->Stats.TotalSent = 0; + _impl->Stats.TotalSentHeading = 0; + _impl->Stats.TotalSentMovement = 0; + _impl->Stats.TotalSentPosition = 0; +} + + +/** + * @param who + * @param x + * @param y + * @param z + * @param mob_movement_mode + */ +void MobMovementManager::UpdatePath(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode) +{ + if (!who->GetMap() /*|| !zone->HasWaterMap()*/) { + MobListMutex.readlock(); + auto iter = _impl->Entries.find(who); + + if (iter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*iter); + + PushMoveTo(ent.second, x, y, z, mob_movement_mode); + PushStopMoving(ent.second); + MobListMutex.releasereadlock(); + return; + } + /* + if (who-?()) { + UpdatePathBoat(who, x, y, z, mob_movement_mode); + } + else if (who->IsUnderwaterOnly()) { + UpdatePathUnderwater(who, x, y, z, mob_movement_mode); + }*/ + //else { + UpdatePathGround(who, x, y, z, mob_movement_mode); + //} +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param mode + */ +void MobMovementManager::UpdatePathGround(Entity *who, float x, float y, float z, MobMovementMode mode) +{ + PathfinderOptions opts; + opts.smooth_path = true; + opts.step_size = 100.0f;//RuleR(Pathing, NavmeshStepSize); + opts.offset = who->GetYOffset()+1.0f; + opts.flags = PathingNotDisabled ^ PathingZoneLine; + + //This is probably pointless since the nav mesh tool currently sets zonelines to disabled anyway + auto partial = false; + auto stuck = false; + auto route = who->GetZone()->pathing->FindPath( + glm::vec3(who->GetX(), who->GetZ(), who->GetY()), + glm::vec3(x, z, y), + partial, + stuck, + opts + ); + + MobListMutex.readlock(); + auto eiter = _impl->Entries.find(who); + + if (eiter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*eiter); + + if (route.size() == 0) { + HandleStuckBehavior(who, x, y, z, mode); + MobListMutex.releasereadlock(); + return; + } + + AdjustRoute(route, who); + + //avoid doing any processing if the mob is stuck to allow normal stuck code to work. + if (!stuck) { + + //there are times when the routes returned are no differen than where the mob is currently standing. What basically happens + //is a mob will get 'stuck' in such a way that it should be moving but the 'moving' place is the exact same spot it is at. + //this is a problem and creates an area of ground that if a mob gets to, will stay there forever. If socal this creates a + //"Ball of Death" (tm). This code tries to prevent this by simply warping the mob to the requested x/y. Better to have a warp than + //have stuck mobs. + + auto routeNode = route.begin(); + bool noValidPath = true; + while (routeNode != route.end() && noValidPath == true) { + auto ¤tNode = (*routeNode); + + if (routeNode == route.end()) { + continue; + } + + if (!(currentNode.pos.x == who->GetX() && currentNode.pos.y == who->GetZ())) { + //if one of the nodes to move to, is not our current node, pass it. + noValidPath = false; + break; + } + //move to the next node + routeNode++; + + } + + if (noValidPath) { + //we are 'stuck' in a path, lets just get out of this by 'teleporting' to the next position. + PushTeleportTo( + ent.second, + x, + y, + z, + CalculateHeadingAngleBetweenPositions(who->GetX(), who->GetZ(), x, z) + ); + + MobListMutex.releasereadlock(); + return; + } + } + + auto iter = route.begin(); + + glm::vec3 previous_pos(who->GetX(), who->GetZ(), who->GetY()); + + bool first_node = true; + while (iter != route.end()) { + auto ¤t_node = (*iter); + + iter++; + + if (iter == route.end()) { + continue; + } + + previous_pos = current_node.pos; + auto &next_node = (*iter); + + if (first_node) { + + if (mode == MovementWalking) { + auto h = who->GetFaceTarget(next_node.pos.x, next_node.pos.y); + PushRotateTo(ent.second, who, h, mode); + } + + first_node = false; + } + + //move to / teleport to node + 1 + if (next_node.teleport && next_node.pos.x != 0.0f && next_node.pos.y != 0.0f) { + float calcedHeading = + CalculateHeadingAngleBetweenPositions( + current_node.pos.x, + current_node.pos.y, + next_node.pos.x, + next_node.pos.y + ); + PushTeleportTo( + ent.second, + next_node.pos.x, + next_node.pos.z, + next_node.pos.y, + calcedHeading + ); + } + else { +/* if (who->GetZone()->watermap->InLiquid(previous_pos)) { + PushSwimTo(ent.second, next_node.pos.x, next_node.pos.y, next_node.pos.z, mode); + } + else {*/ + PushMoveTo(ent.second, next_node.pos.x, next_node.pos.z, next_node.pos.y, mode); +// } + } + } + + if (stuck) { + HandleStuckBehavior(who, x, y, z, mode); + } + else { + PushStopMoving(ent.second); + } + MobListMutex.releasereadlock(); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param movement_mode + */ +void MobMovementManager::UpdatePathUnderwater(Entity *who, float x, float y, float z, MobMovementMode movement_mode) +{ + MobListMutex.readlock(); + auto eiter = _impl->Entries.find(who); + + if (eiter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*eiter); + if (/*zone->watermap->InLiquid(who->GetPosition()) && zone->watermap->InLiquid(glm::vec3(x, y, z)) &&*/ + who->CheckLoS(glm::vec3(who->GetX(),who->GetZ(),who->GetY()), glm::vec3(x, z, y))) { + PushSwimTo(ent.second, x, y, z, movement_mode); + PushStopMoving(ent.second); + MobListMutex.releasereadlock(); + return; + } + + PathfinderOptions opts; + opts.smooth_path = true; + opts.step_size = 100.0f;// RuleR(Pathing, NavmeshStepSize); + opts.offset = who->GetYOffset(); + opts.flags = PathingNotDisabled ^ PathingZoneLine; + + auto partial = false; + auto stuck = false; + auto route = who->GetZone()->pathing->FindPath( + glm::vec3(who->GetX(), who->GetY(), who->GetZ()), + glm::vec3(x, y, z), + partial, + stuck, + opts + ); + + if (route.size() == 0) { + HandleStuckBehavior(who, x, z, y, movement_mode); + MobListMutex.releasereadlock(); + return; + } + + AdjustRoute(route, who); + + auto iter = route.begin(); + glm::vec3 previous_pos(who->GetX(), who->GetY(), who->GetZ()); + bool first_node = true; + + while (iter != route.end()) { + auto ¤t_node = (*iter); + +/* if (!zone->watermap->InLiquid(current_node.pos)) { + stuck = true; + + while (iter != route.end()) { + iter = route.erase(iter); + } + + break; + } + else {*/ + iter++; +// } + } + + if (route.size() == 0) { + HandleStuckBehavior(who, x, y, z, movement_mode); + MobListMutex.releasereadlock(); + return; + } + + iter = route.begin(); + + while (iter != route.end()) { + auto ¤t_node = (*iter); + + iter++; + + if (iter == route.end()) { + continue; + } + + previous_pos = current_node.pos; + auto &next_node = (*iter); + + if (first_node) { + + if (movement_mode == MovementWalking) { + auto h = who->GetFaceTarget(next_node.pos.x, next_node.pos.y); + PushRotateTo(ent.second, who, h, movement_mode); + } + + first_node = false; + } + + //move to / teleport to node + 1 + if (next_node.teleport && next_node.pos.x != 0.0f && next_node.pos.y != 0.0f) { + float calcHeading = CalculateHeadingAngleBetweenPositions( + current_node.pos.x, + current_node.pos.y, + next_node.pos.x, + next_node.pos.y + ); + + PushTeleportTo( + ent.second, next_node.pos.x, next_node.pos.z, next_node.pos.y, calcHeading); + } + else { + PushSwimTo(ent.second, next_node.pos.x, next_node.pos.z, next_node.pos.y, movement_mode); + } + } + + if (stuck) { + HandleStuckBehavior(who, x, y, z, movement_mode); + } + else { + PushStopMoving(ent.second); + } + MobListMutex.releasereadlock(); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param mode + */ +void MobMovementManager::UpdatePathBoat(Entity *who, float x, float y, float z, MobMovementMode mode) +{ + MobListMutex.readlock(); + auto eiter = _impl->Entries.find(who); + + if (eiter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*eiter); + + PushSwimTo(ent.second, x, y, z, mode); + PushStopMoving(ent.second); + MobListMutex.releasereadlock(); +} + +/** + * @param ent + * @param x + * @param y + * @param z + * @param heading + */ +void MobMovementManager::PushTeleportTo(MobMovementEntry &ent, float x, float y, float z, float heading) +{ + ent.Commands.push_back(std::unique_ptr(new TeleportToCommand(x, y, z, heading))); +} + +/** + * @param ent + * @param x + * @param y + * @param z + * @param mob_movement_mode + */ +void MobMovementManager::PushMoveTo(MobMovementEntry &ent, float x, float y, float z, MobMovementMode mob_movement_mode) +{ + ent.Commands.push_back(std::unique_ptr(new MoveToCommand(x, y, z, mob_movement_mode))); +} + +/** + * @param ent + * @param x + * @param y + * @param z + * @param mob_movement_mode + */ +void MobMovementManager::PushSwimTo(MobMovementEntry &ent, float x, float y, float z, MobMovementMode mob_movement_mode) +{ + ent.Commands.push_back(std::unique_ptr(new SwimToCommand(x, y, z, mob_movement_mode))); +} + +/** + * @param ent + * @param who + * @param to + * @param mob_movement_mode + */ +void MobMovementManager::PushRotateTo(MobMovementEntry &ent, Entity *who, float to, MobMovementMode mob_movement_mode) +{ + auto from = FixHeading(who->GetHeading()); + to = FixHeading(to); + + float diff = to - from; + + if (std::abs(diff) < 0.001f) { + return; + } + + while (diff < -256.0) { + diff += 512.0; + } + + while (diff > 256) { + diff -= 512.0; + } + + ent.Commands.push_back(std::unique_ptr(new RotateToCommand(to, diff > 0 ? 1.0 : -1.0, mob_movement_mode))); +} + +/** + * @param mob_movement_entry + */ +void MobMovementManager::PushStopMoving(MobMovementEntry &mob_movement_entry) +{ + mob_movement_entry.Commands.push_back(std::unique_ptr(new StopMovingCommand())); +} + +/** + * @param mob_movement_entry + */ +void MobMovementManager::PushEvadeCombat(MobMovementEntry &mob_movement_entry) +{ + mob_movement_entry.Commands.push_back(std::unique_ptr(new EvadeCombatCommand())); +} + +/** + * @param who + * @param x + * @param y + * @param z + * @param mob_movement_mode + */ +void MobMovementManager::HandleStuckBehavior(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode) +{ + //LogDebug("Handle stuck behavior for {0} at ({1}, {2}, {3}) with movement_mode {4}", who->GetName(), x, y, z, mob_movement_mode); + + MobListMutex.readlock(); + auto sb = RunToTarget;//who->GetStuckBehavior(); + MobStuckBehavior behavior = RunToTarget; + + if (sb >= 0 && sb < MaxStuckBehavior) { + behavior = (MobStuckBehavior) sb; + } + + auto eiter = _impl->Entries.find(who); + + if (eiter == _impl->Entries.end()) + { + MobListMutex.releasereadlock(); + return; // does not exist in navigation + } + + auto &ent = (*eiter); + + switch (sb) { + case RunToTarget: + PushMoveTo(ent.second, x, y, z, mob_movement_mode); + PushStopMoving(ent.second); + break; + case WarpToTarget: + PushTeleportTo(ent.second, x, y, z, 0.0f); + PushStopMoving(ent.second); + break; + case TakeNoAction: + PushStopMoving(ent.second); + break; + case EvadeCombat: + PushEvadeCombat(ent.second); + break; + } + + MobListMutex.releasereadlock(); +} diff --git a/source/WorldServer/Zone/mob_movement_manager.h b/source/WorldServer/Zone/mob_movement_manager.h new file mode 100644 index 0000000..9520261 --- /dev/null +++ b/source/WorldServer/Zone/mob_movement_manager.h @@ -0,0 +1,111 @@ +#pragma once +#include +#include "../Entity.h" +#include "../../common/Mutex.h" +class Mob; +class Client; + +struct RotateCommand; +struct MovementCommand; +struct MobMovementEntry; +struct PlayerPositionUpdateServer_Struct; + +enum ClientRange : int +{ + ClientRangeNone = 0, + ClientRangeClose = 1, + ClientRangeMedium = 2, + ClientRangeCloseMedium = 3, + ClientRangeLong = 4, + ClientRangeCloseLong = 5, + ClientRangeMediumLong = 6, + ClientRangeAny = 7 +}; + +enum MobMovementMode : int +{ + MovementWalking = 0, + MovementRunning = 1 +}; + +enum MobStuckBehavior : int +{ + RunToTarget, + WarpToTarget, + TakeNoAction, + EvadeCombat, + MaxStuckBehavior +}; + +class MobMovementManager +{ +public: + ~MobMovementManager(); + void Process(); + void AddMob(Entity *mob); + void RemoveMob(Entity *mob); + void AddClient(Client *client); + void RemoveClient(Client *client); + + void RotateTo(Entity *who, float to, MobMovementMode mob_movement_mode = MovementRunning); + void Teleport(Entity *who, float x, float y, float z, float heading); + void NavigateTo(Entity *who, float x, float y, float z, MobMovementMode mode = MovementRunning, bool overrideDistance=false); + void StopNavigation(Entity *who); + void DisruptNavigation(Entity* who); + /* + void SendCommandToClients( + Entity *mob, + float delta_x, + float delta_y, + float delta_z, + float delta_heading, + int anim, + ClientRange range, + Client* single_client = nullptr, + Client* ignore_client = nullptr + );*/ + + float FixHeading(float in); + void ClearStats(); + + static MobMovementManager &Get() { + static MobMovementManager inst; + return inst; + } + + MobMovementManager(); + bool IsRunningCommandProcess() { + bool isRunning = false; + MobListMutex.readlock(); + isRunning = RunningCommandProcess; + MobListMutex.releasereadlock(); + return isRunning; + } + + bool SetCommandProcess(bool status) { + MobListMutex.writelock(); + RunningCommandProcess = status; + MobListMutex.releasewritelock(); + return true; + } +private: + MobMovementManager(const MobMovementManager&); + MobMovementManager& operator=(const MobMovementManager&); + + void UpdatePath(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode); + void UpdatePathGround(Entity *who, float x, float y, float z, MobMovementMode mode); + void UpdatePathUnderwater(Entity *who, float x, float y, float z, MobMovementMode movement_mode); + void UpdatePathBoat(Entity *who, float x, float y, float z, MobMovementMode mode); + void PushTeleportTo(MobMovementEntry &ent, float x, float y, float z, float heading); + void PushMoveTo(MobMovementEntry &ent, float x, float y, float z, MobMovementMode mob_movement_mode); + void PushSwimTo(MobMovementEntry &ent, float x, float y, float z, MobMovementMode mob_movement_mode); + void PushRotateTo(MobMovementEntry &ent, Entity *who, float to, MobMovementMode mob_movement_mode); + void PushStopMoving(MobMovementEntry &mob_movement_entry); + void PushEvadeCombat(MobMovementEntry &mob_movement_entry); + void HandleStuckBehavior(Entity *who, float x, float y, float z, MobMovementMode mob_movement_mode); + + struct Implementation; + std::unique_ptr _impl; + Mutex MobListMutex; + bool RunningCommandProcess; +}; diff --git a/source/WorldServer/Zone/pathfinder_interface.cpp b/source/WorldServer/Zone/pathfinder_interface.cpp new file mode 100644 index 0000000..f320eca --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_interface.cpp @@ -0,0 +1,21 @@ +#include "../../common/Log.h" +#include "../client.h" + +#include "pathfinder_null.h" +#include "pathfinder_nav_mesh.h" +#include "pathfinder_waypoint.h" +#include + +IPathfinder *IPathfinder::Load(const std::string &zone) { + struct stat statbuffer; + //std::string navmesh_path = fmt::format("maps/nav/{0}.nav", zone); + std::string navmesh_path = "Maps/nav/" + zone + ".nav"; + + if (stat(navmesh_path.c_str(), &statbuffer) == 0) { + return new PathfinderNavmesh(navmesh_path); + } + else + LogWrite(MAP__INFO, 7, "Map", "Could not find Navmesh File [{%s}]", navmesh_path.c_str()); + + return new PathfinderNull(); +} diff --git a/source/WorldServer/Zone/pathfinder_interface.h b/source/WorldServer/Zone/pathfinder_interface.h new file mode 100644 index 0000000..29a9167 --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_interface.h @@ -0,0 +1,81 @@ +#pragma once + +#include "map.h" +#include + +class Client; +class Seperator; + +enum PathingPolyFlags +{ + PathingNormal = 1, + PathingWater = 2, + PathingLava = 4, + PathingZoneLine = 8, + PathingPvP = 16, + PathingSlime = 32, + PathingIce = 64, + PathingVWater = 128, + PathingGeneralArea = 256, + PathingPortal = 512, + PathingPrefer = 1024, + PathingDisabled = 2048, + PathingAll = 65535, + PathingNotDisabled = PathingAll ^ PathingDisabled +}; + +struct PathfinderOptions +{ + PathfinderOptions() { + flags = PathingNotDisabled; + smooth_path = true; + step_size = 10.0f; + flag_cost[0] = 1.0f; + flag_cost[1] = 3.0f; + flag_cost[2] = 5.0f; + flag_cost[3] = 1.0f; + flag_cost[4] = 2.0f; + flag_cost[5] = 2.0f; + flag_cost[6] = 4.0f; + flag_cost[7] = 1.0f; + flag_cost[8] = 0.1f; + flag_cost[9] = 0.1f; + offset = 3.25f; + } + + int flags; + bool smooth_path; + float step_size; + float flag_cost[10]; + float offset; +}; + +class IPathfinder +{ +public: + struct IPathNode + { + IPathNode(const glm::vec3 &p) { + pos = p; + teleport = false; + } + + IPathNode(bool tp) { + teleport = tp; + } + + glm::vec3 pos; + bool teleport; + }; + + typedef std::list IPath; + + IPathfinder() { } + virtual ~IPathfinder() { } + + virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled) = 0; + virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts) = 0; + virtual glm::vec3 GetRandomLocation(const glm::vec3 &start) = 0; + + static IPathfinder *Load(const std::string &zone); +}; diff --git a/source/WorldServer/Zone/pathfinder_nav_mesh.cpp b/source/WorldServer/Zone/pathfinder_nav_mesh.cpp new file mode 100644 index 0000000..d52b7dc --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_nav_mesh.cpp @@ -0,0 +1,514 @@ +#include +#include +#include +#include "../../common/Log.h" +#include "pathfinder_nav_mesh.h" +#include +#include +#include "../zoneserver.h" +#include "region_map.h" +#include "../client.h" + +struct PathfinderNavmesh::Implementation +{ + dtNavMesh *nav_mesh; + dtNavMeshQuery *query; +}; + +PathfinderNavmesh::PathfinderNavmesh(const std::string &path) +{ + m_impl.reset(new Implementation()); + m_impl->nav_mesh = nullptr; + m_impl->query = nullptr; + Load(path); +} + +PathfinderNavmesh::~PathfinderNavmesh() +{ + Clear(); +} + +IPathfinder::IPath PathfinderNavmesh::FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags) +{ + partial = false; + + if (!m_impl->nav_mesh) { + return IPath(); + } + + if (!m_impl->query) { + m_impl->query = dtAllocNavMeshQuery(); + } + + m_impl->query->init(m_impl->nav_mesh, 4092 /*RuleI(Pathing, MaxNavmeshNodes)*/); + glm::vec3 current_location(start.x, start.z, start.y); + glm::vec3 dest_location(end.x, end.z, end.y); + + dtQueryFilter filter; + filter.setIncludeFlags(flags); + filter.setAreaCost(0, 1.0f); //Normal + filter.setAreaCost(1, 3.0f); //Water + filter.setAreaCost(2, 5.0f); //Lava + filter.setAreaCost(4, 1.0f); //PvP + filter.setAreaCost(5, 2.0f); //Slime + filter.setAreaCost(6, 2.0f); //Ice + filter.setAreaCost(7, 4.0f); //V Water (Frigid Water) + filter.setAreaCost(8, 1.0f); //General Area + filter.setAreaCost(9, 0.1f); //Portal + filter.setAreaCost(10, 0.1f); //Prefer + + dtPolyRef start_ref; + dtPolyRef end_ref; + glm::vec3 ext(5.0f, 100.0f, 5.0f); + + m_impl->query->findNearestPoly(¤t_location[0], &ext[0], &filter, &start_ref, 0); + m_impl->query->findNearestPoly(&dest_location[0], &ext[0], &filter, &end_ref, 0); + + if (!start_ref || !end_ref) { + return IPath(); + } + + int npoly = 0; + dtPolyRef path[1024] = { 0 }; + auto status = m_impl->query->findPath(start_ref, end_ref, ¤t_location[0], &dest_location[0], &filter, path, &npoly, 1024); + + if (npoly) { + glm::vec3 epos = dest_location; + if (path[npoly - 1] != end_ref) { + m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0); + partial = true; + + auto dist = DistanceSquared(epos, current_location); + if (dist < 10000.0f) { + stuck = true; + } + } + + float straight_path[2048 * 3]; + unsigned char straight_path_flags[2048]; + + int n_straight_polys; + dtPolyRef straight_path_polys[2048]; + + status = m_impl->query->findStraightPath(¤t_location[0], &epos[0], path, npoly, + straight_path, straight_path_flags, + straight_path_polys, &n_straight_polys, 2048, DT_STRAIGHTPATH_AREA_CROSSINGS); + + if (dtStatusFailed(status)) { + return IPath(); + } + + if (n_straight_polys) { + IPath Route; + for (int i = 0; i < n_straight_polys; ++i) + { + glm::vec3 node; + node.x = straight_path[i * 3]; + node.z = straight_path[i * 3 + 1]; + node.y = straight_path[i * 3 + 2]; + + Route.push_back(node); + + unsigned short flag = 0; + if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &flag))) { + if (flag & 512) { + Route.push_back(true); + } + } + } + + return Route; + } + } + + IPath Route; + Route.push_back(end); + return Route; +} + +IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions &opts) +{ + partial = false; + + if (!m_impl->nav_mesh) { + return IPath(); + } + + if (!m_impl->query) { + m_impl->query = dtAllocNavMeshQuery(); + } + + m_impl->query->init(m_impl->nav_mesh, 4092 /*RuleI(Pathing, MaxNavmeshNodes)*/); + glm::vec3 current_location(start.x, start.z, start.y); + glm::vec3 dest_location(end.x, end.z, end.y); + + dtQueryFilter filter; + filter.setIncludeFlags(opts.flags); + filter.setAreaCost(0, opts.flag_cost[0]); //Normal + filter.setAreaCost(1, opts.flag_cost[1]); //Water + filter.setAreaCost(2, opts.flag_cost[2]); //Lava + filter.setAreaCost(4, opts.flag_cost[3]); //PvP + filter.setAreaCost(5, opts.flag_cost[4]); //Slime + filter.setAreaCost(6, opts.flag_cost[5]); //Ice + filter.setAreaCost(7, opts.flag_cost[6]); //V Water (Frigid Water) + filter.setAreaCost(8, opts.flag_cost[7]); //General Area + filter.setAreaCost(9, opts.flag_cost[8]); //Portal + filter.setAreaCost(10, opts.flag_cost[9]); //Prefer + + static const int max_polys = 256; + dtPolyRef start_ref; + dtPolyRef end_ref; + glm::vec3 ext(10.0f, 200.0f, 10.0f); + + m_impl->query->findNearestPoly(¤t_location[0], &ext[0], &filter, &start_ref, 0); + m_impl->query->findNearestPoly(&dest_location[0], &ext[0], &filter, &end_ref, 0); + + if (!start_ref || !end_ref) { + return IPath(); + } + + int npoly = 0; + dtPolyRef path[max_polys] = { 0 }; + auto status = m_impl->query->findPath(start_ref, end_ref, ¤t_location[0], &dest_location[0], &filter, path, &npoly, max_polys); + + if (npoly) { + glm::vec3 epos = dest_location; + if (path[npoly - 1] != end_ref) { + m_impl->query->closestPointOnPoly(path[npoly - 1], &dest_location[0], &epos[0], 0); + partial = true; + + auto dist = DistanceSquared(epos, current_location); + if (dist < 10000.0f) { + stuck = true; + } + } + + int n_straight_polys; + glm::vec3 straight_path[max_polys]; + unsigned char straight_path_flags[max_polys]; + dtPolyRef straight_path_polys[max_polys]; + + auto status = m_impl->query->findStraightPath(¤t_location[0], &epos[0], path, npoly, + (float*)&straight_path[0], straight_path_flags, + straight_path_polys, &n_straight_polys, 2048, DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS); + + if (dtStatusFailed(status)) { + return IPath(); + } + + if (n_straight_polys) { + if (opts.smooth_path) { + IPath Route; + + //Add the first point + { + auto &flag = straight_path_flags[0]; + if (flag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) { + auto &p = straight_path[0]; + + Route.push_back(glm::vec3(p.x, p.z, p.y)); + } + else { + auto &p = straight_path[0]; + + float h = 0.0f; + if (dtStatusSucceed(GetPolyHeightOnPath(path, npoly, p, &h))) { + p.y = h + opts.offset; + } + + Route.push_back(glm::vec3(p.x, p.z, p.y)); + } + } + + for (int i = 0; i < n_straight_polys - 1; ++i) + { + auto &flag = straight_path_flags[i]; + + if (flag & DT_STRAIGHTPATH_OFFMESH_CONNECTION) { + auto &poly = straight_path_polys[i]; + + auto &p2 = straight_path[i + 1]; + glm::vec3 node(p2.x, p2.z, p2.y); + Route.push_back(node); + + unsigned short pflag = 0; + if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &pflag))) { + if (pflag & 512) { + Route.push_back(true); + } + } + } + else { + auto &p1 = straight_path[i]; + auto &p2 = straight_path[i + 1]; + auto dist = glm::distance(p1, p2); + auto dir = glm::normalize(p2 - p1); + float total = 0.0f; + glm::vec3 previous_pt = p1; + + while (total < dist) { + glm::vec3 current_pt; + float dist_to_move = opts.step_size; + float ff = opts.step_size / 2.0f; + + if (total + dist_to_move + ff >= dist) { + current_pt = p2; + total = dist; + } + else { + total += dist_to_move; + current_pt = p1 + dir * total; + } + + float h = 0.0f; + if (dtStatusSucceed(GetPolyHeightOnPath(path, npoly, current_pt, &h))) { + current_pt.y = h + opts.offset; + } + + Route.push_back(glm::vec3(current_pt.x, current_pt.z, current_pt.y)); + previous_pt = current_pt; + } + } + } + + return Route; + } + else { + IPath Route; + for (int i = 0; i < n_straight_polys; ++i) + { + auto ¤t = straight_path[i]; + glm::vec3 node(current.x, current.z, current.y); + Route.push_back(node); + + unsigned short flag = 0; + if (dtStatusSucceed(m_impl->nav_mesh->getPolyFlags(straight_path_polys[i], &flag))) { + if (flag & 512) { + Route.push_back(true); + } + } + } + + return Route; + } + } + } + + return IPath(); +} + +glm::vec3 PathfinderNavmesh::GetRandomLocation(const glm::vec3 &start) +{ + if (start.x == 0.0f && start.y == 0.0) + return glm::vec3(0.f); + + if (!m_impl->nav_mesh) { + return glm::vec3(0.f); + } + + if (!m_impl->query) { + m_impl->query = dtAllocNavMeshQuery(); + m_impl->query->init(m_impl->nav_mesh, 4092 /*RuleI(Pathing, MaxNavmeshNodes)*/); + } + + dtQueryFilter filter; + filter.setIncludeFlags(65535U ^ 2048); + filter.setAreaCost(0, 1.0f); //Normal + filter.setAreaCost(1, 3.0f); //Water + filter.setAreaCost(2, 5.0f); //Lava + filter.setAreaCost(4, 1.0f); //PvP + filter.setAreaCost(5, 2.0f); //Slime + filter.setAreaCost(6, 2.0f); //Ice + filter.setAreaCost(7, 4.0f); //V Water (Frigid Water) + filter.setAreaCost(8, 1.0f); //General Area + filter.setAreaCost(9, 0.1f); //Portal + filter.setAreaCost(10, 0.1f); //Prefer + + dtPolyRef randomRef; + float point[3]; + + dtPolyRef start_ref; + glm::vec3 current_location(start.x, start.z, start.y); + glm::vec3 ext(5.0f, 100.0f, 5.0f); + + m_impl->query->findNearestPoly(¤t_location[0], &ext[0], &filter, &start_ref, 0); + + if (!start_ref) + { + return glm::vec3(0.f); + } + + if (dtStatusSucceed(m_impl->query->findRandomPointAroundCircle(start_ref, ¤t_location[0], 100.f, &filter, []() { return MakeRandomFloat(0.0,1.0); /*(float)zone->random.Real(0.0, 1.0);*/ }, &randomRef, point))) + { + return glm::vec3(point[0], point[2], point[1]); + } + + return glm::vec3(0.f); +} + +void PathfinderNavmesh::Clear() +{ + if (m_impl->nav_mesh) { + dtFreeNavMesh(m_impl->nav_mesh); + } + + if (m_impl->query) { + dtFreeNavMeshQuery(m_impl->query); + } +} + +void PathfinderNavmesh::Load(const std::string &path) +{ + Clear(); + + FILE *f = fopen(path.c_str(), "rb"); + if (f) { + NavMeshSetHeader header; + size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, f); + if (readLen != 1) + { + fclose(f); + return; + } + if (header.magic != NAVMESHSET_MAGIC) + { + fclose(f); + return; + } + if (header.version != NAVMESHSET_VERSION) + { + fclose(f); + return; + } + + dtNavMesh* mesh = dtAllocNavMesh(); + if (!mesh) + { + fclose(f); + return; + } + dtStatus status = mesh->init(&header.params); + if (dtStatusFailed(status)) + { + fclose(f); + return; + } + + // Read tiles. + for (int i = 0; i < header.numTiles; ++i) + { + NavMeshTileHeader tileHeader; + readLen = fread(&tileHeader, sizeof(tileHeader), 1, f); + if (readLen != 1) + { + fclose(f); + return; + } + + if (!tileHeader.tileRef || !tileHeader.dataSize) + break; + + unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM); + if (!data) break; + memset(data, 0, tileHeader.dataSize); + readLen = fread(data, tileHeader.dataSize, 1, f); + if (readLen != 1) + { + dtFree(data); + fclose(f); + return; + } + + mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0); + } + + m_impl->nav_mesh = mesh; + + LogWrite(MAP__INFO, 7, "Map", "Loaded Navmesh File [{%s}]", path.c_str()); + } +} + +void PathfinderNavmesh::ShowPath(Client * c, const glm::vec3 &start, const glm::vec3 &end) +{ +/* auto &list = entity_list.GetNPCList(); + + for (auto &iter : list) { + auto npc = iter.second; + auto name = npc->GetName(); + + if (strstr(name, "PathNode") != nullptr) { + npc->Depop(); + } + } + + PathfinderOptions opts; + opts.smooth_path = true; + opts.step_size = RuleR(Pathing, NavmeshStepSize); + bool partial = false; + bool stuck = false; + auto path = FindPath(start, end, partial, stuck, opts); + + for (auto &node : path) { + if (!node.teleport) { + NPC::SpawnNPC("PathNode 2253 1 0 1 2 1", glm::vec4(node.pos, 1.0)); + } + }*/ +} + +dtStatus PathfinderNavmesh::GetPolyHeightNoConnections(dtPolyRef ref, const float *pos, float *height) const +{ + auto *m_nav = m_impl->nav_mesh; + + if (!m_nav) { + return DT_FAILURE; + } + + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) { + return DT_FAILURE | DT_INVALID_PARAM; + } + + if (poly->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) { + const unsigned int ip = (unsigned int)(poly - tile->polys); + const dtPolyDetail* pd = &tile->detailMeshes[ip]; + for (int j = 0; j < pd->triCount; ++j) + { + const unsigned char* t = &tile->detailTris[(pd->triBase + j) * 4]; + const float* v[3]; + for (int k = 0; k < 3; ++k) + { + if (t[k] < poly->vertCount) + v[k] = &tile->verts[poly->verts[t[k]] * 3]; + else + v[k] = &tile->detailVerts[(pd->vertBase + (t[k] - poly->vertCount)) * 3]; + } + float h; + if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h)) + { + if (height) + *height = h; + return DT_SUCCESS; + } + } + } + + return DT_FAILURE | DT_INVALID_PARAM; +} + +dtStatus PathfinderNavmesh::GetPolyHeightOnPath(const dtPolyRef *path, const int path_len, const glm::vec3 &pos, float *h) const +{ + if (!path || !path_len) { + return DT_FAILURE; + } + + for (int i = 0; i < path_len; ++i) { + dtPolyRef ref = path[i]; + + if (dtStatusSucceed(GetPolyHeightNoConnections(ref, &pos[0], h))) { + return DT_SUCCESS; + } + } + + return DT_FAILURE; +} diff --git a/source/WorldServer/Zone/pathfinder_nav_mesh.h b/source/WorldServer/Zone/pathfinder_nav_mesh.h new file mode 100644 index 0000000..889144f --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_nav_mesh.h @@ -0,0 +1,43 @@ +#pragma once + +#include "pathfinder_interface.h" +#include +#include + +static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET'; +static const int NAVMESHSET_VERSION = 1; + +struct NavMeshSetHeader +{ + int magic; + int version; + int numTiles; + dtNavMeshParams params; +}; + +struct NavMeshTileHeader +{ + dtTileRef tileRef; + int dataSize; +}; + +class PathfinderNavmesh : public IPathfinder +{ +public: + PathfinderNavmesh(const std::string &path); + virtual ~PathfinderNavmesh(); + + virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled); + virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts); + virtual glm::vec3 GetRandomLocation(const glm::vec3 &start); + +private: + void Clear(); + void Load(const std::string &path); + void ShowPath(Client *c, const glm::vec3 &start, const glm::vec3 &end); + dtStatus GetPolyHeightNoConnections(dtPolyRef ref, const float *pos, float *height) const; + dtStatus GetPolyHeightOnPath(const dtPolyRef *path, const int path_len, const glm::vec3 &pos, float *h) const; + + struct Implementation; + std::unique_ptr m_impl; +}; diff --git a/source/WorldServer/Zone/pathfinder_null.cpp b/source/WorldServer/Zone/pathfinder_null.cpp new file mode 100644 index 0000000..35d0a13 --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_null.cpp @@ -0,0 +1,26 @@ +#include "pathfinder_null.h" + +IPathfinder::IPath PathfinderNull::FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags) +{ + partial = false; + stuck = false; + IPath ret; + ret.push_back(start); + ret.push_back(end); + return ret; +} + +IPathfinder::IPath PathfinderNull::FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions &opts) +{ + partial = false; + stuck = false; + IPath ret; + ret.push_back(start); + ret.push_back(end); + return ret; +} + +glm::vec3 PathfinderNull::GetRandomLocation(const glm::vec3 &start) +{ + return glm::vec3(0.0f); +} diff --git a/source/WorldServer/Zone/pathfinder_null.h b/source/WorldServer/Zone/pathfinder_null.h new file mode 100644 index 0000000..f451fd4 --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_null.h @@ -0,0 +1,14 @@ +#pragma once + +#include "pathfinder_interface.h" + +class PathfinderNull : public IPathfinder +{ +public: + PathfinderNull() { } + virtual ~PathfinderNull() { } + + virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled); + virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts); + virtual glm::vec3 GetRandomLocation(const glm::vec3 &start); +}; diff --git a/source/WorldServer/Zone/pathfinder_waypoint.cpp b/source/WorldServer/Zone/pathfinder_waypoint.cpp new file mode 100644 index 0000000..56dab85 --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_waypoint.cpp @@ -0,0 +1,517 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pathfinder_waypoint.h" +#include "../zoneserver.h" +#include "../client.h" + +#pragma pack(1) +struct NeighbourNode { + int16 id; + float distance; + uint8 Teleport; + int16 DoorID; +}; + +struct PathNode { + uint16 id; + glm::vec3 v; + float bestz; + NeighbourNode Neighbours[50]; +}; + +struct PathFileHeader { + uint32 version; + uint32 PathNodeCount; +}; +#pragma pack() + +struct Edge +{ + float distance; + bool teleport; + int door_id; +}; + +struct Node +{ + int id; + glm::vec3 v; + float bestz; + std::map edges; +}; + +template +class distance_heuristic : public boost::astar_heuristic +{ +public: + typedef typename boost::graph_traits::vertex_descriptor Vertex; + + distance_heuristic(NodeMap n, Vertex goal) + : m_node(n), m_goal(goal) {} + CostType operator()(Vertex u) + { + CostType dx = m_node[m_goal].v.x - m_node[u].v.x; + CostType dy = m_node[m_goal].v.y - m_node[u].v.y; + CostType dz = m_node[m_goal].v.z - m_node[u].v.z; + return ::sqrt(dx * dx + dy * dy + dz * dz); + } +private: + NodeMap m_node; + Vertex m_goal; +}; + +struct found_goal {}; +template +class astar_goal_visitor : public boost::default_astar_visitor +{ +public: + astar_goal_visitor(Vertex goal) : m_goal(goal) {} + template + void examine_vertex(Vertex u, Graph& g) { + if (u == m_goal) + throw found_goal(); + } +private: + Vertex m_goal; +}; + +typedef boost::geometry::model::point Point; +typedef std::pair RTreeValue; +typedef boost::adjacency_list> GraphType; +typedef boost::property_map::type WeightMap; + +struct PathfinderWaypoint::Implementation { + bool PathFileValid; + boost::geometry::index::rtree> Tree; + GraphType Graph; + std::vector Nodes; + std::string FileName; +}; + +PathfinderWaypoint::PathfinderWaypoint(const std::string &path) +{ + m_impl.reset(new Implementation()); + m_impl->PathFileValid = false; + m_impl->FileName = path; + Load(path); +} + +PathfinderWaypoint::~PathfinderWaypoint() +{ +} + +IPathfinder::IPath PathfinderWaypoint::FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags) +{ + stuck = false; + partial = false; + std::vector result_start_n; + m_impl->Tree.query(boost::geometry::index::nearest(Point(start.x, start.y, start.z), 1), std::back_inserter(result_start_n)); + if (result_start_n.size() == 0) { + return IPath(); + } + + std::vector result_end_n; + m_impl->Tree.query(boost::geometry::index::nearest(Point(end.x, end.y, end.z), 1), std::back_inserter(result_end_n)); + if (result_end_n.size() == 0) { + return IPath(); + } + + auto &nearest_start = *result_start_n.begin(); + auto &nearest_end = *result_end_n.begin(); + + if (nearest_start.second == nearest_end.second) { + IPath Route; + Route.push_back(start); + Route.push_back(end); + return Route; + } + + std::vector p(boost::num_vertices(m_impl->Graph)); + try { + boost::astar_search(m_impl->Graph, nearest_start.second, + distance_heuristic(&m_impl->Nodes[0], nearest_end.second), + boost::predecessor_map(&p[0]) + .visitor(astar_goal_visitor(nearest_end.second))); + } + catch (found_goal) + { + IPath Route; + + Route.push_front(end); + for (size_t v = nearest_end.second;; v = p[v]) { + if (p[v] == v) { + Route.push_front(m_impl->Nodes[v].v); + break; + } + else { + auto &node = m_impl->Nodes[v]; + + auto iter = node.edges.find((int)p[v + 1]); + if (iter != node.edges.end()) { + auto &edge = iter->second; + if (edge.teleport) { + Route.push_front(m_impl->Nodes[v].v); + Route.push_front(true); + } + else { + Route.push_front(m_impl->Nodes[v].v); + } + } + else { + Route.push_front(m_impl->Nodes[v].v); + } + } + } + + Route.push_front(start); + return Route; + } + + return IPath(); +} + +glm::vec3 PathfinderWaypoint::GetRandomLocation(const glm::vec3 &start) +{ + if (m_impl->Nodes.size() > 0) { + auto idx = MakeRandomInt(0, (int)m_impl->Nodes.size() - 1);// zone->random.Int(0, (int)m_impl->Nodes.size() - 1); + auto &node = m_impl->Nodes[idx]; + + return node.v; + } + + return glm::vec3(); +} + +void PathfinderWaypoint::Load(const std::string &filename) { + PathFileHeader Head; + Head.PathNodeCount = 0; + Head.version = 2; + + FILE *f = fopen(filename.c_str(), "rb"); + if (f) { + char Magic[10]; + + fread(&Magic, 9, 1, f); + + if (strncmp(Magic, "EQEMUPATH", 9)) + { + //LogError("Bad Magic String in .path file"); + fclose(f); + return; + } + + fread(&Head, sizeof(Head), 1, f); + + /*LogInfo("Path File Header: Version [{}], PathNodes [{}]", + (long)Head.version, (long)Head.PathNodeCount); + */ + if (Head.version == 2) + { + LoadV2(f, Head); + return; + } + else if (Head.version == 3) { + LoadV3(f, Head); + return; + } + else { + //LogError("Unsupported path file version"); + fclose(f); + return; + } + } +} + +void PathfinderWaypoint::LoadV2(FILE *f, const PathFileHeader &header) +{ + std::unique_ptr PathNodes(new PathNode[header.PathNodeCount]); + + fread(PathNodes.get(), sizeof(PathNode), header.PathNodeCount, f); + + int MaxNodeID = header.PathNodeCount - 1; + + m_impl->PathFileValid = true; + + m_impl->Nodes.reserve(header.PathNodeCount); + for (uint32 i = 0; i < header.PathNodeCount; ++i) + { + auto &n = PathNodes[i]; + Node node; + node.id = i; + node.v = n.v; + node.bestz = n.bestz; + m_impl->Nodes.push_back(node); + } + + auto weightmap = boost::get(boost::edge_weight, m_impl->Graph); + for (uint32 i = 0; i < header.PathNodeCount; ++i) { + for (uint32 j = 0; j < 50; ++j) + { + auto &node = m_impl->Nodes[i]; + if (PathNodes[i].Neighbours[j].id > MaxNodeID) + { + /*LogError("Path Node [{}], Neighbour [{}] ([{}]) out of range", i, j, PathNodes[i].Neighbours[j].id); + m_impl->PathFileValid = false;*/ + } + + if (PathNodes[i].Neighbours[j].id > 0) { + Edge edge; + edge.distance = PathNodes[i].Neighbours[j].distance; + edge.door_id = PathNodes[i].Neighbours[j].DoorID; + edge.teleport = PathNodes[i].Neighbours[j].Teleport; + + node.edges[PathNodes[i].Neighbours[j].id] = edge; + } + } + } + + BuildGraph(); + fclose(f); +} + +void PathfinderWaypoint::LoadV3(FILE *f, const PathFileHeader &header) +{ + m_impl->Nodes.reserve(header.PathNodeCount); + + uint32 edge_count = 0; + fread(&edge_count, sizeof(uint32), 1, f); + + for (uint32 i = 0; i < header.PathNodeCount; ++i) + { + uint32 id = 0; + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float best_z = 0.0f; + + fread(&id, sizeof(uint32), 1, f); + fread(&x, sizeof(float), 1, f); + fread(&y, sizeof(float), 1, f); + fread(&z, sizeof(float), 1, f); + fread(&best_z, sizeof(float), 1, f); + + Node n; + n.id = id; + n.bestz = best_z; + n.v.x = x; + n.v.y = y; + n.v.z = z; + + m_impl->Nodes.push_back(n); + } + + for (uint32 j = 0; j < edge_count; ++j) { + uint32 from = 0; + uint32 to = 0; + int8 teleport = 0; + float distance = 0.0f; + int32 door_id = 0; + + fread(&from, sizeof(uint32), 1, f); + fread(&to, sizeof(uint32), 1, f); + fread(&teleport, sizeof(int8), 1, f); + fread(&distance, sizeof(float), 1, f); + fread(&door_id, sizeof(int32), 1, f); + + Edge e; + e.teleport = teleport > 0 ? true : false; + e.distance = distance; + e.door_id = door_id; + + auto &n = m_impl->Nodes[from]; + n.edges[to] = e; + } + + m_impl->PathFileValid = true; + + BuildGraph(); + fclose(f); +} + +void PathfinderWaypoint::ShowNodes() +{ + for (size_t i = 0; i < m_impl->Nodes.size(); ++i) + { + ShowNode(m_impl->Nodes[i]); + } +} + +void PathfinderWaypoint::ShowPath(Client *c, const glm::vec3 &start, const glm::vec3 &end) +{ + bool partial = false; + bool stuck = false; + auto path = FindRoute(start, end, partial, stuck); + std::vector points; + + FindPerson_Point p; + for (auto &node : path) + { + if (!node.teleport) { + p.x = node.pos.x; + p.y = node.pos.y; + p.z = node.pos.z; + + points.push_back(p); + } + } + +// c->SendPathPacket(points); +} + +void PathfinderWaypoint::NodeInfo(Client *c) +{ + if (!c->GetPlayer()->GetTarget()) { + return; + } + + auto node = FindPathNodeByCoordinates(c->GetPlayer()->GetTarget()->GetX(), c->GetPlayer()->GetTarget()->GetZ(), c->GetPlayer()->GetTarget()->GetY()); + if (node == nullptr) { + return; + } + +/* c->Message(Chat::White, "Pathing node: %i at (%.2f, %.2f, %.2f) with bestz %.2f", + node->id, node->v.x, node->v.y, node->v.z, node->bestz); + + for (auto &edge : node->edges) { + c->Message(Chat::White, "id: %i, distance: %.2f, door id: %i, is teleport: %i", + edge.first, + edge.second.distance, + edge.second.door_id, + edge.second.teleport); + }*/ +} + +Node *PathfinderWaypoint::FindPathNodeByCoordinates(float x, float y, float z) +{ + for (auto &node : m_impl->Nodes) { + auto dist = Distance(glm::vec3(x, y, z), node.v); + + if (dist < 0.1) { + return &node; + } + } + + return nullptr; +} + +void PathfinderWaypoint::BuildGraph() +{ + m_impl->Graph = GraphType(); + m_impl->Tree = boost::geometry::index::rtree>(); + + for (auto &node : m_impl->Nodes) { + RTreeValue rtv; + rtv.first = Point(node.v.x, node.v.y, node.v.z); + rtv.second = node.id; + m_impl->Tree.insert(rtv); + boost::add_vertex(m_impl->Graph); + } + + //Populate edges now that we've created all the nodes + auto weightmap = boost::get(boost::edge_weight, m_impl->Graph); + for (auto &node : m_impl->Nodes) { + for (auto &edge : node.edges) { + GraphType::edge_descriptor e; + bool inserted; + boost::tie(e, inserted) = boost::add_edge(node.id, edge.first, m_impl->Graph); + weightmap[e] = edge.second.distance; + } + } +} + +std::string DigitToWord(int i) +{ + std::string digit = std::to_string(i); + std::string ret; + for (size_t idx = 0; idx < digit.length(); ++idx) { + if (!ret.empty()) { + ret += "_"; + } + + switch (digit[idx]) { + case '0': + ret += "Zero"; + break; + case '1': + ret += "One"; + break; + case '2': + ret += "Two"; + break; + case '3': + ret += "Three"; + break; + case '4': + ret += "Four"; + break; + case '5': + ret += "Five"; + break; + case '6': + ret += "Six"; + break; + case '7': + ret += "Seven"; + break; + case '8': + ret += "Eight"; + break; + case '9': + ret += "Nine"; + break; + default: + break; + } + } + + return ret; +} + +void PathfinderWaypoint::ShowNode(const Node &n) { +/* auto npc_type = new NPCType; + memset(npc_type, 0, sizeof(NPCType)); + + sprintf(npc_type->name, "%s", DigitToWord(n.id).c_str()); + sprintf(npc_type->lastname, "%i", n.id); + npc_type->current_hp = 4000000; + npc_type->max_hp = 4000000; + npc_type->race = 2254; + npc_type->gender = 2; + npc_type->class_ = 9; + npc_type->deity = 1; + npc_type->level = 75; + npc_type->npc_id = 0; + npc_type->loottable_id = 0; + npc_type->texture = 1; + npc_type->light = 0; + npc_type->runspeed = 0; + npc_type->d_melee_texture1 = 1; + npc_type->d_melee_texture2 = 1; + npc_type->merchanttype = 1; + npc_type->bodytype = 1; + npc_type->show_name = true; + + npc_type->STR = 150; + npc_type->STA = 150; + npc_type->DEX = 150; + npc_type->AGI = 150; + npc_type->INT = 150; + npc_type->WIS = 150; + npc_type->CHA = 150; + + npc_type->findable = 1; + auto position = glm::vec4(n.v.x, n.v.y, n.v.z, 0.0f); + auto npc = new NPC(npc_type, nullptr, position, GravityBehavior::Flying); + npc->GiveNPCTypeData(npc_type); + + entity_list.AddNPC(npc, true, true);*/ +} diff --git a/source/WorldServer/Zone/pathfinder_waypoint.h b/source/WorldServer/Zone/pathfinder_waypoint.h new file mode 100644 index 0000000..0bca53a --- /dev/null +++ b/source/WorldServer/Zone/pathfinder_waypoint.h @@ -0,0 +1,36 @@ +#pragma once + +#include "pathfinder_interface.h" + +struct PathFileHeader; +struct Node; + +struct FindPerson_Point { + float y; + float x; + float z; +}; + +class PathfinderWaypoint : public IPathfinder +{ +public: + PathfinderWaypoint(const std::string &path); + virtual ~PathfinderWaypoint(); + + virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled); + virtual glm::vec3 GetRandomLocation(const glm::vec3 &start); + +private: + void Load(const std::string &filename); + void LoadV2(FILE *f, const PathFileHeader &header); + void LoadV3(FILE *f, const PathFileHeader &header); + void ShowNodes(); + void ShowPath(Client *c, const glm::vec3 &start, const glm::vec3 &end); + void NodeInfo(Client *c); + Node *FindPathNodeByCoordinates(float x, float y, float z); + void BuildGraph(); + void ShowNode(const Node &n); + + struct Implementation; + std::unique_ptr m_impl; +}; diff --git a/source/WorldServer/Zone/position.cpp b/source/WorldServer/Zone/position.cpp new file mode 100644 index 0000000..dfaf2fe --- /dev/null +++ b/source/WorldServer/Zone/position.cpp @@ -0,0 +1,255 @@ +#include "position.h" + +#include +#include +#include +#include "../../common/string_util.h" + +static const float position_eps = 0.0001f; + +std::string to_string(const glm::vec4 &position) { + return StringFormat("(%.3f, %.3f, %.3f, %.3f)", position.x,position.y,position.z,position.w); +} + +std::string to_string(const glm::vec3 &position){ + return StringFormat("(%.3f, %.3f, %.3f)", position.x,position.y,position.z); +} + +std::string to_string(const glm::vec2 &position){ + return StringFormat("(%.3f, %.3f)", position.x,position.y); +} + +bool IsOrigin(const glm::vec2 &position) { + return glm::dot(position, position) == 0; +} + +bool IsOrigin(const glm::vec3 &position) { + return glm::dot(position, position) == 0; +} + +bool IsOrigin(const glm::vec4 &position) { + return IsOrigin(glm::vec3(position)); +} + +/** +* Produces the non square root'ed distance between the two points within the XY plane. +*/ +float DistanceSquared(const glm::vec2& point1, const glm::vec2& point2) { + auto diff = point1 - point2; + return glm::dot(diff, diff); +} + +/** +* Produces the distance between the two points on the XY plane. +*/ +float Distance(const glm::vec2& point1, const glm::vec2& point2) { + return std::sqrt(DistanceSquared(point1, point2)); +} + +/** +* Produces the non square root'ed distance between the two points. +*/ +float DistanceSquared(const glm::vec3& point1, const glm::vec3& point2) { + auto diff = point1 - point2; + return glm::dot(diff, diff); +} + +/** +* Produces the non square root'ed distance between the two points. +*/ +float DistanceSquared(const glm::vec4& point1, const glm::vec4& point2) { + return DistanceSquared(static_cast(point1), static_cast(point2)); +} + +/** +* Produces the distance between the two points. +*/ +float Distance(const glm::vec3& point1, const glm::vec3& point2) { + return std::sqrt(DistanceSquared(point1, point2)); +} + +/** +* Produces the distance between the two points. +*/ +float Distance(const glm::vec4& point1, const glm::vec4& point2) { + return Distance(static_cast(point1), static_cast(point2)); +} + +/** +* Produces the distance between the two points within the XY plane. +*/ +float DistanceNoZ(const glm::vec3& point1, const glm::vec3& point2) { + return Distance(static_cast(point1),static_cast(point2)); +} + +/** +* Produces the distance between the two points within the XY plane. +*/ +float DistanceNoZ(const glm::vec4& point1, const glm::vec4& point2) { + return Distance(static_cast(point1),static_cast(point2)); +} + +/** +* Produces the non square root'ed distance between the two points within the XY plane. +*/ +float DistanceSquaredNoZ(const glm::vec3& point1, const glm::vec3& point2) { + return DistanceSquared(static_cast(point1),static_cast(point2)); +} + +/** +* Produces the non square root'ed distance between the two points within the XY plane. +*/ +float DistanceSquaredNoZ(const glm::vec4& point1, const glm::vec4& point2) { + return DistanceSquared(static_cast(point1),static_cast(point2)); +} + +/** +* Determines if 'position' is within (inclusive) the axis aligned +* box (3 dimensional) formed from the points minimum and maximum. +*/ +bool IsWithinAxisAlignedBox(const glm::vec3 &position, const glm::vec3 &minimum, const glm::vec3 &maximum) { + auto actualMinimum = glm::vec3(std::min(minimum.x, maximum.x), std::min(minimum.y, maximum.y),std::min(minimum.z, maximum.z)); + auto actualMaximum = glm::vec3(std::max(minimum.x, maximum.x), std::max(minimum.y, maximum.y),std::max(minimum.z, maximum.z)); + + bool xcheck = position.x >= actualMinimum.x && position.x <= actualMaximum.x; + bool ycheck = position.y >= actualMinimum.y && position.y <= actualMaximum.y; + bool zcheck = position.z >= actualMinimum.z && position.z <= actualMaximum.z; + + return xcheck && ycheck && zcheck; +} + +/** +* Determines if 'position' is within (inclusive) the axis aligned +* box (2 dimensional) formed from the points minimum and maximum. +*/ +bool IsWithinAxisAlignedBox(const glm::vec2 &position, const glm::vec2 &minimum, const glm::vec2 &maximum) { + auto actualMinimum = glm::vec2(std::min(minimum.x, maximum.x), std::min(minimum.y, maximum.y)); + auto actualMaximum = glm::vec2(std::max(minimum.x, maximum.x), std::max(minimum.y, maximum.y)); + + bool xcheck = position.x >= actualMinimum.x && position.x <= actualMaximum.x; + bool ycheck = position.y >= actualMinimum.y && position.y <= actualMaximum.y; + + return xcheck && ycheck; +} + +/** +* Gives the heading directly 180 degrees from the +* current heading. +* Takes the EQfloat from the glm::vec4 and returns +* an EQFloat. +*/ +float GetReciprocalHeading(const glm::vec4& point1) { + return GetReciprocalHeading(point1.w); +} + +/** +* Gives the heading directly 180 degrees from the +* current heading. +* Takes an EQfloat and returns an EQFloat. +*/ +float GetReciprocalHeading(const float heading) +{ + float result = 0; + + // Convert to radians + float h = (heading / 512.0f) * 6.283184f; + + // Calculate the reciprocal heading in radians + result = h + 3.141592f; + + // Convert back to eq heading from radians + result = (result / 6.283184f) * 512.0f; + + return result; +} + +bool IsHeadingEqual(const float h1, const float h2) +{ + return std::abs(h2 - h1) < 0.01f; +} + +bool IsPositionEqual(const glm::vec2 &p1, const glm::vec2 &p2) +{ + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps; +} + +bool IsPositionEqual(const glm::vec3 &p1, const glm::vec3 &p2) +{ + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps && std::abs(p1.z - p2.z) < position_eps; +} + +bool IsPositionEqual(const glm::vec4 &p1, const glm::vec4 &p2) +{ + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps && std::abs(p1.z - p2.z) < position_eps; +} + +bool IsPositionEqualWithinCertainZ(const glm::vec3 &p1, const glm::vec3 &p2, float z_eps) { + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps && std::abs(p1.z - p2.z) < z_eps; +} + +bool IsPositionEqualWithinCertainZ(const glm::vec4 &p1, const glm::vec4 &p2, float z_eps) { + return std::abs(p1.x - p2.x) < position_eps && std::abs(p1.y - p2.y) < position_eps && std::abs(p1.z - p2.z) < z_eps; +} + +bool IsPositionWithinSimpleCylinder(const glm::vec3 &p1, const glm::vec3 &cylinder_center, float cylinder_radius, float cylinder_height) +{ + //If we're outside the height of cylinder then we're not in it (duh) + auto d = std::abs(p1.z - cylinder_center.z); + if (d > cylinder_height / 2.0) { + return false; + } + + glm::vec2 p1d(p1.x, p1.y); + glm::vec2 ccd(cylinder_center.x, cylinder_center.y); + + //If we're outside the radius of the cylinder then we're not in it (also duh) + d = Distance(p1d, ccd); + if (d > cylinder_radius) { + return false; + } + + return true; +} + +bool IsPositionWithinSimpleCylinder(const glm::vec4 &p1, const glm::vec4 &cylinder_center, float cylinder_radius, float cylinder_height) +{ + //If we're outside the height of cylinder then we're not in it (duh) + auto d = std::abs(p1.z - cylinder_center.z); + if (d > cylinder_height / 2.0) { + return false; + } + + glm::vec2 p1d(p1.x, p1.y); + glm::vec2 ccd(cylinder_center.x, cylinder_center.y); + + //If we're outside the radius of the cylinder then we're not in it (also duh) + d = Distance(p1d, ccd); + if (d > cylinder_radius) { + return false; + } + + return true; +} + +float CalculateHeadingAngleBetweenPositions(float x1, float y1, float x2, float y2) +{ + float y_diff = std::abs(y1 - y2); + float x_diff = std::abs(x1 - x2); + if (y_diff < 0.0000009999999974752427) + y_diff = 0.0000009999999974752427; + + float angle = atan2(x_diff, y_diff) * 180.0f * 0.3183099014828645f; // angle, nice "pi" + + // return the right thing based on relative quadrant + // I'm sure this could be improved for readability, but whatever + if (y1 >= y2) { + if (x2 >= x1) + return (90.0f - angle + 90.0f) * 511.5f * 0.0027777778f; + if (x2 <= x1) + return (angle + 180.0f) * 511.5f * 0.0027777778f; + } + if (y1 > y2 || x2 > x1) + return angle * 511.5f * 0.0027777778f; + else + return (90.0f - angle + 270.0f) * 511.5f * 0.0027777778f; +} diff --git a/source/WorldServer/Zone/position.h b/source/WorldServer/Zone/position.h new file mode 100644 index 0000000..3cd4bbe --- /dev/null +++ b/source/WorldServer/Zone/position.h @@ -0,0 +1,67 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY except by those people which sell it, which + are required to give you total support for your newly bought product; + without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef POSITION_H +#define POSITION_H + +#include +#include "../../depends/glm/vec2.hpp" + +#include "../../depends/glm/vec3.hpp" +#include "../../depends/glm/vec4.hpp" +#include "../../depends/glm/geometric.hpp" + +std::string to_string(const glm::vec4 &position); +std::string to_string(const glm::vec3 &position); +std::string to_string(const glm::vec2 &position); + +bool IsWithinAxisAlignedBox(const glm::vec3 &position, const glm::vec3 &minimum, const glm::vec3 &maximum); +bool IsWithinAxisAlignedBox(const glm::vec2 &position, const glm::vec2 &minimum, const glm::vec2 &maximum); + +bool IsOrigin(const glm::vec2 &position); +bool IsOrigin(const glm::vec3 &position); +bool IsOrigin(const glm::vec4 &position); + +float DistanceSquared(const glm::vec2& point1, const glm::vec2& point2); +float Distance(const glm::vec2& point1, const glm::vec2& point2); +float DistanceSquared(const glm::vec3& point1, const glm::vec3& point2); +float Distance(const glm::vec3& point1, const glm::vec3& point2); +float DistanceNoZ(const glm::vec3& point1, const glm::vec3& point2); +float DistanceSquaredNoZ(const glm::vec3& point1, const glm::vec3& point2); + +float DistanceSquared(const glm::vec4& point1, const glm::vec4& point2); +float Distance(const glm::vec4& point1, const glm::vec4& point2); +float DistanceNoZ(const glm::vec4& point1, const glm::vec4& point2); +float DistanceSquaredNoZ(const glm::vec4& point1, const glm::vec4& point2); + +float GetReciprocalHeading(const glm::vec4& point1); +float GetReciprocalHeading(const float heading); + +bool IsHeadingEqual(const float h1, const float h2); + +bool IsPositionEqual(const glm::vec2 &p1, const glm::vec2 &p2); +bool IsPositionEqual(const glm::vec3 &p1, const glm::vec3 &p2); +bool IsPositionEqual(const glm::vec4 &p1, const glm::vec4 &p2); +bool IsPositionEqualWithinCertainZ(const glm::vec3 &p1, const glm::vec3 &p2, float z_eps); +bool IsPositionEqualWithinCertainZ(const glm::vec4 &p1, const glm::vec4 &p2, float z_eps); + +bool IsPositionWithinSimpleCylinder(const glm::vec3 &p1, const glm::vec3 &cylinder_center, float cylinder_radius, float cylinder_height); +bool IsPositionWithinSimpleCylinder(const glm::vec4 &p1, const glm::vec4 &cylinder_center, float cylinder_radius, float cylinder_height); + +float CalculateHeadingAngleBetweenPositions(float x1, float y1, float x2, float y2); + +#endif diff --git a/source/WorldServer/Zone/raycast_mesh.cpp b/source/WorldServer/Zone/raycast_mesh.cpp new file mode 100644 index 0000000..ba2f698 --- /dev/null +++ b/source/WorldServer/Zone/raycast_mesh.cpp @@ -0,0 +1,973 @@ +#include "raycast_mesh.h" +#include +#include +#include +#include +#include +#include +#include + +// This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do +// high-speed raycasting. +// +// There are much better implementations of this available on the internet. In particular I recommend that you use +// OPCODE written by Pierre Terdiman. +// @see: http://www.codercorner.com/Opcode.htm +// +// OPCODE does a whole lot more than just raycasting, and is a rather significant amount of source code. +// +// I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting. +// I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However, +// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders +// of magnitude; so this implementation should work fine for simple tools and utilities. +// +// It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees. +// AABB = Axis Aligned Bounding Volume trees. +// +// http://www.cgal.org/Manual/3.5/doc_html/cgal_manual/AABB_tree/Chapter_main.html +// +// +// This code snippet was written by John W. Ratcliff on August 18, 2011 and released under the MIT. license. +// +// mailto:jratcliffscarab@gmail.com +// +// The official source can be found at: http://code.google.com/p/raycastmesh/ +// +// + +#pragma warning(disable:4100) + +namespace RAYCAST_MESH +{ + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** +* A method to compute a ray-AABB intersection. +* Original code by Andrew Woo, from "Graphics Gems", Academic Press, 1990 +* Optimized code by Pierre Terdiman, 2000 (~20-30% faster on my Celeron 500) +* Epsilon value added by Klaus Hartmann. (discarding it saves a few cycles only) +* +* Hence this version is faster as well as more robust than the original one. +* +* Should work provided: +* 1) the integer representation of 0.0f is 0x00000000 +* 2) the sign bit of the RmReal is the most significant one +* +* Report bugs: p.terdiman@codercorner.com +* +* \param aabb [in] the axis-aligned bounding box +* \param origin [in] ray origin +* \param dir [in] ray direction +* \param coord [out] impact coordinates +* \return true if ray intersects AABB +*/ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define RAYAABB_EPSILON 0.00001f +//! Integer representation of a RmRealing-point value. +#define IR(x) ((RmUint32&)x) + +bool intersectRayAABB(const RmReal MinB[3],const RmReal MaxB[3],const RmReal origin[3],const RmReal dir[3],RmReal coord[3]) +{ + bool Inside = true; + RmReal MaxT[3]; + MaxT[0]=MaxT[1]=MaxT[2]=-1.0f; + + // Find candidate planes. + for(RmUint32 i=0;i<3;i++) + { + if(origin[i] < MinB[i]) + { + coord[i] = MinB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir[i])) MaxT[i] = (MinB[i] - origin[i]) / dir[i]; + } + else if(origin[i] > MaxB[i]) + { + coord[i] = MaxB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir[i])) MaxT[i] = (MaxB[i] - origin[i]) / dir[i]; + } + } + + // Ray origin inside bounding box + if(Inside) + { + coord[0] = origin[0]; + coord[1] = origin[1]; + coord[2] = origin[2]; + return true; + } + + // Get largest of the maxT's for final choice of intersection + RmUint32 WhichPlane = 0; + if(MaxT[1] > MaxT[WhichPlane]) WhichPlane = 1; + if(MaxT[2] > MaxT[WhichPlane]) WhichPlane = 2; + + // Check final candidate actually inside box + if(IR(MaxT[WhichPlane])&0x80000000) return false; + + for(RmUint32 i=0;i<3;i++) + { + if(i!=WhichPlane) + { + coord[i] = origin[i] + MaxT[WhichPlane] * dir[i]; + #ifdef RAYAABB_EPSILON + if(coord[i] < MinB[i] - RAYAABB_EPSILON || coord[i] > MaxB[i] + RAYAABB_EPSILON) return false; + #else + if(coord[i] < MinB[i] || coord[i] > MaxB[i]) return false; + #endif + } + } + return true; // ray hits box +} + + + + +bool intersectLineSegmentAABB(const RmReal bmin[3],const RmReal bmax[3],const RmReal p1[3],const RmReal dir[3],RmReal &dist,RmReal intersect[3]) +{ + bool ret = false; + + if ( dist > RAYAABB_EPSILON ) + { + ret = intersectRayAABB(bmin,bmax,p1,dir,intersect); + if ( ret ) + { + RmReal dx = p1[0]-intersect[0]; + RmReal dy = p1[1]-intersect[1]; + RmReal dz = p1[2]-intersect[2]; + RmReal d = dx*dx+dy*dy+dz*dz; + if ( d < dist*dist ) + { + dist = sqrtf(d); + } + else + { + ret = false; + } + } + } + return ret; +} + +/* a = b - c */ +#define vector(a,b,c) \ + (a)[0] = (b)[0] - (c)[0]; \ + (a)[1] = (b)[1] - (c)[1]; \ + (a)[2] = (b)[2] - (c)[2]; + +#define innerProduct(v,q) \ + ((v)[0] * (q)[0] + \ + (v)[1] * (q)[1] + \ + (v)[2] * (q)[2]) + +#define crossProduct(a,b,c) \ + (a)[0] = (b)[1] * (c)[2] - (c)[1] * (b)[2]; \ + (a)[1] = (b)[2] * (c)[0] - (c)[2] * (b)[0]; \ + (a)[2] = (b)[0] * (c)[1] - (c)[0] * (b)[1]; + + +static inline bool rayIntersectsTriangle(const RmReal *p,const RmReal *d,const RmReal *v0,const RmReal *v1,const RmReal *v2,RmReal &t) +{ + RmReal e1[3],e2[3],h[3],s[3],q[3]; + RmReal a,f,u,v; + + vector(e1,v1,v0); + vector(e2,v2,v0); + crossProduct(h,d,e2); + a = innerProduct(e1,h); + + if (a > -0.00001 && a < 0.00001) + return(false); + + f = 1/a; + vector(s,p,v0); + u = f * (innerProduct(s,h)); + + if (u < 0.0 || u > 1.0) + return(false); + + crossProduct(q,s,e1); + v = f * innerProduct(d,q); + if (v < 0.0 || u + v > 1.0) + return(false); + // at this stage we can compute t to find out where + // the intersection point is on the line + t = f * innerProduct(e2,q); + if (t > 0) // ray intersection + return(true); + else // this means that there is a line intersection + // but not a ray intersection + return (false); +} + +static RmReal computePlane(const RmReal *A,const RmReal *B,const RmReal *C,RmReal *n) // returns D +{ + RmReal vx = (B[0] - C[0]); + RmReal vy = (B[1] - C[1]); + RmReal vz = (B[2] - C[2]); + + RmReal wx = (A[0] - B[0]); + RmReal wy = (A[1] - B[1]); + RmReal wz = (A[2] - B[2]); + + RmReal vw_x = vy * wz - vz * wy; + RmReal vw_y = vz * wx - vx * wz; + RmReal vw_z = vx * wy - vy * wx; + + RmReal mag = sqrt((vw_x * vw_x) + (vw_y * vw_y) + (vw_z * vw_z)); + + if ( mag < 0.000001f ) + { + mag = 0; + } + else + { + mag = 1.0f/mag; + } + + RmReal x = vw_x * mag; + RmReal y = vw_y * mag; + RmReal z = vw_z * mag; + + + RmReal D = 0.0f - ((x*A[0])+(y*A[1])+(z*A[2])); + + n[0] = x; + n[1] = y; + n[2] = z; + + return D; +} + + +#define TRI_EOF 0xFFFFFFFF + +enum AxisAABB +{ + AABB_XAXIS, + AABB_YAXIS, + AABB_ZAXIS +}; + +enum ClipCode +{ + CC_MINX = (1<<0), + CC_MAXX = (1<<1), + CC_MINY = (1<<2), + CC_MAXY = (1<<3), + CC_MINZ = (1<<4), + CC_MAXZ = (1<<5), +}; + + +class BoundsAABB +{ +public: + + + void setMin(const RmReal *v) + { + mMin[0] = v[0]; + mMin[1] = v[1]; + mMin[2] = v[2]; + } + + void setMax(const RmReal *v) + { + mMax[0] = v[0]; + mMax[1] = v[1]; + mMax[2] = v[2]; + } + + void setMin(RmReal x,RmReal y,RmReal z) + { + mMin[0] = x; + mMin[1] = y; + mMin[2] = z; + } + + void setMax(RmReal x,RmReal y,RmReal z) + { + mMax[0] = x; + mMax[1] = y; + mMax[2] = z; + } + + void include(const RmReal *v) + { + if ( v[0] < mMin[0] ) mMin[0] = v[0]; + if ( v[1] < mMin[1] ) mMin[1] = v[1]; + if ( v[2] < mMin[2] ) mMin[2] = v[2]; + + if ( v[0] > mMax[0] ) mMax[0] = v[0]; + if ( v[1] > mMax[1] ) mMax[1] = v[1]; + if ( v[2] > mMax[2] ) mMax[2] = v[2]; + } + + void getCenter(RmReal *center) const + { + center[0] = (mMin[0]+mMax[0])*0.5f; + center[1] = (mMin[1]+mMax[1])*0.5f; + center[2] = (mMin[2]+mMax[2])*0.5f; + } + + bool intersects(const BoundsAABB &b) const + { + if ((mMin[0] > b.mMax[0]) || (b.mMin[0] > mMax[0])) return false; + if ((mMin[1] > b.mMax[1]) || (b.mMin[1] > mMax[1])) return false; + if ((mMin[2] > b.mMax[2]) || (b.mMin[2] > mMax[2])) return false; + return true; + } + + bool containsTriangle(const RmReal *p1,const RmReal *p2,const RmReal *p3) const + { + BoundsAABB b; + b.setMin(p1); + b.setMax(p1); + b.include(p2); + b.include(p3); + return intersects(b); + } + + bool containsTriangleExact(const RmReal *p1,const RmReal *p2,const RmReal *p3,RmUint32 &orCode) const + { + bool ret = false; + + RmUint32 andCode; + orCode = getClipCode(p1,p2,p3,andCode); + if ( andCode == 0 ) + { + ret = true; + } + + return ret; + } + + inline RmUint32 getClipCode(const RmReal *p1,const RmReal *p2,const RmReal *p3,RmUint32 &andCode) const + { + andCode = 0xFFFFFFFF; + RmUint32 c1 = getClipCode(p1); + RmUint32 c2 = getClipCode(p2); + RmUint32 c3 = getClipCode(p3); + andCode&=c1; + andCode&=c2; + andCode&=c3; + return c1|c2|c3; + } + + inline RmUint32 getClipCode(const RmReal *p) const + { + RmUint32 ret = 0; + + if ( p[0] < mMin[0] ) + { + ret|=CC_MINX; + } + else if ( p[0] > mMax[0] ) + { + ret|=CC_MAXX; + } + + if ( p[1] < mMin[1] ) + { + ret|=CC_MINY; + } + else if ( p[1] > mMax[1] ) + { + ret|=CC_MAXY; + } + + if ( p[2] < mMin[2] ) + { + ret|=CC_MINZ; + } + else if ( p[2] > mMax[2] ) + { + ret|=CC_MAXZ; + } + + return ret; + } + + inline void clamp(const BoundsAABB &aabb) + { + if ( mMin[0] < aabb.mMin[0] ) mMin[0] = aabb.mMin[0]; + if ( mMin[1] < aabb.mMin[1] ) mMin[1] = aabb.mMin[1]; + if ( mMin[2] < aabb.mMin[2] ) mMin[2] = aabb.mMin[2]; + if ( mMax[0] > aabb.mMax[0] ) mMax[0] = aabb.mMax[0]; + if ( mMax[1] > aabb.mMax[1] ) mMax[1] = aabb.mMax[1]; + if ( mMax[2] > aabb.mMax[2] ) mMax[2] = aabb.mMax[2]; + } + + RmReal mMin[3]; + RmReal mMax[3]; +}; + + +class NodeAABB; + +class NodeInterface +{ +public: + virtual NodeAABB * getNode(void) = 0; + virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) = 0; +}; + + + + + + class NodeAABB + { + public: + NodeAABB(void) + { + mLeft = NULL; + mRight = NULL; + mLeafTriangleIndex= TRI_EOF; + } + + NodeAABB(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,RmUint32 *indices, + RmUint32 maxDepth, // Maximum recursion depth for the triangle mesh. + RmUint32 minLeafSize, // minimum triangles to treat as a 'leaf' node. + RmReal minAxisSize, + NodeInterface *callback, + TriVector &leafTriangles) // once a particular axis is less than this size, stop sub-dividing. + + { + mLeft = NULL; + mRight = NULL; + mLeafTriangleIndex = TRI_EOF; + TriVector triangles; + triangles.reserve(tcount); + for (RmUint32 i=0; i dx ) + { + axis = AABB_YAXIS; + laxis = dy; + } + + if ( dz > dx && dz > dy ) + { + axis = AABB_ZAXIS; + laxis = dz; + } + + RmUint32 count = triangles.size(); + + // if the number of triangles is less than the minimum allowed for a leaf node or... + // we have reached the maximum recursion depth or.. + // the width of the longest axis is less than the minimum axis size then... + // we create the leaf node and copy the triangles into the leaf node triangle array. + if ( count < minLeafSize || depth >= maxDepth || laxis < minAxisSize ) + { + // Copy the triangle indices into the leaf triangles array + mLeafTriangleIndex = leafTriangles.size(); // assign the array start location for these leaf triangles. + leafTriangles.push_back(count); + for (auto i = triangles.begin(); i != triangles.end(); ++i) { + RmUint32 tri = *i; + leafTriangles.push_back(tri); + } + } + else + { + RmReal center[3]; + mBounds.getCenter(center); + BoundsAABB b1,b2; + splitRect(axis,mBounds,b1,b2,center); + + // Compute two bounding boxes based upon the split of the longest axis + + BoundsAABB leftBounds,rightBounds; + + TriVector leftTriangles; + TriVector rightTriangles; + + + // Create two arrays; one of all triangles which intersect the 'left' half of the bounding volume node + // and another array that includes all triangles which intersect the 'right' half of the bounding volume node. + for (auto i = triangles.begin(); i != triangles.end(); ++i) { + + RmUint32 tri = (*i); + + { + RmUint32 i1 = indices[tri*3+0]; + RmUint32 i2 = indices[tri*3+1]; + RmUint32 i3 = indices[tri*3+2]; + + const RmReal *p1 = &vertices[i1*3]; + const RmReal *p2 = &vertices[i2*3]; + const RmReal *p3 = &vertices[i3*3]; + + RmUint32 addCount = 0; + RmUint32 orCode=0xFFFFFFFF; + if ( b1.containsTriangleExact(p1,p2,p3,orCode)) + { + addCount++; + if ( leftTriangles.empty() ) + { + leftBounds.setMin(p1); + leftBounds.setMax(p1); + } + leftBounds.include(p1); + leftBounds.include(p2); + leftBounds.include(p3); + leftTriangles.push_back(tri); // Add this triangle to the 'left triangles' array and revise the left triangles bounding volume + } + // if the orCode is zero; meaning the triangle was fully self-contiained int he left bounding box; then we don't need to test against the right + if ( orCode && b2.containsTriangleExact(p1,p2,p3,orCode)) + { + addCount++; + if ( rightTriangles.empty() ) + { + rightBounds.setMin(p1); + rightBounds.setMax(p1); + } + rightBounds.include(p1); + rightBounds.include(p2); + rightBounds.include(p3); + rightTriangles.push_back(tri); // Add this triangle to the 'right triangles' array and revise the right triangles bounding volume. + } + assert( addCount ); + } + } + + if ( !leftTriangles.empty() ) // If there are triangles in the left half then... + { + leftBounds.clamp(b1); // we have to clamp the bounding volume so it stays inside the parent volume. + mLeft = callback->getNode(); // get a new AABB node + new ( mLeft ) NodeAABB(leftBounds); // initialize it to default constructor values. + // Then recursively split this node. + mLeft->split(leftTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles); + } + + if ( !rightTriangles.empty() ) // If there are triangles in the right half then.. + { + rightBounds.clamp(b2); // clamps the bounding volume so it stays restricted to the size of the parent volume. + mRight = callback->getNode(); // allocate and default initialize a new node + new ( mRight ) NodeAABB(rightBounds); + // Recursively split this node. + mRight->split(rightTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles); + } + + } + } + + void splitRect(AxisAABB axis,const BoundsAABB &source,BoundsAABB &b1,BoundsAABB &b2,const RmReal *midpoint) + { + switch ( axis ) + { + case AABB_XAXIS: + { + b1.setMin( source.mMin ); + b1.setMax( midpoint[0], source.mMax[1], source.mMax[2] ); + + b2.setMin( midpoint[0], source.mMin[1], source.mMin[2] ); + b2.setMax(source.mMax); + } + break; + case AABB_YAXIS: + { + b1.setMin(source.mMin); + b1.setMax(source.mMax[0], midpoint[1], source.mMax[2]); + + b2.setMin(source.mMin[0], midpoint[1], source.mMin[2]); + b2.setMax(source.mMax); + } + break; + case AABB_ZAXIS: + { + b1.setMin(source.mMin); + b1.setMax(source.mMax[0], source.mMax[1], midpoint[2]); + + b2.setMin(source.mMin[0], source.mMin[1], midpoint[2]); + b2.setMax(source.mMax); + } + break; + } + } + + + virtual void raycast(bool &hit, + const RmReal *from, + const RmReal *to, + const RmReal *dir, + RmReal *hitLocation, + RmReal *hitNormal, + RmReal *hitDistance, + RmUint32 *GridID, + RmUint32 *WidgetID, + const RmReal *vertices, + const RmUint32 *indices, + const RmUint32 *grids, + const RmUint32 *widgets, + RmReal &nearestDistance, + NodeInterface *callback, + RmUint32 *raycastTriangles, + RmUint32 raycastFrame, + const TriVector &leafTriangles, + RmUint32 &nearestTriIndex, + RmMap* ignored_widgets) + { + RmReal sect[3]; + RmReal nd = nearestDistance; + if ( !intersectLineSegmentAABB(mBounds.mMin,mBounds.mMax,from,dir,nd,sect) ) + { + return; + } + if ( mLeafTriangleIndex != TRI_EOF ) + { + const RmUint32 *scan = &leafTriangles[mLeafTriangleIndex]; + RmUint32 count = *scan++; + for (RmUint32 i=0; ifind(widgets[tri]) != ignored_widgets->end()) { + continue; + } + + nearestDistance = t; + if ( hitLocation ) + { + hitLocation[0] = from[0]+dir[0]*t; + hitLocation[1] = from[1]+dir[1]*t; + hitLocation[2] = from[2]+dir[2]*t; + } + if ( hitNormal ) + { + callback->getFaceNormal(tri,hitNormal); + } + if ( hitDistance ) + { + *hitDistance = t; + } + if(GridID) { + *GridID = grids[tri]; + } + if(WidgetID) { + *WidgetID = widgets[tri]; + } + + nearestTriIndex = tri; + hit = true; + } + } + } + } + } + else + { + if ( mLeft ) + { + mLeft->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,vertices,indices,grids,widgets,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex,ignored_widgets); + } + if ( mRight ) + { + mRight->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,vertices,indices,grids,widgets,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex,ignored_widgets); + } + } + } + + NodeAABB *mLeft; // left node + NodeAABB *mRight; // right node + BoundsAABB mBounds; // bounding volume of node + RmUint32 mLeafTriangleIndex; // if it is a leaf node; then these are the triangle indices. + }; + +class MyRaycastMesh : public RaycastMesh, public NodeInterface +{ +public: + + MyRaycastMesh(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,const RmUint32 *indices,const RmUint32 *grids,const RmUint32 *widgets,RmUint32 maxDepth,RmUint32 minLeafSize,RmReal minAxisSize) + { + mRaycastFrame = 0; + if ( maxDepth < 2 ) + { + maxDepth = 2; + } + if ( maxDepth > 15 ) + { + maxDepth = 15; + } + RmUint32 pow2Table[16] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536 }; + mMaxNodeCount = 0; + for (RmUint32 i=0; i<=maxDepth; i++) + { + mMaxNodeCount+=pow2Table[i]; + } + mNodes = new NodeAABB[mMaxNodeCount]; + mNodeCount = 0; + mVcount = vcount; + mVertices = (RmReal *)::malloc(sizeof(RmReal)*3*vcount); + memcpy(mVertices,vertices,sizeof(RmReal)*3*vcount); + mTcount = tcount; + mIndices = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount*3); + memcpy(mIndices,indices,sizeof(RmUint32)*tcount*3); + mRaycastTriangles = (RmUint32 *)::malloc(tcount*sizeof(RmUint32)); + memset(mRaycastTriangles,0,tcount*sizeof(RmUint32)); + mGrids = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount); + memcpy(mGrids,grids,sizeof(RmUint32)*tcount); + mWidgets = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount); + memcpy(mWidgets,widgets,sizeof(RmUint32)*tcount); + mRoot = getNode(); + mFaceNormals = NULL; + new ( mRoot ) NodeAABB(mVcount,mVertices,mTcount,mIndices,maxDepth,minLeafSize,minAxisSize,this,mLeafTriangles); + } + + ~MyRaycastMesh(void) + { + delete []mNodes; + ::free(mVertices); + ::free(mIndices); + ::free(mFaceNormals); + ::free(mRaycastTriangles); + ::free(mGrids); + ::free(mWidgets); + } + + virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance,RmUint32 *GridID,RmUint32 *WidgetID, RmMap* ignored_widgets) + { + bool ret = false; + + RmReal dir[3]; + dir[0] = to[0] - from[0]; + dir[1] = to[1] - from[1]; + dir[2] = to[2] - from[2]; + RmReal distance = sqrtf( dir[0]*dir[0] + dir[1]*dir[1]+dir[2]*dir[2] ); + if ( distance < 0.0000000001f ) return false; + RmReal recipDistance = 1.0f / distance; + dir[0]*=recipDistance; + dir[1]*=recipDistance; + dir[2]*=recipDistance; + mRaycastFrame++; + RmUint32 nearestTriIndex=TRI_EOF; + mRoot->raycast(ret,from,to,dir,hitLocation,hitNormal,hitDistance,GridID,WidgetID,mVertices,mIndices,mGrids,mWidgets,distance,this,mRaycastTriangles,mRaycastFrame,mLeafTriangles,nearestTriIndex,ignored_widgets); + return ret; + } + + virtual void release(void) + { + delete this; + } + + virtual const RmReal * getBoundMin(void) const // return the minimum bounding box + { + return mRoot->mBounds.mMin; + } + virtual const RmReal * getBoundMax(void) const // return the maximum bounding box. + { + return mRoot->mBounds.mMax; + } + + virtual NodeAABB * getNode(void) + { + assert( mNodeCount < mMaxNodeCount ); + NodeAABB *ret = &mNodes[mNodeCount]; + mNodeCount++; + return ret; + } + + virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) + { + if ( mFaceNormals == NULL ) + { + mFaceNormals = (RmReal *)::malloc(sizeof(RmReal)*3*mTcount); + for (RmUint32 i=0; ifind(mWidgets[tri]) != ignored_widgets->end()) { + continue; + } + + nearestDistance = t; + if ( hitLocation ) + { + hitLocation[0] = from[0]+dir[0]*t; + hitLocation[1] = from[1]+dir[1]*t; + hitLocation[2] = from[2]+dir[2]*t; + } + + if ( hitNormal ) + { + getFaceNormal(tri,hitNormal); + } + + if ( hitDistance ) + { + *hitDistance = t; + } + + if(GridID) { + *GridID = mGrids[tri]; + } + if(WidgetID) { + *WidgetID = mWidgets[tri]; + } + + ret = true; + } + } + } + return ret; + } + + RmUint32 mRaycastFrame; + RmUint32 *mRaycastTriangles; + RmUint32 mVcount; + RmReal *mVertices; + RmReal *mFaceNormals; + RmUint32 mTcount; + RmUint32 *mIndices; + NodeAABB *mRoot; + RmUint32 mNodeCount; + RmUint32 mMaxNodeCount; + NodeAABB *mNodes; + TriVector mLeafTriangles; + RmUint32 *mGrids; + RmUint32 *mWidgets; +}; + +}; + + + +using namespace RAYCAST_MESH; + + +RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in the source triangle mesh + const RmReal *vertices, // The array of vertex positions in the format x1,y1,z1..x2,y2,z2.. etc. + RmUint32 tcount, // The number of triangles in the source triangle mesh + const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ... + const RmUint32 *grids, + const RmUint32 *widgets, + RmUint32 maxDepth, // Maximum recursion depth for the triangle mesh. + RmUint32 minLeafSize, // minimum triangles to treat as a 'leaf' node. + RmReal minAxisSize ) // once a particular axis is less than this size, stop sub-dividing. +{ + auto m = new MyRaycastMesh(vcount, vertices, tcount, indices, grids, widgets, maxDepth, minLeafSize, minAxisSize); + return static_cast< RaycastMesh * >(m); +} \ No newline at end of file diff --git a/source/WorldServer/Zone/raycast_mesh.h b/source/WorldServer/Zone/raycast_mesh.h new file mode 100644 index 0000000..1bfea8d --- /dev/null +++ b/source/WorldServer/Zone/raycast_mesh.h @@ -0,0 +1,73 @@ +#ifndef RAYCAST_MESH_H + +#define RAYCAST_MESH_H + +// This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do +// high-speed raycasting. +// +// There are much better implementations of this available on the internet. In particular I recommend that you use +// OPCODE written by Pierre Terdiman. +// @see: http://www.codercorner.com/Opcode.htm +// +// OPCODE does a whole lot more than just raycasting, and is a rather significant amount of source code. +// +// I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting. +// I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However, +// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders +// of magnitude; so this implementation should work fine for simple tools and utilities. +// +// It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees. +// AABB = Axis Aligned Bounding Volume trees. +// +// http://www.cgal.org/Manual/3.5/doc_html/cgal_manual/AABB_tree/Chapter_main.html +// +// +// This code snippet was written by John W. Ratcliff on August 18, 2011 and released under the MIT. license. +// +// mailto:jratcliffscarab@gmail.com +// +// The official source can be found at: http://code.google.com/p/raycastmesh/ +// +// + +typedef float RmReal; +typedef unsigned int RmUint32; +#include +#include +using namespace std; +typedef std::vector< RmUint32 > TriVector; +typedef std::map< unsigned int , bool> RmMap; + +class RaycastMesh +{ +public: + virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance, RmUint32 *GridID, RmUint32 *WidgetID, RmMap* ignored_widgets) = 0; + virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance, RmUint32 *GridID, RmUint32 *WidgetID, RmMap* ignored_widgets) = 0; + + virtual const RmReal * getBoundMin(void) const = 0; // return the minimum bounding box + virtual const RmReal * getBoundMax(void) const = 0; // return the maximum bounding box. + virtual void release(void) = 0; +protected: + virtual ~RaycastMesh(void) { }; +}; + + +RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in the source triangle mesh + const RmReal *vertices, // The array of vertex positions in the format x1,y1,z1..x2,y2,z2.. etc. + RmUint32 tcount, // The number of triangles in the source triangle mesh + const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ... + const RmUint32 *grids, + const RmUint32 *widgets, + RmUint32 maxDepth=15, // Maximum recursion depth for the triangle mesh. + RmUint32 minLeafSize=4, // minimum triangles to treat as a 'leaf' node. + RmReal minAxisSize=0.01f // once a particular axis is less than this size, stop sub-dividing. + ); + +#ifdef USE_MAP_MMFS +#include + +RaycastMesh* loadRaycastMesh(std::vector& rm_buffer, bool& load_success); +void serializeRaycastMesh(RaycastMesh* rm, std::vector& rm_buffer); +#endif /*USE_MAP_MMFS*/ + +#endif \ No newline at end of file diff --git a/source/WorldServer/Zone/region_map.cpp b/source/WorldServer/Zone/region_map.cpp new file mode 100644 index 0000000..981f050 --- /dev/null +++ b/source/WorldServer/Zone/region_map.cpp @@ -0,0 +1,125 @@ + + +#include "region_map.h" +#include "region_map_v1.h" +#include "../../common/Log.h" + + +#ifdef WIN32 +#define _snprintf snprintf +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @param name + * @return + */ +inline bool file_exists(const std::string& name) { + std::ifstream f(name.c_str()); + return f.good(); +} + +/** + * @param zone_name + * @return + */ +RegionMap* RegionMap::LoadRegionMapfile(std::string filename, std::string zone_name) { + std::string loadedFile = "Regions/"; + loadedFile += filename; + loadedFile += ".EQ2Region"; + FILE* f = fopen(loadedFile.c_str(), "rb"); + + LogWrite(REGION__DEBUG, 7, "Region", "Attempting load of %s", filename.c_str()); + + if (!f) + { + LogWrite(REGION__ERROR, 7, "Region", "Failed to load of %s", filename.c_str()); + return nullptr; + } + + // Read the string for the zone file name this was created for + int8 strSize; + char name[256]; + fread(&strSize, sizeof(int8), 1, f); + LogWrite(REGION__DEBUG, 7, "Region", "strSize = %u", strSize); + + size_t len = fread(&name, sizeof(char), strSize, f); + name[len] = '\0'; + LogWrite(REGION__DEBUG, 7, "Region", "name = %s", name); + + string inFileName(name); + boost::algorithm::to_lower(inFileName); + string zoneNameLwr(zone_name); + boost::algorithm::to_lower(zoneNameLwr); + + std::size_t found = inFileName.find(zoneNameLwr); + // Make sure file contents are for the correct zone + if (found == std::string::npos) { + fclose(f); + LogWrite(REGION__ERROR, 0, "Region", "RegionMap::LoadRegionMapfile() map contents (%s) do not match its name (%s).", inFileName, zoneNameLwr.c_str()); + return nullptr; + } + + int32 regionMapVersion; + fread(®ionMapVersion, sizeof(int32), 1, f); + LogWrite(REGION__INFO, 0, "Region", "Loading %s RegionMapVersion = %u", name, regionMapVersion); + + RegionMapV1* regionmap = new RegionMapV1(); + regionmap->Load(f, zoneNameLwr, regionMapVersion); + + return regionmap; +} + + +void RegionMapRange::AddVersionRange(std::string zoneName) { + boost::filesystem::path targetDir("Regions/"); + + // crash fix since the dir isn't present + if(!boost::filesystem::is_directory(targetDir)) + { + LogWrite(REGION__ERROR, 7, "Region", "Unable to find directory %s", targetDir.c_str()); + return; + } + + boost::filesystem::recursive_directory_iterator iter(targetDir), eod; + boost::smatch base_match; + std::string formula = "(.*\\/|.*\\\\)((" + zoneName + ")(\\-([0-9]+)\\-([0-9]+))?)\\.EQ2Region$"; + boost::regex re(formula.c_str()); + LogWrite(REGION__INFO, 0, "Region", "Region Formula to match: %s\n", formula.c_str()); + + BOOST_FOREACH(boost::filesystem::path + const & i, make_pair(iter, eod)) { + if (is_regular_file(i)) { + std::string fileName(i.string()); + if (boost::regex_match(fileName, base_match, re)) { + boost::ssub_match base_sub_match = base_match[2]; + boost::ssub_match base_sub_match2 = base_match[5]; + boost::ssub_match base_sub_match3 = base_match[6]; + std::string baseMatch(base_sub_match.str().c_str()); + std::string baseMatch2(base_sub_match2.str().c_str()); + std::string baseMatch3(base_sub_match3.str().c_str()); + LogWrite(REGION__INFO, 0, "Region", "Region to Load: %s, size: %i, string: %s, min: %s, max: %s\n", fileName.c_str(), base_match.size(), baseMatch.c_str(), baseMatch2.c_str(), baseMatch3.c_str()); + RegionMap * regionmap = RegionMap::LoadRegionMapfile(base_sub_match.str().c_str(), zoneName); + + int32 min_version = 0, max_version = 0; + if (strlen(base_sub_match2.str().c_str()) > 0) + min_version = atoul(base_sub_match2.str().c_str()); + + if (strlen(base_sub_match2.str().c_str()) > 0) + max_version = atoul(base_sub_match3.str().c_str()); + version_map.insert(std::make_pair(new VersionRange(min_version, max_version), regionmap)); + } + } + } +} \ No newline at end of file diff --git a/source/WorldServer/Zone/region_map.h b/source/WorldServer/Zone/region_map.h new file mode 100644 index 0000000..e2414f9 --- /dev/null +++ b/source/WorldServer/Zone/region_map.h @@ -0,0 +1,130 @@ +#ifndef EQ2EMU_REGION_MAP_H +#define EQ2EMU_REGION_MAP_H + +#include "../../common/types.h" +#include "../../common/MiscFunctions.h" + +#include "position.h" +#include + +class Client; +class Spawn; +class ZoneServer; +class Region_Node; +class ZBSP_Node; + +enum WaterRegionType : int { + RegionTypeUnsupported = -2, + RegionTypeUntagged = -1, + RegionTypeNormal = 0, + RegionTypeWater = 1, + RegionTypeLava = 2, + RegionTypeZoneLine = 3, + RegionTypePVP = 4, + RegionTypeSlime = 5, + RegionTypeIce = 6, + RegionTypeVWater = 7 +}; + +enum WaterRegionClass : int32 { + ClassWaterVolume = 0, // matching .region file type by name "watervol" + ClassWaterRegion = 1, // matching .region file type by name "waterregion" + ClassWaterRegion2 = 2, // represents .region file name "water_region" potentially defunct and just a WaterVolume (0) + ClassWaterOcean = 3, // represents .region file with "ocean" and a select node as a parent + ClassWaterCavern = 4, // represents .region file with matches on name "ocean" and "water" + ClassWaterOcean2 = 5 // represents .region file with matches on name "ocean" without previous matches (no select node parent and no water string match) +}; + +class RegionMap +{ +public: + RegionMap() { } + virtual ~RegionMap() { } + + static RegionMap* LoadRegionMapfile(std::string filename, std::string zone_name); + virtual WaterRegionType ReturnRegionType(const glm::vec3& location, int32 gridid=0) const = 0; + virtual bool InWater(const glm::vec3& location, int32 gridid=0) const = 0; + virtual bool InLava(const glm::vec3& location, int32 gridid=0) const = 0; + virtual bool InLiquid(const glm::vec3& location) const = 0; + virtual bool InPvP(const glm::vec3& location) const = 0; + virtual bool InZoneLine(const glm::vec3& location) const = 0; + + virtual void IdentifyRegionsInGrid(Client* client, const glm::vec3& location) const = 0; + virtual void MapRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0; + virtual void UpdateRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0; + virtual void TicRegionsNearSpawn(Spawn* spawn, Client* client=0) const = 0; + + virtual void InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist = 0.0f) = 0; + virtual void RemoveRegionNode(std::string regionName) = 0; +protected: + virtual bool Load(FILE *fp) { return false; } +}; + + +class RegionMapRange { +public: + RegionMapRange() + { + + } + + ~RegionMapRange() + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + RegionMap* map = itr->second; + delete range; + delete map; + } + + version_map.clear(); + } + + void AddVersionRange(std::string zoneName); + + map::iterator FindVersionRange(int32 min_version, int32 max_version) + { + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if (range->GetMinVersion() <= min_version && max_version <= range->GetMaxVersion()) + return itr; + // if the min version is in range, but max range is 0 + else if (range->GetMinVersion() <= min_version && range->GetMaxVersion() == 0) + return itr; + // if min version is 0 and max_version has a cap + else if (range->GetMinVersion() == 0 && max_version <= range->GetMaxVersion()) + return itr; + } + + return version_map.end(); + } + + map::iterator FindRegionByVersion(int32 version) + { + map::iterator enditr = version_map.end(); + map::iterator itr; + for (itr = version_map.begin(); itr != version_map.end(); itr++) + { + VersionRange* range = itr->first; + // if min and max version are both in range + if(range->GetMinVersion() == 0 && range->GetMaxVersion() == 0) + enditr = itr; + else if (version >= range->GetMinVersion() && version <= range->GetMaxVersion()) + return itr; + } + + return enditr; + } + + map::iterator GetRangeEnd() { return version_map.end(); } +private: + std::map version_map; + string name; +}; + +#endif diff --git a/source/WorldServer/Zone/region_map_v1.cpp b/source/WorldServer/Zone/region_map_v1.cpp new file mode 100644 index 0000000..78dee76 --- /dev/null +++ b/source/WorldServer/Zone/region_map_v1.cpp @@ -0,0 +1,868 @@ +#include "region_map_v1.h" +#include "../../common/Log.h" +#include "../client.h" +#include "../Spawn.h" +#include "../LuaInterface.h" +#include "../World.h" + +#undef snprintf +#include + +extern LuaInterface* lua_interface; +extern World world; + +RegionMapV1::RegionMapV1() { + mVersion = 1; +} + +RegionMapV1::~RegionMapV1() { + std::unique_lock lock(MRegions); + map::const_iterator itr; + int region_num = 0; + for (itr = Regions.begin(); itr != Regions.end();) + { + Region_Node* node = itr->first; + ZBSP_Node* bsp_node = itr->second; + map::const_iterator deleteItr = itr; + itr++; + Regions.erase(deleteItr); + safe_delete(node); + safe_delete_array(bsp_node); + } + + Regions.clear(); +} + +WaterRegionType RegionMapV1::ReturnRegionType(const glm::vec3& location, int32 gridid) const { + return BSPReturnRegionType(1, glm::vec3(location.x, location.y, location.z), gridid); +} + +bool RegionMapV1::InWater(const glm::vec3& location, int32 gridid) const { + return ReturnRegionType(location, gridid) == RegionTypeWater; +} + +bool RegionMapV1::InLava(const glm::vec3& location, int32 gridid) const { + return ReturnRegionType(location, gridid) == RegionTypeLava; +} + +bool RegionMapV1::InLiquid(const glm::vec3& location) const { + return InWater(location) || InLava(location); +} + +bool RegionMapV1::InPvP(const glm::vec3& location) const { + return ReturnRegionType(location) == RegionTypePVP; +} + +bool RegionMapV1::InZoneLine(const glm::vec3& location) const { + return ReturnRegionType(location) == RegionTypeZoneLine; +} + +std::string RegionMapV1::TestFile(std::string testFile) +{ + std::string tmpStr(testFile); + std::size_t pos = tmpStr.find("."); + if ( pos != testFile.npos ) + tmpStr = testFile.substr (0, pos); + + string tmpScript("RegionScripts/"); + tmpScript.append(mZoneNameLower); + tmpScript.append("/" + tmpStr + ".lua"); + std::ifstream f(tmpScript.c_str()); + return f.good() ? tmpScript : string(""); +} + + +bool RegionMapV1::Load(FILE* fp, std::string inZoneNameLwr, int32 version) { + mZoneNameLower = string(inZoneNameLwr.c_str()); + + uint32 region_size; + if (fread(®ion_size, sizeof(uint32), 1, fp) != 1) { + return false; + } + + LogWrite(REGION__DEBUG, 0, "RegionMap", "region count = %u", region_size); + + for (int i = 0; i < region_size; i++) + { + uint32 region_num; + if (fread(®ion_num, sizeof(uint32), 1, fp) != 1) { + return false; + } + + uint32 region_type; + if (fread(®ion_type, sizeof(uint32), 1, fp) != 1) { + return false; + } + + float x, y, z, dist; + if (fread(&x, sizeof(float), 1, fp) != 1) { + return false; + } + if (fread(&y, sizeof(float), 1, fp) != 1) { + return false; + } + if (fread(&z, sizeof(float), 1, fp) != 1) { + return false; + } + if (fread(&dist, sizeof(float), 1, fp) != 1) { + return false; + } + + int8 strSize; + char envName[256] = {""}; + char regionName[256] = {""}; + uint32 grid_id = 0; + + if ( version > 1 ) + { + fread(&strSize, sizeof(int8), 1, fp); + LogWrite(REGION__DEBUG, 7, "Region", "Region environment strSize = %u", strSize); + + if(strSize) + { + size_t len = fread(&envName, sizeof(char), strSize, fp); + envName[len] = '\0'; + } + + LogWrite(REGION__DEBUG, 7, "Region", "Region environment file name = %s", envName); + + fread(&strSize, sizeof(int8), 1, fp); + LogWrite(REGION__DEBUG, 7, "Region", "Region name strSize = %u", strSize); + + if(strSize) + { + size_t len = fread(®ionName, sizeof(char), strSize, fp); + regionName[len] = '\0'; + } + + LogWrite(REGION__DEBUG, 7, "Region", "Region name file name = %s", regionName); + + if (fread(&grid_id, sizeof(uint32), 1, fp) != 1) { + return false; + } + + } + + int32 bsp_tree_size; + if (fread(&bsp_tree_size, sizeof(int32), 1, fp) != 1) { + return false; + } + + LogWrite(REGION__DEBUG, 7, "Region", "region x,y,z,dist = %f, %f, %f, %f, region bsp tree size: %i\n", x, y, z, dist, bsp_tree_size); + + ZBSP_Node* BSP_Root = new ZBSP_Node[bsp_tree_size]; + if (fread(BSP_Root, sizeof(ZBSP_Node), bsp_tree_size, fp) != bsp_tree_size) { + LogWrite(REGION__ERROR, 0, "RegionMap", "Failed to load region."); + return false; + } + + Region_Node* tmpNode = new Region_Node; + tmpNode->x = x; + tmpNode->y = y; + tmpNode->z = z; + tmpNode->dist = dist; + tmpNode->region_type = region_type; + tmpNode->regionName = string(regionName); + tmpNode->regionEnvFileName = string(envName); + tmpNode->grid_id = grid_id; + tmpNode->regionScriptName = string(""); + + tmpNode->regionScriptName = TestFile(regionName); + + if ( tmpNode->regionScriptName.size() < 1 ) + { + tmpNode->regionScriptName = TestFile(envName); + } + if ( tmpNode->regionScriptName.size() < 1 ) + { + tmpNode->regionScriptName = TestFile("default"); + } + + tmpNode->vert_count = bsp_tree_size; + + MRegions.lock(); + Regions.insert(make_pair(tmpNode, BSP_Root)); + MRegions.unlock(); + } + + fclose(fp); + + LogWrite(REGION__DEBUG, 0, "RegionMap", "completed load!"); + + return true; +} + +void RegionMapV1::IdentifyRegionsInGrid(Client *client, const glm::vec3 &location) const +{ + std::shared_lock lock(MRegions); + map::const_iterator itr; + int region_num = 0; + + int32 grid = 0; + int32 widget_id = 0; + float x =0.0f,y = 0.0f,z = 0.0f; + if (client->GetPlayer()->GetMap() != nullptr && client->GetPlayer()->GetMap()->IsMapLoaded()) + { + auto loc = glm::vec3(location.x, location.z, location.y); + + float new_z = client->GetPlayer()->FindBestZ(loc, nullptr, &grid, &widget_id); + + std::map::iterator itr = client->GetPlayer()->GetMap()->widget_map.find(widget_id); + if(itr != client->GetPlayer()->GetMap()->widget_map.end()) { + x = itr->second.x; + y = itr->second.y; + z = itr->second.z; + } + } + else + client->SimpleMessage(CHANNEL_COLOR_RED, "No map to establish grid id, using grid id 0 (attempt match all)."); + + client->Message(2, "Region check against location %f / %f / %f. Grid to try: %u, player grid is %u, widget id is %u. Widget location is %f %f %f.", location.x, location.y, location.z, grid, client->GetPlayer()->GetLocation(), widget_id, x, y, z); + for (itr = Regions.begin(); itr != Regions.end(); itr++) + { + Region_Node *node = itr->first; + ZBSP_Node *BSP_Root = itr->second; + + if (grid == 0 || node->grid_id == grid) + { + float x1 = node->x - location.x; + float y1 = node->y - location.y; + float z1 = node->z - location.z; + float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1); + glm::vec3 testLoc(location.x, location.y, location.z); + if(!BSP_Root) { + if(client) + client->Message(CHANNEL_COLOR_YELLOW, "[%s] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, Script: %s. X: %f, Y: %f, Z: %f, Distance: %f, Widget ID Marker: %u", (widget_id == node->trigger_widget_id) ? "IN REGION" : "WIDGET MARKER", region_num, + node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), node->regionScriptName.c_str(), node->x, node->y, node->z, node->dist, node->trigger_widget_id); + } + else if (dist <= node->dist) + { + WaterRegionType regionType = RegionTypeUntagged; + + if (node->region_type == ClassWaterRegion) + regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist); + else + regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist); + + if (regionType != RegionTypeNormal) + { + client->Message(CHANNEL_COLOR_YELLOW, "[DETECTED IN REGION %i] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", regionType, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + else + { + client->Message(CHANNEL_COLOR_RED, "[IN DIST RANGE] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + } + else + client->Message(CHANNEL_COLOR_RED, "[OUT OF RANGE] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + else + client->Message(CHANNEL_COLOR_RED, "[OUT OF GRID] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + + region_num++; + } +} + +void RegionMapV1::MapRegionsNearSpawn(Spawn *spawn, Client *client) const +{ + std::shared_lock lock(MRegions); + map::const_iterator itr; + int region_num = 0; + + spawn->RegionMutex.writelock(); + + glm::vec3 testLoc(spawn->GetX(), spawn->GetY(), spawn->GetZ()); + for (itr = Regions.begin(); itr != Regions.end(); itr++) + { + Region_Node *node = itr->first; + ZBSP_Node *BSP_Root = itr->second; + + if (node->regionScriptName.size() < 1) // only track ones that are used with LUA scripting + continue; + + if(!BSP_Root) { + int32 currentGridID = spawn->GetLocation(); + bool inRegion = false; + if(!(inRegion = spawn->InRegion(node, nullptr)) && currentGridID == node->grid_id && + ( node->trigger_widget_id == spawn->trigger_widget_id || (node->dist > 0.0f && spawn->GetDistance(node->x, node->y, node->z) < node->dist)) ) { + int32 returnValue = spawn->InsertRegionToSpawn(node, nullptr, RegionTypeUntagged); + if (client) + client->Message(CHANNEL_COLOR_YELLOW, "[ENTER REGION %i %u] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", RegionTypeUntagged, returnValue, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + continue; + } + + float x1 = node->x - testLoc.x; + float y1 = node->y - testLoc.y; + float z1 = node->z - testLoc.z; + float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1); + if (dist <= node->dist) + { + WaterRegionType regionType = RegionTypeUntagged; + + if (node->region_type == ClassWaterRegion) + regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist); + else + regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist); + + if (regionType != RegionTypeNormal) + { + if (!spawn->InRegion(node, BSP_Root)) + { + spawn->DeleteRegion(node, BSP_Root); + int32 returnValue = spawn->InsertRegionToSpawn(node, BSP_Root, regionType); + if (client) + client->Message(CHANNEL_COLOR_YELLOW, "[ENTER REGION %i %u] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", regionType, returnValue, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + } + else + { + if(spawn->HasRegionTracked(node, BSP_Root, false)) { + continue; + } // UpdateRegionsNearSpawn will capture it for nodes that have BSP_Root's + if (spawn->InRegion(node, BSP_Root)) + { + if (client) + client->Message(CHANNEL_COLOR_RED, "[LEAVE REGION] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + WaterRegionType whatWasRegionType = (WaterRegionType) spawn->GetRegionType(node, BSP_Root); + lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, whatWasRegionType); + } + spawn->DeleteRegion(node, BSP_Root); + + spawn->InsertRegionToSpawn(node, BSP_Root, RegionTypeNormal, false); + if (client) + client->Message(CHANNEL_COLOR_RED, "[NEAR REGION] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + } + } + + region_num++; + } + + spawn->RegionMutex.releasewritelock(); +} + +void RegionMapV1::UpdateRegionsNearSpawn(Spawn *spawn, Client *client) const +{ + std::shared_lock lock(MRegions); + map, Region_Status>::iterator testitr; + int region_num = 0; + + spawn->RegionMutex.writelock(); + + glm::vec3 testLoc(spawn->GetX(), spawn->GetY(), spawn->GetZ()); + + map deleteNodes; + for (testitr = spawn->Regions.begin(); testitr != spawn->Regions.end(); testitr++) + { + map::const_iterator actualItr = testitr->first.begin(); + Region_Node *node = actualItr->first; + ZBSP_Node *BSP_Root = actualItr->second; + + std::map::const_iterator dead_itr = dead_nodes.find(node); + if(dead_itr != dead_nodes.end()) { + deleteNodes.insert(make_pair(node, BSP_Root)); + continue; + } + if(!BSP_Root) { + continue; + } + + float x1 = node->x - testLoc.x; + float y1 = node->y - testLoc.y; + float z1 = node->z - testLoc.z; + float dist = sqrt(x1 *x1 + y1 *y1 + z1 *z1); + if (dist <= node->dist) + { + WaterRegionType regionType = RegionTypeUntagged; + + if (node->region_type == ClassWaterRegion) + regionType = BSPReturnRegionWaterRegion(node, BSP_Root, 1, testLoc, dist); + else + regionType = BSPReturnRegionTypeNode(node, BSP_Root, 1, testLoc, dist); + + if (regionType != RegionTypeNormal) + { + if (!testitr->second.inRegion) + { + testitr->second.inRegion = true; + int32 returnValue = 0; + lua_interface->RunRegionScript(node->regionScriptName, "EnterRegion", spawn->GetZone(), spawn, regionType, &returnValue); + if (client) + client->Message(CHANNEL_COLOR_YELLOW, "[ENTER RANGE %i %u] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", regionType, returnValue, region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + testitr->second.timerTic = returnValue; + testitr->second.lastTimerTic = returnValue ? Timer::GetCurrentTime2() : 0; + } + } + else + { + if (testitr->second.inRegion) + { + testitr->second.inRegion = false; + testitr->second.timerTic = 0; + testitr->second.lastTimerTic = 0; + if (client) + client->Message(CHANNEL_COLOR_RED, "[LEAVE RANGE] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + WaterRegionType whatWasRegionType = (WaterRegionType) spawn->GetRegionType(node, BSP_Root); + lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, whatWasRegionType); + } + } + } + else + { + if (client) + client->Message(CHANNEL_COLOR_RED, "[LEAVE RANGE - OOR] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + deleteNodes.insert(make_pair(node, BSP_Root)); + } + + region_num++; + } + + map::const_iterator deleteItr; + for (deleteItr = deleteNodes.begin(); deleteItr != deleteNodes.end(); deleteItr++) + { + Region_Node *tmpNode = deleteItr->first; + ZBSP_Node *bspNode = deleteItr->second; + spawn->DeleteRegion(tmpNode, bspNode); + } + + spawn->RegionMutex.releasewritelock(); +} + +void RegionMapV1::TicRegionsNearSpawn(Spawn *spawn, Client *client) const +{ + std::shared_lock lock(MRegions); + map, Region_Status>::iterator testitr; + int region_num = 0; + + spawn->RegionMutex.writelock(); + + for (testitr = spawn->Regions.begin(); testitr != spawn->Regions.end();) + { + map::const_iterator actualItr = testitr->first.begin(); + + if(actualItr == testitr->first.end()) { + testitr++; + continue; + } + + Region_Node *node = actualItr->first; + ZBSP_Node *BSP_Root = actualItr->second; + + std::map::const_iterator dead_itr = dead_nodes.find(node); + if(dead_itr != dead_nodes.end()) { + testitr++; + continue; + } + + if(!BSP_Root) { + bool passDistCheck = false; + int32 currentGridID = spawn->GetLocation(); + if(testitr->second.timerTic && currentGridID == node->grid_id && (node->trigger_widget_id == spawn->trigger_widget_id || (node->dist > 0.0f && spawn->GetDistance(node->x, node->y, node->z) <= node->dist && (passDistCheck = true))) + && Timer::GetCurrentTime2() >= (testitr->second.lastTimerTic + testitr->second.timerTic)) { + testitr->second.lastTimerTic = Timer::GetCurrentTime2(); + if (client) + client->Message(CHANNEL_COLOR_RED, "[TICK] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + + int32 returnValue = 0; + lua_interface->RunRegionScript(node->regionScriptName, "Tick", spawn->GetZone(), spawn, RegionTypeUntagged, &returnValue); + + if (returnValue == 1) + { + testitr->second.lastTimerTic = 0; + testitr->second.timerTic = 0; + } + } + else if(currentGridID != node->grid_id || (node->trigger_widget_id != spawn->trigger_widget_id && !passDistCheck)) { + if (client) + client->Message(CHANNEL_COLOR_RED, "[LEAVE REGION] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + lua_interface->RunRegionScript(node->regionScriptName, "LeaveRegion", spawn->GetZone(), spawn, RegionTypeUntagged); + map, Region_Status>::iterator endItr = testitr; + endItr++; + spawn->DeleteRegion(node, nullptr); + if(endItr == spawn->Regions.end()) { + break; + } + else { + testitr++; + continue; + } + + } + } + else if (testitr->second.timerTic && testitr->second.inRegion && Timer::GetCurrentTime2() >= (testitr->second.lastTimerTic + testitr->second.timerTic)) + { + testitr->second.lastTimerTic = Timer::GetCurrentTime2(); + if (client) + client->Message(CHANNEL_COLOR_RED, "[TICK] Region %i, GridID: %u, RegionName: %s, RegionEnvFileName: %s, distance: %f, X/Y/Z: %f / %f / %f. Script: %s", region_num, node->grid_id, node->regionName.c_str(), node->regionEnvFileName.c_str(), + node->dist, node->x, node->y, node->z, node->regionScriptName.c_str()); + WaterRegionType whatWasRegionType = RegionTypeNormal; // default will be 0 + + if (BSP_Root->special == SPECIAL_REGION_LAVA_OR_DEATH) + whatWasRegionType = RegionTypeLava; // 2 + else if (BSP_Root->special == SPECIAL_REGION_WATER) + whatWasRegionType = RegionTypeWater; // 1 + + int32 returnValue = 0; + lua_interface->RunRegionScript(node->regionScriptName, "Tick", spawn->GetZone(), spawn, whatWasRegionType, &returnValue); + + if (returnValue == 1) + { + testitr->second.lastTimerTic = 0; + testitr->second.timerTic = 0; + } + } + + region_num++; + testitr++; + } + + spawn->RegionMutex.releasewritelock(); +} + +WaterRegionType RegionMapV1::BSPReturnRegionType(int32 node_number, const glm::vec3& location, int32 gridid) const { + std::shared_lock lock(MRegions); + map::const_iterator itr; + int region_num = 0; + for (itr = Regions.begin(); itr != Regions.end(); itr++) + { + + Region_Node* node = itr->first; + + // did not match grid id of current region, skip + //if ( gridid > 0 && gridid != node->grid_id) + // continue; + + ZBSP_Node* BSP_Root = itr->second; + + float x1 = node->x - location.x; + float y1 = node->y - location.y; + float z1 = node->z - location.z; + float dist = sqrt(x1 * x1 + y1 * y1 + z1 * z1); + +#ifdef REGIONDEBUG + printf("Region %i (%i) dist %f / node dist %f. NodeXYZ: %f %f %f, XYZ: %f %f %f.\n", region_num, node->region_type, dist, node->dist, node->x, node->y, node->z, location.x, location.y, location.z); +#endif + + if (dist <= node->dist) + { + ZBSP_Node* BSP_Root = itr->second; + + WaterRegionType regionType = RegionTypeUntagged; + + if (node->region_type == ClassWaterRegion) + regionType = BSPReturnRegionWaterRegion(node, BSP_Root, node_number, location, dist); + else + regionType = BSPReturnRegionTypeNode(node, BSP_Root, node_number, location, dist); + + if (regionType != RegionTypeNormal) + return regionType; + } + region_num++; + } + + return(RegionTypeNormal); +} + +WaterRegionType RegionMapV1::BSPReturnRegionTypeNode(const Region_Node* region_node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode) const { + if(node_number > region_node->vert_count) + { + LogWrite(REGION__DEBUG, 0, "Region", "Region %s grid %u (%s) - Node %u is out of range for region max vert count of %i. Hit at location %f %f %f.", + region_node->regionName.c_str(), region_node->grid_id, region_node->regionScriptName.c_str(), node_number, region_node->vert_count, + location.x, location.y, location.z); + return (RegionTypeWater); + } + + const ZBSP_Node* current_node = &BSP_Root[node_number - 1]; + float distance; + +#ifdef REGIONDEBUG + printf("left = %u, right %u (Size: %i)\n", current_node->left, current_node->right, region_node->vert_count); +#endif + + if (region_node->region_type == ClassWaterRegion2) + { + distance = (location.x * current_node->normal[0]) + + (location.y * current_node->normal[1]) + + (location.z * current_node->normal[2]) + + current_node->splitdistance; + } + else { + distance = (location.x * current_node->normal[0]) + + (location.y * current_node->normal[1]) + + (location.z * current_node->normal[2]) - + current_node->splitdistance; + } + + float absDistance = distance; + if (absDistance < 0.0f) + absDistance *= -1.0f; + + float absSplitDist = current_node->splitdistance; + if (absSplitDist < 0.0f) + absSplitDist *= -1.0f; + +#ifdef REGIONDEBUG + printf("distance = %f, normals: %f %f %f, location: %f %f %f, split distance: %f\n", distance, current_node->left, current_node->right, current_node->normal[0], current_node->normal[1], current_node->normal[2], + location.x, location.y, location.z, current_node->splitdistance); +#endif + + if ((current_node->left == -2) && + (current_node->right == -1 || current_node->right == -2)) { + if (region_node->region_type == ClassWaterOcean || region_node->region_type == ClassWaterOcean2) + { + if ( region_node->region_type == ClassWaterOcean && current_node->right == -1 && + current_node->normal[1] >= 0.9f && distance > 0 ) + return RegionTypeWater; + else + return EstablishDistanceAtAngle(region_node, current_node, distance, absDistance, absSplitDist, true); + } + else + { + if (distance > 0) + return(RegionTypeWater); + else + return RegionTypeNormal; + } + } + else if ((region_node->region_type == ClassWaterOcean || region_node->region_type == ClassWaterOcean2) && current_node->normal[1] != 1.0f && current_node->normal[1] != -1.0f) + { + float fraction = abs(current_node->normal[0] * current_node->normal[2]); + float diff = distToNode / region_node->dist; + if (distance > 0) + diff = distance * diff; + +#ifdef REGIONDEBUG + printf("Diff: %f (%f + %f), fraction %f\n", diff, distToNode, distance, fraction); +#endif + if ((abs(diff) / 2.0f) > (absSplitDist * (1.0f / fraction)) * 2.0f) + return RegionTypeNormal; + } + + if (distance == 0.0f) { + return(RegionTypeNormal); + } + + if (distance > 0.0f) { + +#ifdef REGIONDEBUG + printf("to left node %i\n", current_node->left); +#endif + if (current_node->left == -2) + { + switch(region_node->region_type) + { + case ClassWaterVolume: + case ClassWaterOcean: + return RegionTypeWater; + break; + + case ClassWaterOcean2: + return EstablishDistanceAtAngle(region_node, current_node, distance, absDistance, absSplitDist, false); + break; + + case ClassWaterCavern: + return EstablishDistanceAtAngle(region_node, current_node, distance, absDistance, absSplitDist, true); + break; + + default: + return RegionTypeNormal; + break; + } + } + else if (current_node->left == -1) { + return(RegionTypeNormal); + } + return BSPReturnRegionTypeNode(region_node, BSP_Root, current_node->left + 1, location, distToNode); + } + +#ifdef REGIONDEBUG + printf("to right node %i, sign bit %i\n", current_node->right, signbit(current_node->normal[1])); +#endif + if (current_node->right == -1) { + if (region_node->region_type == ClassWaterOcean2 && signbit(current_node->normal[1]) == 0 && absDistance < absSplitDist) + return RegionTypeWater; + else if ((region_node->region_type == ClassWaterOcean || region_node->region_type == ClassWaterOcean2) && + (current_node->normal[1] > 0.0f && distance < 0.0f && absDistance < absSplitDist)) + { + return(RegionTypeWater); + } + return(RegionTypeNormal); + } + + return BSPReturnRegionTypeNode(region_node, BSP_Root, current_node->right + 1, location, distToNode); +} + + +WaterRegionType RegionMapV1::BSPReturnRegionWaterRegion(const Region_Node* region_node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode) const { + if(node_number > region_node->vert_count) + { + LogWrite(REGION__DEBUG, 0, "Region", "Region %s grid %u (%s) - Node %u is out of range for region max vert count of %i. Hit at location %f %f %f.", + region_node->regionName.c_str(), region_node->grid_id, region_node->regionScriptName.c_str(), node_number, region_node->vert_count, + location.x, location.y, location.z); + return (RegionTypeNormal); + } + + const ZBSP_Node* current_node = &BSP_Root[node_number - 1]; + float distance; + +#ifdef REGIONDEBUG + printf("left = %u, right %u\n", current_node->left, current_node->right); +#endif + + distance = (location.x * current_node->normal[0]) + + (location.y * current_node->normal[1]) + + (location.z * current_node->normal[2]) - + current_node->splitdistance; + +#ifdef REGIONDEBUG + printf("distance = %f, normals: %f %f %f, location: %f %f %f, split distance: %f\n", distance, current_node->left, current_node->right, current_node->normal[0], current_node->normal[1], current_node->normal[2], + location.x, location.y, location.z, current_node->splitdistance); +#endif + + if (distance > 0.0f) { +#ifdef REGIONDEBUG + printf("to left node %i\n", current_node->left); +#endif + if (current_node->left == -1) { + return(RegionTypeNormal); + } + else if (current_node->left == -2) { + switch(current_node->special) + { + case SPECIAL_REGION_LAVA_OR_DEATH: + return(RegionTypeLava); + break; + case SPECIAL_REGION_WATER: + return(RegionTypeWater); + break; + default: + return(RegionTypeUntagged); + break; + } + } + return BSPReturnRegionWaterRegion(region_node, BSP_Root, current_node->left + 1, location, distToNode); + } + +#ifdef REGIONDEBUG + printf("to right node %i, sign bit %i\n", current_node->right, signbit(current_node->normal[1])); +#endif + + if (current_node->right == -1) { + return(RegionTypeNormal); + } + + return BSPReturnRegionWaterRegion(region_node, BSP_Root, current_node->right + 1, location, distToNode); +} + +WaterRegionType RegionMapV1::EstablishDistanceAtAngle(const Region_Node* region_node, const ZBSP_Node* current_node, float distance, float absDistance, float absSplitDist, bool checkEdgedAngle) const { + float fraction = abs(current_node->normal[0] * current_node->normal[2]); +#ifdef REGIONDEBUG + printf("Distcheck: %f < %f\n", absDistance, absSplitDist); +#endif + if (absDistance < absSplitDist && + (current_node->normal[0] >= 1.0f || current_node->normal[0] <= -1.0f || + (current_node->normal[1] >= .9f && distance < 0.0f) || + (current_node->normal[1] <= -.9f && distance > 0.0f))) + { + return RegionTypeWater; + } + else if (fraction > 0.0f && (region_node->region_type == ClassWaterOcean2 || checkEdgedAngle)) + { + if (current_node->normal[2] >= 1.0f || current_node->normal[2] <= -1.0f) + return RegionTypeNormal; + else if (current_node->normal[1] == 0.0f && (current_node->normal[0] < -0.5f || current_node->normal[0] > 0.5f) && + ((abs(absDistance * current_node->normal[0]) / 2.0f) < ((abs(absSplitDist * (1.0f / fraction)))))) + { + return RegionTypeWater; + } + else if (current_node->normal[1] == 0.0f && (current_node->normal[2] < -0.5f || current_node->normal[2] > 0.5f) && + ((abs(absDistance * current_node->normal[2]) / 2.0f) < ((abs(absSplitDist * (1.0f / fraction)))))) + { + return RegionTypeWater; + } + } + + return RegionTypeNormal; +} + +void RegionMapV1::InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist) +{ + Region_Node* tmpNode = new Region_Node; + + tmpNode->x = 0.0f; + tmpNode->y = 0.0f; + tmpNode->z = 0.0f; + + if(!zone) + return; + + Map* current_map = world.GetMap(std::string(zone->GetZoneFile()), version); + if(current_map) { + std::map::iterator itr = current_map->widget_map.find(triggerWidgetID); + if(itr != current_map->widget_map.end()) { + tmpNode->x = itr->second.x; + tmpNode->y = itr->second.y; + tmpNode->z = itr->second.z; + } + } + + tmpNode->dist = dist; + tmpNode->region_type = RegionTypeUntagged; + tmpNode->regionName = string(regionName); + tmpNode->regionEnvFileName = string(envName); + tmpNode->grid_id = gridID; + tmpNode->regionScriptName = string(""); + tmpNode->trigger_widget_id = triggerWidgetID; + + tmpNode->regionScriptName = TestFile(regionName); + + if ( tmpNode->regionScriptName.size() < 1 ) + { + tmpNode->regionScriptName = TestFile(envName); + } + if ( tmpNode->regionScriptName.size() < 1 ) + { + tmpNode->regionScriptName = TestFile("default"); + } + + tmpNode->vert_count = 0; + + ZBSP_Node* BSP_Root = nullptr; + + MRegions.lock(); + Regions.insert(make_pair(tmpNode, BSP_Root)); + MRegions.unlock(); +} + +void RegionMapV1::RemoveRegionNode(std::string name) { + + std::unique_lock lock(MRegions); + map::const_iterator itr; + for (itr = Regions.begin(); itr != Regions.end();) + { + Region_Node *node = itr->first; + ZBSP_Node *BSP_Root = itr->second; + if(node->regionName.find(name) != node->regionName.npos) { + itr = Regions.erase(itr); + dead_nodes.insert(make_pair(node, true)); + safe_delete(node); + safe_delete_array(BSP_Root); + } + else { + itr++; + } + } +} \ No newline at end of file diff --git a/source/WorldServer/Zone/region_map_v1.h b/source/WorldServer/Zone/region_map_v1.h new file mode 100644 index 0000000..0016a42 --- /dev/null +++ b/source/WorldServer/Zone/region_map_v1.h @@ -0,0 +1,90 @@ +#ifndef EQ2EMU_REGION_MAP_V1_H +#define EQ2EMU_REGION_MAP_V1_H + +#include +#include +#include + +#include "region_map.h" + +class Client; +class Spawn; + +#define SPECIAL_REGION_LAVA_OR_DEATH 4294967293 +#define SPECIAL_REGION_WATER 1 + +#pragma pack(1) +typedef struct ZBSP_Node { + int32 node_number; + float normal[3], splitdistance; + int32 region; + int32 special; + int32 left, right; +} ZBSP_Node; + +typedef struct Region_Node { + int32 region_type; + float x; + float y; + float z; + float dist; + string regionEnvFileName; + string regionName; + int32 grid_id; + string regionScriptName; + int32 vert_count; + int32 trigger_widget_id; +} Region_Node; +#pragma pack() + +struct Region_Status { + bool inRegion; + int32 timerTic; + int32 lastTimerTic; + int32 regionType; +}; + +class RegionMapV1 : public RegionMap +{ +public: + RegionMapV1(); + ~RegionMapV1(); + + virtual WaterRegionType ReturnRegionType(const glm::vec3& location, int32 grid_id=0) const; + virtual bool InWater(const glm::vec3& location, int32 grid_id=0) const; + virtual bool InLava(const glm::vec3& location, int32 grid_id=0) const; + virtual bool InLiquid(const glm::vec3& location) const; + virtual bool InPvP(const glm::vec3& location) const; + virtual bool InZoneLine(const glm::vec3& location) const; + + virtual void IdentifyRegionsInGrid(Client* client, const glm::vec3& location) const; + virtual void MapRegionsNearSpawn(Spawn* spawn, Client* client=0) const; + virtual void UpdateRegionsNearSpawn(Spawn* spawn, Client* client=0) const; + virtual void TicRegionsNearSpawn(Spawn* spawn, Client* client=0) const; + + virtual void InsertRegionNode(ZoneServer* zone, int32 version, std::string regionName, std::string envName, uint32 gridID, uint32 triggerWidgetID, float dist = 0.0f); + virtual void RemoveRegionNode(std::string regionName); +protected: + virtual bool Load(FILE *fp, std::string inZoneLowerName, int32 regionVersion); + +private: + WaterRegionType BSPReturnRegionType(int32 node_number, const glm::vec3& location, int32 gridid=0) const; + WaterRegionType BSPReturnRegionTypeNode(const Region_Node* node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode=0.0f) const; + + WaterRegionType BSPReturnRegionWaterRegion(const Region_Node* node, const ZBSP_Node* BSP_Root, int32 node_number, const glm::vec3& location, float distToNode=0.0f) const; + map Regions; + + WaterRegionType EstablishDistanceAtAngle(const Region_Node* region_node, const ZBSP_Node* current_node, float distance, float absDistance, float absSplitDist, bool checkEdgedAngle=false) const; + + std::string TestFile(std::string testFile); + + friend class RegionMap; + + int32 mVersion; + std::string mZoneNameLower; + + mutable std::shared_mutex MRegions; + std::map dead_nodes; +}; + +#endif diff --git a/source/WorldServer/classes.cpp b/source/WorldServer/classes.cpp new file mode 100644 index 0000000..ba9d7fd --- /dev/null +++ b/source/WorldServer/classes.cpp @@ -0,0 +1,199 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +#include "../common/Log.h" +#include "classes.h" +#include "../common/MiscFunctions.h" +#include + +Classes::Classes(){ + class_map["COMMONER"] = 0; + class_map["FIGHTER"] = 1; + class_map["WARRIOR"] = 2; + class_map["GUARDIAN"] = 3; + class_map["BERSERKER"] = 4; + class_map["BRAWLER"] = 5; + class_map["MONK"] = 6; + class_map["BRUISER"] = 7; + class_map["CRUSADER"] = 8; + class_map["SHADOWKNIGHT"] = 9; + class_map["PALADIN"] = 10; + class_map["PRIEST"] = 11; + class_map["CLERIC"] = 12; + class_map["TEMPLAR"] = 13; + class_map["INQUISITOR"] = 14; + class_map["DRUID"] = 15; + class_map["WARDEN"] = 16; + class_map["FURY"] = 17; + class_map["SHAMAN"] = 18; + class_map["MYSTIC"] = 19; + class_map["DEFILER"] = 20; + class_map["MAGE"] = 21; + class_map["SORCERER"] = 22; + class_map["WIZARD"] = 23; + class_map["WARLOCK"] = 24; + class_map["ENCHANTER"] = 25; + class_map["ILLUSIONIST"] = 26; + class_map["COERCER"] = 27; + class_map["SUMMONER"] = 28; + class_map["CONJUROR"] = 29; + class_map["NECROMANCER"] = 30; + class_map["SCOUT"] = 31; + class_map["ROGUE"] = 32; + class_map["SWASHBUCKLER"] = 33; + class_map["BRIGAND"] = 34; + class_map["BARD"] = 35; + class_map["TROUBADOR"] = 36; + class_map["DIRGE"] = 37; + class_map["PREDATOR"] = 38; + class_map["RANGER"] = 39; + class_map["ASSASSIN"] = 40; + class_map["ANIMALIST"] = 41; + class_map["BEASTLORD"] = 42; + class_map["SHAPER"] = 43; + class_map["CHANNELER"] = 44; + class_map["ARTISAN"] = 45; + class_map["CRAFTSMAN"] = 46; + class_map["PROVISIONER"] = 47; + class_map["WOODWORKER"] = 48; + class_map["CARPENTER"] = 49; + class_map["OUTFITTER"] = 50; + class_map["ARMORER"] = 51; + class_map["WEAPONSMITH"] = 52; + class_map["TAILOR"] = 53; + class_map["SCHOLAR"] = 54; + class_map["JEWELER"] = 55; + class_map["SAGE"] = 56; + class_map["ALCHEMIST"] = 57; +} + +int8 Classes::GetBaseClass(int8 class_id) { + int8 ret = 0; + if(class_id>=WARRIOR && class_id <= PALADIN) + ret = FIGHTER; + if((class_id>=CLERIC && class_id <= DEFILER) || (class_id == SHAPER || class_id == CHANNELER)) + ret = PRIEST; + if(class_id>=SORCERER && class_id <= NECROMANCER) + ret = MAGE; + if(class_id>=ROGUE && class_id <= BEASTLORD) + ret = SCOUT; + LogWrite(WORLD__DEBUG, 5, "World", "%s returning base class ID: %i", __FUNCTION__, ret); + return ret; +} + +int8 Classes::GetSecondaryBaseClass(int8 class_id){ + int8 ret = 0; + if(class_id==GUARDIAN || class_id == BERSERKER) + ret = WARRIOR; + if(class_id==MONK || class_id == BRUISER) + ret = BRAWLER; + if(class_id==SHADOWKNIGHT || class_id == PALADIN) + ret = CRUSADER; + if(class_id==TEMPLAR || class_id == INQUISITOR) + ret = CLERIC; + if(class_id==WARDEN || class_id == FURY) + ret = DRUID; + if(class_id==MYSTIC || class_id == DEFILER) + ret = SHAMAN; + if(class_id==WIZARD || class_id == WARLOCK) + ret = SORCERER; + if(class_id==ILLUSIONIST || class_id == COERCER) + ret = ENCHANTER; + if(class_id==CONJUROR || class_id == NECROMANCER) + ret = SUMMONER; + if(class_id==SWASHBUCKLER || class_id == BRIGAND) + ret = ROGUE; + if(class_id==TROUBADOR || class_id == DIRGE) + ret = BARD; + if(class_id==RANGER || class_id == ASSASSIN) + ret = PREDATOR; + if(class_id==BEASTLORD) + ret = ANIMALIST; + if(class_id == CHANNELER) + ret = SHAPER; + LogWrite(WORLD__DEBUG, 5, "World", "%s returning secondary class ID: %i", __FUNCTION__, ret); + return ret; +} + +int8 Classes::GetTSBaseClass(int8 class_id) { + int8 ret = 0; + if (class_id + 42 >= ARTISAN) + ret = ARTISAN - 44; + else + ret = class_id; + + LogWrite(WORLD__DEBUG, 5, "World", "%s returning base tradeskill class ID: %i", __FUNCTION__, ret); + return ret; +} + +int8 Classes::GetSecondaryTSBaseClass(int8 class_id) { + int8 ret = class_id + 42; + if (ret == ARTISAN) + ret = ARTISAN - 44; + else if (ret >= CRAFTSMAN && ret < OUTFITTER) + ret = CRAFTSMAN - 44; + else if (ret >= OUTFITTER && ret < SCHOLAR) + ret = OUTFITTER - 44; + else if (ret >= SCHOLAR) + ret = SCHOLAR - 44; + else + ret = class_id; + + LogWrite(WORLD__DEBUG, 5, "World", "%s returning secondary tradeskill class ID: %i", __FUNCTION__, ret); + return ret; +} + +sint8 Classes::GetClassID(const char* name){ + string class_name = string(name); + class_name = ToUpper(class_name); + if(class_map.count(class_name) == 1) { + LogWrite(WORLD__DEBUG, 5, "World", "%s returning class ID: %i for class name %s", __FUNCTION__, class_map[class_name], class_name.c_str()); + return class_map[class_name]; + } + LogWrite(WORLD__WARNING, 0, "World", "Could not find class_id in function: %s (return -1)", __FUNCTION__); + return -1; +} + +const char* Classes::GetClassName(int8 class_id){ + map::iterator itr; + for(itr = class_map.begin(); itr != class_map.end(); itr++){ + if(itr->second == class_id) { + LogWrite(WORLD__DEBUG, 5, "World", "%s returning class name: %s for class_id %i", __FUNCTION__, itr->first.c_str(), class_id); + return itr->first.c_str(); + } + } + LogWrite(WORLD__WARNING, 0, "World", "Could not find class name in function: %s (return 0)", __FUNCTION__); + return 0; +} + +string Classes::GetClassNameCase(int8 class_id) { + map::iterator itr; + for (itr = class_map.begin(); itr != class_map.end(); itr++){ + if (itr->second == class_id) { + string class_name = string(itr->first); + transform(itr->first.begin() + 1, itr->first.end(), class_name.begin() + 1, ::tolower); + class_name[0] = ::toupper(class_name[0]); + LogWrite(WORLD__DEBUG, 5, "World", "%s returning class name: %s for class_id %i", __FUNCTION__, class_name.c_str(), class_id); + return class_name; + } + } + LogWrite(WORLD__WARNING, 0, "World", "Could not find class name in function: %s (return blank)", __FUNCTION__); + return ""; +} diff --git a/source/WorldServer/classes.h b/source/WorldServer/classes.h new file mode 100644 index 0000000..58beefe --- /dev/null +++ b/source/WorldServer/classes.h @@ -0,0 +1,119 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef CLASSES_CH +#define CLASSES_CH +#include "../common/types.h" +#include +using namespace std; + +#define COMMONER 0 +#define FIGHTER 1 +#define WARRIOR 2 +#define GUARDIAN 3 +#define BERSERKER 4 +#define BRAWLER 5 +#define MONK 6 +#define BRUISER 7 +#define CRUSADER 8 +#define SHADOWKNIGHT 9 +#define PALADIN 10 +#define PRIEST 11 +#define CLERIC 12 +#define TEMPLAR 13 +#define INQUISITOR 14 +#define DRUID 15 +#define WARDEN 16 +#define FURY 17 +#define SHAMAN 18 +#define MYSTIC 19 +#define DEFILER 20 +#define MAGE 21 +#define SORCERER 22 +#define WIZARD 23 +#define WARLOCK 24 +#define ENCHANTER 25 +#define ILLUSIONIST 26 +#define COERCER 27 +#define SUMMONER 28 +#define CONJUROR 29 +#define NECROMANCER 30 +#define SCOUT 31 +#define ROGUE 32 +#define SWASHBUCKLER 33 +#define BRIGAND 34 +#define BARD 35 +#define TROUBADOR 36 +#define DIRGE 37 +#define PREDATOR 38 +#define RANGER 39 +#define ASSASSIN 40 +#define ANIMALIST 41 +#define BEASTLORD 42 +#define SHAPER 43 +#define CHANNELER 44 + +//Tradeskills +// 0 - transmuting/tinkering +#define ARTISAN 45 // 1 +#define CRAFTSMAN 46 // 2 +#define PROVISIONER 47 // 3 +#define WOODWORKER 48 // 4 +#define CARPENTER 49 // 5 +#define OUTFITTER 50 // 6 +#define ARMORER 51 // 7 +#define WEAPONSMITH 52 // 8 +#define TAILOR 53 // 9 +#define SCHOLAR 54 // 10 +#define JEWELER 55 // 11 +#define SAGE 56 // 12 +#define ALCHEMIST 57 // 13 +//43 - artisan + //44 - craftsman + //45 - provisioner + //46 - Woodworker + //47 - carpenter + //48 - armorer + //49 - weaponsmith + //50 - tailor + //51 - + //52 - jeweler + //53 - sage + //54 - alch +#define CLASSIC_MAX_ADVENTURE_CLASS 40 // there is a 41, but its 'scantestbase' +#define CLASSIC_MAX_TRADESKILL_CLASS 13 +#define MAX_CLASSES 58 + +class Classes { +public: + Classes(); + char* GetEQClassName(int8 class_, int8 level); + const char* GetClassName(int8 class_id); + string GetClassNameCase(int8 class_id); + sint8 GetClassID(const char* name); + int8 GetBaseClass(int8 class_id); + int8 GetSecondaryBaseClass(int8 class_id); + int8 GetTSBaseClass(int8 class_id); + int8 GetSecondaryTSBaseClass(int8 class_id); + +private: + map class_map; +}; +#endif + diff --git a/source/WorldServer/client.cpp b/source/WorldServer/client.cpp new file mode 100644 index 0000000..342b6d8 --- /dev/null +++ b/source/WorldServer/client.cpp @@ -0,0 +1,13140 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. + +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +#include "../common/Log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Player.h" +#include "Commands/Commands.h" +#include "ClientPacketFunctions.h" +#include "../common/ConfigReader.h" +#include "Guilds/Guild.h" +#include "Variables.h" +#include "Rules/Rules.h" +#include "Titles.h" +#include "Chat/Chat.h" +#include "SpellProcess.h" +#include "Zone/ChestTrap.h" +#include "../../common/GlobalHeaders.h" + +//#include "Quests.h" + +#ifdef WIN32 +#include +#include +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include +#ifdef FREEBSD +#include +#endif +#include +#include +#include +#endif + +#if defined(__GNUC__) +#define _snprintf snprintf +#endif + + +#include "client.h" +#include "../common/emu_opcodes.h" +#include "../common/packet_dump.h" +#include "WorldDatabase.h" +#include "races.h" +#include "classes.h" +#include "LoginServer.h" +#include "World.h" +#include "../common/EQ2_Common_Structs.h" +#include "net.h" +#include "../common/MiscFunctions.h" +#include "Skills.h" +#include "LuaInterface.h" +#include "Quests.h" +#include "Collections/Collections.h" +#include "Achievements/Achievements.h" +#include "Traits/Traits.h" +#include "Recipes/Recipe.h" +#include "Tradeskills/Tradeskills.h" +#include "AltAdvancement/AltAdvancement.h" +#include "Bots/Bot.h" +#include "VisualStates.h" + +extern WorldDatabase database; +extern const char* ZONE_NAME; +extern LoginServer loginserver; +extern sint32 numclients; +extern NetConnection net; +extern Commands commands; +extern ClientList client_list; +extern ZoneList zone_list; +extern ZoneAuth zone_auth; +extern MasterItemList master_item_list; +extern MasterSkillList master_skill_list; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern MasterQuestList master_quest_list; +extern MasterFactionList master_faction_list; +extern MasterRecipeList master_recipe_list; +extern volatile bool RunLoops; +extern ConfigReader configReader; +extern LuaInterface* lua_interface; +extern World world; +extern Variables variables; +extern Classes classes; +extern Races races; +extern GuildList guild_list; +extern MasterCollectionList master_collection_list; +extern MasterAchievementList master_achievement_list; +extern RuleManager rule_manager; +extern Chat chat; +extern MasterAAList master_aa_list; +extern MasterAAList master_tree_nodes; +extern ChestTrapList chest_trap_list; +extern MasterRecipeBookList master_recipebook_list; +extern VisualStates visual_states; + +using namespace std; + +Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125), quest_pos_timer(2000), lua_debug_timer(30000), delayTimer(500), transmuteID(0), temp_placement_timer(10), spawn_removal_timer(250) { + eqs = ieqs; + ip = eqs->GetrIP(); + port = ntohs(eqs->GetrPort()); + merchant_transaction = nullptr; + mail_window.item = nullptr; // don't want this to be set(loose ptr) when using ResetSendMail to provide rest of the defaults + ResetSendMail(false); + timestamp_flag = 0; + current_quest_id = 0; + last_update_time = 0; + quest_updates = false; + + //autobootup_timeout = new Timer(10000); + //autobootup_timeout->Disable(); + + CLE_keepalive_timer = new Timer(15000); + connect = new Timer(1000); + connect->Disable(); + zoneID = 0; + account_name[0] = 0; + character_id = 0; + account_id = 0; + pwaitingforbootup = 0; + current_zone = 0; + connected_to_zone = false; + connected = false; + camp_timer = 0; + linkdead_timer = 0; + client_zoning = false; + zoning_id = 0; + zoning_x = 0; + zoning_y = 0; + zoning_z = 0; + zoning_instance_id = 0; + player_pos_changed = false; + player_pos_timer = Timer::GetCurrentTime2()+1000; + enabled_player_pos_timer = true; + ++numclients; + if (world.GetServerStatisticValue(STAT_SERVER_MOST_CONNECTIONS) < numclients) + world.UpdateServerStatistic(STAT_SERVER_MOST_CONNECTIONS, numclients, true); + remove_from_list = false; + new_client_login = NewLoginState::LOGIN_NONE; + UpdateWindowTitle(0); + num_active_failures = 0; + player = new Player(); + player->SetClient(this); + combine_spawn = 0; + lua_debug = false; + ready_for_spawns = false; + ready_for_updates = false; + lua_debug_timer.Disable(); + transport_spawn = 0; + MBuyBack.SetName("Client::MBuyBack"); + MDeletePlayer.SetName("Client::MDeletePlayer"); + MQuestPendingUpdates.SetName("Client::MQuestPendingUpdates"); + search_items = 0; + version = 0; + next_conversation_id = 0; + pending_guild_invite.guild = 0; + pending_guild_invite.invited_by = 0; + m_recipeListSent = false; + m_resurrect.SetName("Client::m_resurrect"); + current_rez.expire_timer = 0; + current_rez.should_delete = true; + pending_last_name = 0; + should_target = false; + initial_spawns_sent = false; + MQuestTimers.SetName("Client::quest_timers"); + memset(&incoming_paperdoll, 0, sizeof(incoming_paperdoll)); + on_auto_mount = false; + should_load_spells = true; + spawnPlacementMode = ServerSpawnPlacementMode::DEFAULT; + delayedLogin = false; + delayedAccountID = 0; + delayedAccessKey = 0; + delayTimer.Disable(); + tempPlacementSpawn = nullptr; + placement_unique_item_id = 0; + SetHasOwnerOrEditAccess(false); + temporary_transport_id = 0; + rejoin_group_id = 0; + lastRegionRemapTime = 0; + regionDebugMessaging = false; + client_reloading_zone = false; + last_saved_timestamp = 0; + MQueueStateCmds.SetName("Client::MQueueStateCmds"); + save_spell_state_timer.Disable(); + save_spell_state_time_bucket = 0; + player_loading_complete = false; + MItemDetails.SetName("Client::MItemDetails"); + MSpellDetails.SetName("Client::MSpellDetails"); + hasSentTempPlacementSpawn = false; + spawn_removal_timer.Start(); + disable_save = false; + SetZoningDestination(nullptr); + underworld_cooldown_timer.Disable(); + player_pos_change_count = 0; + pov_ghost_spawn_id = 0; + recipe_orig_packet = nullptr; + recipe_xor_packet = nullptr; + recipe_packet_count = 0; + recipe_orig_packet_size = 0; +} + +Client::~Client() { + RemoveClientFromZone(); + + //let the stream factory know were done with this stream + if (eqs) { + eqs->Close(); + try { + eqs->ReleaseFromUse(); + } + catch (...) {} + } + eqs = NULL; + + //safe_delete(autobootup_timeout); + + safe_delete(linkdead_timer); + vector::iterator itr; + QueuedQuest* queued_quest = 0; + for (itr = quest_queue.begin(); itr != quest_queue.end(); itr++) { + queued_quest = *itr; + safe_delete(queued_quest); + } + quest_queue.clear(); + + vector::iterator rwd_itr; + QuestRewardData* quest_rwd_data = 0; + for (rwd_itr = quest_pending_reward.begin(); rwd_itr != quest_pending_reward.end(); rwd_itr++) { + quest_rwd_data = *rwd_itr; + safe_delete(quest_rwd_data); + } + quest_pending_reward.clear(); + + safe_delete(CLE_keepalive_timer); + safe_delete(connect); + --numclients; + UpdateWindowTitle(0); +} + + +void Client::RemoveClientFromZone() { + if(player && player->GetZone()) + player->GetZone()->GetSpellProcess()->RemoveSpellTimersFromSpawn(player, true, false, true, true); + + if (GetTempPlacementSpawn() && GetCurrentZone()) { + Spawn* tmp = GetTempPlacementSpawn(); + SetTempPlacementSpawn(nullptr); + GetCurrentZone()->RemoveSpawn(tmp, true, false, true, true, true); + } + + if (current_zone && player) { + if (player->GetGroupMemberInfo()) { + TempRemoveGroup(); + } + world.GetGroupManager()->ClearPendingInvite(player); + } + if (lua_interface) + lua_interface->RemoveDebugClients(this); + + if (player) + zone_list.RemoveClientFromMap(player->GetName(), this); + + safe_delete(camp_timer); + safe_delete(search_items); + safe_delete(current_rez.expire_timer); + safe_delete(pending_last_name); + safe_delete_array(incoming_paperdoll.image_bytes); + + MDeletePlayer.writelock(__FUNCTION__, __LINE__); + player = nullptr; + MDeletePlayer.releasewritelock(__FUNCTION__, __LINE__); + + deque::iterator itr; + MBuyBack.writelock(__FUNCTION__, __LINE__); + for (itr = buy_back_items.begin(); itr != buy_back_items.end();) { + safe_delete(*itr); + itr = buy_back_items.erase(itr); + } + MBuyBack.releasewritelock(__FUNCTION__, __LINE__); +} + + +void Client::QueuePacket(EQ2Packet* app, bool attemptedCombine) { + if (eqs) { + if (!eqs->CheckActive()) { + client_list.Remove(this); + eqs = 0; + } + } + if (app && eqs && version > 0) + eqs->EQ2QueuePacket(app, attemptedCombine); + else { + safe_delete(app); + } + +} + +void Client::PopulateSkillMap() { + EQ2Packet* app = master_skill_list.GetPopulateSkillsPacket(GetVersion()); + if (app) + QueuePacket(app); + else { + LogWrite(WORLD__ERROR, 0, "World", "Unable to send populate skills packet for version: %i!", GetVersion()); + Disconnect(); //the client cant proceed without the skill packet, might as well kick it now + } + +} + +void Client::SendLoginInfo() { + if(GetPlayer()->IsReturningFromLD()) + firstlogin = true; + + if (firstlogin) { + LogWrite(WORLD__DEBUG, 0, "World", "Increment Server_Accepted_Connection + 1"); + world.UpdateServerStatistic(STAT_SERVER_ACCEPTED_CONNECTION, 1); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Populate Skill Map..."); + PopulateSkillMap(); + // JA: Check client version and move player to valid zone if current client does not support last saved zone (loading SF client on DoV saved zone) IT CAN HAPPEN! + LogWrite(MISC__TODO, 1, "TODO", "Check client version at login, move char if invalid zone file"); + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Toggle Character Online..."); + database.ToggleCharacterOnline(this, 1); + + int32 count = 0; + + if(!GetPlayer()->IsReturningFromLD()) + { + count = database.LoadCharacterTitles(GetCharacterID(), player); + if (count == 0) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "No character titles found!"); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Initializing starting values - Titles"); + database.UpdateStartingTitles(GetCharacterID(), player->GetAdventureClass(), player->GetRace(), player->GetGender()); + } + } + + if(!GetPlayer()->IsReturningFromLD()) + { + count = database.LoadCharacterLanguages(GetCharacterID(), player); + if (count == 0) + LogWrite(CCLIENT__DEBUG, 0, "Client", "No character languages loaded!"); + + count = database.LoadPlayerRecipeBooks(GetCharacterID(), player); + if (count == 0) + LogWrite(CCLIENT__DEBUG, 0, "Client", "No character recipe books found!"); + } + + ClientPacketFunctions::SendLoginAccepted(this); + + ClientPacketFunctions::SendAbilities ( this ); + + ClientPacketFunctions::SendCommandNamePacket(this); + + ClientPacketFunctions::SendQuickBarInit(this); + + // we only need to send the MOTD if it is the first time the person is logging in. + if (firstlogin) { + ClientPacketFunctions::SendMOTD(this); + ClientPacketFunctions::SendCharacterMacros(this); + zone_list.CheckFriendList(this); + } + + if(!GetPlayer()->IsReturningFromLD()) + { + database.LoadCharacterItemList(GetAccountID(), GetCharacterID(), player, GetVersion()); + if (firstlogin && player->item_list.GetNumberOfItems() == 0 && player->GetEquipmentList()->GetNumberOfItems() == 0) //re-add starting items if missing + { + LogWrite(CCLIENT__WARNING, 0, "Client", "Player has no items - reloading starting items: '%s' (%u)", player->GetName(), GetCharacterID()); + database.UpdateStartingItems(GetCharacterID(), player->GetAdventureClass(), player->GetRace()); + database.LoadCharacterItemList(GetAccountID(), GetCharacterID(), player, GetVersion()); + } + GetPlayer()->item_list.SetMaxItemIndex(); + database.LoadPlayerFactions(this); + database.LoadCharacterQuests(this); + database.LoadCharacterQuestRewards(this); + database.LoadPlayerMail(this); + } + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(true, 0, false); + + if (version > 561) // right version? possibly not! + master_aa_list.DisplayAA(this, 0, 3); + + if (version > 373) + SendCollectionList(); + SendBiography(); + + map::iterator itr; + Quest* quest = 0; + GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); + for (itr = player->player_quests.begin(); itr != player->player_quests.end(); itr++) { + quest = itr->second; + if (quest->IsTracked()) { + quest->SetTracked(false); + QueuePacket(itr->second->QuestJournalReply(version, GetNameCRC(), player)); + quest->SetTracked(true); + QueuePacket(itr->second->QuestJournalReply(version, GetNameCRC(), player)); + } + } + GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + + // SendAchievementsList(); + + /*Guild* guild = player->GetGuild(); + if (guild) { + guild->UpdateGuildMemberInfo(GetPlayer()); + if (firstlogin) + guild->SendGuildMOTD(this); + guild->SendGuildUpdate(this); + guild->SendGuildMember(GetPlayer(), firstlogin); + guild->SendGuildEventList(this); + guild->SendGuildBankEventList(this); + guild->SendAllGuildEvents(this); + guild->SendGuildMemberList(this); + }*/ + if (version > 373) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Faction Updates..."); + EQ2Packet* outapp = player->GetFactions()->FactionUpdate(GetVersion()); + if (outapp) { + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //DumpPacket(outapp); + QueuePacket(outapp); + } + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Command List..."); + ClientPacketFunctions::SendCommandList(this); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Language Updates..."); + + // kos doesn't like one of these language or instance list + SendLanguagesUpdate(database.GetCharacterCurrentLang(GetCharacterID(), player)); + + //ClientPacketFunctions::SendInstanceList(this); + + SendZoneInfo(); + /*Spell* spell = 0; + vector::iterator itr; + for(itr = player->GetQuickbar()->begin(); itr != player->GetQuickbar()->end(); itr++){ + if((*itr)->type == 1){ + spell = master_spell_list.GetSpell((*itr)->id); + if(spell) + QueuePacket(spell->serialize(this, false, 0x20)); + } + }*/ +} + +void Client::SendPlayerDeathWindow() +{ + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendPlayerDeathWindow"); + vector* results = GetCurrentZone()->GetRevivePoints(this); + vector::iterator itr; + + if (results && results->size() > 0) + { + PacketStruct* packet = configReader.getStruct("WS_DeathWindow", GetVersion()); + if (packet) + { + packet->setArrayLengthByName("location_count", results->size()); + RevivePoint* point = 0; + int32 i = 0; + + for (itr = results->begin(); itr != results->end(); itr++, i++) + { + point = *itr; + if (point) + { + packet->setArrayDataByName("location_id", point->id, i); + //zone_name = database.GetZoneName(point->zone_id); + string zone_name = database.GetZoneDescription(point->zone_id); + if (zone_name.length() > 0) + packet->setArrayDataByName("zone_name", zone_name.c_str(), i); + packet->setArrayDataByName("location_name", point->location_name.c_str(), i); + packet->setArrayDataByName("distance", GetPlayer()->GetDistance(point->x, point->y, point->z), i); + } + + if (point->id == 0xFFFFFFFF)//tmp location + safe_delete(point); + } +#if EQDEBUG >= 3 + LogWrite(CCLIENT__DEBUG, 0, "Client", "WS_DeathWindow Packet:"); + packet->PrintPacket(); +#endif + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + // done with the revive points so lets free up the pointer + safe_delete(results); + } + +} + +void Client::DisplayDeadWindow() +{ + LogWrite(ZONE__DEBUG, 0, "Zone", "DisplayDeadWindow()"); + + player->SetHP(0); + player->SetPower(0); + GetCurrentZone()->TriggerCharSheetTimer(); + + if(GetVersion() <= 561) { + ClientPacketFunctions::SendServerControlFlagsClassic(this, 8, 1); + ClientPacketFunctions::SendServerControlFlagsClassic(this, 16, 1); + } + else { + ClientPacketFunctions::SendServerControlFlags(this, 1, 8, 1); + ClientPacketFunctions::SendServerControlFlags(this, 1, 16, 1); + } + + PacketStruct* packet = configReader.getStruct("WS_ServerUpdateTarget", GetVersion()); + if (packet) + { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + SendPlayerDeathWindow(); + +} + +void Client::HandlePlayerRevive(int32 point_id) +{ + if(GetVersion() <= 561) { + ClientPacketFunctions::SendServerControlFlagsClassic(this, 8, 0); + ClientPacketFunctions::SendServerControlFlagsClassic(this, 16, 0); + } + else { + ClientPacketFunctions::SendServerControlFlags(this, 1, 8, 0); + ClientPacketFunctions::SendServerControlFlags(this, 1, 16, 0); + } + + SimpleMessage(CHANNEL_NARRATIVE, "You regain consciousness!"); + PacketStruct* packet = configReader.getStruct("WS_Resurrected", GetVersion()); + if (packet) + { + QueuePacket(packet->serialize()); + safe_delete(packet); + } + float origX, origY, origZ, origHeading = 0.0f; + + origX = player->GetX(); + origY = player->GetY(); + origZ = player->GetZ(); + origHeading = player->GetHeading(); + ZoneServer* originalZone = GetCurrentZone(); + int32 origGridID = GetPlayer()->GetLocation(); + + float x, y, z, heading; + RevivePoint* revive_point = 0; + if (point_id != 0xFFFFFFFF) + revive_point = GetCurrentZone()->GetRevivePoint(point_id); + + string zone_desc; + const char* location_name = "Unknown"; + + player->SetAlive(true); + player->SetResurrecting(true); + player->SetHP(player->GetTotalHP()); + player->SetPower(player->GetTotalPower()); + + //revive at zone safe coords + if (!revive_point) + { + LogWrite(CCLIENT__WARNING, 0, "Client", "No Revive Point! Spawning player at safe coordinates!"); + x = GetCurrentZone()->GetSafeX(); + y = GetCurrentZone()->GetSafeY(); + z = GetCurrentZone()->GetSafeZ(); + heading = GetCurrentZone()->GetSafeHeading(); + zone_desc = GetCurrentZone()->GetZoneDescription(); + location_name = "Zone Safe Point"; + Zone(GetCurrentZone()->GetZoneName(), false); + } + else + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Sending player to chosen Revive Point."); + x = revive_point->x; + y = revive_point->y; + z = revive_point->z; + heading = revive_point->heading; + zone_desc = database.GetZoneDescription(revive_point->zone_id); + location_name = revive_point->location_name.c_str(); + Zone(GetCurrentZone()->GetZoneName(), false); + } + player->SetX(x); + player->SetY(y); + player->SetZ(z); + player->SetHeading(heading); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Attempt Revive @ %s, %.2f, %.2f, %.2f, %.2f, HP: %i, Pow: %i, %s", + zone_desc.c_str(), + player->GetX(), + player->GetY(), + player->GetZ(), + player->GetHeading(), + player->GetHP(), + player->GetPower(), + location_name); + + //player->ClearEverything(); + Save(); + + if (revive_point && revive_point->zone_id != GetCurrentZone()->GetZoneID() && revive_point->zone_id != 0) + { + string zone_name = database.GetZoneName(revive_point->zone_id); + if (zone_name.length() == 0) + { + LogWrite(CCLIENT__WARNING, 0, "Client", "Unable to zone player to revive zone ID '%u', using current zone's safe coords.", revive_point->zone_id); + x = GetCurrentZone()->GetSafeX(); + y = GetCurrentZone()->GetSafeY(); + z = GetCurrentZone()->GetSafeZ(); + heading = GetCurrentZone()->GetSafeHeading(); + location_name = "Zone Safe Point"; + } + else + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Sending player to revive zone ID '%u', using current zone's safe coords.", revive_point->zone_id); + location_name = revive_point->location_name.c_str(); + //player->ClearEverything(); + Save(); + Zone(zone_name.c_str(), false); + } + } + + zone_desc = GetCurrentZone()->GetZoneDescription(); + Message(CHANNEL_NARRATIVE, "Reviving in %s at %s.", zone_desc.c_str(), location_name); + player->SetSpawnType(4); + if (version > 373) { + packet = configReader.getStruct("WS_CancelMoveObjectMode", GetVersion()); + if (packet) + { + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + + packet = configReader.getStruct("WS_TeleportWithinZone", GetVersion()); + if (packet) + { + packet->setDataByName("x", x); + packet->setDataByName("y", y); + packet->setDataByName("z", z); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + SendControlGhost(); + + packet = configReader.getStruct("WS_SetPOVGhostCmd", GetVersion()); + if (packet) + { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + if(rule_manager.GetGlobalRule(R_Combat, EnableSpiritShards)->GetBool()) + { + NPC* shard = player->InstantiateSpiritShard(origX, origY, origZ, origHeading, origGridID, originalZone); + + if(shard->GetSpawnScript() && strlen(shard->GetSpawnScript()) > 0) + originalZone->CallSpawnScript(shard, SPAWN_SCRIPT_PRESPAWN); + + originalZone->RemoveSpawn(player, false, true, true, true, true); + + originalZone->AddSpawn(shard); + + if(shard->GetSpawnScript() && strlen(shard->GetSpawnScript()) > 0) + originalZone->CallSpawnScript(shard, SPAWN_SCRIPT_SPAWN); + } + + m_resurrect.writelock(__FUNCTION__, __LINE__); + if (current_rez.active) + current_rez.should_delete = true; + m_resurrect.releasewritelock(__FUNCTION__, __LINE__); +} + +void Client::SendControlGhost(int32 send_id, int8 unknown2) { + PacketStruct* packet = configReader.getStruct("WS_SetControlGhost", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", send_id); + packet->setDataByName("speed", GetPlayer()->GetSpeed()); + packet->setDataByName("size", 0.51); + packet->setDataByName("unknown2", unknown2); + packet->setDataByName("air_speed", player->GetAirSpeed()); + EQ2Packet* app = packet->serialize(); + QueuePacket(app); + safe_delete(packet); + } +} + +void Client::SendCharInfo() { + EQ2Packet* app; + + player->SetEquippedItemAppearances(); + + ClientPacketFunctions::SendCharacterData(this); + + SendCharPOVGhost(); + + SendControlGhost(player->GetIDWithPlayerSpawn(player), 255); + + //sending bad spawn packet? + + //SendAchievementsList(); + //if (version > 561) + //ClientPacketFunctions::SendHousingList(this); + + ClientPacketFunctions::SendCharacterSheet(this); + ClientPacketFunctions::SendTraitList(this);// moved from below + ClientPacketFunctions::SendAbilities(this); + + ClientPacketFunctions::SendSkillBook(this); + if (!IsReloadingZone() && !player->IsResurrecting() && GetVersion() >= 546) { + ClientPacketFunctions::SendUpdateSpellBook(this); + } + else { + player->SetResurrecting(false); + } + + GetCurrentZone()->AddSpawn(player); + if(IsReloadingZone() && (zoning_x || zoning_y || zoning_z)) { + GetPlayer()->SetX(zoning_x); + GetPlayer()->SetY(zoning_y); + GetPlayer()->SetZ(zoning_z); + GetPlayer()->SetHeading(zoning_h); + + EQ2Packet* packet = GetPlayer()->Move(zoning_x, zoning_y, zoning_z, GetVersion(), zoning_h); + QueuePacket(packet); + } + //SendCollectionList(); + Guild* guild = player->GetGuild(); + if (guild) + guild->GuildMemberLogin(this, firstlogin); + + app = player->GetPlayerItemList()->serialize(GetPlayer(), GetVersion()); + if (app) { + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //DumpPacket(app); + QueuePacket(app); + } + app = player->GetEquipmentList()->serialize(GetVersion(), player); + if (app) { + QueuePacket(app); + } + + app = player->GetAppearanceEquipmentList()->serialize(GetVersion(), player); + if (app) { + QueuePacket(app); + } + + vector* items = player->GetPlayerItemList()->GetItemsFromBagID(-3); // bank items + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) { + EQ2Packet* outapp = items->at(i)->serialize(GetVersion(), false, GetPlayer()); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //DumpPacket(outapp); + QueuePacket(outapp); + } + } + + if (firstlogin && (app = chat.GetWorldChannelList(this)) != NULL) + QueuePacket(app); + + safe_delete(items); + items = player->GetPlayerItemList()->GetItemsFromBagID(-4); //shared bank items + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) + QueuePacket(items->at(i)->serialize(GetVersion(), false, GetPlayer())); + } + safe_delete(items); + if (version >= 373) { + SendTitleUpdate(); + } + + GetPlayer()->UpdateWeapons(); + if(!GetPlayer()->IsReturningFromLD()) { + database.LoadBuyBacks(this); + } + if (version > 561) + master_aa_list.DisplayAA(this, 0, 0); + + string zone_motd = GetCurrentZone()->GetZoneMOTD(); + if (zone_motd.length() > 0 && zone_motd[0] != ' ') { + string zone_motd_send = "Zone MOTD: " + zone_motd; + SimpleMessage(CHANNEL_NARRATIVE, zone_motd_send.c_str()); + } + const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "player_entry", GetCurrentZone(), GetPlayer()); + this->client_zoning = false; + this->zoning_id = 0; + this->zoning_instance_id = 0; + SetZoningDestination(nullptr); + + if (player->GetHP() < player->GetTotalHP() || player->GetPower() < player->GetTotalPower()) + GetCurrentZone()->AddDamagedSpawn(player); + + if (firstlogin) + firstlogin = false; + + player->ClearProcs(); + items = player->GetEquippedItemList(); + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) { + Item* item = items->at(i); + if (item && item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "equipped", item, player); + } + } + + //Allow this player to change their last name if they meet the level requirement + if (!player->get_character_flag(CF_ENABLE_CHANGE_LASTNAME) && player->GetLevel() >= rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8()) + player->set_character_flag(CF_ENABLE_CHANGE_LASTNAME); + + safe_delete(items); + + if (!player->Alive()) + DisplayDeadWindow(); + + ClientPacketFunctions::SendLocalizedTextMessage(this); + + if (GetCurrentZone()->GetInstanceID()) + { + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(GetCurrentZone()->GetInstanceID()); + if (ph) { + //HouseZone* hz = world.GetHouseZone(ph->house_id); + string name = string(GetPlayer()->GetName()); + if (name.compare(ph->player_name) == 0) + SetHasOwnerOrEditAccess(true); + } + } + + bool groupMentor = false; + GetPlayer()->group_id = rejoin_group_id; + if(!world.RejoinGroup(this, rejoin_group_id)) + GetPlayer()->group_id = 0; + else + { + Entity* ent = world.GetGroupManager()->IsPlayerInGroup(rejoin_group_id, GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id); + if(ent && ent->IsPlayer()) + { + GetPlayer()->SetMentorStats(ent->GetLevel(), ent->GetID(), false); + groupMentor = true; + } + } + + if(!groupMentor) + GetPlayer()->SetMentorStats(GetPlayer()->GetLevel(), 0, false); + + if(!GetPlayer()->IsReturningFromLD()) { + database.LoadCharacterSpellEffects(GetCharacterID(), this, DB_TYPE_MAINTAINEDEFFECTS); + database.LoadCharacterSpellEffects(GetCharacterID(), this, DB_TYPE_SPELLEFFECTS); + } + else { + Spawn* pet_spawn = nullptr; + if(GetPlayer()->GetPet()) + pet_spawn = GetPlayer()->GetPet(); + else if(GetPlayer()->GetCharmedPet()) + pet_spawn = GetPlayer()->GetCharmedPet(); + else if(GetPlayer()->GetCosmeticPet()) + pet_spawn = GetPlayer()->GetCosmeticPet(); + else if(GetPlayer()->GetDeityPet()) + pet_spawn = GetPlayer()->GetDeityPet(); + + if(pet_spawn) { + GetPlayer()->GetInfoStruct()->set_pet_id(GetPlayer()->GetIDWithPlayerSpawn(pet_spawn)); + } + } + + GetPlayer()->SetSaveSpellEffects(false); + GetPlayer()->SetCharSheetChanged(true); + GetPlayer()->SetReturningFromLD(false); +} + +void Client::SendZoneSpawns() { + //Allows us to place spawns almost anywhere + if (version > 373) { + uchar blah[] = { 0x00,0x3C,0x1C,0x46,0x00,0x3C,0x1C,0x46,0x00,0x3C,0x1C,0x46 }; + EQ2Packet* app = new EQ2Packet(OP_MoveableObjectPlacementCriteri, blah, sizeof(blah)); + QueuePacket(app); + } + + ClientPacketFunctions::SendSkillSlotMappings(this); + ClientPacketFunctions::SendGameWorldTime(this); + GetCurrentZone()->StartZoneInitialSpawnThread(this); +} + +void Client::SendCharPOVGhost() { + bool use_ghost_pov = false; + PacketStruct* set_pov = configReader.getStruct("WS_SetPOVGhostCmd", GetVersion()); + int32 ghost_id = 0; + if (set_pov) { + if(pov_ghost_spawn_id) { + Spawn* spawn = GetCurrentZone()->GetSpawnByID(pov_ghost_spawn_id); + ghost_id = player->GetIDWithPlayerSpawn(spawn); + if(spawn) { + use_ghost_pov = true; + } + } + if(use_ghost_pov) { + set_pov->setDataByName("spawn_id", ghost_id); + } + else { + set_pov->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(player)); + } + EQ2Packet* app_pov = set_pov->serialize(); + QueuePacket(app_pov); + safe_delete(set_pov); + } + +} + +void Client::SendZoneInfo() { + ZoneServer* zone = GetCurrentZone(); + if (zone) { + EQ2Packet* packet = zone->GetZoneInfoPacket(this); + QueuePacket(packet); + if (version > 561) { + PacketStruct* fog_packet = configReader.getStruct("WS_FogInit", GetVersion()); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + fog_packet->PrintPacket(); +#endif + + if (fog_packet) { + database.LoadFogInit(zone->GetZoneFile(), fog_packet); + QueuePacket(fog_packet->serialize()); + safe_delete(fog_packet); + } + + zone->SendFlightPathsPackets(this); + } + } + /* + uchar blah[] ={0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x01,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + ,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x10,0x49,0x2B,0x62,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00}; + EQ2Packet* appA = new EQ2Packet(OP_GuildUpdateMsg, blah, sizeof(blah)); + QueuePacket(appA); + + uchar blahA[] ={0x45,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00 + ,0x00,0x10,0xE2,0x10,0x6C,0x00,0x00,0x00,0x00}; + EQ2Packet* appB = new EQ2Packet(OP_KeymapDataMsg, blahA, sizeof(blahA)); + QueuePacket(appB); + */ + + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendFriendList"); + SendFriendList(); + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendIgnoreList"); + SendIgnoreList(); +} + +void Client::SendDefaultGroupOptions() { + /* + 0 - loot method + 1 - loot items rarity + 2 - Auto split coin + 4 - default yell method + 6 - group autolock + 7 - solo autolock + */ + PacketStruct* default_options = configReader.getStruct("WS_DefaultGroupOptions", GetVersion()); + if (default_options) { + default_options->setDataByName("loot_method", GetPlayer()->GetInfoStruct()->get_group_loot_method()); + default_options->setDataByName("loot_items_rarity", GetPlayer()->GetInfoStruct()->get_group_loot_items_rarity()); + default_options->setDataByName("auto_split_coin", GetPlayer()->GetInfoStruct()->get_group_auto_split()); + default_options->setDataByName("default_yell_method", GetPlayer()->GetInfoStruct()->get_group_default_yell()); + default_options->setDataByName("group_autolock", GetPlayer()->GetInfoStruct()->get_group_autolock()); + default_options->setDataByName("default_group_lock_method", GetPlayer()->GetInfoStruct()->get_group_lock_method()); + if(GetVersion() > 561) { + default_options->setDataByName("solo_autolock", GetPlayer()->GetInfoStruct()->get_group_solo_autolock()); + default_options->setDataByName("auto_loot_method", GetPlayer()->GetInfoStruct()->get_group_auto_loot_method()); + } + EQ2Packet* app7 = default_options->serialize(); + QueuePacket(app7); + safe_delete(default_options); + } +} + +bool Client::HandlePacket(EQApplicationPacket* app) { + bool ret = true; + //cout << "INCOMING PACKET!!!!!!!: " << app->GetOpcodeName() << endl; + //DumpPacket(app); +#if EQDEBUG >= 9 + LogWrite(PACKET__DEBUG, 9, "Packet", "[EQDEBUG] Received Packet:"); + DumpPacket(app, true); +#endif + + EmuOpcode opcode = app->GetOpcode(); + +#if EQDEBUG >= 9 + const char* name = app->GetOpcodeName(); + if (name) + cout << name; + else + cout << "Unknown"; + cout << " Packet: OPCode: 0x" << hex << setw(2) << setfill('0') << app->GetOpcode() << dec << ", size: " << setw(5) << setfill(' ') << app->Size() << endl; + DumpPacket(app); +#endif + + //if (opcode != OP_UpdatePositionMsg) { + // LogWrite(PACKET__DEBUG, 0, "opcode %s received", app->GetOpcodeName()); + //} + + if (!connected_to_zone && opcode != OP_LoginByNumRequestMsg) + { + opcode = _maxEmuOpcode; // skip since this is not a valid packet, sent before we allowed the login + } + + switch (opcode) { + case _maxEmuOpcode: + break; + case OP_LoginByNumRequestMsg: { + LogWrite(OPCODE__DEBUG, 0, "Opcode", "Opcode 0x%X (%i): OP_LoginByNumRequestMsg", opcode, opcode); + + PacketStruct* request; + request = configReader.getStruct("LoginByNumRequest", 1); + if (request) { + if(request->LoadPacketData(app->pBuffer, app->size)) { + // test the original location of Version for clients older than 1212 + version = request->getType_int16_ByName("version"); + + if (version == 0 || version >= 1208 || EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) { + // must be new client data version method, re-fetch the packet + safe_delete(request); + request = configReader.getStruct("LoginByNumRequest", 1208); + + if (request && request->LoadPacketData(app->pBuffer, app->size)) { + // Xinux suggests using an INT16 here. Our first new version = 57000 + version = request->getType_int16_ByName("version"); + } + else { + LogWrite(LOGIN__ERROR, 0, "Login", "Nasty Horrible things happening. Tell a dev asap! Version: %i", version); + break; + } + } + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) { + LogWrite(WORLD__ERROR, 0, "World", "Incompatible version: %i", version); + ClientPacketFunctions::SendLoginDenied(this); + + /* reset version and protect server from trying to send packets out to a bad client + ** cause of Dec 6th/Dec 7th 2023 crash + ** Client::MakeSpawnChangePacket + ** int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); <-- crashes pulling opcode with bad version + */ + version = 546; + ready_for_updates = false; + ready_for_spawns = false; + return false; + } + + int32 account_id = request->getType_int32_ByName("account_id"); + int32 access_code = request->getType_int32_ByName("access_code"); + + if (!HandleNewLogin(account_id, access_code)) + return false; + } + } + safe_delete(request); + break; + } + case OP_DefaultGroupOptionsMsg: { + PacketStruct* packet = configReader.getStruct("WS_DefaultGroupOptions", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + packet->PrintPacket(); + int8 loot_method = packet->getType_int8_ByName("loot_method"); + int8 loot_items_rarity = packet->getType_int8_ByName("loot_items_rarity"); + int8 auto_split_coin = packet->getType_int8_ByName("auto_split_coin"); + int8 default_yell_method = packet->getType_int8_ByName("default_yell_method"); + int8 autolock = packet->getType_int8_ByName("group_autolock"); + int8 group_lock_method = packet->getType_int8_ByName("default_group_lock_method"); + int8 solo_autolock = packet->getType_int8_ByName("solo_autolock"); + int8 auto_loot_method = 0; + + if (GetVersion() > 561) { + auto_loot_method = packet->getType_int8_ByName("auto_loot_method"); + if (auto_loot_method > AutoLootMode::METHOD_DECLINE) + auto_loot_method = AutoLootMode::METHOD_DECLINE; + } + GetPlayer()->GetInfoStruct()->set_group_loot_method(loot_method); + GetPlayer()->GetInfoStruct()->set_group_loot_items_rarity(loot_items_rarity); + GetPlayer()->GetInfoStruct()->set_group_auto_split(auto_split_coin); + GetPlayer()->GetInfoStruct()->set_group_default_yell(default_yell_method); + GetPlayer()->GetInfoStruct()->set_group_autolock(autolock); + GetPlayer()->GetInfoStruct()->set_group_lock_method(group_lock_method); + GetPlayer()->GetInfoStruct()->set_group_solo_autolock(solo_autolock); + GetPlayer()->GetInfoStruct()->set_group_auto_loot_method(auto_loot_method); + + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOOTMETHOD, (char*)std::to_string(loot_method).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOOTITEMRARITY, (char*)std::to_string(loot_items_rarity).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPAUTOSPLIT, (char*)std::to_string(auto_split_coin).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPDEFAULTYELL, (char*)std::to_string(default_yell_method).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPAUTOLOCK, (char*)std::to_string(autolock).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPLOCKMETHOD, (char*)std::to_string(group_lock_method).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_GROUPSOLOAUTOLOCK, (char*)std::to_string(solo_autolock).c_str()); + database.insertCharacterProperty(this, CHAR_PROPERTY_AUTOLOOTMETHOD, (char*)std::to_string(auto_loot_method).c_str()); + + if (this->GetPlayer()->GetGroupMemberInfo() && this->GetPlayer()->GetGroupMemberInfo()->leader) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); + if (group) + { + GroupOptions goptions; + goptions.loot_method = loot_method; + goptions.loot_items_rarity = loot_items_rarity; + goptions.auto_split = auto_split_coin; + goptions.default_yell = default_yell_method; + goptions.group_autolock = autolock; + goptions.solo_autolock = solo_autolock; + goptions.auto_loot_method = auto_loot_method; + group->SetDefaultGroupOptions(&goptions); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + } + safe_delete(packet); + } + break; + } + case OP_MapRequest: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MapRequest", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_MapRequest", GetVersion()); + if (packet && app->size > 2 && GetCurrentZone()) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + PacketStruct* fog_packet = configReader.getStruct("WS_FogInit", GetVersion()); + if (fog_packet) { + LogWrite(PACKET__DEBUG, 0, "Packet", "In OP_MapRequest: Fog Packet"); + database.LoadFogInit(packet->getType_EQ2_16BitString_ByName("zone").data, fog_packet); + fog_packet->setDataByName("unknown1", 1); + fog_packet->setDataByName("unknown3", 1); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //fog_packet->PrintPacket(); + QueuePacket(fog_packet->serialize()); + safe_delete(fog_packet); + } + } + safe_delete(packet); + } + break; + } + case OP_RequestCampMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_RequestCampMsg", opcode, opcode); + + PacketStruct* request = configReader.getStruct("WS_RequestCamp", GetVersion()); + if (request && request->LoadPacketData(app->pBuffer, app->size)) { + LogWrite(CCLIENT__DEBUG, 0, "CClient", "Client '%s' (%u) is camping...", GetPlayer()->GetName(), GetPlayer()->GetCharacterID()); + LogWrite(CCLIENT__DEBUG, 0, "CClient", "WS_RequestCamp - quit: %i, camp_desktop: %i, camp_char_select: %i, (to) char_name: %s", + request->getType_int8_ByName("quit"), + request->getType_int8_ByName("camp_desktop"), + request->getType_int16_ByName("camp_char_select"), + (request->getType_EQ2_16BitString_ByName("char_name").data.length() > 0) ? request->getType_EQ2_16BitString_ByName("char_name").data.c_str() : ""); + + //DumpPacket(app->pBuffer, app->size); + //request->PrintPacket(); + + if (!camp_timer) { + int16 camp_time = 20; // default if rule cannot be found + if (GetAdminStatus() >= 100) + camp_time = rule_manager.GetGlobalRule(R_World, GMCampTimer)->GetInt16(); + else + camp_time = rule_manager.GetGlobalRule(R_World, PlayerCampTimer)->GetInt16(); + + PacketStruct* response = configReader.getStruct("WS_Camp", GetVersion()); + if (response) { + bool disconnect = false; + if (request->getType_int8_ByName("camp_desktop") == 1 && request->getType_int8_ByName("quit") == 1) { + // Command: /camp desktop + // Command: /quit + response->setDataByName("camp_desktop", 1); + disconnect = true; + } + else { + // Command: /camp + response->setDataByName("camp_desktop", request->getType_int8_ByName("camp_desktop")); + response->setDataByName("camp_char_select", request->getType_int16_ByName("camp_char_select")); + response->setDataByName("seconds", camp_time); + } + + camp_timer = new Timer(camp_time * 1000); + camp_timer->Enable(); + + if (request->getType_EQ2_16BitString_ByName("char_name").data.length() > 0) { + // /camp {char_name} + response->setDataByName("char_name", request->getType_EQ2_16BitString_ByName("char_name").data.c_str()); + } + else if (request->getType_int8_ByName("camp_desktop") == 0 && request->getType_int16_ByName("camp_char_select") == 0) { + // /camp (go back to char selection screen) + response->setDataByName("char_name", " "); + response->setDataByName("camp_char_select", 1); + } + + LogWrite(CCLIENT__DEBUG, 0, "CClient", "WS_Camp - seconds: %i, camp_desktop: %i, camp_char_select: %i, (to) char_name: %s", + response->getType_int8_ByName("seconds"), + response->getType_int8_ByName("camp_desktop"), + response->getType_int8_ByName("camp_char_select"), + (response->getType_EQ2_16BitString_ByName("char_name").data.length() > 0) ? response->getType_EQ2_16BitString_ByName("char_name").data.c_str() : ""); + + // JA: trying to recognize /camp vs LD (see ZoneServer::ClientProcess()) + if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) + player->SetActivityStatus(player->GetActivityStatus() + ACTIVITY_STATUS_CAMPING); + //response->PrintPacket(); + QueuePacket(response->serialize()); + safe_delete(response); + if (disconnect) + Disconnect(); + } + } + } + safe_delete(request); + break; + } + case OP_StoodMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StoodMsg", opcode, opcode); + if (camp_timer) + { + // JA: clear camping flag + if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) > 0) + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_CAMPING); + safe_delete(camp_timer); + EQ2Packet* outapp = new EQ2Packet(OP_CampAbortedMsg, 0, 0); + QueuePacket(outapp); + } + player->SetTempVisualState(0); + break; + } + case OP_StandMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StandMsg", opcode, opcode); + if (camp_timer) + { + // JA: clear camping flag + if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) > 0) + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_CAMPING); + safe_delete(camp_timer); + EQ2Packet* outapp = new EQ2Packet(OP_CampAbortedMsg, 0, 0); + QueuePacket(outapp); + } + player->SetTempVisualState(539); + break; + } + case OP_SitMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SitMsg", opcode, opcode); + player->SetTempVisualState(538); + break; + } + case OP_SatMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SatMsg", opcode, opcode); + player->SetTempVisualState(540); + break; + } + case OP_QuestJournalOpenMsg: + case OP_QuestJournalInspectMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuestJournalOpenMsg, OP_QuestJournalInspectMsg", opcode, opcode); + if (app->size < sizeof(int32)) + break; + int32 quest_id = 0; + memcpy(&quest_id, app->pBuffer, sizeof(int32)); + GetPlayer()->SendQuest(quest_id); + break; + } + case OP_QuestJournalSetVisibleMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuestJournalSetVisibleMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_QuestJournalVisible", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 quest_id = packet->getType_int32_ByName("quest_id"); + bool hidden = packet->getType_int8_ByName("visible") == 1 ? false : true; + GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); + map* player_quests = player->GetPlayerQuests(); + if (player_quests) { + if (player_quests->count(quest_id) > 0) + player_quests->at(quest_id)->SetHidden(hidden); + else + LogWrite(CCLIENT__ERROR, 0, "Client", "OP_QuestJournalSetVisibleMsg error: Player does not have quest with id of %u", quest_id); + } + else + LogWrite(CCLIENT__ERROR, 0, "Client", "OP_QuestJournalSetVisibleMsg error: Unable to get player(%s) quests", player->GetName()); + GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + } + safe_delete(packet); + } + break; + } + case OP_MacroUpdateMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MacroUpdateMsg", opcode, opcode); + PacketStruct* macro_update = configReader.getStruct("WS_MacroUpdate", GetVersion()); + if (macro_update) { + if(macro_update->LoadPacketData(app->pBuffer, app->size)) { + vector* update = new vector; + int8 number = macro_update->getType_int8_ByName("number"); + int16 icon = macro_update->getType_int16_ByName("icon"); + string name = macro_update->getType_EQ2_8BitString_ByName("name").data; + int8 count = macro_update->getType_int8_ByName("macro_count"); + + if (GetVersion() <= 373) { + update->push_back(macro_update->getType_EQ2_8BitString_ByName("command").data); + } + else { + for (int8 i = 0; i < count; i++) { + char tmp_command[15] = { 0 }; + sprintf(tmp_command, "command_%i", i); + update->push_back(macro_update->getType_EQ2_16BitString_ByName(tmp_command).data); + } + } + if (name.length() == 0) + database.UpdateCharacterMacro(GetCharacterID(), number, 0, icon, update); + else + database.UpdateCharacterMacro(GetCharacterID(), number, name.c_str(), icon, update); + safe_delete(update); + } + safe_delete(macro_update); + } + break; + } + case OP_DialogSelectMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DialogSelectMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_DialogSelect", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 conversation_id = packet->getType_int32_ByName("conversation_id"); + int32 response_index = packet->getType_int32_ByName("response"); + HandleDialogSelectMsg(conversation_id, response_index); + } + } + safe_delete(packet); + break; + } + case OP_CancelMoveObjectModeMsg: { + SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT); + if (GetTempPlacementSpawn()) + { + Spawn* tmp = GetTempPlacementSpawn(); + SetTempPlacementSpawn(nullptr); + SetPlacementUniqueItemID(0); + GetCurrentZone()->RemoveSpawn(tmp, true, false, true, true, true); + break; // break out early if we are tied to a temp spawn + } + + // if we are moving some other object? other use-cases not covered + break; + } + case OP_PositionMoveableObject: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PositionMoveableObject", opcode, opcode); + PacketStruct* place_object = configReader.getStruct("WS_PlaceMoveableObject", GetVersion()); + if (place_object && place_object->LoadPacketData(app->pBuffer, app->size)) { + Spawn* spawn = 0; + + if (GetTempPlacementSpawn()) + spawn = GetTempPlacementSpawn(); + else + spawn = GetPlayer()->GetSpawnWithPlayerID(place_object->getType_int32_ByName("spawn_id")); + + if (!spawn) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find spawn."); + break; + } + else if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE && !HasOwnerOrEditAccess()) + { + SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!"); + break; + } + + + int32 uniqueID = spawn->GetPickupUniqueItemID(); + if(uniqueID) { + Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); + if(uniqueItem && uniqueItem->CheckFlag2(HOUSE_LORE) && GetCurrentZone()->HouseItemSpawnExists(uniqueItem->details.item_id)) { + Message(CHANNEL_COLOR_RED, "Item %s is house lore and you cannot place another.", uniqueItem->name.c_str()); + break; + } + } + + // handles instantiation logic + adding to zone of a new house object + PopulateHouseSpawn(place_object); + + float newHeading = place_object->getType_float_ByName("heading") + 180; + + char query[256]; + + switch (GetSpawnPlacementMode()) + { + case ServerSpawnPlacementMode::OPEN_HEADING: + { + if (spawn && spawn->IsWidget()) + { + Widget* widget = (Widget*)spawn; + widget->SetOpenHeading(newHeading); + widget->SetIncludeHeading(true); + + spawn->position_changed = true; + + _snprintf(query, 256, "open_heading=%f,include_heading=1", newHeading); + if (database.UpdateSpawnWidget(widget->GetWidgetID(), query)) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved widget open heading information."); + } + else + SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn is not widget, unable to set close heading information."); + break; + } + case ServerSpawnPlacementMode::CLOSE_HEADING: + { + if (spawn && spawn->IsWidget()) + { + Widget* widget = (Widget*)spawn; + widget->SetClosedHeading(newHeading); + widget->SetIncludeHeading(true); + + spawn->position_changed = true; + _snprintf(query, 256, "closed_heading=%f,include_heading=1", newHeading); + if (database.UpdateSpawnWidget(widget->GetWidgetID(), query)) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved widget close heading information."); + + if (spawn->GetSpawnLocationID()) + { + Query query; + query.RunQuery2(Q_INSERT, "update spawn_location_placement set heading = %f where id = %u", newHeading, spawn->GetSpawnLocationID()); + } + } + else + SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawn is not widget, unable to set close heading information."); + break; + } + default: + { + spawn->SetX(place_object->getType_float_ByName("x")); + spawn->SetY(place_object->getType_float_ByName("y")); + spawn->SetZ(place_object->getType_float_ByName("z")); + spawn->SetHeading(newHeading); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + if (spawn->GetSpawnLocationID() > 0 && database.UpdateSpawnLocationSpawns(spawn)) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved spawn information."); + else if (spawn->GetSpawnLocationID() > 0) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn information, see console window for details."); + } + } + + PopulateHouseSpawnFinalize(); + + SetSpawnPlacementMode(Client::ServerSpawnPlacementMode::DEFAULT); + } + safe_delete(place_object); + break; + } + case OP_CampAbortedMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_CampAbortedMsg", opcode, opcode); + if (camp_timer) + { + // JA: clear camping flag + if ((player->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) > 0) + player->SetActivityStatus(player->GetActivityStatus() - ACTIVITY_STATUS_CAMPING); + safe_delete(camp_timer); + EQ2Packet* outapp = new EQ2Packet(OP_CampAbortedMsg, 0, 0); + QueuePacket(outapp); + } + break; + } + case OP_DoneLoadingUIResourcesMsg: { + if(GetVersion() <= 561) { + ClientPacketFunctions::SendUpdateSpellBook(this); + } + // need to quickly flash the DoF client the rest of their inventory + if(GetVersion() <= 561) { + EQ2Packet* item_app = player->GetPlayerItemList()->serialize(GetPlayer(), GetVersion()); + if (item_app) { + QueuePacket(item_app); + } + } + + EQ2Packet* app = new EQ2Packet(OP_DoneLoadingUIResourcesMsg, 0, 0); + QueuePacket(app); + if(!player_loading_complete) + { + const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "player_loadcomplete", GetCurrentZone(), GetPlayer()); + player_loading_complete = true; + } + break; + } + case OP_DoneLoadingZoneResourcesMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DoneLoadingZoneResourcesMsg", opcode, opcode); + SendZoneSpawns(); + break; + } + case OP_DefaultGroupOptionsRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DefaultGroupOptionsRequestMsg", opcode, opcode); + SendDefaultGroupOptions(); + break; + } + case OP_DoneLoadingEntityResourcesMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DoneLoadingEntityResourcesMsg", opcode, opcode); + if (!IsReadyForSpawns()) { + if(GetPlayer()->GetMap()) { + auto loc = glm::vec3(GetPlayer()->GetX(), GetPlayer()->GetZ(), GetPlayer()->GetY()); + uint32 GridID = 0; + float new_z = GetPlayer()->FindBestZ(loc, nullptr, &GridID); + GetPlayer()->SetLocation(GridID); + } + SetReadyForSpawns(true); + } + player->CalculateApplyWeight(); + SendCharInfo(); + GetPlayer()->GetZone()->GetSpellProcess()->SendSpellBookUpdate(this); + pos_update.Start(); + quest_pos_timer.Start(); + break; + } + case OP_LootItemsRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_LootItemsRequestMsg", opcode, opcode); + HandleLootItemRequestPacket(app); + break; + } + case OP_StoppedLootingMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StoppedLootingMsg", opcode, opcode); + if (app->size < sizeof(int32)) + break; + + int32 loot_id = 0; + memcpy(&loot_id, app->pBuffer, sizeof(int32)); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(loot_id); + if(spawn) { + spawn->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + spawn->SetLooterSpawnID(0); + } + break; + } + case OP_WaypointSelectMsg: { + PacketStruct* packet = configReader.getStruct("WS_WaypointSelect", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + int32 selection = packet->getType_int32_ByName("selection"); + if (selection > 0) { + SelectWaypoint(selection); + } + } + } + safe_delete(packet); + break; + } + case OP_KnowledgeWindowSlotMappingMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_KnowledgeWindowSlotMappingMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_SpellSlotMapping", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + int num_updates = packet->getType_int16_ByName("spell_count"); + int32 spell_id = 0; + int16 slot_id = 0; + char tmp_spell_id[15]; + char tmp_slot[15]; + for (int i = 0; i < num_updates; i++) { + memset(tmp_spell_id, 0, 15); + memset(tmp_slot, 0, 15); + sprintf(tmp_spell_id, "spell_id_%i", i); + sprintf(tmp_slot, "slot_id_%i", i); + spell_id = packet->getType_int32_ByName(tmp_spell_id); + if (spell_id > 0) { + slot_id = packet->getType_int16_ByName(tmp_slot); + SpellBookEntry* spell = player->GetSpellBookSpell(spell_id); + if (spell && spell->slot != slot_id) { + spell->slot = slot_id; + spell->save_needed = true; + } + } + } + } + safe_delete(packet); + } + //SendKnowledgeWindowSlot(); + break; + } + case OP_ReadyToZoneMsg: { + if (!IsReadyForSpawns()) + { + LogWrite(WORLD__INFO, 0, "World", "OP_ReadyToZone: Player %s is logging into zone, skipping disconnect."); + } + else + { + if(zoning_destination) { + SetCurrentZone(zoning_destination); + } + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ReadyToZoneMsg", opcode, opcode); + bool succeed_override_zone = true; + if(!GetCurrentZone()) { + LogWrite(WORLD__ERROR, 0, "World", "OP_ReadyToZone: Player %s attempting to zone and zone is not there! Emergency boot!", player->GetName()); + if(zoning_instance_id) { + ZoneServer* zone = zone_list.GetByInstanceID(zoning_instance_id, zoning_id, true); + if(!zone) { + LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Emergency boot failed for %s, unable to get zoneserver instance id %u zone id %u.", player->GetName(), zoning_instance_id, zoning_id); + succeed_override_zone = false; + } + else { + SetCurrentZone(zone); + LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Unique instance has been created for %s through emergency boot!", player->GetName()); + + } + } + else if(zoning_id) { + + ZoneServer* zone = zone_list.Get(zoning_id, true, true); + if(!zone) { + LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Emergency boot failed for %s, unable to get zoneserver zone id %u.", player->GetName(), zoning_id); + succeed_override_zone = false; + } + else { + SetCurrentZone(zone); + LogWrite(WORLD__WARNING, 0, "World", "OP_ReadyToZone: Unique zone has been created for %s through emergency boot!", player->GetName()); + } + } + } + + if (client_zoning) + LogWrite(WORLD__INFO, 0, "World", "OP_ReadyToZone: Player %s zoning to %s", player->GetName(), GetCurrentZone()->GetZoneName()); + else + LogWrite(WORLD__ERROR, 0, "World", "OP_ReadyToZone: Player %s attempting to zone without server authorization.", player->GetName()); + Disconnect(); + } + break; + } + case OP_ClientFellMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ClientFellMsg (ouch!)", opcode, opcode); + PacketStruct* request = configReader.getStruct("WS_ClientFell", GetVersion()); + if (request && request->LoadPacketData(app->pBuffer, app->size)) { + float height = request->getType_float_ByName("height"); + /*int32 spawn_id = request->getType_int32_ByName("spawn_id"); + if(GetPlayer()->GetSpawnWithPlayerID(spawn_id) != GetPlayer()){ + cout << "Error: " << GetPlayer()->GetName() << " called ClientFell with an invalid ID of: " << spawn_id << endl; + break; + }*/ + float safe_height = 13.0f; + float safe_skill_with_bonus = GetPlayer()->CalculateSkillWithBonus("Safe Fall", ITEM_STAT_SAFE_FALL, true); + if (safe_skill_with_bonus > 0.0f) + safe_height += (1 + safe_skill_with_bonus) / 5; + + if (height > safe_height) { + int16 damage = (int16)ceil((height - safe_height) * 125); + if (height >= 80) + damage = 30000; + //cout << "Detected fall height:" << height << " damage:" << damage << endl; + if (damage > 0) { + GetPlayer()->TakeDamage(damage); + if (GetPlayer()->GetPlayerStatisticValue(STAT_PLAYER_HIGHEST_FALLING_HIT) < damage) + GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_HIGHEST_FALLING_HIT, damage, true); + if (!GetPlayer()->GetInvulnerable()) + GetPlayer()->SetCharSheetChanged(true); + GetCurrentZone()->SendDamagePacket(0, GetPlayer(), DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, GetPlayer()->GetInvulnerable() ? DAMAGE_PACKET_RESULT_INVULNERABLE : DAMAGE_PACKET_RESULT_SUCCESSFUL, DAMAGE_PACKET_DAMAGE_TYPE_FALLING, damage, 0); + if (GetPlayer()->GetHP() == 0) { + GetCurrentZone()->KillSpawn(false, GetPlayer(), 0); + } + } + } + } + safe_delete(request); + break; + } + case OP_MapFogDataUpdateMsg: { + + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MapFogDataUpdateMsg", opcode, opcode); + LogWrite(MISC__TODO, 3, "TODO", "Handle (OP_MapFogDataUpdateMsg), ignoring it for now\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + break; + } + case OP_SelectZoneTeleporterDestinatio: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SelectZoneTeleporterDestinatio", opcode, opcode); + ProcessTeleportLocation(app); + break; + } + case OP_SendLatestRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SendLatestRequestMsg", opcode, opcode); + if(GetVersion() < 60085) { + // this does not exist in newer clients like AoM, confirmed to exist in DoF, other clients will need review at a later time + uchar blah25[] = { 0x01 }; + EQ2Packet* app25 = new EQ2Packet(OP_ClearDataMsg, blah25, sizeof(blah25)); + QueuePacket(app25); + } + break; + } + case OP_RequestRecipeDetailsMsg: { + PacketStruct* packet = configReader.getStruct("WS_RequestRecipeDetail", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + vector recipes; + int32 recipe_id = 0; + char recipe_prop_name[30]; + int32 num_recipes = packet->getType_int32_ByName("num_recipes"); + // WS_RecipeDetails + for (int32 i = 0; i < num_recipes; i++) { + memset(recipe_prop_name, 0, 30); + snprintf(recipe_prop_name, 30, "recipe_id_%i", i); + recipe_id = packet->getType_int32_ByName(recipe_prop_name); + if(recipe_id > 0) { + recipes.push_back(recipe_id); + } + } + SendRecipeDetails(&recipes); + } + + safe_delete(packet); + } + break; + } + case OP_ShowCreateFromRecipeUIMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ShowCreateFromRecipeUIMsg", opcode, opcode); + break; + /*uchar blah[] ={0x09,0x0e,0x00,0x51,0x75,0x65,0x65,0x6e,0x27,0x73,0x20,0x43,0x6f,0x6c,0x6f,0x6e + ,0x79,0x00,0x00,0x00,0x00,0x40,0x40,0xff,0xff,0xff}; + EQ2Packet* app = new EQ2Packet(OP_EncounterBrokenMsg, blah, sizeof(blah)); + QueuePacket(app); + + uchar blah2[] = {0x00,0x00,0xff,0xff,0xff,0xff}; + app = new EQ2Packet(OP_CreateCharFromCBBRequestMsg, blah2, sizeof(blah2)); + QueuePacket(app); + + uchar blah3[] ={0x09,0x17,0x00,0x5c,0x23,0x46,0x46,0x45,0x34,0x30,0x30,0x20,0x51,0x75,0x65,0x65 + ,0x6e,0x27,0x73,0x20,0x43,0x6f,0x6c,0x6f,0x6e,0x79,0x00,0x00,0x00,0x00,0xa0,0x40 + ,0xff,0xff,0xff}; + app = new EQ2Packet(OP_CreateCharFromCBBRequestMsg, blah3, sizeof(blah3)); + QueuePacket(app); + + uchar blah4[] ={0x0b,0x00,0x21,0x00,0x00,0x00,0x1d,0x81,0x42,0x17,0x81,0x42,0x17,0x81,0x42,0x17 + ,0x81,0x42,0x17,0x81,0x42,0x17,0x81,0x42,0x17,0x81,0x42,0x17,0x81,0x42,0x17,0x81 + ,0x42,0x17,0x81,0x42,0x17,0x81,0x42}; + app = new EQ2Packet(OP_UpdateSpellBookMsg, blah4, sizeof(blah4)); + QueuePacket(app); + uchar blah5[] ={0x00,0x00}; + app = new EQ2Packet(OP_RecipeDetailsMsg, blah5, sizeof(blah5)); + QueuePacket(app); + break;*/ + //player->GetPlayerInfo()->GetInfo()->cur_power = 100; + //EQ2Packet* app = player->GetPlayerInfo()->serialize(1); + //QueuePacket(app); + } + case OP_BeginItemCreationMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BeginItemCreationMsg", opcode, opcode); + //DumpPacket(app->pBuffer, app->size); + PacketStruct* packet = configReader.getStruct("WS_BeginItemCreation", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + Recipe* recipe = master_recipe_list.GetRecipe(GetPlayer()->GetCurrentRecipe()); + if(recipe) { + int32 item = 0; + int8 qty = 0; + vector> items; + char tmp_item_id[30]; + if(GetVersion() > 1193) { + int8 num_primary_selected_items = packet->getType_int8_ByName("num_primary_selected_items"); + for (int8 i = 0; i < num_primary_selected_items; i++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "primary_selected_item_id_%i", i); + item = packet->getType_int32_ByName(tmp_item_id); + sprintf(tmp_item_id, "primary_selected_item_qty_%i", i); + qty = packet->getType_int16_ByName(tmp_item_id); + if (item > 0) + items.push_back(make_pair(item,qty)); + item = 0; + } + } + else { + item = packet->getType_int32_ByName("primary_component_id"); + qty = 1; + if (item > 0) + items.push_back(make_pair(item,qty)); + } + int8 build_components = packet->getType_int8_ByName("num_build_components"); + + if(GetVersion() > 1193) { + for (int8 i = 0; i < build_components; i++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "num_selected_items_%i", i); + int8 num_selected_items = packet->getType_int8_ByName(tmp_item_id); + for (int8 j = 0; j < num_selected_items; j++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "selected_id%i_%i", i,j); + item = packet->getType_int32_ByName(tmp_item_id); + sprintf(tmp_item_id, "selected_qty%i_%i", i, j); + qty = packet->getType_int16_ByName(tmp_item_id); + if (item > 0) + items.push_back(make_pair(item, qty)); + + item = 0; + } + } + } + else { + for (int8 i = 0; i < build_components; i++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "component_id_%i", i); + int32 item = packet->getType_int32_ByName(tmp_item_id); + sprintf(tmp_item_id, "component_qty_%i", i); + qty = packet->getType_int32_ByName(tmp_item_id); + if (item > 0) + items.push_back(make_pair(item, qty)); + } + } + if(GetVersion() > 1193) { + int8 num_fuel_items = packet->getType_int8_ByName("num_fuel_items"); + for (int8 i = 0; i < num_fuel_items; i++) { + memset(tmp_item_id, 0, 30); + sprintf(tmp_item_id, "fuel_id_%i", i); + item = packet->getType_int32_ByName(tmp_item_id); + sprintf(tmp_item_id, "fuel_qty_%i", i); + qty = packet->getType_int16_ByName(tmp_item_id); + if (item > 0) + items.push_back(make_pair(item, qty)); + item = 0; + } + } + else { + + item = packet->getType_int32_ByName("fuel_id"); + qty = packet->getType_int16_ByName("fuel_qty"); + if (item > 0) + items.push_back(make_pair(item, qty)); + } + + GetCurrentZone()->GetTradeskillMgr()->BeginCrafting(this, items); + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "Client '%s' (%u) attempted to call OP_BeginItemCreationMsg, but with no recipe selected.", GetPlayer()->GetName(), GetPlayer()->GetCharacterID()); + } + } + safe_delete(packet); + } + break; + } + case OP_StopItemCreationMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StopItemCreationMsg", opcode, opcode); + //DumpPacket(app->pBuffer, app->size); + GetCurrentZone()->GetTradeskillMgr()->StopCrafting(this); + break; + } + case OP_SysClient: + case OP_SignalMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SysClient/OP_SignalMsg", opcode, opcode); + + PacketStruct* packet = configReader.getStruct("WS_Signal", 1); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + EQ2_16BitString str = packet->getType_EQ2_16BitString_ByName("signal"); + if (strcmp(str.data.c_str(), "sys_client_avatar_ready") == 0) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Client '%s' (%u) is ready for spawn updates.", GetPlayer()->GetName(), GetPlayer()->GetCharacterID()); + SetReloadingZone(false); + if(GetPlayer()->IsDeletedSpawn()) { + GetPlayer()->SetDeletedSpawn(false); + } + ResetZoningCoords(); + SetReadyForUpdates(); + GetPlayer()->SendSpawnChanges(true); + ProcessStateCommands(); + GetPlayer()->changed = true; + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + player_pos_changed = true; + GetPlayer()->AddChangedZoneSpawn(); + ProcessZoneIgnoreWidgets(); + if (version <= 561) { + master_trait_list.ChooseNextTrait(this); + } + + const char* zone_script = world.GetZoneScript(GetPlayer()->GetZone()->GetZoneID()); + + if (zone_script && lua_interface) { + lua_interface->RunZoneScript(zone_script, "enter_location", GetPlayer()->GetZone(), GetPlayer(), GetPlayer()->GetLocation()); + } + + if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower()) + GetCurrentZone()->AddDamagedSpawn(GetPlayer()); + } + else { + LogWrite(CCLIENT__WARNING, 0, "Client", "Player %s reported SysClient/SignalMsg state %s.", GetPlayer()->GetName(), str.data.c_str()); + } + const char* zone_script = world.GetZoneScript(player->GetZone()->GetZoneID()); + if (zone_script && lua_interface) + { + lua_interface->RunZoneScript(zone_script, "signal_changed", player->GetZone(), player, 0, str.data.c_str()); + } + } + safe_delete(packet); + } + break; + } + case OP_EntityVerbsRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EntityVerbsRequestMsg", opcode, opcode); + HandleVerbRequest(app); + break; + } + case OP_EntityVerbsVerbMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EntityVerbsVerbMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_EntityVerbsVerb", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 spawn_id = packet->getType_int32_ByName("spawn_id"); + player->SetTarget(player->GetSpawnWithPlayerID(spawn_id)); + Spawn* spawn = player->GetTarget(); + if (spawn && !spawn->IsNPC() && !spawn->IsPlayer()) { + string command = packet->getType_EQ2_16BitString_ByName("command").data; + + if (!HandleHouseEntityCommands(spawn, spawn_id, command)) + { + if (EntityCommandPrecheck(spawn, command.c_str())) { + if (spawn->IsGroundSpawn()) + ((GroundSpawn*)spawn)->HandleUse(this, command); + else if (spawn->IsObject()) + ((Object*)spawn)->HandleUse(this, command); + else if (spawn->IsWidget()) + ((Widget*)spawn)->HandleUse(this, command); + else if (spawn->IsSign()) + ((Sign*)spawn)->HandleUse(this, command); + } + } + } + else { + EQ2_16BitString command = packet->getType_EQ2_16BitString_ByName("command"); + if (command.size > 0) { + string command_name = command.data; + if (command_name.find(" ") < 0xFFFFFFFF) { + if (GetVersion() <= 561) { //this version uses commands in the form "Buy From Merchant" instead of buy_from_merchant + string::size_type pos = command_name.find(" "); + while(pos != string::npos){ + command_name.replace(pos, 1, "_"); + pos = command_name.find(" "); + } + } + else + command_name = command_name.substr(0, command_name.find(" ")); + } + int32 handler = commands.GetCommandHandler(command_name.c_str()); + if (handler != 0xFFFFFFFF) { + if (command.data == command_name) { + command.data = ""; + command.size = 0; + } + else { + command.data = command.data.substr(command.data.find(" ") + 1); + command.size = command.data.length(); + } + commands.Process(handler, &command, this); + } + else { + if (spawn && spawn->IsNPC()) { + if (EntityCommandPrecheck(spawn, command.data.c_str())) { + if (!((NPC*)spawn)->HandleUse(this, command.data)) { + command_name = command.data; + string::size_type pos = command_name.find(" "); + while (pos != string::npos) { + command_name.replace(pos, 1, "_"); + pos = command_name.find(" "); + } + if (!((NPC*)spawn)->HandleUse(this, command_name)) { //convert the spaces to underscores and see if that makes a difference + LogWrite(WORLD__ERROR, 0, "World", "Unhandled command in OP_EntityVerbsVerbMsg: %s", command.data.c_str()); + } + } + } + } + else + LogWrite(WORLD__ERROR, 0, "World", "Unknown command in OP_EntityVerbsVerbMsg: %s", command.data.c_str()); + } + } + } + } + safe_delete(packet); + } + break; + } + case OP_SkillInfoRequest: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SkillInfoRequest", opcode, opcode); + HandleSkillInfoRequest(app); + break; + } + case OP_UpdateTargetMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_UpdateTargetMsg", opcode, opcode); + int16 index = 0; + memcpy(&index, app->pBuffer, sizeof(int16)); + if (index == 0xFFFF) + GetPlayer()->SetTarget(0); + else { + Spawn* spawn = GetPlayer()->GetSpawnByIndex(index); + if(spawn) + GetPlayer()->SetTarget(spawn); + else { + LogWrite(PLAYER__ERROR, 1, "Player", "Player %s tried to target %u index, but that index was not valid.", GetPlayer()->GetName(), index); + } + } + if (GetPlayer()->GetTarget()) + GetCurrentZone()->CallSpawnScript(GetPlayer()->GetTarget(), SPAWN_SCRIPT_TARGETED, GetPlayer()); + //player->SetTarget((int16*)app->pBuffer); + break; + } + case OP_ExamineInfoRequestMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ExamineInfoRequestMsg", opcode, opcode); + HandleExamineInfoRequest(app); + break; + } + case OP_QuickbarUpdateMsg: + //case OP_QuickbarAddMsg: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuickbarUpdateMsg, OP_QuickbarAddMsg", opcode, opcode); + HandleQuickbarUpdateRequest(app); + break; + } + case OP_PredictionUpdateMsg: { + LogWrite(OPCODE__DEBUG, 7, "Opcode", "Opcode 0x%X (%i): OP_PredictionUpdateMsg from %s", opcode, opcode, GetPlayer()->GetName()); + if (version <= 561) { + int8 offset = 9; + if (app->pBuffer[0] == 0xFF) + offset += 2; + if (app->size > offset) { + if (player->IsCasting()) { + float distance = 0; + float x = player->GetX(); + float y = player->GetY(); + float z = player->GetZ(); + player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); + distance = player->GetDistance(x, y, z, false); + if (distance > .5) + current_zone->Interrupted(player, 0, SPELL_ERROR_INTERRUPTED, false, true); + } + else + player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); + player_pos_changed = true; + + GetPlayer()->changed = true; + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + GetPlayer()->AddChangedZoneSpawn(); + //DumpPacket(app); + } + } + else { + EQ2Packet* app = new EQ2Packet(OP_PredictionUpdateMsg, 0, 0); + QueuePacket(app); + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + } + break; + } + case OP_RemoteCmdMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_RemoteCmdMsg", opcode, opcode); + if (app->size > 0) { + EQ2_CommandString remote(app->pBuffer, app->size); + + LogWrite(PACKET__DEBUG, 1, "Packet", "RemoteCmdMsg Packet dump:"); +#if EQDEBUG >= 9 + DumpPacket(app); +#endif + commands.Process(remote.handler, &remote.command, this); + } + else //bad client, disconnect + Disconnect(); + break; + } + case OP_CancelSpellCast: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_CancelSpellCast", opcode, opcode); + current_zone->Interrupted(player, 0, 0, true); + SimpleMessage(CHANNEL_SPELLS_OTHER, "You stop casting."); + break; + } + case OP_UpdatePositionMsg: { + LogWrite(OPCODE__DEBUG, 7, "Opcode", "Opcode 0x%X (%i): OP_UpdatePositionMsg from %s", opcode, opcode, GetPlayer()->GetName()); + int8 offset = 13; + if (app->pBuffer[0] == 0xFF) + offset += 2; + if (app->size > offset) { + if (player->IsCasting()) { + float distance = 0; + float x = player->GetX(); + float y = player->GetY(); + float z = player->GetZ(); + player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); + distance = player->GetDistance(x, y, z, false); + if (distance > .5) + current_zone->Interrupted(player, 0, SPELL_ERROR_INTERRUPTED, false, true); + } + else + player->PrepareIncomingMovementPacket(app->size - offset, app->pBuffer + offset, version); + player_pos_changed = true; + + GetPlayer()->changed = true; + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + GetPlayer()->AddChangedZoneSpawn(); + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); + //DumpPacket(app); + } + break; + } + case OP_MailSendMessageMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_MailSendMessageMsg", opcode, opcode); + HandleSentMail(app); + break; + } + case OP_StopTrackingMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_StopTrackingMsg", opcode, opcode); + player->GetZone()->RemovePlayerTracking(player, TRACKING_STOP); + break; + } + case OP_BeginTrackingMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BeginTrackingMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_BeginTracking", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 spawn_id = packet->getType_int32_ByName("spawn_id"); + Spawn* spawn = player->GetSpawnWithPlayerID(spawn_id); + if (spawn) { + AddWaypoint(spawn->GetName(), WAYPOINT_CATEGORY_TRACKING, spawn_id); + BeginWaypoint(spawn->GetName(), spawn->GetX(), spawn->GetY(), spawn->GetZ()); + player->GetZone()->RemovePlayerTracking(player, TRACKING_CLOSE_WINDOW); + } + } + safe_delete(packet); + } + break; + } + case OP_BioUpdateMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BioUpdateMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_BioUpdate", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + player->SetBiography(packet->getType_EQ2_16BitString_ByName("biography").data); + } + safe_delete(packet); + } + break; + } + case OP_RewardPackMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_RewardPackMsg", opcode, opcode); + + /* This logging is still here because I remember another system using this packet and just want to make sure we can figure out that it's being sent + when we come across it (scatman) */ + const char* name = app->GetOpcodeName(); + if (name) + LogWrite(WORLD__DEBUG, 0, "World", "%s Received OP_RewardPackMsg %04i", name, app->GetRawOpcode()); + else + LogWrite(WORLD__DEBUG, 0, "World", "Received OP_RewardPackMsg %04i", app->GetRawOpcode()); + //DumpPacket(app); + PacketStruct* packet = configReader.getStruct("WS_RewardPackMsg", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + string recruiter_name = packet->getType_EQ2_16BitString_ByName("recruiter_name").data; + + /* Player has contacted a guild recruiter */ + if (recruiter_name.length() > 0) { + Guild* guild = guild_list.GetGuild(packet->getType_int32_ByName("guild_id")); + Client* recruiter = zone_list.GetClientByCharName(recruiter_name); + if (recruiter && guild) { + Message(CHANNEL_GUILD_EVENT, "Contact request sent to %s of %s.", recruiter->GetPlayer()->GetName(), guild->GetName()); + recruiter->Message(CHANNEL_GUILD_EVENT, "%s [%u %s], [0 Unskilled] (%s) is requesting to speak to YOU about joining the guild.", player->GetName(), player->GetLevel(), classes.GetClassNameCase(player->GetAdventureClass()).c_str(), races.GetRaceNameCase(player->GetRace())); + recruiter->PlaySound("ui_guild_page"); + } + } + /* New picture taken for guild recruiting */ + else { + //DumpPacket(app->pBuffer, app->size); + int32 guild_id = 0; + int16 picture_data_size = 0; + unsigned char* recruiter_picture_data = 0; + memcpy(&guild_id, app->pBuffer + 4, sizeof(int32)); + memcpy(&picture_data_size, app->pBuffer + 15, sizeof(int16)); + Guild* guild = guild_list.GetGuild(guild_id); + if (guild) { + GuildMember* gm = guild->GetGuildMember(player); + if (gm) { + safe_delete_array(gm->recruiter_picture_data); + recruiter_picture_data = new unsigned char[picture_data_size]; + for (int16 i = 0; i < picture_data_size; i++) + memcpy(recruiter_picture_data + i, app->pBuffer + 17 + i, 2); + gm->recruiter_picture_data = recruiter_picture_data; + gm->recruiter_picture_data_size = picture_data_size; + guild->SetMemberSaveNeeded(true); + } + } + } + } + safe_delete(packet); + } + break; + } + case OP_PetOptions: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PetOptions", opcode, opcode); + Spawn* target = player->GetTarget(); + PacketStruct* packet = configReader.getStruct("WS_PetOptions", GetVersion()); + if (packet && target && (target == player->GetPet() || target == player->GetCharmedPet() || target == player->GetDeityPet() || target == player->GetCosmeticPet())) { + bool change = false; + if(packet->LoadPacketData(app->pBuffer, app->size)) { + string name = packet->getType_EQ2_16BitString_ByName("pet_name").data; + if (strlen(name.c_str()) != 0 && SetPetName(name.c_str())) { + target->SetName(name.c_str()); + GetCurrentZone()->SendUpdateTitles(target); + change = true; + } + + int8 pet_behavior = player->GetInfoStruct()->get_pet_behavior(); + // Check protect self setting and update if needed + if (packet->getType_int8_ByName("protect_self") == 1) { + if ((pet_behavior & 2) == 0) { + player->GetInfoStruct()->set_pet_behavior(pet_behavior + 2); + change = true; + } + } + else { + if ((pet_behavior & 2) != 0) { + player->GetInfoStruct()->set_pet_behavior(pet_behavior - 2); + change = true; + } + } + + // Check protect master setting and update if needed + if (packet->getType_int8_ByName("protect_master") == 1) { + if ((pet_behavior & 1) == 0) { + player->GetInfoStruct()->set_pet_behavior(pet_behavior + 1); + change = true; + } + } + else { + if ((pet_behavior & 1) != 0) { + player->GetInfoStruct()->set_pet_behavior(pet_behavior - 1); + change = true; + } + } + + int8 pet_movement = player->GetInfoStruct()->get_pet_movement(); + // Check stay/follow setting and update if needed + if (packet->getType_int8_ByName("stay_follow_toggle") == 1) { + if (pet_movement != 2) { + player->GetInfoStruct()->set_pet_movement(2); + change = true; + } + } + else { + if (pet_movement != 1) { + player->GetInfoStruct()->set_pet_movement(1); + change = true; + } + } + + // Ranged/Melee settings are not implemented yet + + if (change) + player->SetCharSheetChanged(true); + + } + safe_delete(packet); + } + break; + } + case OP_RecipeBook: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_RecipeBook", opcode, opcode); + SendRecipeList(); + break; + } + case OP_BuyPlayerHouseMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_BuyPlayerHouseMsg", opcode, opcode); + //DumpPacket(app); + int64 bank_money = GetPlayer()->GetBankCoinsPlat(); + PacketStruct* packet = configReader.getStruct("WS_BuyHouse", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int64 house_id = 0; + if(GetVersion() <= 561) { + house_id = packet->getType_int32_ByName("house_id"); + } + else { + house_id = packet->getType_int64_ByName("house_id"); + } + HouseZone* hz = world.GetHouseZone(house_id); + if (hz) { + bool got_bank_money = BankHasCoin(hz->cost_coin); + int8 disable_alignment_req = rule_manager.GetGlobalRule(R_Player, DisableHouseAlignmentRequirement)->GetInt8(); + std::vector houses = world.GetAllPlayerHouses(GetCharacterID()); + if (houses.size() > 24) + { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You already own 25 houses and may not own another."); + safe_delete(packet); + break; + } + if(disable_alignment_req && hz->alignment > 0 && hz->alignment != GetPlayer()->GetAlignment()) + { + std::string req = "You must be of "; + if (hz->alignment == 1) + req.append("Good"); + else + req.append("Evil"); + req.append(" alignment to purchase this house"); + SimpleMessage(CHANNEL_COLOR_YELLOW, req.c_str()); + safe_delete(packet); + break; + } + int32 status_req = hz->cost_status; + int32 available_status = player->GetInfoStruct()->get_status_points(); + if (status_req <= available_status && (!hz->cost_coin || (hz->cost_coin && player->RemoveCoins(hz->cost_coin)))) + + { + player->GetInfoStruct()->subtract_status_points(status_req); + ZoneServer* instance_zone = zone_list.GetByInstanceID(0, hz->zone_id, false, false); + int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; // 604800 = 7 days + int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, instance_zone->GetInstanceID(), upkeep_due); + world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, instance_zone->GetInstanceID(), upkeep_due, 0, 0, GetPlayer()->GetName()); + //ClientPacketFunctions::SendHousingList(this); + PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } + else if (status_req <= available_status && got_bank_money == 1) { + player->GetInfoStruct()->subtract_status_points(status_req); + bool bankwithdrawl = BankWithdrawalNoBanker(hz->cost_coin); + + //this should NEVER happen since we check with got_bank_money, however adding it here should something go nutty. + if (bankwithdrawl == 0) { + PlaySound("buy_failed"); + SimpleMessage(CHANNEL_COLOR_RED, "There was an error in bankwithdrawl function."); + safe_delete(packet); + break; + } + + ZoneServer* instance_zone = zone_list.GetByInstanceID(0, hz->zone_id, false, false); + int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; // 604800 = 7 days + int64 unique_id = database.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, instance_zone->GetInstanceID(), upkeep_due); + world.AddPlayerHouse(GetPlayer()->GetCharacterID(), hz->id, unique_id, instance_zone->GetInstanceID(), upkeep_due, 0, 0, GetPlayer()->GetName()); + PlayerHouse* ph = world.GetPlayerHouseByUniqueID(unique_id); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } + else + { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have enough money to purchase the house."); + PlaySound("buy_failed"); + } + } + } + } + + safe_delete(packet); + break; + } + case OP_EnterHouseMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EnterHouseMsg", opcode, opcode); + //DumpPacket(app); + PacketStruct* packet = configReader.getStruct("WS_EnterHouse", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + PlayerHouse* ph = nullptr; + HouseZone* hz = nullptr; + int64 house_id = 0; + int32 spawn_index = 0; + + if(GetVersion() <= 561) { + spawn_index = packet->getType_int32_ByName("house_id"); + } + else { + house_id = packet->getType_int64_ByName("house_id"); + } + + ZoneServer* house = GetHouseZoneServer(spawn_index, house_id); + if (house) { + Zone(house, true); + } + } + } + + safe_delete(packet); + break; + } + case OP_PayHouseUpkeepMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_PayHouseUpkeepMsg", opcode, opcode); + + PacketStruct* packet = configReader.getStruct("WS_PayUpkeep", GetVersion()); + + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int64 house_id = 0; + + if(GetVersion() <= 561) { + house_id = packet->getType_int32_ByName("house_id"); + } + else { + house_id = packet->getType_int64_ByName("house_id"); + } + HouseZone* hz = nullptr; + PlayerHouse* ph = world.GetPlayerHouse(this, GetVersion() <= 561 ? house_id : 0, GetVersion() > 561 ? house_id : 0, &hz); + if (ph) + { + if (!hz) + { + Message(CHANNEL_COLOR_YELLOW, "HouseZone ID %u does NOT exist!", ph->house_id); + safe_delete(packet); + break; + } + + int32 upkeep_due = Timer::GetUnixTimeStamp() + 604800; + if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0) + { + upkeep_due = ph->upkeep_due + 604800; // 604800 = 7 days + + if (upkeep_due > (Timer::GetUnixTimeStamp() + 7257600)) // 84 days max upkeep to pay https://eq2.zam.com/wiki/Housing_%28EQ2%29#Upkeep + { + Message(CHANNEL_COLOR_YELLOW, "You cannot pay more than 3 months of upkeep."); + PlaySound("buy_failed"); + safe_delete(packet); + break; + } + } + bool escrowChange = false; + int64 statusReq = hz->upkeep_status; + int64 tmpRecoverStatus = 0; + if(ph->escrow_status && statusReq >= ph->escrow_status ) + { + escrowChange = true; + tmpRecoverStatus = ph->escrow_status; + statusReq -= ph->escrow_status; + ph->escrow_status = 0; + } + else if (ph->escrow_status && statusReq && statusReq <= ph->escrow_status) + { + escrowChange = true; + ph->escrow_status -= statusReq; + tmpRecoverStatus = statusReq; + statusReq = 0; + } + + int64 coinReq = hz->upkeep_coin; + int64 tmpRecoverCoins = 0; + if (ph->escrow_coins && coinReq >= ph->escrow_coins) // more required to upkeep than in escrow, subtract what we have left + { + escrowChange = true; + tmpRecoverCoins = ph->escrow_coins; + coinReq -= ph->escrow_coins; + ph->escrow_coins = 0; + } + else if (ph->escrow_coins && coinReq && coinReq <= ph->escrow_coins) + { + escrowChange = true; + // more than enough in escrow, subtract and make our cost 0! + ph->escrow_coins -= coinReq; + tmpRecoverCoins = coinReq; + coinReq = 0; + } + + int32 available_status_points = player->GetInfoStruct()->get_status_points(); + if(!statusReq || (statusReq && statusReq <= available_status_points)) + { + if(coinReq && player->RemoveCoins(coinReq)) + coinReq = 0; + + if(!coinReq && statusReq && player->GetInfoStruct()->subtract_status_points(statusReq)) + statusReq = 0; + } + bool got_bank_money = BankHasCoin(hz->upkeep_coin); + if (!coinReq && !statusReq) // TODO: Need option to take from bank if player does not have enough coin on them + { + database.AddHistory(ph, GetPlayer()->GetName(), "Paid Upkeep", Timer::GetUnixTimeStamp(), hz->upkeep_coin, 0, 0); + + if (escrowChange) + database.UpdateHouseEscrow(ph->house_id, ph->instance_id, ph->escrow_coins, ph->escrow_status); + + ph->upkeep_due = upkeep_due; + database.SetHouseUpkeepDue(GetCharacterID(), ph->house_id, ph->instance_id, ph->upkeep_due); + //ClientPacketFunctions::SendHousingList(this); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } + else if (!statusReq && got_bank_money == 1) { + bool bankwithdrawl = BankWithdrawalNoBanker(hz->upkeep_coin); + + //this should NEVER happen since we check with got_bank_money, however adding it here should something go nutty. + if (bankwithdrawl == 0) { + PlaySound("buy_failed"); + SimpleMessage(CHANNEL_COLOR_RED, "There was an error in bankwithdrawl function."); + safe_delete(packet); + break; + } + + database.AddHistory(ph, GetPlayer()->GetName(), "Paid Upkeep", Timer::GetUnixTimeStamp(), hz->upkeep_coin, 0, 0); + + if (escrowChange) + database.UpdateHouseEscrow(ph->house_id, ph->instance_id, ph->escrow_coins, ph->escrow_status); + + ph->upkeep_due = upkeep_due; + database.SetHouseUpkeepDue(GetCharacterID(), ph->house_id, ph->instance_id, ph->upkeep_due); + ClientPacketFunctions::SendBaseHouseWindow(this, hz, ph, GetVersion() <= 561 ? house_id : this->GetPlayer()->GetID()); + PlaySound("coin_cha_ching"); + } + else + { + // recover the escrow we were going to use but could not spend due to lack of funds + if (tmpRecoverCoins) + ph->escrow_coins += tmpRecoverCoins; + if(tmpRecoverStatus) + ph->escrow_status += tmpRecoverStatus; + + SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have enough money or status to pay for upkeep."); + PlaySound("buy_failed"); + } + } + else + Message(CHANNEL_COLOR_YELLOW, "PlayerHouse ID %u does NOT exist!", house_id); + } + } + + safe_delete(packet); + break; + } + case OP_ExitHouseMsg: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ExitHouseMsg", opcode, opcode); + int32 instance_id = GetCurrentZone()->GetInstanceID(); + if (instance_id > 0) { + PlayerHouse* ph = world.GetPlayerHouseByInstanceID(instance_id); + if (ph) { + HouseZone* hz = world.GetHouseZone(ph->house_id); + if (hz) { + ZoneServer* new_zone = zone_list.Get(hz->exit_zone_id); + + // determine if this is an instanced zone that already exists + ZoneServer* instance_zone = GetPlayer()->GetGroupMemberInZone(hz->exit_zone_id); + if (instance_zone || new_zone) { + GetPlayer()->SetX(hz->exit_x); + GetPlayer()->SetY(hz->exit_y); + GetPlayer()->SetZ(hz->exit_z); + GetPlayer()->SetHeading(hz->exit_heading); + if (instance_zone) + Zone(instance_zone->GetInstanceID(), false, true); + else + Zone(new_zone, false); + } + } + } + } + break; + } + case OP_QuestJournalWaypointMsg: { + //DumpPacket(app); + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_QuestJournalWaypointMsg", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_QuestJournalWaypoint", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + if(GetVersion() <= 561) { + int32 quest_id = packet->getType_int32_ByName("quest_id"); + GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); + if (player->player_quests.count(quest_id) > 0 && player->player_quests[quest_id]) { + if(player->player_quests[quest_id]->GetTracked()) + player->player_quests[quest_id]->SetTracked(false); + else + player->player_quests[quest_id]->SetTracked(true); + + player->player_quests[quest_id]->SetSaveNeeded(true); + } + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + } + else { + int32 quests = packet->getType_int32_ByName("num_quests"); + + if (quests > 100) // just picking a number higher than max allowed + { + LogWrite(CCLIENT__ERROR, 0, "Client", "num_quests = %u - quantity too high, aborting load.", quests); + break; + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "num_quests = %u", quests); + + for (int32 i = 0; i < quests; i++) { + int32 id = packet->getType_int32_ByName("quest_id_0", i); + if (id == 0) + continue; + LogWrite(CCLIENT__DEBUG, 5, "Client", "quest_id = %u", id); + bool tracked = packet->getType_int8_ByName("quest_tracked_0", i) == 1 ? true : false; + GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); + if (player->player_quests.count(id) > 0 && player->player_quests[id]) { + player->player_quests[id]->SetTracked(tracked); + player->player_quests[id]->SetSaveNeeded(true); + } + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + } + } + } + safe_delete(packet); + } + + break; + } + case OP_PaperdollImage: { +/* PacketStruct* packet = configReader.getStruct("WS_PaperdollImage", version); + if (packet && packet->LoadPacketData(app->pBuffer, app->size)) { + + //First check if this is a new image... delete an existing partial image if one exists + int8 packet_index = packet->getType_int8_ByName("packetIndex"); + if (packet_index == 0) { + safe_delete_array(incoming_paperdoll.image_bytes); + incoming_paperdoll.last_received_packet_index = 0; + incoming_paperdoll.current_size_bytes = 0; + } + //return if this packet is not the one we are expecting... + else if (packet_index != incoming_paperdoll.last_received_packet_index + 1) { + safe_delete(packet); + break; + } + + //Check how many packets we're supposed to be receiving for this/these images + incoming_paperdoll.image_num_packets = packet->getType_int8_ByName("totalNumPackets"); + + //Check the image type, if this is a new type in the same series of packets we have a new image + int8 img_type = packet->getType_int8_ByName("image_type"); + if (packet_index != 0 && img_type != incoming_paperdoll.image_type) { + //We have a new image. Save the old data and clear before continuing + SavePlayerImages(); + } + incoming_paperdoll.image_type = img_type; + + //Get the size of the image data in this packet + sint64 image_size = packet->getType_int32_ByName("imageSize"); + if (image_size <= 0 || image_size > 1048576) { + //If this packet is saying that the array is size <= 0 or > 1 MiB return out... it shouldn't be those sizes ever + safe_delete(packet); + break; + } + + //Create a new array + int32 new_image_size = image_size; + uchar* new_image = new uchar[incoming_paperdoll.current_size_bytes + new_image_size]; + if (incoming_paperdoll.image_bytes) { + memcpy(new_image, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + safe_delete_array(incoming_paperdoll.image_bytes); + } + + //variable i should be the index in the packet of the first PNG file byte + vector* d_structs = packet->getStructs(); + vector::iterator itr; + int32 i = 0; + for (itr = d_structs->begin(); itr != d_structs->end(); itr++) { + DataStruct* ds = (*itr); + if (strcmp(ds->GetName(), "pngData_0") != 0) + i += ds->GetDataSizeInBytes(); + else + break; + } + + //Return if this packet is bad and we would read out of bounds + if (app->size - i < new_image_size) { + safe_delete(packet); + safe_delete_array(new_image); + break; + } + + uchar* tmp = new_image + incoming_paperdoll.current_size_bytes; + memcpy(tmp, app->pBuffer + i, new_image_size); + + incoming_paperdoll.current_size_bytes += new_image_size; + incoming_paperdoll.image_bytes = new_image; + + //Check if this is the last packet we're expecting for this image. Create a final image if so + if (incoming_paperdoll.image_num_packets == 1 || + incoming_paperdoll.last_received_packet_index + 2 == incoming_paperdoll.image_num_packets) { + SavePlayerImages(); + } + + incoming_paperdoll.last_received_packet_index = packet_index; + } + safe_delete(packet); +*/ + break; + } + + case OP_ReadyForTakeOffMsg: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_ReadyForTakeOffMsg", opcode, opcode); + + int32 index = GetCurrentZone()->GetFlightPathIndex(GetPendingFlightPath()); + if (GetPendingFlightPath() > 0) { + if (index != -1) { + PacketStruct* packet = configReader.getStruct("WS_ClearForTakeOff", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(GetPlayer())); + packet->setDataByName("path_id", index); + packet->setDataByName("speed", GetCurrentZone()->GetFlightPathSpeed(GetPendingFlightPath())); + QueuePacket(packet->serialize()); + safe_delete(packet); + + on_auto_mount = true; + } + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "OP_ReadyForTakeOffMsg recieved but unable to get an index for path (%u) in zone (%u)", GetPendingFlightPath(), GetCurrentZone()->GetZoneID()); + Message(CHANNEL_ERROR, "Unable to get index for path (%u) in zone (%u)", GetPendingFlightPath(), GetCurrentZone()->GetZoneID()); + EndAutoMount(); + } + + SetPendingFlightPath(0); + + } + else + LogWrite(CCLIENT__ERROR, 0, "Client", "OP_ReadyForTakeOffMsg recieved but there is no pending flight path..."); + + break; + } + + case OP_EarlyLandingRequestMsg: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_EarlyLandingRequestMsg", opcode, opcode); + + EndAutoMount(); + + break; + } + + case OP_SubmitCharCust: + { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_SubmitCharCust", opcode, opcode); + PacketStruct* packet = configReader.getStruct("WS_SubmitCharCust", version); + if (packet && packet->LoadPacketData(app->pBuffer, app->size, GetVersion() <= 561 ? false : true)) { + int8 type = packet->getType_int8_ByName("type"); + if (type == 0) { + /*if (player->custNPC) { + player->custNPCTarget->CustomizeAppearance(packet); + current_zone->SendSpawnChanges(player->custNPCTarget); + } + else {*/ + player->CustomizeAppearance(packet); + current_zone->SendSpawnChanges(player); + //} + } + } + safe_delete(packet); + /* + if (player->custNPC) { + memcpy(&player->appearance, &player->SavedApp, sizeof(AppearanceData)); + memcpy(&player->features, &player->SavedFeatures, sizeof(CharFeatures)); + + if (player->custNPCTarget->IsBot()) + database.SaveBotAppearance((Bot*)player->custNPCTarget); + + player->custNPC = false; + player->custNPCTarget = 0; + player->changed = true; + player->info_changed = true; + current_zone->SendSpawnChanges(player, this); + }*/ + + break; + } + + default: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): Unknown in %s", opcode, opcode, __FILE__); + const char* name = app->GetOpcodeName(); + if (name) + LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", name, app->GetRawOpcode(), app->GetRawOpcode()); + else + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", app->GetRawOpcode(), app->GetRawOpcode()); + + // keeping this around for debugging purposes + DumpPacket(app); + + } + } + if (!eqs || !eqs->CheckActive()) { + return false; + } + + return ret; +} + +bool Client::HandleLootItem(Spawn* entity, Item* item, Spawn* target, bool overrideLootRestrictions) { + if (!item) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find item to loot!"); + return false; + } + int32 conflictItemList = 0, conflictequipmentList = 0, conflictAppearanceEquipmentList = 0; + int16 lore_stack_count = 0; + + Player* lootingPlayer = player; + Client* lootingClient = this; + if (target != nullptr && target != lootingPlayer && target->IsPlayer()) { + lootingPlayer = (Player*)target; + lootingClient = lootingPlayer->GetClient(); + } + + // needs to only be checked before expiration of loot restrictions + if (entity && !overrideLootRestrictions) { + if (entity->GetLootGroupID() > 0 && (!lootingPlayer->GetGroupMemberInfo() || lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID())) { + LogWrite(LOOT__ERROR, 0, "Loot", "%s: Loot Group ID from %s did not match Item: %s (%u), expected group id %u.", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id, entity->GetLootGroupID()); + return false; + } + if (entity->GetLootMethod() != GroupLootMethod::METHOD_FFA) { + switch (entity->GetLootMethod()) { + case GroupLootMethod::METHOD_LEADER: { + if (entity->GetLootGroupID() > 0 && (!lootingPlayer->GetGroupMemberInfo() || (lootingPlayer->GetGroupMemberInfo() && (lootingPlayer->GetGroupMemberInfo()->group_id != entity->GetLootGroupID() || !lootingPlayer->GetGroupMemberInfo()->leader)))) { + LogWrite(LOOT__ERROR, 0, "Loot", "%s: Loot Attempt from %s was not allowed with Item: %s (%u), must be group leader.", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id); + return false; + } + break; + } + case GroupLootMethod::METHOD_LOTTO: + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + if (entity->IsLootTimerRunning()) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: Loot Timer is still running, flag player %s to lotto Item: %s (%u).", entity->GetName(), lootingPlayer->GetName(), item->name.c_str(), item->details.item_id); + return false; + } + break; + } + } + } + } + + if (((conflictItemList = lootingPlayer->item_list.CheckSlotConflict(item, true, true, &lore_stack_count)) == LORE || + (conflictequipmentList = lootingPlayer->equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE || + (conflictAppearanceEquipmentList = lootingPlayer->appearance_equipment_list.CheckSlotConflict(item, true, &lore_stack_count)) == LORE) && !item->CheckFlag(STACK_LORE)) { + Message(CHANNEL_COLOR_RED, "You cannot loot %s due to lore conflict.", item->name.c_str()); + return false; + } + else if (conflictItemList == STACK_LORE || conflictequipmentList == STACK_LORE || conflictAppearanceEquipmentList == STACK_LORE) { + Message(CHANNEL_COLOR_RED, "You cannot loot %s due to stack lore conflict.", item->name.c_str()); + return false; + } + + if (lootingPlayer->item_list.HasFreeSlot() || lootingPlayer->item_list.CanStack(item)) { + if (lootingPlayer->item_list.AssignItemToFreeSlot(item)) { + + if (item->CheckFlag2(HEIRLOOM)) { // TODO: RAID Support + GroupMemberInfo* gmi = lootingClient->GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + if (members) { + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member) + continue; + + if ((member->GetZone() != lootingClient->GetPlayer()->GetZone())) + continue; + + if (member->IsPlayer()) { + item->grouped_char_ids.insert(std::make_pair(((Player*)member)->GetCharacterID(), true)); + item->save_needed = true; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + } + + int8 type = CHANNEL_LOOT; + if (entity) { + lootingClient->Message(type, "You loot %s from the corpse of %s", item->CreateItemLink(GetVersion()).c_str(), entity->GetName()); + } + else { + lootingClient->Message(type, "You found a %s.", item->CreateItemLink(GetVersion()).c_str()); + } + Guild* guild = lootingPlayer->GetGuild(); + if (guild && item->details.tier >= ITEM_TAG_LEGENDARY) { + char adjective[32]; + int8 type; + memset(adjective, 0, sizeof(adjective)); + if (item->details.tier >= ITEM_TAG_MYTHICAL) { + strncpy(adjective, "Mythical", sizeof(adjective) - 1); + type = GUILD_EVENT_LOOTS_MYTHICAL_ITEM; + } + else if (item->details.tier >= ITEM_TAG_FABLED) { + strncpy(adjective, "Fabled", sizeof(adjective) - 1); + type = GUILD_EVENT_LOOTS_FABELED_ITEM; + } + else { + strncpy(adjective, "Legendary", sizeof(adjective) - 1); + type = GUILD_EVENT_LOOTS_LEGENDARY_ITEM; + } + guild->AddNewGuildEvent(type, "%s has looted the %s %s", Timer::GetUnixTimeStamp(), true, lootingPlayer->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str()); + guild->SendMessageToGuild(type, "%s has looted the %s %s", lootingPlayer->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str()); + } + + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, lootingPlayer); + + lootingClient->CheckPlayerQuestsItemUpdate(item); + + if (GetVersion() <= 561) { + EQ2Packet* outapp = lootingPlayer->SendInventoryUpdate(GetVersion()); + if (outapp) + lootingClient->QueuePacket(outapp); + } + return true; + } + else + lootingClient->SimpleMessage(CHANNEL_COLOR_RED, "Could not find free slot to place item."); + } + else + lootingClient->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to loot item: Inventory is FULL."); + + return false; +} + +bool Client::HandleLootItemByID(Spawn* entity, int32 item_id, Spawn* target) { + if (!entity) { + return false; + } + Item* item = entity->LootItem(item_id); + bool success = false; + success = HandleLootItem(entity, item, target); + if (!success) + entity->AddLootItem(item); + + return success; +} + +void Client::HandleLootItemRequestPacket(EQApplicationPacket* app) { + PacketStruct* packet = configReader.getStruct("WS_LootItem", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + int32 loot_id = packet->getType_int32_ByName("loot_id"); + bool loot_all = (packet->getType_int8_ByName("loot_all") == 1); + int32 target_id = packet->getType_int32_ByName("target_id"); + int8 button_clicked = packet->getType_int8_ByName("button_clicked"); + Spawn* spawn = GetCurrentZone()->GetSpawnByID(loot_id); + if(!spawn) { + safe_delete(packet); + return; + } + Item* item = nullptr; + vector* items = player->GetPendingLootItems(loot_id); + if (items) { + int32 items_looted = 0; + int32 item_id = packet->getType_int32_ByName("item_id_0"); + for (int32 i = 0; i < items->size(); i++) { + Item* master_item = items->at(i); + if (master_item && (loot_all || master_item->details.item_id == item_id)) { + item = new Item(master_item); + if (item) { + loot_all = HandleLootItem(0, item); + if (loot_all) { + player->RemovePendingLootItem(loot_id, item->details.item_id); + + if (GetVersion() <= 561) { + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + } + if(master_item->details.item_id == item_id) { + break; + } + } + } + + if (!loot_all) + break; + } + } + if(((loot_all && !item_id) || (loot_all && item_id && items->size() == 1)) || items->size() < 1) { + CloseLoot(loot_id); + } + else { + vector* items2 = player->GetPendingLootItems(loot_id); + SendLootResponsePacket(spawn->GetLootCoins(), items2, spawn, true); + safe_delete(items2); + } + safe_delete(items); + safe_delete(packet); + return; + } + + spawn->LockLoot(); + bool unlockedLoot = false; + if (spawn && !spawn->Alive() && spawn->IsNPC() && ((NPC*)spawn)->Brain()->CheckLootAllowed(player)) { + if (loot_all) { + switch (spawn->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: { + spawn->AddLottoItemRequest(0xFFFFFFFF, GetPlayer()->GetID()); + break; + } + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + spawn->AddNeedGreedItemRequest(0xFFFFFFFF, GetPlayer()->GetID(), true); + } + default: { + if (!unlockedLoot) { + spawn->UnlockLoot(); + unlockedLoot = true; + } + int32 item_id = 0; + while (loot_all && ((item_id = spawn->GetLootItemID()) > 0)) { + loot_all = HandleLootItemByID(spawn, item_id, GetPlayer()); + } + break; + } + } + spawn->UnlockLoot(); + if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { + CloseLoot(loot_id); + } + } + else { + int8 item_count = packet->getType_int8_ByName("item_count"); + for (int8 cur = 0; cur < item_count; cur++) { + char item_field_name[64]; + snprintf(item_field_name, 64, "item_id_%u", cur); + int32 item_id = packet->getType_int32_ByName(item_field_name); + Spawn* target = this->GetPlayer(); + if (target_id != 0xFFFFFFFF && GetPlayer()->GetGroupMemberInfo()) { + Spawn* destTarget = GetPlayer()->GetSpawnWithPlayerID(target_id); + if (destTarget && (!destTarget->IsPlayer() || !world.GetGroupManager()->IsInGroup(GetPlayer()->GetGroupMemberInfo()->group_id, ((Player*)destTarget)))) { + SimpleMessage(CHANNEL_COMMAND_TEXT, "HACKS!!"); + safe_delete(packet); + spawn->UnlockLoot(); + return; + } + target = destTarget; + } + bool breakLoopAllLooted = false; + switch (spawn->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: { + spawn->AddLottoItemRequest(item_id, GetPlayer()->GetID()); + break; + } + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + if (button_clicked == 3) { // decline + break; + } + if (GetVersion() <= 561) { + button_clicked = 1; // selecting is need + } + spawn->AddNeedGreedItemRequest(item_id, GetPlayer()->GetID(), (button_clicked == 1)); + break; + } + default: { + if (!unlockedLoot) { + spawn->UnlockLoot(); + unlockedLoot = true; + } + if (!loot_all) { + HandleLootItemByID(spawn, item_id, target); + } + else { + while (loot_all && ((item_id = spawn->GetLootItemID()) > 0)) { + loot_all = HandleLootItemByID(spawn, item_id, target); + } + breakLoopAllLooted = true; + } + break; + } + } + if (breakLoopAllLooted) { + break; + } + } + if (!unlockedLoot) { + spawn->UnlockLoot(); + } + if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || + (spawn->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED && item_count >= spawn->GetLootCount())) { + CloseLoot(loot_id); + } + } + + if (GetVersion() > 561) { + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + } + if (spawn->GetLootMethod() != GroupLootMethod::METHOD_LOTTO && spawn->GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) { + LootSpawnRequest(spawn); + } + else { + spawn->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + } + + if (!spawn->HasLoot()) { + CloseLoot(loot_id); + if (spawn->IsNPC()) + GetCurrentZone()->RemoveDeadSpawn(spawn); + } + } + else { + spawn->UnlockLoot(); + if (!spawn) { + LogWrite(WORLD__ERROR, 0, "World", "Unknown id of %u when looting!", loot_id); + SimpleMessage(CHANNEL_COLOR_YELLOW, "Unable to find spawn to loot!"); + } + else + SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not unable to loot that at this time."); + } + } + + safe_delete(packet); + } +} + +void Client::HandleSkillInfoRequest(EQApplicationPacket* app) { + PacketStruct* request = 0; + // cout << "Request: \n"; + // DumpPacket(app); + switch (app->pBuffer[0]) { + case 0: { //items + request = configReader.getStruct("WS_SkillInfoItemRequest", GetVersion()); + if (request) { + if(request->LoadPacketData(app->pBuffer, app->size)) { + Item* item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(request->getType_int32_ByName("unique_id")); + if (!item) + item = GetPlayer()->item_list.GetItemFromUniqueID(request->getType_int32_ByName("unique_id"), true); + if (item) { + PacketStruct* response = configReader.getStruct("WS_SkillInfoItemResponse", GetVersion()); + if (response) { + response->setDataByName("request_type", request->getType_int32_ByName("request_type")); + response->setDataByName("unique_id", request->getType_int32_ByName("unique_id")); + response->setSmallStringByName("name", item->name.c_str()); + EQ2Packet* app2 = response->serialize(); + //DumpPacket(app2); + QueuePacket(app2); + safe_delete(response); + } + } + } + safe_delete(request); + } + break; + } + case 2: {//spells + request = configReader.getStruct("WS_SkillInfoSpellRequest", GetVersion()); + if (request) { + if(request->LoadPacketData(app->pBuffer, app->size)) { + int32 id = request->getType_int32_ByName("id"); + int8 tier = request->getType_int32_ByName("unique_id"); //on live this is really unique_id, but I'm going to make it tier instead :) + Spell* spell = master_spell_list.GetSpell(id, tier); + PacketStruct* response = configReader.getStruct("WS_SkillInfoResponse", GetVersion()); + if (response) { + response->setDataByName("request_type", 2); + response->setDataByName("unique_id", tier); + response->setDataByName("id", id); + if (spell) + response->setSmallStringByName("name", spell->GetName()); + else + response->setSmallStringByName("name", "Unknown Spell"); + EQ2Packet* app2 = response->serialize(); + // DumpPacket(app2); + QueuePacket(app2); + safe_delete(response); + } + } + safe_delete(request); + } + break; + } + default: { + LogWrite(WORLD__ERROR, 0, "World", "Unknown SkillInfoRequest type of %i", (int)app->pBuffer[0]); + } + } + safe_delete(request); + +} + +void Client::HandleExamineInfoRequest(EQApplicationPacket* app) { + PacketStruct* request = 0; + if (!app || app->size == 0) + return; + //LogWrite(CCLIENT__DEBUG, 0, "Client", "Request2:"); + + int8 type = app->pBuffer[0]; + DumpPacket(app->pBuffer,app->size); + //283: item: 0, effect: 1, recipe: 2, spell: 3 + if (version <= 373) { + if (type == 1) + type = 4; + else if (type == 2) + type = 5; + } + if (type == 3) { + Spell* spell = 0; + bool trait_display; + + request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); + if (!request) { + return; + } + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + + int32 id = request->getType_int32_ByName("id"); + int32 tier = request->getType_int32_ByName("tier"); + int32 trait_tier = request->getType_int32_ByName("unknown_id"); + + if(GetVersion() > 373 && GetVersion() <= 561) { + trait_tier = request->getType_int32_ByName("unique_id"); + } + bool display = true; + if (version <= 373 && request->getType_int8_ByName("display") == 1) // this is really requesting a partial packet + display = false; + else if (version <= 561) + display = request->getType_int8_ByName("display"); + else if (version > 561) + display = false; // clients default is false otherwise it pops up a window when hovering over the knowledge book abilities + + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Type: (%i) Tier: (%u) Unknown ID: (%u) Item ID: (%u)",GetPlayer()->GetName(),type,tier,trait_tier,id); + + if (trait_tier != 0xFFFFFFFF) { + spell = master_spell_list.GetSpell(id, trait_tier); + if (!spell) { + spell = master_spell_list.GetSpell(id, trait_tier + 1); + } + trait_display = true; + } + else { + spell = master_spell_list.GetSpell(id, tier); + trait_display = false; + } + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + if (tmpSpell) + spell = tmpSpell->spell; + lua_interface->FindCustomSpellUnlock(); + } + + if(!spell) { // fix ui timeout for classic, isle of refuge, dof, kos clients + int8 playerTier = GetPlayer()->GetSpellTier(id); + spell = master_spell_list.GetSpell(id, playerTier); + LogWrite(CCLIENT__WARNING, 0, "Client", "Client::HandleExamineInfoRequest from %s: Failed to find tier 1 spell. Last resort try to get the spell from the player book, spell %u, tier %u", GetPlayer()->GetName(),id,playerTier); + } + + if (spell && !CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) { + if (!spell->IsCopiedSpell()) + SetSentSpell(spell->GetSpellID(), spell->GetSpellTier()); + + EQ2Packet* app = spell->SerializeSpell(this, display, trait_display); + //DumpPacket(app); + QueuePacket(app); + } + else if(spell && GetVersion() <=561 && CountSentSpell(spell->GetSpellID(), spell->GetSpellTier())) { + EQ2Packet* app = spell->SerializeSpell(this, display, trait_display, GetVersion() <= 561 ? true : false); + //DumpPacket(app); + QueuePacket(app); + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "Client::HandleExamineInfoRequest from %s: Failed to successfully send Type: (%i) Tier: (%u) Unknown ID: (%u) Item ID: (%u)",GetPlayer()->GetName(),type,tier,trait_tier,id); + } + } + else if (type == 0) { + request = configReader.getStruct("WS_ExamineInfoItemRequest", GetVersion()); + if (!request) { + return; + } + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + + int32 id = request->getType_int32_ByName("id"); + + Item* item = 0; + + // translate from unique id to spawn id for houses + Spawn* spawn = this->GetCurrentZone()->GetSpawnFromUniqueItemID(id); + + bool wasSpawn = false; + if (spawn) + { + item = master_item_list.GetItem(spawn->GetPickupItemID()); + if (item) + { + wasSpawn = true; + item = new Item(item); + item->details.unique_id = spawn->GetPickupUniqueItemID(); + } + } + + if (!item) + item = GetPlayer()->item_list.GetItemFromUniqueID(id, true); + if (!item) + item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(id); + if (!item) + item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(id); + if (!item) + item = master_item_list.GetItem(id); + if (item) {// && sent_item_details.count(id) == 0){ + MItemDetails.writelock(__FUNCTION__, __LINE__); + sent_item_details[id] = true; + MItemDetails.releasewritelock(__FUNCTION__, __LINE__); + EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer()); + + QueuePacket(app); + if (wasSpawn) + delete item; + } + else { + LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id); + DumpPacket(app); + } + } + else if (type == 1) { + request = configReader.getStruct("WS_ExamineInfoItemRequest", GetVersion()); + if (!request) { + return; + } + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + int32 id = request->getType_int32_ByName("id"); + int32 unique_id = request->getType_int32_ByName("unique_id"); + + Item* item = GetPlayer()->item_list.GetItemFromUniqueID(unique_id, true); + if (!item) + item = GetPlayer()->GetEquipmentList()->GetItemFromUniqueID(unique_id); + if (!item) + item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(unique_id); + if (!item) + item = master_item_list.GetItem(id); + if (item) { + MItemDetails.writelock(__FUNCTION__, __LINE__); + sent_item_details[id] = true; + MItemDetails.releasewritelock(__FUNCTION__, __LINE__); + EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer()); + QueuePacket(app); + } + else { + LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id); + DumpPacket(app); + } + } + else if (type == 2) { + request = configReader.getStruct("WS_ExamineInfoItemLinkRequest", GetVersion()); + if (!request) { + return; + } + + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + int32 id = request->getType_int32_ByName("item_id"); + + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Found Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + //int32 unknown_0 = request->getType_int32_ByName("unknown",0); + //int32 unknown_1 = request->getType_int32_ByName("unknown",1); + //int8 unknown2 = request->getType_int8_ByName("unknown2"); + //int32 unique_id = request->getType_int32_ByName("unique_id"); + //int16 unknown5 = request->getType_sint16_ByName("unknown5"); + //printf("Type: (%i) Unknown_0: (%u) Unknown_1: (%u) Unknown2: (%i) Unique ID: (%u) Unknown5: (%i) Item ID: (%u)\n",type,unknown_0,unknown_1,unknown2,unique_id,unknown5,id); + Item* item = master_item_list.GetItem(id); + if (item) { + //only display popup for non merchant links + EQ2Packet* app = item->serialize(GetVersion(), (request->getType_int8_ByName("show_popup") != 0), GetPlayer(), true, 0, 0, GetVersion() > 561 ? true : false); + + QueuePacket(app); + } + else { + LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id); + DumpPacket(app); + } + } + else if (type == 4) { //spell effect + request = configReader.getStruct("WS_ExamineSpellEffectRequest", GetVersion()); + if (!request) { + return; + } + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + int32 id = request->getType_int32_ByName("id"); + int16 display = request->getType_int8_ByName("partial_info"); + SpellEffects* effect = player->GetSpellEffect(id); + //printf("Type: (%i) Unknown5: (%i) Item ID: (%u)\n",type,unknown5,id); + if (effect) { + LogWrite(CCLIENT__DEBUG, 5, "Client", "Client::HandleExamineInfoRequest from %s: Found Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + int8 tier = effect->tier; + Spell* spell = master_spell_list.GetSpell(id, tier); + + // if we can't find it on the master spell list, see if it is a custom spell + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + EQ2Packet* pack = 0; + if (tmpSpell) + spell = tmpSpell->spell; + lua_interface->FindCustomSpellUnlock(); + } + + if (spell && (version <= 561 || !CountSentSpell(id, tier))) { // fix DoF and KoS clients UI Timeout + if (!spell->IsCopiedSpell()) + SetSentSpell(spell->GetSpellID(), spell->GetSpellTier()); + int8 type = 0; + if (version <= 373) + type = 1; + EQ2Packet* app = spell->SerializeSpecialSpell(this, false, type, 0x81); + //DumpPacket(app); + QueuePacket(app); + } + } + else { + LogWrite(CCLIENT__ERROR, 0, "Client", "Client::HandleExamineInfoRequest from %s: Cannot Find Type: (%i) Item ID: (%u)",GetPlayer()->GetName(),type,id); + } + } + else if (type == 5) { // recipe info + request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); + if (!request) + return; + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + + int32 id = 0; + if(GetVersion() < 546) { + id = request->getType_int32_ByName("id"); + } + else if(GetVersion() <= 561) { + id = request->getType_int32_ByName("unique_id"); + } + else { + id = request->getType_int32_ByName("unknown_id"); + } + Recipe* recipe = master_recipe_list.GetRecipe(id); + if (recipe) { + EQ2Packet* app = recipe->SerializeRecipe(this, recipe, false, GetItemPacketType(GetVersion()), 0x02); + //DumpPacket(app); + QueuePacket(app); + } + + } + else if (type == 6) { // AA spell info + Spell* spell = 0; + //Spell* spell2 = 0; + //AltAdvanceData* data = 0; + request = configReader.getStruct((GetVersion() <= 373) ? "WS_ExamineInfoRequest" : "WS_ExamineInfoRequestMsg", GetVersion()); + if (!request) + return; + if(!request->LoadPacketData(app->pBuffer, app->size)) { + safe_delete(request); + return; + } + int32 id = request->getType_int32_ByName("id"); + int32 tier = GetPlayer()->GetSpellTier(id); + LogWrite(WORLD__INFO, 0, "World", "Examine Info Request->Unique ID: %u Tier: %u ", id, tier); + //data = master_aa_list.GetAltAdvancement(id); + //LogWrite(WORLD__INFO, 0, "World", "SOE Spell CRC: %u", data->spell_crc); + //spell = master_spell_list.GetSpellByCRC(data->spell_crc); + spell = master_spell_list.GetSpell(id, tier); + + if (!spell) + spell = master_spell_list.GetSpell(id, 1); + + if (!spell) + { + lua_interface->FindCustomSpellLock(); + LuaSpell* tmpSpell = lua_interface->FindCustomSpell(id); + if (tmpSpell) + spell = tmpSpell->spell; + lua_interface->FindCustomSpellUnlock(); + } + + if (!spell) + { + LogWrite(CCLIENT__ERROR, 0, "Client", "WORLD", "Client::HandleExamineInfoRequest from %s: FAILED Examine Info Request-> Spell ID: %u, tier: %i", GetPlayer()->GetName(), id, tier); + return; + } + + //if (spell && sent_spell_details.count(spell->GetSpellID()) == 0) { + if (!spell->IsCopiedSpell()) + SetSentSpell(spell->GetSpellID(), spell->GetSpellTier()); + // EQ2Packet* app = spell->SerializeAASpell(this,tier, data, false, GetItemPacketType(GetVersion()), 0x04); + LogWrite(WORLD__INFO, 0, "WORLD", "Examine Info Request-> Spell ID: %u", spell->GetSpellID()); + if(GetVersion() > 561) { + EQ2Packet* app = master_spell_list.GetAASpellPacket(id, tier, this, false, 0x4F);//0x45 change version to match client + /////////////////////////////////////////GetAASpellPacket(int32 id, int8 tier, Client* client, bool display, int8 packet_type) { + //DumpPacket(app); + QueuePacket(app); + } + //} + } + else { + LogWrite(CCLIENT__ERROR, 0, "World", "Client::HandleExamineInfoRequest from %s: Unknown examine request: %i", GetPlayer()->GetName(), (int)type); + DumpPacket(app); + } + safe_delete(request); + +} + +void Client::HandleQuickbarUpdateRequest(EQApplicationPacket* app) { + PacketStruct* request = configReader.getStruct("WS_QuickBarUpdateRequest", GetVersion()); + if (request) { + if(request->LoadPacketData(app->pBuffer, app->size)) { + int32 id = request->getType_int32_ByName("id"); + int32 bar = request->getType_int32_ByName("hotbar_number"); + int32 slot = request->getType_int32_ByName("hotkey_slot"); + int32 type = request->getType_int32_ByName("type"); + int8 tier = request->getType_int32_ByName("unique_id"); + EQ2_16BitString text = request->getType_EQ2_16BitString_ByName("text"); + Spell* spell = 0; + if (type == 0xFFFFFFFF) + GetPlayer()->RemoveQuickbarItem(bar, slot); + else { + if (type == QUICKBAR_NORMAL) + spell = master_spell_list.GetSpell(id, tier); + if (spell) + GetPlayer()->AddQuickbarItem(bar, slot, type, spell->GetSpellIcon(), spell->GetSpellIconBackdrop(), id, tier, 0, text.data.c_str()); + else + GetPlayer()->AddQuickbarItem(bar, slot, type, 0, 0, id, 0, 0, text.data.c_str()); + } + } + safe_delete(request); + } + +} + +bool Client::Process(bool zone_process) { + + bool ret = true; + // EQS can become null if player is linkdead, we want to always be able to process the camp/linkdead timers when active + if ((camp_timer && camp_timer->Check()) || (linkdead_timer && linkdead_timer->Check())) { + ResetSendMail(); + if(getConnection()) + getConnection()->SendDisconnect(false); + safe_delete(camp_timer); + if(linkdead_timer) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Player %s triggered linkdead timer, disconnecting", GetPlayer()->GetName()); + // we remove the linkdead status and force a camp out immediately + if((GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) { + GetPlayer()->SetActivityStatus(GetPlayer()->GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD); + } + if ((GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) { + GetPlayer()->SetActivityStatus(GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_CAMPING); + } + } + safe_delete(linkdead_timer); + ret = false; + } + + if (!eqs) { + return false; + } + if ((connected_to_zone && !zone_process) || (!connected_to_zone && zone_process)) { + return true; + } + + switch(new_client_login) { + case NewLoginState::LOGIN_SEND: { + LogWrite(CCLIENT__DEBUG, 0, "Client", "SendLoginInfo to new client..."); + SendLoginInfo(); + + new_client_login = NewLoginState::LOGIN_NONE; + break; + } + case NewLoginState::LOGIN_INITIAL_LOAD: { + bool isDBActive = database.IsActiveQuery(GetCharacterID()); + + // wait for starting skills/spells to load and reload from DB. + if(!isDBActive) { + if (GetPlayer()->GetInfoStruct()->get_reload_player_spells()) { + database.LoadCharacterSpells(GetCharacterID(), GetPlayer()); + GetPlayer()->GetInfoStruct()->set_reload_player_spells(0); + } + new_client_login = NewLoginState::LOGIN_SEND; + } + break; + } + case NewLoginState::LOGIN_ALLOWED: { + int32 count = 0; + + if(!GetPlayer()->IsReturningFromLD()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Skills for player '%s'...", player->GetName()); + count = database.LoadCharacterSkills(GetCharacterID(), player); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Loading Character Spells for player '%s'...", player->GetName()); + count = database.LoadCharacterSpells(GetCharacterID(), player); + } + else + { + LogWrite(CCLIENT__INFO, 0, "Client", "Player is returning from linkdead status (Player does not need reload) thus skipping database loading for '%s'...", player->GetName()); + } + + // get the latest character starting skills / spells, may have been updated after character creation + if(GetPlayer()->GetInfoStruct()->get_first_world_login()) { + world.SyncCharacterAbilities(this); + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_UPDATE, "UPDATE characters set first_world_login = 0 where id=%u", GetCharacterID()); + GetPlayer()->GetInfoStruct()->set_first_world_login(0); + } + + new_client_login = NewLoginState::LOGIN_INITIAL_LOAD; + break; + } + case NewLoginState::LOGIN_DELAYED: { + if(!delay_msg_timer.Enabled() || delay_msg_timer.Check()) { + LogWrite(CCLIENT__INFO, 0, "Client", "Wait for zone %s to load for new client %s...", GetCurrentZone()->GetZoneName(), GetPlayer()->GetName()); + delay_msg_timer.Start(1000, true); + } + if(!GetCurrentZone()->IsLoading()) { + new_client_login = NewLoginState::LOGIN_ALLOWED; + } + + return true; + break; + } + } + + delay_msg_timer.Disable(); + + sockaddr_in to; + + memset((char*)&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_port = port; + to.sin_addr.s_addr = ip; + + /************ Get all packets from packet manager out queue and process them ************/ + EQApplicationPacket* app = 0; + if (eqs && !eqs->CheckActive()) { + num_active_failures++; + + LogWrite(CCLIENT__DEBUG, 7, "Client", "%s, num_active_failures = %i", __FUNCTION__, num_active_failures); + + if (num_active_failures > 100) { + return false; + } + return true; + } + while (ret && eqs && (app = eqs->PopPacket())) { + ret = HandlePacket(app); + + LogWrite(CCLIENT__DEBUG, 5, "Client", "Func: %s, Line: %i, Opcode: '%s'", __FUNCTION__, __LINE__, app->GetOpcodeName()); + + delete app; + } + + if (GetCurrentZone() && !GetCurrentZone()->IsLoading() && GetCurrentZone()->GetSpawnByID(GetPlayer()->GetID()) && should_load_spells) { + //database.LoadCharacterActiveSpells(player); + player->UnlockAllSpells(true); + + should_load_spells = false; + } + + if(spawn_removal_timer.Check() && GetPlayer()) { + GetPlayer()->ProcessSpawnRangeUpdates(); + GetPlayer()->CheckSpawnStateQueue(); + } + + if (delayedLogin && delayTimer.Enabled() && delayTimer.Check()) + { + if (!HandleNewLogin(delayedAccountID, delayedAccessKey)) + return false; + } + + if (quest_updates) { + LogWrite(CCLIENT__DEBUG, 1, "Client", "%s, ProcessQuestUpdates", __FUNCTION__, __LINE__); + ProcessQuestUpdates(); + } + int32 queue_timer_delay = rule_manager.GetGlobalRule(R_Client, QuestQueueTimer)->GetInt32(); + if(queue_timer_delay < 10) { + queue_timer_delay = 10; + } + if (last_update_time > 0 && last_update_time < (Timer::GetCurrentTime2() - queue_timer_delay)) { + LogWrite(CCLIENT__DEBUG, 1, "Client", "%s, CheckQuestQueue", __FUNCTION__, __LINE__); + CheckQuestQueue(); + } + + MSaveSpellStateMutex.lock(); + if(save_spell_state_timer.Check()) + { + save_spell_state_timer.Disable(); + GetPlayer()->SaveSpellEffects(); + } + MSaveSpellStateMutex.unlock(); + + if (temp_placement_timer.Check()) { + if(GetTempPlacementSpawn() && GetPlayer()->WasSentSpawn(GetTempPlacementSpawn()->GetID()) && !hasSentTempPlacementSpawn) { + SendMoveObjectMode(GetTempPlacementSpawn(), 0); + hasSentTempPlacementSpawn = true; + temp_placement_timer.Disable(); + } + } + if (pos_update.Check()) + { + ProcessStateCommands(); + + GetPlayer()->ResetMentorship(); // check if we need to asynchronously reset mentorship + + if(GetPlayer()->GetRegionMap()) + GetPlayer()->GetRegionMap()->TicRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr); + + if(!player_pos_changed && IsReadyForUpdates() && player_pos_timer < Timer::GetCurrentTime2() && enabled_player_pos_timer) { + if(version > 373) { + GetPlayer()->info_changed = true; + GetPlayer()->vis_changed = true; + GetPlayer()->position_changed = true; + GetPlayer()->changed = true; + GetPlayer()->AddChangedZoneSpawn(); + } + player_pos_timer = Timer::GetCurrentTime2()+5000; + enabled_player_pos_timer = false; + } + if(player_pos_changed && IsReadyForUpdates()) { + player_pos_timer = Timer::GetCurrentTime2()+500; + enabled_player_pos_timer = true; + if(!underworld_cooldown_timer.Enabled() || (underworld_cooldown_timer.Enabled() && underworld_cooldown_timer.Check())) { + bool underworld = false; + if(rule_manager.GetGlobalRule(R_Zone, UseMapUnderworldCoords)->GetBool()) { + if(GetCurrentZone()->GetUnderWorld() != -1000000.0f) { + if(GetPlayer()->GetY() < GetCurrentZone()->GetUnderWorld()) + underworld = true; + } + else if(GetPlayer()->GetMap() && GetPlayer()->GetMap()->GetMinY() != 9999999.0f && GetPlayer()->GetY() < (GetPlayer()->GetMap()->GetMinY() + rule_manager.GetGlobalRule(R_Zone, MapUnderworldCoordOffset)->GetFloat())) { + underworld = true; + } + } + else if(GetPlayer()->GetMap() && GetPlayer()->GetY() < GetCurrentZone()->GetUnderWorld()) { + underworld = true; + } + if(underworld) { + player->SetX(GetCurrentZone()->GetSafeX()); + player->SetY(GetCurrentZone()->GetSafeY()); + player->SetZ(GetCurrentZone()->GetSafeZ()); + player->SetHeading(GetCurrentZone()->GetSafeHeading()); + EQ2Packet* app = GetPlayer()->Move(player->GetX(), player->GetY(), player->GetZ(), GetVersion(), player->GetHeading()); + if(app){ + QueuePacket(app); + } + SimpleMessage(CHANNEL_COLOR_RED, "You have been teleported to a safe location in the zone, because you appeared to have fallen through the world."); + } + underworld_cooldown_timer.Start(); + } + //GetPlayer()->CalculateLocation(); + client_list.CheckPlayersInvisStatus(this); + + player_pos_changed = false; + + GetCurrentZone()->CheckTransporters(this); + + if(GetPlayer()->GetRegionMap()) + { + GetPlayer()->GetRegionMap()->MapRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr); + } + } + else if (IsReadyForUpdates() && GetPlayer()->GetRegionMap()) { + GetPlayer()->GetRegionMap()->UpdateRegionsNearSpawn(this->GetPlayer(), regionDebugMessaging ? this : nullptr); + } + } + if (lua_interface && lua_debug && lua_debug_timer.Check()) + lua_interface->UpdateDebugClients(this); + if (quest_pos_timer.Check()) + CheckPlayerQuestsLocationUpdate(); + if (player->GetSkills()->HasSkillUpdates()) { + vector* skills = player->GetSkills()->GetSkillUpdates(); + if (skills) { + Skill* skill = 0; + vector::iterator itr; + for (itr = skills->begin(); itr != skills->end(); itr++) { + skill = *itr; + SkillChanged(skill, skill->previous_val, skill->current_val); + } + EQ2Packet* app = GetPlayer()->skill_list.GetSkillPacket(GetVersion()); + if (app) QueuePacket(app); + safe_delete(skills); + } + } + m_resurrect.writelock(__FUNCTION__, __LINE__); + if (current_rez.should_delete || (current_rez.expire_timer && current_rez.expire_timer->Check(false))) { + safe_delete(current_rez.expire_timer); + current_rez.expire_timer = 0; + current_rez.active = false; + current_rez.caster = 0; + current_rez.crit = false; + current_rez.crit_mod = 0; + current_rez.expire_timer = 0; + current_rez.heal_name = ""; + current_rez.hp_perc = 0; + current_rez.mp_perc = 0; + current_rez.no_calcs = false; + current_rez.range = 0; + current_rez.should_delete = false; + current_rez.spell_name = ""; + current_rez.spell_visual = 0; + current_rez.subspell = 0; + } + m_resurrect.releasewritelock(__FUNCTION__, __LINE__); + + // Quest timers + Quest* failed_step = 0; + MQuestTimers.writelock(__FUNCTION__, __LINE__); + if (quest_timers.size() > 0) { + vector::iterator itr; + GetPlayer()->MPlayerQuests.readlock(__FUNCTION__, __LINE__); + map* player_quests = player->GetPlayerQuests(); + for (itr = quest_timers.begin(); itr != quest_timers.end(); itr++) { + if (player_quests->count(*itr) > 0 && player_quests->at(*itr)->GetStepTimer() != 0) { + Quest* quest = player_quests->at(*itr); + if (Timer::GetUnixTimeStamp() >= quest->GetStepTimer()) { + failed_step = quest; + break; + } + } + else { + itr = quest_timers.erase(itr); + break; + } + } + GetPlayer()->MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); + } + MQuestTimers.releasewritelock(__FUNCTION__, __LINE__); + + if (failed_step) + failed_step->StepFailed(failed_step->GetTimerStep()); + + if (player->ControlFlagsChanged()) + player->SendControlFlagUpdates(this); + + if (!eqs || (eqs && !eqs->CheckActive())) + ret = false; + + // redundant to client disconnect + // if(!ret) + // Save(); + + return ret; +} + +ClientList::ClientList() { + MClients.SetName("ClientList::MClients"); + +} + +ClientList::~ClientList() { +} + +void ClientList::ReloadQuests() { + list::iterator client_iter; + Client* client = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client) + client->ReloadQuests(); + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + +} + +void ClientList::CheckPlayersInvisStatus(Client* owner) { + if (!owner->GetPlayer() || (!owner->GetPlayer()->IsInvis() && !owner->GetPlayer()->IsStealthed())) + return; + + list::iterator client_iter; + Client* client = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client == owner || client->GetPlayer() == NULL) + continue; + + if (client->GetPlayer()->CheckChangeInvisHistory((Entity*)owner->GetPlayer())) + client->GetPlayer()->GetZone()->SendSpawnChanges(owner->GetPlayer(), client, true, true); + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + +} + +void ClientList::RemovePlayerFromInvisHistory(int32 spawnID) { + list::iterator client_iter; + Client* client = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (!client->GetPlayer()) + continue; + + client->GetPlayer()->RemoveTargetInvisHistory(spawnID); + } + MClients.releasereadlock(__FUNCTION__, __LINE__); +} + + + +int32 ClientList::Count() { + return client_list.size(); +} + +void ClientList::Add(Client* client) { + MClients.writelock(__FUNCTION__, __LINE__); + client_list.push_back(client); + MClients.releasewritelock(__FUNCTION__, __LINE__); + +} + +Client* ClientList::FindByAccountID(int32 account_id) { + list::iterator client_iter; + Client* client = 0; + Client* ret = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_list.size() > 0 && client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client->GetAccountID() == account_id) { + ret = client; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Client* ClientList::FindByName(char* charName) { + list::iterator client_iter; + Client* client = 0; + Client* ret = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_list.size() > 0 && client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (!client || !client->GetPlayer()) + continue; + + if (!strncmp(client->GetPlayer()->GetName(), charName, strlen(charName))) { + ret = client; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +Client* ClientList::Get(int32 ip, int16 port) { + list::iterator client_iter; + Client* client = 0; + Client* ret = 0; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_list.size() > 0 && client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client->GetIP() == ip && client->GetPort() == port) { + ret = client; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void ClientList::Process() { + + list::iterator client_iter; + list::iterator erase_iter; + Client* client = 0; + MClients.readlock(__FUNCTION__, __LINE__); + erase_iter = client_list.end(); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + // have a sanity check because the client list can sometimes obtain null client pointers + if (!client || client->remove_from_list || (!client->Process())) { // if we should be removing from list, don't process any further + erase_iter = client_iter; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + if (erase_iter != client_list.end()) { + client = *erase_iter; + MClients.writelock(__FUNCTION__, __LINE__); + client_list.erase(erase_iter); + MClients.releasewritelock(__FUNCTION__, __LINE__); + if (client && !client->remove_from_list) { + struct in_addr in; + in.s_addr = client->GetIP(); + LogWrite(WORLD__INFO, 0, "World", "Removing client from ip: %s port: %i", inet_ntoa(in), client->GetPort()); + safe_delete(client); + } + } + +} + +void ClientList::RemoveConnection(EQStream* eqs) { + Client* client; + + if (eqs) { + list::iterator client_iter; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + client = *client_iter; + if (client->getConnection() == eqs) + client->Disconnect(true); + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + } + +} + +bool ClientList::ContainsStream(EQStream* eqs) { + if (!eqs) { + return false; + } + list::iterator client_iter; + bool ret = false; + MClients.readlock(__FUNCTION__, __LINE__); + for (client_iter = client_list.begin(); client_iter != client_list.end(); client_iter++) { + if ((*client_iter)->getConnection() && (*client_iter)->getConnection()->GetRemotePort() == eqs->GetRemotePort() && (*client_iter)->getConnection()->GetRemoteIP() == eqs->GetRemoteIP()) { + ret = true; + break; + } + } + MClients.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void ClientList::Remove(Client* client, bool remove_data) { + client->remove_from_list = true; + if (remove_data) { + safe_delete(client); + } + +} + +void Client::SetCurrentZone(ZoneServer* zone) { + current_zone = zone; + if(player) { + player->SetZone(zone, GetVersion()); + } +} + +void Client::SetCurrentZone(int32 id) { + if (current_zone) { + //current_zone->GetCombat()->RemoveHate(player); + current_zone->RemoveSpawn(player, false, true, true, true, true); + } + SetCurrentZone(zone_list.Get(id)); + +} + +void Client::SetCurrentZoneByInstanceID(int32 id, int32 zoneid) { + if (current_zone) { + //current_zone->GetCombat()->RemoveHate(player); + current_zone->RemoveSpawn(player, false, true, true, true, true); + } + SetCurrentZone(zone_list.GetByInstanceID(id, zoneid)); + +} + +ZoneServer* Client::GetCurrentZone() { + return current_zone; +} + +int8 Client::GetMessageChannelColor(int8 channel_type) { + if (GetVersion() >= 973 && GetVersion() <= 1000) { + if (channel_type == CHANNEL_LOOT) + return CHANNEL_COLOR_NEW_LOOT; + } + else if (GetVersion() >= 973) { + if (channel_type == CHANNEL_LOOT) + return CHANNEL_COLOR_NEWEST_LOOT; + } + if (GetVersion() <= 283) { + if (channel_type <=12) + return channel_type; + switch (channel_type) { + case CHANNEL_GROUP_CHAT: + case CHANNEL_GROUP_SAY: + case CHANNEL_RAID_SAY: + case CHANNEL_GUILD_CHAT: + case CHANNEL_GUILD_SAY: + case CHANNEL_OFFICER_SAY: + case CHANNEL_GUILD_MOTD: + return channel_type - 1; + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return channel_type - 5; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + return channel_type - 6; + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_AUCTION: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + return channel_type - 7; + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 8; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + return channel_type - 9; + case CHANNEL_COMBAT: + return channel_type - 15; + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + return channel_type - 16; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + return channel_type - 18; + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + case CHANNEL_CRITICAL_HIT: + return channel_type - 22; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 30; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + return channel_type - 31; + default: { + return CHANNEL_DEFAULT; + } + } + } + else if (GetVersion() <= 373) { + if (channel_type <=18) + return channel_type; + switch (channel_type) { + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return 22; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + return 23; + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + return 26; + case CHANNEL_AUCTION: + return 27; + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 8; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + return channel_type - 9; + case CHANNEL_COMBAT: + return channel_type - 15; + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + return channel_type - 16; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + return channel_type - 18; + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + case CHANNEL_CRITICAL_HIT: + return channel_type - 22; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 30; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + return 68; + default: { + return CHANNEL_DEFAULT; + } + } + } + else if (GetVersion() <= 561) { + if (channel_type < 20) + return channel_type; + switch (channel_type) { + case CHANNEL_GUILD_MOTD: + case CHANNEL_GUILD_MEMBER_ONLINE: + case CHANNEL_GUILD_EVENT: + return channel_type + 1; + case CHANNEL_PRIVATE_CHAT: + case CHANNEL_NONPLAYER_TELL: + return channel_type - 1; + case CHANNEL_PRIVATE_TELL: + case CHANNEL_TELL_FROM_CS: + case CHANNEL_ARENA: + case CHANNEL_CHAT_CHANNEL_TEXT: + case CHANNEL_OUT_OF_CHARACTER: + case CHANNEL_AUCTION: + case CHANNEL_CUSTOM_CHANNEL: + case CHANNEL_CHARACTER_TEXT: + case CHANNEL_REWARD: + case CHANNEL_DEATH: + case CHANNEL_PET_CHAT: + case CHANNEL_SKILL: + case CHANNEL_FACTION: + case CHANNEL_SPELLS: + case CHANNEL_YOU_CAST: + case CHANNEL_YOU_FAIL: + return channel_type - 2; + case CHANNEL_FRIENDLY_CAST: + case CHANNEL_FRIENDLY_FAIL: + case CHANNEL_OTHER_CAST: + case CHANNEL_OTHER_FAIL: + case CHANNEL_HOSTILE_CAST: + case CHANNEL_HOSTILE_FAIL: + case CHANNEL_WORN_OFF: + case CHANNEL_SPELLS_OTHER: + case CHANNEL_HEAL_SPELLS: + case CHANNEL_HEALS: + case CHANNEL_FRIENDLY_HEALS: + case CHANNEL_OTHER_HEALS: + case CHANNEL_HOSTILE_HEALS: + return channel_type - 3; + case CHANNEL_COMBAT: + case CHANNEL_GENERAL_COMBAT: + case CHANNEL_HEROIC_OPPORTUNITY: + case CHANNEL_NON_MELEE_DAMAGE: + case CHANNEL_DAMAGE_SHIELD: + case CHANNEL_WARD: + return channel_type - 4; + case CHANNEL_MELEE_COMBAT: + case CHANNEL_WARNINGS: + case CHANNEL_YOU_HIT: + case CHANNEL_YOU_MISS: + case CHANNEL_ATTACKER_HITS: + case CHANNEL_ATTACKER_MISSES: + case CHANNEL_YOUR_PET_HITS: + case CHANNEL_YOUR_PET_MISSES: + case CHANNEL_ATTACKER_HITS_PET: + case CHANNEL_ATTACKER_MISSES_PET: + case CHANNEL_OTHER_HIT: + case CHANNEL_OTHER_MISSES: + return channel_type - 5; + case CHANNEL_OTHER: + case CHANNEL_MONEY_SPLIT: + case CHANNEL_LOOT: + return channel_type - 14; + case CHANNEL_COMMAND_TEXT: + case CHANNEL_BROADCAST: + case CHANNEL_WHO: + case CHANNEL_COMMANDS: + case CHANNEL_MERCHANT: + case CHANNEL_MERCHANT_BUY_SELL: + case CHANNEL_CONSIDER_MESSAGE: + case CHANNEL_CON_MINUS_2: + case CHANNEL_CON_MINUS_1: + case CHANNEL_CON_0: + case CHANNEL_CON_1: + case CHANNEL_CON_2: + case CHANNEL_TRADESKILLS: + case CHANNEL_HARVESTING: + case CHANNEL_HARVESTING_WARNINGS: + return channel_type - 15; + default: { + return CHANNEL_DEFAULT; + } + } + } + else { + switch (channel_type) { + default: { + return channel_type; + } + } + } + return channel_type; +} + +void Client::HandleTellMessage(Client* from, const char* message, const char* to, int32 current_language_id) { + if (!from || GetPlayer()->IsIgnored(from->GetPlayer()->GetName())) + return; + PacketStruct* packet = configReader.getStruct("WS_HearChat", GetVersion()); + if (packet) { + packet->setDataByName("from", from->GetPlayer()->GetName()); + packet->setDataByName("to", to); + packet->setDataByName("channel", GetMessageChannelColor(CHANNEL_PRIVATE_TELL)); + packet->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet->setDataByName("to_spawn_id", 0xFFFFFFFF); + packet->setDataByName("unknown2", 1, 1); + packet->setDataByName("show_bubble", 1); + + if (current_language_id == 0 || GetPlayer()->HasLanguage(current_language_id)) { + packet->setDataByName("understood", 1); + } + + packet->setDataByName("time", 2); + packet->setDataByName("language", current_language_id); + packet->setMediumStringByName("message", message); + EQ2Packet* outpacket = packet->serialize(); + QueuePacket(outpacket); + safe_delete(packet); + } +} + +void Client::SimpleMessage(int8 color, const char* message) { + PacketStruct* command_packet = configReader.getStruct("WS_DisplayText", GetVersion()); + if (command_packet) { + command_packet->setDataByName("color", GetMessageChannelColor(color));//convert this to the correct client type (different clients have different chat numbers) + command_packet->setMediumStringByName("text", message); + command_packet->setDataByName("unknown02", 0x00ff); + EQ2Packet* outapp = command_packet->serialize(); + QueuePacket(outapp); + safe_delete(command_packet); + } +} + +void Client::SendSpellUpdate(Spell* spell, bool add_silently, bool add_to_hotbar) { + PacketStruct* packet = configReader.getStruct("WS_SpellGainedMsg", GetVersion()); + if (packet) { + int8 xxx = spell->GetSpellData()->is_aa; + packet->setDataByName("spell_type", spell->GetSpellData()->type); + packet->setDataByName("spell_id", spell->GetSpellID()); + packet->setDataByName("unique_id", spell->GetSpellData()->spell_name_crc); + packet->setDataByName("spell_name", spell->GetName()); + if(add_silently) + packet->setDataByName("add_silently", 1); + if(add_to_hotbar) + packet->setDataByName("add_to_hotbar", 1); + packet->setDataByName("unknown", xxx); + packet->setDataByName("display_spell_tier", 1); + packet->setDataByName("unknown3", 1); + packet->setDataByName("tier", spell->GetSpellTier()); + packet->setDataByName("icon", spell->GetSpellIcon()); + packet->setDataByName("icon_type", spell->GetSpellIconBackdrop()); + packet->setDataByName("unknown5", 0xFFFFFFFF); + //packet->PrintPacket(); + + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + +} + +void Client::Message(int8 type, const char* message, ...) { + va_list argptr; + char buffer[4096]; + + va_start(argptr, message); + vsnprintf(buffer, sizeof(buffer), message, argptr); + va_end(argptr); + SimpleMessage(type, buffer); +} + +void Client::Disconnect(bool send_disconnect) +{ + LogWrite(CCLIENT__DEBUG, 0, "CClient", "Client Disconnect..."); + this->Save(); + this->GetPlayer()->WritePlayerStatistics(); + + SetConnected(false); + + if (send_disconnect && getConnection()) + getConnection()->SendDisconnect(true); + + eqs = 0; +} + +bool Client::Summon(const char* search_name) { + Spawn* target = 0; + if (search_name || GetPlayer()->GetTarget()) { + Client* search_client = 0; + if (search_name) { + target = GetCurrentZone()->FindSpawn(GetPlayer(), search_name); + if (target && target->IsPlayer()) + search_client = target->GetClient(); + if (!target) { + search_client = zone_list.GetClientByCharName(string(search_name)); + if (search_client) + target = search_client->GetPlayer(); + } + } + else + target = GetPlayer()->GetTarget(); + if (target && target != GetPlayer()) { + target->SetX(GetPlayer()->GetX()); + target->SetY(GetPlayer()->GetY()); + target->SetZ(GetPlayer()->GetZ()); + target->SetHeading(GetPlayer()->GetHeading()); + if (!target->IsPlayer()) { + target->SetSpawnOrigX(target->GetX()); + target->SetSpawnOrigY(target->GetY()); + target->SetSpawnOrigZ(target->GetZ()); + target->SetSpawnOrigHeading(target->GetHeading()); + } + target->SetLocation(GetPlayer()->GetLocation()); + if(target->IsNPC()) { + ((NPC*)target)->HaltMovement(); + } + } + else if (target) + Message(CHANNEL_COLOR_RED, "Error: You cannot summon yourself!"); + if (search_client && search_client != this) { + search_client->Message(CHANNEL_COLOR_YELLOW, "You have been summoned by '%s'!", GetPlayer()->GetName()); + Message(CHANNEL_COLOR_YELLOW, "Summoning '%s'...", search_client->GetPlayer()->GetName()); + if (search_client->GetCurrentZone() != GetCurrentZone()) + search_client->Zone(GetCurrentZone()->GetZoneName(), false); + else { + EQ2Packet* app = search_client->GetPlayer()->Move(GetPlayer()->GetX(), GetPlayer()->GetY(), GetPlayer()->GetZ(), search_client->GetVersion()); + if (app) + search_client->QueuePacket(app); + } + } + } + + if (!target) + return false; + else + return true; +} + +std::string Client::IdentifyInstanceLockout(int32 zoneID, bool displayClient) { + int8 instanceType = database.GetInstanceTypeByZoneID(zoneID); + if(instanceType < 1) + return std::string(""); + + ZoneServer* instance_zone = nullptr; + InstanceData* data = GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zoneID); + if (data) { + // If lockout instances check to see if we are locked out + if (instanceType == SOLO_LOCKOUT_INSTANCE || instanceType == GROUP_LOCKOUT_INSTANCE || instanceType == RAID_LOCKOUT_INSTANCE) { + int32 time = 0; + // Check success timer + if (data->last_success_timestamp > 0) { + if (Timer::GetUnixTimeStamp() < data->last_success_timestamp + data->success_lockout_time) { + // Timer has not expired yet can't re enter + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Success lockout not expired for character %s in zone %u", GetPlayer()->GetName(), zoneID); + time = (data->last_success_timestamp + data->success_lockout_time) - Timer::GetUnixTimeStamp(); + } + } + + // Check failure timer + if (data->last_failure_timestamp > 0) { + if (Timer::GetUnixTimeStamp() < data->last_failure_timestamp + data->failure_lockout_time) { + // Timer has not expired yet + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Failure lockout not expired for character %s in zone %u", GetPlayer()->GetName(), zoneID); + time = (data->last_failure_timestamp + data->failure_lockout_time) - Timer::GetUnixTimeStamp(); + } + } + + // Time > 0 then we are locked out, make the message and send it and return true + if (time > 0) { + string time_msg = ""; + int16 hour; + int8 min; + int8 sec; + hour = time / 3600; + time = time % 3600; + min = time / 60; + time = time % 60; + sec = time; + + if (hour > 0) { + char temp[10]; + snprintf(temp, 9," %i", hour); + time_msg.append(temp); + time_msg.append(" hour"); + time_msg.append((hour > 1) ? "s" : ""); + } + if (min > 0) { + char temp[5]; + snprintf(temp, 4," %i", min); + time_msg.append(temp); + time_msg.append(" minute"); + time_msg.append((min > 1) ? "s" : ""); + } + // Only add seconds if minutes and hours are 0 + if (hour == 0 && min == 0 && sec > 0) { + char temp[5]; + snprintf(temp, 4," %i", sec); + time_msg.append(temp); + time_msg.append(" second"); + time_msg.append((sec > 1) ? "s" : ""); + } + + if(displayClient) + Message(CHANNEL_COLOR_YELLOW, "You may not enter again for%s.", time_msg.c_str()); + return time_msg; + } + } + } + return std::string(""); +} + +ZoneServer* Client::IdentifyInstance(int32 zoneID) { + int8 instanceType = database.GetInstanceTypeByZoneID(zoneID); + if(instanceType < 1) + return nullptr; + + ZoneServer* instance_zone = nullptr; + InstanceData* data = GetPlayer()->GetCharacterInstances()->FindInstanceByZoneID(zoneID); + if (data) { + std::string lockoutTime = IdentifyInstanceLockout(zoneID); + // If lockout instances check to see if we are locked out + if (lockoutTime.length() > 0) { + return nullptr; + } + + // Need to update `character_instances` table with new timestamps (for persistent) and instance id's + instance_zone = zone_list.GetByInstanceID(data->instance_id, zoneID, false, false); + + // if we got an instance_zone and the instance_id from the data is 0 or data instance id is not the same as the zone instance id then update values + if (instance_zone && (data->instance_id == 0 || data->instance_id != instance_zone->GetInstanceID())) { + if (instanceType == SOLO_PERSIST_INSTANCE || instanceType == GROUP_PERSIST_INSTANCE || instanceType == RAID_PERSIST_INSTANCE) { + database.UpdateCharacterInstance(GetCharacterID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceID(), 1, Timer::GetUnixTimeStamp()); + data->last_success_timestamp = Timer::GetUnixTimeStamp(); + } + else + database.UpdateCharacterInstance(GetCharacterID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceID()); + + data->instance_id = instance_zone->GetInstanceID(); + } + } + return instance_zone; +} + +bool Client::TryZoneInstance(int32 zoneID, bool zone_coords_valid) { + ZoneServer* instance_zone = NULL; + int8 instanceType = 0; + + // determine if this is a group instanced zone that already exists + instance_zone = GetPlayer()->GetGroupMemberInZone(zoneID); + + if (instance_zone != NULL) + Zone(instance_zone->GetInstanceID(), zone_coords_valid); + else if ((instanceType = database.GetInstanceTypeByZoneID(zoneID)) > 0) + { + // best to check if we already have our own instance! + if((instance_zone = IdentifyInstance(zoneID)) == nullptr) + { + switch (instanceType) + { + case SOLO_LOCKOUT_INSTANCE: + case GROUP_LOCKOUT_INSTANCE: + case RAID_LOCKOUT_INSTANCE: + { + instance_zone = zone_list.GetByInstanceID(0, zoneID); + if (instance_zone) { + // once lockout instance zone shuts down you can't renenter if you have a lockout or if you don't you get a new zone + // so delete `instances` entry for the zone when it shuts down. + int32 db_id = database.AddCharacterInstance(GetPlayer()->GetCharacterID(), instance_zone->GetInstanceID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), 0, 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); + + if (db_id > 0) + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, instance_zone->GetInstanceID(), 0, 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime(), zoneID, instance_zone->GetInstanceType(), string(instance_zone->GetZoneName())); + } + break; + } + case SOLO_PERSIST_INSTANCE: + case GROUP_PERSIST_INSTANCE: + case RAID_PERSIST_INSTANCE: + { + instance_zone = zone_list.GetByInstanceID(0, zoneID); + if (instance_zone) { + int32 db_id = database.AddCharacterInstance(GetPlayer()->GetCharacterID(), instance_zone->GetInstanceID(), string(instance_zone->GetZoneName()), instance_zone->GetInstanceType(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime()); + + if (db_id > 0) + GetPlayer()->GetCharacterInstances()->AddInstance(db_id, instance_zone->GetInstanceID(), Timer::GetUnixTimeStamp(), 0, instance_zone->GetDefaultLockoutTime(), instance_zone->GetDefaultReenterTime(), zoneID, instance_zone->GetInstanceType(), string(instance_zone->GetZoneName())); + } + break; + } + case PUBLIC_INSTANCE: + case TRADESKILL_INSTANCE: + { + // if its public/tradeskill, look for a public already setup + instance_zone = zone_list.GetByLowestPopulation(zoneID); + if (instance_zone) { + // Check the current population against the max population, if greater or equal start a new version + if (instance_zone->GetClientCount() >= rule_manager.GetGlobalRule(R_Zone, MaxPlayers)->GetInt32()) + instance_zone = zone_list.GetByInstanceID(0, zoneID); + } + else + instance_zone = zone_list.GetByInstanceID(0, zoneID); + + break; + } + + case PERSONAL_HOUSE_INSTANCE: + case GUILD_HOUSE_INSTANCE: + { + // Because of the way housing works (need to load a specific instance id supplied in a packet) we can't + // use this function without some rework, so it will all be handled in Client::HandlePacket() + // with the OP_EnterHouseMsg opcode + break; + } + + case QUEST_INSTANCE: + { + instance_zone = zone_list.GetByInstanceID(0, zoneID); + break; + /* + ALTER TABLE `zones` CHANGE COLUMN `instance_type` `instance_type` ENUM('NONE','GROUP_LOCKOUT_INSTANCE','GROUP_PERSIST_INSTANCE','RAID_LOCKOUT_INSTANCE','RAID_PERSIST_INSTANCE','SOLO_LOCKOUT_INSTANCE','SOLO_PERSIST_INSTANCE','TRADESKILL_INSTANCE','PUBLIC_INSTANCE','PERSONAL_HOUSE_INSTANCE','GUILD_HOUSE_INSTANCE','QUEST_INSTANCE') NOT NULL DEFAULT 'NONE' COLLATE 'latin1_general_ci' AFTER `start_zone`; + */ + } + + default: + { + // NONE + } + + } + + } + + if (instance_zone != NULL) + Zone(instance_zone, zone_coords_valid); + } + + + if (instance_zone != NULL) + return true; + else + return false; +} + +bool Client::GotoSpawn(const char* search_name, bool forceTarget) { + Spawn* target = 0; + if (search_name || GetPlayer()->GetTarget()) { + Client* search_client = 0; + if (search_name) { + target = GetCurrentZone()->FindSpawn(GetPlayer(), search_name); + if (!target) { + search_client = zone_list.GetClientByCharName(search_name); + if (search_client) + target = search_client->GetPlayer(); + } + if (target) + GetPlayer()->SetTarget(target); + } + else + target = GetPlayer()->GetTarget(); + + float y = (target != nullptr) ? target->GetY() : 0.0f; + if(target && target->GetMap() != GetPlayer()->GetMap()) { + auto loc = glm::vec3(target->GetX(), target->GetZ(), target->GetY()); + y = GetPlayer()->FindBestZ(loc, nullptr); + } + if (target && target != GetPlayer()) { + GetPlayer()->SetX(target->GetX()); + GetPlayer()->SetY(y); + GetPlayer()->SetZ(target->GetZ()); + GetPlayer()->SetHeading(target->GetHeading()); + GetPlayer()->SetLocation(target->GetLocation()); + Message(CHANNEL_COLOR_YELLOW, "Warping to '%s'", target->GetName()); + } + else if (target) + Message(CHANNEL_COLOR_RED, "Error: You cannot goto yourself!"); + if (search_client && search_client->GetCurrentZone() != GetCurrentZone()) + Zone(search_client->GetCurrentZone()->GetZoneName(), false); + else if (target) { + EQ2Packet* app = GetPlayer()->Move(target->GetX(), y, target->GetZ(), GetVersion(), (target->GetHeading() + 180.0f)); + if (app) + QueuePacket(app); + } + } + + if (!target) + return false; + else + return true; +} + +bool Client::CheckZoneAccess(const char* zoneName) { + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone access check for %s (%u), client: %u", zoneName, database.GetZoneID(zoneName), GetVersion()); + + ZoneServer* zone = zone_list.Get(zoneName, false, false, false); + + // JA: implemented /zone lock|unlock commands (2012.07.28) + if (zone && zone->GetZoneLockState()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone currently LOCKED: '%s' (%ul)", zoneName, zone->GetZoneID()); + Message(CHANNEL_COLOR_RED, "This zone is locked, and you don't have the key! (%s).", zoneName); + return false; + } + + sint16 zoneMinStatus = 0; + int16 zoneMinLevel = 0; + int16 zoneMaxLevel = 0; + int16 zoneMinVersion = 0; + if (!zone) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Grabbing zone requirements for %s", zoneName); + bool success = database.GetZoneRequirements(zoneName, &zoneMinStatus, &zoneMinLevel, &zoneMaxLevel, &zoneMinVersion); + + if (!success) { // couldn't even find the zone, this shouldn't happen though.. + return true; + } + } + else + { + zoneMinStatus = zone->GetMinimumStatus(); + zoneMinLevel = zone->GetMinimumLevel(); + zoneMaxLevel = zone->GetMaximumLevel(); + zoneMinVersion = zone->GetMinimumVersion(); + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "Access Requirements: status %i, level %i - %i, req >= %i version", zoneMinStatus, zoneMinLevel, zoneMaxLevel, zoneMinVersion); + + // use ZoneLevelOverrideStatus in both min_level and max_level checks + sint16 ZoneLevelOverrideStatus = rule_manager.GetGlobalRule(R_Zone, MinZoneLevelOverrideStatus)->GetSInt16(); + + if ((zoneMinVersion > 0) && (GetVersion() < zoneMinVersion)) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone MinVersion of %i challenge...", zoneMinVersion); + Message(CHANNEL_COLOR_RED, "You do not have the required expansion pack to enter here (%s).", database.GetExpansionIDByVersion(zoneMinVersion).c_str()); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Client denied access to zone '%s' (version req: %i)", zoneName, zoneMinVersion); + return false; + } + + + if ((zoneMinLevel > 1) && (player->GetLevel() < zoneMinLevel)) + { + if (ZoneLevelOverrideStatus && ZoneLevelOverrideStatus > GetAdminStatus()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Player denied access to zone '%s' (level req: %i)", zoneName, player->GetLevel()); + Message(CHANNEL_COLOR_RED, "Your level is too low to enter here (%s)", zoneMinLevel); + return false; + } + } + + if ((zoneMaxLevel > 1) && (player->GetLevel() > zoneMaxLevel)) + { + if (ZoneLevelOverrideStatus && ZoneLevelOverrideStatus > GetAdminStatus()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Player denied access to zone '%s' (level req: %i)", zoneName, player->GetLevel()); + Message(CHANNEL_COLOR_RED, "Your level is too high to enter here (%s)", zoneMaxLevel); + return false; + } + } + + if ((zoneMinStatus > 0) && (GetAdminStatus() < zoneMinStatus)) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone MinStatus of %i challenge...", zoneMinStatus); + + sint16 ZoneAccessOverrideStatus = rule_manager.GetGlobalRule(R_Zone, MinZoneAccessOverrideStatus)->GetSInt16(); + if (ZoneAccessOverrideStatus && ZoneAccessOverrideStatus > GetAdminStatus()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Player denied access to zone '%s' (status req: %i)", zoneName, GetAdminStatus()); + Message(CHANNEL_COLOR_RED, "You do not have permission to enter here (%i).", zoneMinStatus); + return false; + } + } + return true; +} + +void Client::Zone(int32 instanceid, bool set_coords, bool byInstanceID, bool is_spell) { + Zone(zone_list.GetByInstanceID(instanceid, 0, false, true), set_coords, is_spell); + +} + +void Client::Zone(ZoneServer* new_zone, bool set_coords, bool is_spell) { + if (!new_zone) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request Denied! No 'new_zone' value"); + return; + } + + if(GetVersion() == 373 && GetAdminStatus() == 0) { + if(strncasecmp(new_zone->GetZoneFile(),"boat_06p_tutorial02",19) && strncasecmp(new_zone->GetZoneFile(),"tutorial_island02",17) + && strncasecmp(new_zone->GetZoneFile(),"tutorial_island02_epic",22)) { // accounts for 01 and 02 zones + SimpleMessage(CHANNEL_COLOR_RED, "This client does not currently support beyond the boat tutorial (farjourneyfreeport) or island of refuge (islerefuge1)"); + return; + } + } + else if(GetVersion() < 546 && GetVersion() > 561) { + if(strncasecmp(new_zone->GetZoneFile(),"design_path_script",18) == 0) { + SimpleMessage(CHANNEL_COLOR_RED, "This zone is only available for KoS and earlier clients."); + return; + } + } + + client_zoning = true; + zoning_id = new_zone->GetZoneID(); + zoning_instance_id = new_zone->GetInstanceID(); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting player Resurrecting to 'true'", __FUNCTION__); + player->SetResurrecting(true); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from fighting...", __FUNCTION__); + //GetCurrentZone()->GetCombat()->RemoveHate(player); + SaveSpells(); + + ResetSendMail(); + // Remove players pet from zone if there is one + ((Entity*)player)->DismissAllPets(); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from current zone...", __FUNCTION__); + GetCurrentZone()->RemoveSpawn(player, false, true, true, true, !is_spell); + + GetPlayer()->DeleteSpellEffects(true); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting zone to '%s'...", __FUNCTION__, new_zone->GetZoneName()); + SetZoningDestination(new_zone); + SetCurrentZone(new_zone); + + if (player->GetGroupMemberInfo()) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Player in group, updating group info...", __FUNCTION__); + player->UpdateGroupMemberInfo(); + world.GetGroupManager()->SendGroupUpdate(player->GetGroupMemberInfo()->group_id, this); + } + + // block out the member info for the group + TempRemoveGroup(); + + UpdateTimeStampFlag(ZONE_UPDATE_FLAG); + + if (set_coords) + { + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Zoning player to coordinates x: %2f, y: %2f, z: %2f, heading: %2f in zone '%s'...", + __FUNCTION__, + GetCurrentZone()->GetSafeX(), + GetCurrentZone()->GetSafeY(), + GetCurrentZone()->GetSafeZ(), + GetCurrentZone()->GetSafeHeading(), + new_zone->GetZoneName() + ); + player->SetX(GetCurrentZone()->GetSafeX()); + player->SetY(GetCurrentZone()->GetSafeY()); + player->SetZ(GetCurrentZone()->GetSafeZ()); + player->SetHeading(GetCurrentZone()->GetSafeHeading()); + + SetZoningCoords(GetCurrentZone()->GetSafeX(), GetCurrentZone()->GetSafeY(), + GetCurrentZone()->GetSafeZ(), GetCurrentZone()->GetSafeHeading()); + } + else { + ResetZoningCoords(); + } + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Saving Player info...", __FUNCTION__); + Save(); + + char* new_zone_ip = 0; + if (IsPrivateAddress(this->GetIP()) && strlen(net.GetInternalWorldAddress()) > 0) + new_zone_ip = net.GetInternalWorldAddress(); + else + new_zone_ip = net.GetWorldAddress(); + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: New Zone IP '%s'...", __FUNCTION__, new_zone_ip); + + int32 key = Timer::GetUnixTimeStamp(); + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Sending ZoneChangeMsg...", __FUNCTION__); + ClientPacketFunctions::SendZoneChange(this, new_zone_ip, net.GetWorldPort(), key); + + LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Sending to zone_auth.AddAuth...", __FUNCTION__); + zone_auth.AddAuth(new ZoneAuthRequest(GetAccountID(), player->GetName(), key)); + if (version > 373) { + PacketStruct* packet = configReader.getStruct("WS_CancelMoveObjectMode", version); + if (packet) + { + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Client::Zone(const char* new_zone, bool set_coords, bool is_spell) +{ + LogWrite(CCLIENT__DEBUG, 0, "Client", "Zone Request to '%s'", new_zone); + Zone(zone_list.Get(new_zone), set_coords, is_spell); +} + +float Client::DistanceFrom(Client* client) { + float ret = 0; + if (client && client != this) { + ret = pow(player->GetX() - client->player->GetX(), 2) + pow(player->GetY() - client->player->GetY(), 2) + pow(player->GetZ() - client->player->GetZ(), 2); + ret = sqrt(ret); + } + + return ret; +} + +void Client::DetermineCharacterUpdates() { + ServerPacket* outpack = new ServerPacket(ServerOP_BasicCharUpdate, sizeof(CharDataUpdate_Struct)); + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*)outpack->pBuffer; + cdu->account_id = GetAccountID(); + cdu->char_id = GetCharacterID(); + int32 timestamp = Timer::GetUnixTimeStamp(); + int8 flag = GetTimeStampFlag(); + if (flag & LEVEL_UPDATE_FLAG) + { + cdu->update_field = LEVEL_UPDATE_FLAG; + cdu->update_data = player->GetLevel(); + loginserver.SendPacket(outpack); + } + //if(flag&CLASS_UPDATE_FLAG && player->GetLevel() >= 20)// Perseverance only + if (flag & CLASS_UPDATE_FLAG) + { + cdu->update_field = CLASS_UPDATE_FLAG; + cdu->update_data = player->GetAdventureClass(); + loginserver.SendPacket(outpack); + } + if (flag & GENDER_UPDATE_FLAG) + { + cdu->update_field = GENDER_UPDATE_FLAG; + cdu->update_data = player->GetGender(); + loginserver.SendPacket(outpack); + } + if (flag & DELETE_UPDATE_FLAG) { + LogWrite(MISC__TODO, 1, "TODO", "Delete update req in func: %s, line: %i", __FUNCTION__, __LINE__); + } + + safe_delete(outpack); // Zone, armor and name use a different structure + + if (flag & RACE_UPDATE_FLAG) + { + outpack = new ServerPacket(ServerOP_RaceUpdate, sizeof(RaceUpdate_Struct)); + RaceUpdate_Struct* ru = (RaceUpdate_Struct*)outpack->pBuffer; + ru->account_id = GetAccountID(); + ru->char_id = GetCharacterID(); + ru->race = player->GetRace(); + ru->model_type = player->GetModelType(); + loginserver.SendPacket(outpack); + safe_delete(outpack); + } + + if (flag & ZONE_UPDATE_FLAG) { + ServerPacket* outpack = new ServerPacket(ServerOP_ZoneUpdate, CHARZONESTRUCT_MAXSIZE); + memset(outpack->pBuffer, 0, CHARZONESTRUCT_MAXSIZE); + CharZoneUpdate_Struct* czu = (CharZoneUpdate_Struct*)outpack->pBuffer; + czu->account_id = GetAccountID(); + czu->char_id = GetCharacterID(); + czu->zone_id = GetCurrentZone()->GetZoneID(); + const char* zone_file = GetCurrentZone()->GetZoneFile(); + czu->zone_length = strlen(zone_file); + if (czu->zone_length > 64) + czu->zone_length = 64; + strncpy(czu->new_zone, zone_file, czu->zone_length); + loginserver.SendPacket(outpack); + safe_delete(outpack); + } + if (flag & ARMOR_UPDATE_FLAG) { + LogWrite(MISC__TODO, 1, "TODO", "Armor update req in func: %s, line: %i", __FUNCTION__, __LINE__); + } + if (flag & NAME_UPDATE_FLAG) { + LogWrite(MISC__TODO, 1, "TODO", "Name update req in func: %s, line: %i", __FUNCTION__, __LINE__); + } + + database.UpdateCharacterTimeStamp(GetAccountID(), GetCharacterID(), timestamp); + +} + +void Client::Save() { + if (GetCharacterID() == 0 || IsSaveDisabled()) + return; + + if (current_zone) { + DetermineCharacterUpdates(); + + UpdateCharacterInstances(); + + this->SetLastSavedTimeStamp(Timer::GetCurrentTime2()); + database.Save(this); + if (GetPlayer()->UpdateQuickbarNeeded()) { + database.SaveQuickBar(GetCharacterID(), GetPlayer()->GetQuickbar()); + GetPlayer()->ResetQuickbarNeeded(); + } + database.SaveItems(this); + database.SaveBuyBacks(this); + + GetPlayer()->SaveHistory(); + GetPlayer()->SaveLUAHistory(); + MSaveSpellStateMutex.lock(); + GetPlayer()->SaveSpellEffects(); + MSaveSpellStateMutex.unlock(); + } + +} + +void Client::UpdateCharacterInstances() { + if (GetPlayer() != NULL) + GetPlayer()->GetCharacterInstances()->ProcessInstanceTimers(GetPlayer()); + + /*if ( GetPlayer() != NULL ) + { + // determine the last timestamp then get a new one, determine the difference in the timestamp + // to use for applying the update to each instances timer + int32 lastSaveTS = GetLastSavedTimeStamp(); + int32 newSaveTS = Timer::GetUnixTimeStamp(); + int32 diffTS = newSaveTS - lastSaveTS; + + // update instance timers + GetPlayer()->GetCharacterInstances().ProcessInstanceTimers(GetPlayer(),diffTS); + + // update with the new timestamp and save the db + this->SetLastSavedTimeStamp(newSaveTS); + }*/ + +} + +void Client::HandleVerbRequest(EQApplicationPacket* app) { + PacketStruct* packet = configReader.getStruct("WS_EntityVerbsRequest", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + int32 spawn_id = packet->getType_int32_ByName("spawn_id"); + PacketStruct* out = configReader.getStruct("WS_EntityVerbsResponse", GetVersion()); + Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(spawn_id); + vector commands; + vector delete_commands; + if (out && spawn) { + for (int32 i = 0; i < spawn->primary_command_list.size(); i++) + { + // default is a deny list not allow, only allow if on the iterator list and itr second is not false (deny) + if (!spawn->primary_command_list[i]->default_allow_list) + { + map::iterator itr = spawn->primary_command_list[i]->allow_or_deny.find(GetPlayer()->GetCharacterID()); + if (itr == spawn->primary_command_list[i]->allow_or_deny.end() || !itr->second) + continue; + } + else + { + // default is allow list, only deny if added to the list as deny (false itr second) + map::iterator itr = spawn->primary_command_list[i]->allow_or_deny.find(GetPlayer()->GetCharacterID()); + if (itr != spawn->primary_command_list[i]->allow_or_deny.end() && !itr->second) + continue; + } + commands.push_back(spawn->primary_command_list[i]); + } + for (int32 i = 0; i < spawn->secondary_command_list.size(); i++) + commands.push_back(spawn->secondary_command_list[i]); + if (spawn->IsPlayer()) { + if (player->IsFriend(spawn->GetName())) + delete_commands.push_back(player->CreateEntityCommand("remove from friends list", 10000, "friend_remove", "", 0, 0)); + else + delete_commands.push_back(player->CreateEntityCommand("add to friends list", 10000, "friend_add", "", 0, 0)); + if (player->IsIgnored(spawn->GetName())) + delete_commands.push_back(player->CreateEntityCommand("remove from ignore list", 10000, "ignore_remove", "", 0, 0)); + else + { + delete_commands.push_back(player->CreateEntityCommand("add to ignore list", 10000, "ignore_add", "", 0, 0)); + delete_commands.push_back(player->CreateEntityCommand("Trade", 10, "start_trade", "", 0, 0)); + } + if (((Player*)spawn)->GetGroupMemberInfo()) { + if (player->IsGroupMember((Player*)spawn) && player->GetGroupMemberInfo()->leader) { //group leader + delete_commands.push_back(player->CreateEntityCommand("kick from group", 10000, "kickfromgroup", "", 0, 0)); + delete_commands.push_back(player->CreateEntityCommand("make group leader", 10000, "makeleader", "", 0, 0)); + } + if(spawn->IsPlayer() && player->GetGroupMemberInfo() && !player->GetGroupMemberInfo()->mentor_target_char_id) + delete_commands.push_back(player->CreateEntityCommand("Mentor", 10000, "mentor", "", 0, 0)); + else if(spawn->IsPlayer() && player->GetGroupMemberInfo() && player->GetGroupMemberInfo()->mentor_target_char_id == ((Player*)spawn)->GetCharacterID()) + delete_commands.push_back(player->CreateEntityCommand("Stop Mentoring", 10000, "unmentor", "", 0, 0)); + } + else if (!player->GetGroupMemberInfo() || (player->GetGroupMemberInfo()->leader && world.GetGroupManager()->GetGroupSize(player->GetGroupMemberInfo()->group_id) < 6)) + delete_commands.push_back(player->CreateEntityCommand("invite to group", 10000, "invite", "", 0, 0)); + commands.insert(commands.end(), delete_commands.begin(), delete_commands.end()); + } + out->setDataByName("spawn_id", spawn_id); + out->setArrayLengthByName("num_verbs", commands.size()); + + for (int32 i = 0; i < commands.size(); i++) { + out->setArrayDataByName("command", commands[i]->command.c_str(), i); + out->setArrayDataByName("distance", commands[i]->distance, i); + if (commands[i]->error_text.length() == 0) + out->setArrayAddToPacketByName("error", false, i); + else { + out->setArrayDataByName("display_error", 1, i); + out->setArrayDataByName("error", commands[i]->error_text.c_str(), i); + } + out->setArrayDataByName("display_text", commands[i]->name.c_str(), i); + } + EQ2Packet* outapp = out->serialize(); + //DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(out); + for (int32 i = 0; i < delete_commands.size(); i++) { + safe_delete(delete_commands[i]); + } + } + } + safe_delete(packet); + } + +} + +void Client::SkillChanged(Skill* skill, int16 previous_value, int16 new_value) { + if (previous_value != new_value) { + Message(CHANNEL_SKILL, "You get %s at %s (%i/%i).", new_value > previous_value ? "better" : "worse", skill->name.data.c_str(), new_value, skill->max_val); + char tmp[200] = { 0 }; + sprintf(tmp, "\\#6EFF6EYou get %s at \12\\#C8FFC8%s\\#6EFF6E! (%i/%i)", new_value > previous_value ? "better" : "worse", skill->name.data.c_str(), new_value, skill->max_val); + SendPopupMessage(6, tmp, new_value > previous_value ? "skill_up" : "skill_down", 2.75f, 0xFF, 0xFF, 0xFF); + } + +} + +void Client::SendPopupMessage(int8 unknown, const char* text, const char* type, float size, int8 red, int8 green, int8 blue) +{ + /* JA notes on the unknown: + 2 = ding glimmer + 16 = Achievement Unlocked + 6 no longer does anything + */ + + PacketStruct* packet = configReader.getStruct("WS_OnScreenMsg", GetVersion()); + if (packet) { + packet->setDataByName("unknown", unknown); + packet->setMediumStringByName("text", text); + if (type && strlen(type) > 0) + packet->setMediumStringByName("message_type", type); + packet->setDataByName("size", size); + packet->setDataByName("red", red); + packet->setDataByName("green", green); + packet->setDataByName("blue", blue); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::ChangeLevel(int16 old_level, int16 new_level) { + if (new_level < 1) { + SimpleMessage(CHANNEL_COLOR_RED, "You cannot be lower than level 1!"); + return; + } + + if (player->GetLevel() != new_level) { + player->SetLevel(new_level); + if (player->GetGroupMemberInfo()) { + player->UpdateGroupMemberInfo(); + world.GetGroupManager()->SendGroupUpdate(player->GetGroupMemberInfo()->group_id); + } + } + + if (new_level > old_level) { + player->UpdatePlayerHistory(HISTORY_TYPE_XP, HISTORY_SUBTYPE_ADVENTURE, new_level, player->GetAdventureClass()); + } + + if (player->GetPet()) { + NPC* pet = (NPC*)player->GetPet(); + if (pet->GetMaxPetLevel() == 0 || new_level <= pet->GetMaxPetLevel()) { + pet->SetLevel(new_level); + pet->UpdateWeapons(); + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", GetVersion()); + if (command_packet) { + command_packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(pet)); + command_packet->setDataByName("anim_type", 1753); + QueuePacket(command_packet->serialize()); + safe_delete(command_packet); + } + } + } + + PacketStruct* level_update = configReader.getStruct("WS_LevelChanged", GetVersion()); + if (level_update) { + level_update->setDataByName("old_level", old_level); + level_update->setDataByName("new_level", new_level); + QueuePacket(level_update->serialize()); + safe_delete(level_update); + GetCurrentZone()->StartZoneSpawnsForLevelThread(this); + GetCurrentZone()->SendCastSpellPacket(322, player, player); //send level up spell effect + //GetCurrentZone()->SendAllSpawnsForLevelChange(this); + } + + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", GetVersion()); + if (command_packet) { + command_packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(player)); + command_packet->setDataByName("anim_type", 1753); + QueuePacket(command_packet->serialize()); + safe_delete(command_packet); + } + + if (!player->get_character_flag(CF_ENABLE_CHANGE_LASTNAME) && new_level >= rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8()) + player->set_character_flag(CF_ENABLE_CHANGE_LASTNAME); + + SendNewAdventureSpells(); + + GetPlayer()->GetInfoStruct()->set_level(new_level); + GetPlayer()->UpdateWeapons(); + // GetPlayer()->SetLevel(new_level); + + LogWrite(MISC__TODO, 1, "TODO", "Get new HP/POWER/stat based on default values from DB\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + GetPlayer()->CalculatePlayerHPPower(); + GetPlayer()->CalculateBonuses(); + GetPlayer()->SetHP(GetPlayer()->GetTotalHP()); + GetPlayer()->SetPower(GetPlayer()->GetTotalPower()); + /*InfoStruct* info = player->GetInfoStruct(); + info->set_agi_base(new_level * 2 + 15); + info->set_intel_base(new_level * 2 + 15); + info->set_wis_base(new_level * 2 + 15); + info->set_str_base(new_level * 2 + 15); + info->set_sta_base(new_level * 2 + 15); + info->set_cold_base((int16)(new_level * 1.5 + 10)); + info->set_heat_base((int16)(new_level * 1.5 + 10)); + info->set_disease_base((int16)(new_level * 1.5 + 10)); + info->set_mental_base((int16)(new_level * 1.5 + 10)); + info->set_magic_base((int16)(new_level * 1.5 + 10)); + info->set_divine_base((int16)(new_level * 1.5 + 10)); + info->set_poison_base((int16)(new_level * 1.5 + 10)); + GetPlayer()->GetInfoStruct()->set_poison_base((int16)(new_level * 1.5 + 10));*/ + UpdateTimeStampFlag(LEVEL_UPDATE_FLAG); + GetPlayer()->SetCharSheetChanged(true); + + Message(CHANNEL_REWARD, "You are now level %i!", new_level); + LogWrite(WORLD__DEBUG, 0, "World", "Player: %s leveled from %u to %u", GetPlayer()->GetName(), old_level, new_level); + int16 new_skill_cap = 5 * new_level; + PlayerSkillList* player_skills = player->GetSkills(); + + player_skills->SetSkillCapsByType(SKILL_TYPE_ARMOR, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_SHIELD, new_skill_cap); + + if(rule_manager.GetGlobalRule(R_Player, AutoSkillUpBaseSkills)->GetBool()) { + //SKILL_TYPE_ARMOR/SKILL_TYPE_SHIELD always has the same current / max values + player_skills->SetSkillValuesByType(SKILL_TYPE_ARMOR, new_skill_cap, false); + player_skills->SetSkillValuesByType(SKILL_TYPE_SHIELD, new_skill_cap, false); + } + + player_skills->SetSkillCapsByType(SKILL_TYPE_CLASS, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_WEAPON, new_skill_cap); + + if(rule_manager.GetGlobalRule(R_Player, AutoSkillUpBaseSkills)->GetBool()) { + //SKILL_TYPE_CLASS/SKILL_TYPE_WEAPON always has the same current/max values + player_skills->SetSkillValuesByType(SKILL_TYPE_CLASS, new_skill_cap, false); + player_skills->SetSkillValuesByType(SKILL_TYPE_WEAPON, new_skill_cap, false); + } + + player_skills->SetSkillCapsByType(SKILL_TYPE_COMBAT, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_GENERAL, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_SPELLCASTING, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_AVOIDANCE, new_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_WEAPONRY, new_skill_cap); + + if (new_level > player->GetTSLevel()) + player_skills->SetSkillCapsByType(SKILL_TYPE_HARVESTING, new_skill_cap); + + //SKILL_ID_DUALWIELD, SKILL_ID_FISTS, SKILL_ID_DESTROYING, and SKILL_ID_MAGIC_AFFINITY always have the current_val equal to max_val + if (player_skills->HasSkill(SKILL_ID_DUALWIELD)) + player_skills->SetSkill(SKILL_ID_DUALWIELD, new_skill_cap); + if (player_skills->HasSkill(SKILL_ID_FISTS)) + player_skills->SetSkill(SKILL_ID_FISTS, new_skill_cap); + if (player_skills->HasSkill(SKILL_ID_DESTROYING)) + player_skills->SetSkill(SKILL_ID_DESTROYING, new_skill_cap); + if (player_skills->HasSkill(SKILL_ID_MAGIC_AFFINITY)) + player_skills->SetSkill(SKILL_ID_MAGIC_AFFINITY, new_skill_cap); + + Guild* guild = GetPlayer()->GetGuild(); + if (guild) { + int8 event_type = 0; + if (new_level < 10) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_1_10; + else if (new_level == 10) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_10; + else if (new_level >= 11 && new_level < 20) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_11_20; + else if (new_level == 20) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_20; + else if (new_level >= 21 && new_level < 30) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_21_30; + else if (new_level == 30) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_30; + else if (new_level >= 31 && new_level < 40) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_31_40; + else if (new_level == 40) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_40; + else if (new_level >= 41 && new_level < 50) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_41_50; + else if (new_level == 50) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_50; + else if (new_level >= 51 && new_level < 60) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_51_60; + else if (new_level == 60) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_60; + else if (new_level >= 61 && new_level < 70) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_61_70; + else if (new_level == 70) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_70; + else if (new_level >= 71 && new_level < 80) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_71_80; + else if (new_level == 80) + event_type = GUILD_EVENT_GAINS_ADV_LEVEL_80; + + guild->AddNewGuildEvent(event_type, "%s has gained an adventure level and is now a level %u %s.", Timer::GetUnixTimeStamp(), true, GetPlayer()->GetName(), new_level, classes.GetClassNameCase(GetPlayer()->GetAdventureClass()).c_str()); + guild->SendMessageToGuild(event_type, "%s has gained an adventure level and is now a level %u %s.", GetPlayer()->GetName(), new_level, classes.GetClassNameCase(GetPlayer()->GetAdventureClass()).c_str()); + guild->UpdateGuildMemberInfo(GetPlayer()); + guild->SendGuildMember(GetPlayer()); + guild->SendGuildMemberList(); + } + + // Need to send the trait list every time the players level changes + // Also need to force the char sheet update or else there can be a large delay from when you level + // to when you are actually able to select traits. + QueuePacket(GetPlayer()->GetPlayerInfo()->serialize(GetVersion())); + + GetPlayer()->need_trait_update = true; + if (version > 561) { + QueuePacket(master_trait_list.GetTraitListPacket(this)); + master_aa_list.DisplayAA(this, 0, 0); + } + else + master_trait_list.ChooseNextTrait(this); + + if (GetPlayer()->SpawnedBots.size() > 0) { + map::iterator itr; + for (itr = GetPlayer()->SpawnedBots.begin(); itr != GetPlayer()->SpawnedBots.end(); itr++) { + Spawn* bot = GetCurrentZone()->GetSpawnByID(itr->second); + if (bot && bot->IsBot()) + ((Bot*)bot)->ChangeLevel(old_level, new_level); + } + } + + if (GetPlayer()->GetHP() < GetPlayer()->GetTotalHP() || GetPlayer()->GetPower() < GetPlayer()->GetTotalPower()) + GetPlayer()->GetZone()->AddDamagedSpawn(GetPlayer()); +} + +void Client::ChangeTSLevel(int16 old_level, int16 new_level) { + if (new_level < 1) { + SimpleMessage(CHANNEL_COLOR_RED, "You cannot be lower than level 1!"); + return; + } + if ((player->GetTSLevel() >= 9 && player->GetTradeskillClass() == 1) || (player->GetTSLevel() >= 19 && (player->GetTradeskillClass() == 1 || player->GetTradeskillClass() == 2 || player->GetTradeskillClass() == 6 || player->GetTradeskillClass() == 10))) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You can not gain levels until you select your next class!"); + return; + } + + if (new_level > old_level) + player->UpdatePlayerHistory(HISTORY_TYPE_XP, HISTORY_SUBTYPE_TRADESKILL, new_level, player->GetTradeskillClass()); + + if (player->GetTSLevel() != new_level) { + player->SetTSLevel(new_level); + if (player->GetGroupMemberInfo()) { + player->UpdateGroupMemberInfo(); + world.GetGroupManager()->SendGroupUpdate(player->GetGroupMemberInfo()->group_id); + } + } + // Only tradeskill skills should increace, and then only those related to your class + PacketStruct* level_update = configReader.getStruct("WS_LevelChanged", GetVersion()); + if (level_update) { + level_update->setDataByName("old_level", old_level); + level_update->setDataByName("new_level", new_level); + level_update->setDataByName("type", 1); + QueuePacket(level_update->serialize()); + safe_delete(level_update); + } + + // provide new spells upon levelling + SendNewTradeskillSpells(); + + PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", GetVersion()); + if (command_packet) { + command_packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(GetPlayer())); + command_packet->setDataByName("anim_type", 1753); + QueuePacket(command_packet->serialize()); + safe_delete(command_packet); + } + GetPlayer()->GetInfoStruct()->set_tradeskill_level(new_level); + GetPlayer()->SetTSLevel(new_level); + + + UpdateTimeStampFlag(LEVEL_UPDATE_FLAG); + GetPlayer()->SetCharSheetChanged(true); + Message(CHANNEL_NARRATIVE, "Your tradeskill level is now %i!", new_level); + LogWrite(WORLD__DEBUG, 0, "World", "Player: %s leveled from %u to %u", GetPlayer()->GetName(), old_level, new_level); + + PlayerSkillList* player_skills = player->GetSkills(); + int16 specialize_skill_cap = new_level * 5; + int16 artisan_skill_cap = std::max(specialize_skill_cap, 49); + int16 specialize_10_skill_cap = std::max(specialize_skill_cap, 99); + + int8 ts_class = player->GetTradeskillClass(); + int8 base_ts_class = classes.GetSecondaryTSBaseClass(ts_class); + int32 skill_id_1, skill_id_2, skill_id_3; + + switch (base_ts_class) { + case ARTISAN: + player_skills->SetSkillCapsByType(SKILL_TYPE_OUTFITTER, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_SCHOLAR, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_CRAFTSMAN, artisan_skill_cap); + break; + case OUTFITTER: + player_skills->SetSkillCapsByType(SKILL_TYPE_SCHOLAR, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_CRAFTSMAN, artisan_skill_cap); + + skill_id_1 = SKILL_ID_TAILORING; + skill_id_2 = SKILL_ID_METALSHAPING; + skill_id_3 = SKILL_ID_METALWORKING; + + if (ts_class == TAILOR) { + player_skills->SetSkillCap(skill_id_1, specialize_skill_cap); + skill_id_1 = 0; + } + else if (ts_class == ARMORER) { + player_skills->SetSkillCap(skill_id_2, specialize_skill_cap); + skill_id_2 = 0; + } + else if (ts_class == WEAPONSMITH) { + player_skills->SetSkillCap(skill_id_3, specialize_skill_cap); + skill_id_3 = 0; + } + + if (skill_id_1) player_skills->SetSkillCap(skill_id_1, specialize_10_skill_cap); + if (skill_id_2) player_skills->SetSkillCap(skill_id_2, specialize_10_skill_cap); + if (skill_id_3) player_skills->SetSkillCap(skill_id_3, specialize_10_skill_cap); + break; + case SCHOLAR: + player_skills->SetSkillCapsByType(SKILL_TYPE_OUTFITTER, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_CRAFTSMAN, artisan_skill_cap); + + skill_id_1 = SKILL_ID_SCRIBING; + skill_id_2 = SKILL_ID_CHEMISTRY; + skill_id_3 = SKILL_ID_ARTIFICING; + + if (ts_class == SAGE) { + player_skills->SetSkillCap(skill_id_1, specialize_skill_cap); + skill_id_1 = 0; + } + else if (ts_class == ALCHEMIST) { + player_skills->SetSkillCap(skill_id_2, specialize_skill_cap); + skill_id_2 = 0; + } + else if (ts_class == JEWELER) { + player_skills->SetSkillCap(skill_id_3, specialize_skill_cap); + skill_id_3 = 0; + } + + if (skill_id_1) player_skills->SetSkillCap(skill_id_1, specialize_10_skill_cap); + if (skill_id_2) player_skills->SetSkillCap(skill_id_2, specialize_10_skill_cap); + if (skill_id_3) player_skills->SetSkillCap(skill_id_3, specialize_10_skill_cap); + break; + case CRAFTSMAN: + player_skills->SetSkillCapsByType(SKILL_TYPE_OUTFITTER, artisan_skill_cap); + player_skills->SetSkillCapsByType(SKILL_TYPE_SCHOLAR, artisan_skill_cap); + + skill_id_1 = SKILL_ID_ARTISTRY; + skill_id_2 = SKILL_ID_FLETCHING; + skill_id_3 = SKILL_ID_SCULPTING; + + if (ts_class == PROVISIONER) { + player_skills->SetSkillCap(skill_id_1, specialize_skill_cap); + skill_id_1 = 0; + } + else if (ts_class == WOODWORKER) { + player_skills->SetSkillCap(skill_id_2, specialize_skill_cap); + skill_id_2 = 0; + } + else if (ts_class == CARPENTER) { + player_skills->SetSkillCap(skill_id_3, specialize_skill_cap); + skill_id_3 = 0; + } + + if (skill_id_1) player_skills->SetSkillCap(skill_id_1, specialize_10_skill_cap); + if (skill_id_2) player_skills->SetSkillCap(skill_id_2, specialize_10_skill_cap); + if (skill_id_3) player_skills->SetSkillCap(skill_id_3, specialize_10_skill_cap); + break; + default: + break; + } + + if (new_level > player->GetAdventureClass()) + player_skills->SetSkillCapsByType(SKILL_TYPE_HARVESTING, specialize_skill_cap); + + Guild* guild = GetPlayer()->GetGuild(); + if (guild) { + int8 event_type = 0; + if (new_level < 10) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_1_10; + else if (new_level == 10) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_10; + else if (new_level >= 11 && new_level < 20) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_11_20; + else if (new_level == 20) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_20; + else if (new_level >= 21 && new_level < 30) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_21_30; + else if (new_level == 30) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_30; + else if (new_level >= 31 && new_level < 40) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_31_40; + else if (new_level == 40) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_40; + else if (new_level >= 41 && new_level < 50) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_41_50; + else if (new_level == 50) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_50; + else if (new_level >= 51 && new_level < 60) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_51_60; + else if (new_level == 60) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_60; + else if (new_level >= 61 && new_level < 70) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_61_70; + else if (new_level == 70) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_70; + else if (new_level >= 71 && new_level < 80) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_71_80; + else if (new_level == 80) + event_type = GUILD_EVENT_GAINS_TS_LEVEL_80; + guild->AddNewGuildEvent(event_type, "%s has gained a tradeskill level and is now a level %u %s.", Timer::GetUnixTimeStamp(), true, GetPlayer()->GetName(), new_level, classes.GetClassNameCase(GetPlayer()->GetTradeskillClass() + 42).c_str()); + guild->SendMessageToGuild(event_type, "%s has gained a tradeskill level and is now a level %u %s.", GetPlayer()->GetName(), new_level, classes.GetClassNameCase(GetPlayer()->GetTradeskillClass() + 42).c_str()); + guild->UpdateGuildMemberInfo(GetPlayer()); + guild->SendGuildMember(GetPlayer()); + guild->SendGuildMemberList(); + } + + // Need to send the trait list every time the players level changes + // Also need to force the char sheet update or else there can be a large delay from when you level + // to when you are actually able to select traits. + QueuePacket(GetPlayer()->GetPlayerInfo()->serialize(GetVersion())); + QueuePacket(master_trait_list.GetTraitListPacket(this)); +} + +void Client::CloseLoot(int32 spawn_id) { + if (GetVersion() > 561) { + PacketStruct* packet = configReader.getStruct("WS_CloseWindow", GetVersion()); + if (packet) { + packet->setDataByName("window_id", 4); + EQ2Packet* outapp = packet->serialize(); + if (outapp) { + //DumpPacket(outapp); + QueuePacket(outapp); + } + safe_delete(packet); + } + } + + if(spawn_id > 0){ + PacketStruct* packet = configReader.getStruct("WS_StoppedLooting", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", spawn_id); + EQ2Packet* outapp = packet->serialize(); + if (outapp) + QueuePacket(outapp); + safe_delete(packet); + } + + Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(spawn_id); + if(spawn) { + spawn->CloseLoot(GetPlayer()); + } + } +} + +string Client::GetCoinMessage(int32 total_coins) { + if (total_coins == 0) { + return " 0 Copper"; + } + char tmp[64] = { 0 }; + string message = ""; + int32 val = 0; + if (total_coins >= 1000000) { + val = total_coins / 1000000; + total_coins -= 1000000 * val; + sprintf(tmp, " %u Platinum", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 10000) { + val = total_coins / 10000; + total_coins -= 10000 * val; + sprintf(tmp, " %u Gold", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 100) { + val = total_coins / 100; + total_coins -= 100 * val; + sprintf(tmp, " %u Silver", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins > 0) { + sprintf(tmp, " %u Copper", (int32)total_coins); + message.append(tmp); + } + + return message; +} + +void Client::SendLootResponsePacket(int32 total_coins, vector* items, Spawn* entity, bool ignore_loot_tier) { + if (!entity) { + CloseLoot(0); + return; + } + if (total_coins > 0) { + player->AddCoins(total_coins); + //PlaySound("coin_cha_ching"); + string message = ""; + if (entity->GetHP() == 0) { + message = "You loot "; + entity->SetLootCoins(0, false); + } + else + message = "You receive "; + message.append(GetCoinMessage(total_coins)); + if (entity->GetHP() == 0) + message.append(" from the corpse of ").append(entity->GetName()); + int8 type = CHANNEL_LOOT; + + SimpleMessage(type, message.c_str()); + } + + entity->StartLootTimer(GetPlayer()); + + PacketStruct* packet = configReader.getStruct("WS_UpdateLoot", GetVersion()); + if (packet) { + entity->AddSpawnLootWindowCompleted(GetPlayer()->GetID(), false); + vector::iterator itr; + int32 packet_size = 0; + EQ2Packet* outapp = 0; + uchar* data = 0; + vector send_items; + if (items && items->size() > 0) { + for (int i = 0; i < items->size(); i++) { + Item* item = (*items)[i]; + + if (entity->GetLootMethod() > GroupLootMethod::METHOD_FFA && !ignore_loot_tier) { + bool skipItem = entity->IsItemInLootTier(item); + if (!skipItem) { + send_items.push_back(item); + } + } + else { + send_items.push_back(item); + } + } + } + if (GetVersion() >= 374) { + if (GetVersion() > 561) { + if (send_items.size() > 0) { + packet->setDataByName("loot_count", send_items.size()); + packet->setDataByName("display", 1); + } + packet->setDataByName("loot_type", entity->GetLootMethod()); + + packet->setDataByName("lotto_timeout", entity->GetLootTimeRemaining() / 1000); + + packet->setDataByName("loot_id", entity->GetID()); + EQ2Packet* tmpPacket = packet->serialize(); + packet_size += tmpPacket->size; + if (send_items.size() > 0) { + data = new uchar[send_items.size() * 1000 + packet_size]; + memset(data, 0, send_items.size() * 1000 + packet_size); + } + else { + data = new uchar[packet_size]; + memset(data, 0, packet_size); + } + uchar* ptr = data; + memcpy(ptr, tmpPacket->pBuffer, tmpPacket->size); + ptr += tmpPacket->size; + safe_delete(tmpPacket); + Item* item = 0; + if (send_items.size() > 0) { + for (itr = send_items.begin(); itr != send_items.end(); itr++) { + item = *itr; + memcpy(ptr, &item->details.item_id, sizeof(int32)); + ptr += sizeof(int32); + packet_size += sizeof(int32); + + tmpPacket = item->serialize(GetVersion(), true, GetPlayer(), false, 1, 0, false, true); + + int8 offset = 0; + if (GetVersion() >= 1188) { + offset = 13; + } + else if (GetVersion() >= 860) { + offset = 11; + } + else if (GetVersion() <= 561) { + offset = 19; + } + else { + offset = 10; + } + + memcpy(ptr, tmpPacket->pBuffer + offset, tmpPacket->size - offset); + ptr += tmpPacket->size - offset; + packet_size += tmpPacket->size - offset; + + safe_delete(tmpPacket); + } + } + packet_size -= sizeof(int32); + memcpy(data, &packet_size, sizeof(int32)); + packet_size += sizeof(int32); + outapp = new EQ2Packet(OP_ClientCmdMsg, data, packet_size); + } + else { + if (send_items.size() > 0) { + packet->setArrayLengthByName("loot_count", send_items.size()); + Item* item = 0; + if (send_items.size() > 0) { + int i = 0; + for (itr = send_items.begin(); itr != send_items.end(); itr++) { + item = *itr; + packet->setArrayDataByName("loot_id", item->details.item_id, i); + packet->setItemArrayDataByName("item", item, GetPlayer(), i, 0, 2, true); + i++; + } + } + packet->setDataByName("display", 1); + } + packet->setDataByName("loot_type", entity->GetLootMethod()); // normal + packet->setDataByName("lotto_timeout", entity->GetLootTimeRemaining() / 1000); // 60 seconds + packet->setDataByName("spawn_id", entity->GetID()); + outapp = packet->serialize(); + } + } + else { + if (send_items.size() > 0) { + packet->setArrayLengthByName("loot_count", send_items.size()); + for (int i = 0; i < send_items.size(); i++) { + Item* item = (send_items)[i]; + packet->setArrayDataByName("name", item->name.c_str(), i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("count", item->details.count, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + if (item->generic_info.skill_req1 > 0 && item->generic_info.skill_req1 < 0xFFFFFFFF) + packet->setArrayDataByName("ability_id", item->generic_info.skill_req1, i); + else if (item->generic_info.skill_req2 > 0 && item->generic_info.skill_req2 < 0xFFFFFFFF) + packet->setArrayDataByName("ability_id", item->generic_info.skill_req2, i); + else + packet->setArrayDataByName("ability_id", 0xFFFFFFFF, i); + } + } + packet->setDataByName("display", 1); + packet->setDataByName("loot_type", entity->GetLootMethod()); // normal + packet->setDataByName("lotto_timeout", entity->GetLootTimeRemaining() / 1000); // 60 seconds + packet->setDataByName("object_id", entity->GetID()); + outapp = packet->serialize(); + } + if (outapp) { + QueuePacket(outapp); + } + safe_delete_array(data); + safe_delete(packet); + + if (!items || items->size() == 0) + CloseLoot(entity->GetID()); + + } + +} + +bool Client::LootSpawnByMethod(Spawn* entity) { + bool sentLoot = false; + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + GroupMemberInfo* gmi = GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + int8 auto_split_coin = group->GetGroupOptions()->auto_split; + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + int32 split_coin_per_player = 0; + int32 coins_remain_after_split = entity->GetLootCoins(); + int32 total_coins = entity->GetLootCoins(); + + if (auto_split_coin) { + int8 members_in_zone = 0; + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || !member->IsPlayer()) + continue; + + if (member->GetZone() != GetPlayer()->GetZone()) + continue; + + members_in_zone++; + } + + if (members_in_zone < 1) // this should not happen, but divide by zero checked + members_in_zone = 0; + + split_coin_per_player = entity->GetLootCoins() / members_in_zone; + coins_remain_after_split = entity->GetLootCoins() - (split_coin_per_player * members_in_zone); + entity->SetLootCoins(0, false); + } + + LogWrite(LOOT__INFO, 0, "Loot", "%s: Group LootSpawnByMethod %u, auto coin split %u, split coin per player %u, remaining coin after split %u", entity->GetName(), entity->GetLootMethod(), auto_split_coin, split_coin_per_player, coins_remain_after_split); + bool startWithLooter = true; + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if (!member || !member->IsPlayer()) + continue; + + if (member->GetZone() != GetPlayer()->GetZone()) + continue; + + // this will make sure we properly send the loot window to the initial requester if there is no item rarity matches + if (startWithLooter && member != GetPlayer()) + continue; + else if (!startWithLooter && member == GetPlayer()) + continue; + else if (startWithLooter) { + i = 0; + startWithLooter = false; + } + + if (auto_split_coin && (split_coin_per_player + coins_remain_after_split) > 0) { + player->AddCoins(split_coin_per_player + coins_remain_after_split); + if (((Player*)member)->GetClient()) { + ((Player*)member)->GetClient()->Message(CHANNEL_MONEY_SPLIT, "Your share of %s from the corpse of %s is %s.", GetCoinMessage(total_coins).c_str(), entity->GetLootName(), GetCoinMessage(split_coin_per_player + coins_remain_after_split).c_str()); + } + if (coins_remain_after_split > 0) // overflow of coin division went to the first player + coins_remain_after_split = 0; + } + switch (entity->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + if (((Player*)member)->GetClient()) { + switch (member->GetInfoStruct()->get_group_auto_loot_method()) { + case 1: { // lotto, need + if (entity->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { + entity->AddLottoItemRequest(0xFFFFFFFF, GetPlayer()->GetID()); + } + else { // *need* before greed + entity->AddNeedGreedItemRequest(0xFFFFFFFF, GetPlayer()->GetID(), true); + } + entity->AddSpawnLootWindowCompleted(member->GetID(), true); + // if it already exists we have to override the setting + entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + break; + } + case 2: { // decline + entity->AddSpawnLootWindowCompleted(member->GetID(), true); + // if it already exists we have to override the setting + entity->SetSpawnLootWindowCompleted(GetPlayer()->GetID()); + break; + } + default: { // presume 0 or higher than 2 + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity, true); + break; + } + } + sentLoot = true; + } + break; + } + case GroupLootMethod::METHOD_ROUND_ROBIN: { + entity->AddSpawnLootWindowCompleted(member->GetID(), true); + sentLoot = true; + break; + } + case GroupLootMethod::METHOD_LEADER: { + if (member->GetGroupMemberInfo()->leader) + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity); + break; + } + case GroupLootMethod::METHOD_FFA: { + if(member == GetPlayer()) { + ((Player*)member)->GetClient()->SendLootResponsePacket((!auto_split_coin && member == GetPlayer()) ? entity->GetLootCoins() : 0, entity->GetLootItems(), entity); + } + break; + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + return sentLoot; +} +void Client::LootSpawnRequest(Spawn* entity, bool attemptDisarm) { + bool lootAllowed = false; + bool sentLoot = false; + std::vector item_list; + if (entity->IsNPC()) { + entity->LockLoot(); + lootAllowed = ((NPC*)entity)->Brain()->CheckLootAllowed(GetPlayer()); + entity->UnlockLoot(); + + if (lootAllowed) { + OpenChest(entity, attemptDisarm); + } + else { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not allowed to loot at this time."); + return; + } + + entity->LockLoot(); + if (((NPC*)entity)->Brain()->CheckLootAllowed(GetPlayer())) { + lootAllowed = true; + if ((sentLoot = LootSpawnByMethod(entity))) { + entity->GetLootItemsList(&item_list); + } + else { + SendLootResponsePacket(entity->GetLootCoins(), entity->GetLootItems(), entity); + } + } + entity->UnlockLoot(); + + if (lootAllowed) { + entity->DistributeGroupLoot_RoundRobin(&item_list, true); + } + } +} + +void Client::OpenChest(Spawn* entity, bool attemptDisarm) +{ + if (!entity) + return; + + int8 chest_difficulty = 0; + int32 state = 0; + // Check for the chest and set the action state + /*4034 = small chest | 5864 = treasure chest | 5865 = ornate treasure chest | 4015 = exquisite chest*/ + string modelName; + if (entity->GetModelType() == 4034) { + // small chest, open with copper coins + // does not include traps, however can be disarmed + chest_difficulty = 1; + state = 11899; + modelName.append("Small Chest"); + } + else if (entity->GetModelType() == 5864) { + // treasure chest, open with silver coins + chest_difficulty = 2; + state = 11901; + modelName.append("Treasure Chest"); + } + else if (entity->GetModelType() == 5865) { + // ornate chest, open with gold coins + chest_difficulty = 3; + state = 11900; + modelName.append("Ornate Chest"); + } + else if (entity->GetModelType() == 4015) { + // exquisite chest, open with gold coins and jewels as well as a glow effect + chest_difficulty = 5; + state = 11898; + modelName.append("Exquisite Chest"); + } + bool firstChestOpen = false; + + if (chest_difficulty > 0 && !entity->HasTrapTriggered()) + { + ChestTrap::ChestTrapInfo nextTrap; + + bool ret = chest_trap_list.GetNextTrap(GetCurrentZone()->GetZoneID(), chest_difficulty, &nextTrap); + + Skill* disarmSkill = GetPlayer()->GetSkillByName("Disarm Trap", false); + firstChestOpen = true; + entity->SetTrapTriggered(true, state); + if (ret) + { + if (disarmSkill && attemptDisarm) + { + if (disarmSkill->CheckDisarmSkill(entity->GetLevel(), chest_difficulty) < 1) + { + CastGroupOrSelf(entity && entity->IsEntity() ? (Entity*)entity : 0, nextTrap.spell_id, nextTrap.spell_tier, + rule_manager.GetGlobalRule(R_Loot, ChestTriggerRadiusGroup)->GetFloat()); + Message(CHANNEL_NARRATIVE, "You trigger the trap on %s!", modelName.c_str()); + } + else + { + Message(CHANNEL_NARRATIVE, "You disarm the trap on %s", modelName.c_str()); + } + + // despite fail/succeed we should always try to increase skill if disarm is available + GetPlayer()->GetSkillByName("Disarm Trap", true); + } + else // no disarm skill, always fail + { + CastGroupOrSelf(entity && entity->IsEntity() ? (Entity*)entity : 0, nextTrap.spell_id, nextTrap.spell_tier, + rule_manager.GetGlobalRule(R_Loot, ChestTriggerRadiusGroup)->GetFloat()); + Message(CHANNEL_NARRATIVE, "You trigger the trap on %s!", modelName.c_str()); + } + } + } + else if (!entity->HasTrapTriggered()) + { + firstChestOpen = true; + entity->SetTrapTriggered(true, state); + } + + // We set the visual state with out updating so those not in range will see it opened when it is finally sent to them, + // for those in range the SendStateCommand will cause it to animate open. + + // players not currently in radius will have it queued with client->QueueStateCommand when SendSpawn takes place + if (firstChestOpen) + GetCurrentZone()->SendStateCommand(entity, state); +} + +void Client::CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier, float restrictiveRadius) +{ + Spell* spell = master_spell_list.GetSpell(spellID, spellTier); + SpellProcess* spellProcess = GetCurrentZone()->GetSpellProcess(); + if (source == NULL) + source = (Entity*)GetPlayer(); + if (spell) + { + GroupMemberInfo* gmi = GetPlayer()->GetGroupMemberInfo(); + if (gmi && gmi->group_id) + { + PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + + for (int8 i = 0; i < members->size(); i++) { + Entity* member = members->at(i)->member; + if(!member) + continue; + + if (!member->Alive() || (member->GetZone() != source->GetZone())) + continue; + // if we have a radius provided then check if the group member is outside the radius or not + if (restrictiveRadius > 0.0f && member->GetDistance(source) > restrictiveRadius) + continue; + + spellProcess->CastInstant(spell, source, (Entity*)GetPlayer()); + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + } + else + spellProcess->CastInstant(spell, source, (Entity*)GetPlayer()); + } +} + +Spawn* Client::GetBanker() { + return banker; +} + +void Client::SetBanker(Spawn* in_banker) { + banker = in_banker; + +} + +void Client::Bank(Spawn* banker, bool cancel) { + if (banker && banker->primary_command_list.size() > 0 && banker->primary_command_list[0]->command == "bank") { + if (!cancel) + SetBanker(banker); + else + SetBanker(0); + PacketStruct* packet = configReader.getStruct("WS_UpdateBank", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(banker)); + int64 coins = GetPlayer()->GetInfoStruct()->get_bank_coin_copper() + GetPlayer()->GetInfoStruct()->get_bank_coin_silver() * 100 + + GetPlayer()->GetInfoStruct()->get_bank_coin_gold() * 10000 + (int64)GetPlayer()->GetInfoStruct()->get_bank_coin_plat() * 1000000; + int32 coins1, coins2; + coins1 = ((int32*)&coins)[0]; + coins2 = ((int32*)&coins)[1]; + packet->setDataByName("bank_coins", coins1); + packet->setDataByName("bank_coins2", coins2); + packet->setDataByName("copper", GetPlayer()->GetInfoStruct()->get_coin_copper()); + packet->setDataByName("silver", GetPlayer()->GetInfoStruct()->get_coin_silver()); + packet->setDataByName("gold", GetPlayer()->GetInfoStruct()->get_coin_gold()); + packet->setDataByName("plat", GetPlayer()->GetInfoStruct()->get_coin_plat()); + if (!cancel) + packet->setDataByName("display", 1); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +bool Client::BankHasCoin(int64 amount){ + int32 tmp = 0; + + if(amount <= 0) + return 0; + + //plat + if (amount >= 1000000) { + tmp = amount / 1000000; + int32 bank_coins_plat = GetPlayer()->GetBankCoinsPlat(); + + if(bank_coins_plat >= tmp) + return 1; + } + //gold + if (amount >= 10000) { + tmp = amount / 10000; + int32 bank_coins_gold = GetPlayer()->GetBankCoinsGold(); + + if(bank_coins_gold >= tmp) + return 1; + } + //silver + if (amount >= 100) { + tmp = amount / 100; + int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); + + if(bank_coins_silver >= tmp) + return 1; + } + //copper + if (amount > 0) { + int32 bank_coins_copper = GetPlayer()->GetBankCoinsCopper(); + + if(bank_coins_copper >= amount) + return 1; + } + +return 0; +} + +bool Client::BankWithdrawalNoBanker(int64 amount) { + bool cheater = false; + if (amount > 0) { + string withdrawal = ""; + char withdrawal_data[512] = { 0 }; + int32 tmp = 0; + if (amount >= 1000000) { + tmp = amount / 1000000; + int32 bank_coins_plat = GetPlayer()->GetBankCoinsPlat(); + if (tmp > bank_coins_plat) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->set_bank_coin_plat(bank_coins_plat - tmp); + GetPlayer()->GetInfoStruct()->add_coin_plat(tmp); + amount -= (int64)tmp * 1000000; + sprintf(withdrawal_data, "%u Platinum ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater && amount >= 10000) { + tmp = amount / 10000; + if (tmp > GetPlayer()->GetBankCoinsGold()) + cheater = true; + else { + int32 bank_coins_gold = GetPlayer()->GetInfoStruct()->get_bank_coin_gold(); + bank_coins_gold -= tmp; + if ((GetPlayer()->GetInfoStruct()->get_coin_gold() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_gold((GetPlayer()->GetInfoStruct()->get_coin_gold() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_coin_plat(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_gold(tmp); + amount -= tmp * 10000; + sprintf(withdrawal_data, "%u Gold ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater && amount >= 100) { + tmp = amount / 100; + int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); + if (tmp > bank_coins_silver) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->set_bank_coin_silver(bank_coins_silver - tmp); + if ((GetPlayer()->GetInfoStruct()->get_coin_silver() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_silver((GetPlayer()->GetInfoStruct()->get_coin_silver() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_coin_gold(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_silver(tmp); + amount -= tmp * 100; + sprintf(withdrawal_data, "%u Silver ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater) { + if (amount > 0) { + sprintf(withdrawal_data, "%u Copper ", (int32)amount); + withdrawal.append(withdrawal_data); + int32 bank_coin_copper = GetPlayer()->GetInfoStruct()->get_bank_coin_copper(); + + GetPlayer()->GetInfoStruct()->set_bank_coin_copper(bank_coin_copper - amount); + if ((GetPlayer()->GetInfoStruct()->get_coin_copper() + amount) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_copper((GetPlayer()->GetInfoStruct()->get_coin_copper() + amount) - 100); + GetPlayer()->GetInfoStruct()->add_coin_silver(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_copper(amount); + } + if (withdrawal.length() > 0) { + withdrawal.append("withdrawn "); + sprintf(withdrawal_data, "(%u Plat %u Gold %u Silver %u Copper in the bank now.)", GetPlayer()->GetInfoStruct()->get_bank_coin_plat(), + GetPlayer()->GetInfoStruct()->get_bank_coin_gold(), GetPlayer()->GetInfoStruct()->get_bank_coin_silver(), GetPlayer()->GetInfoStruct()->get_bank_coin_copper()); + withdrawal.append(withdrawal_data); + SimpleMessage(CHANNEL_NARRATIVE, withdrawal.c_str()); + return 1; + } + } + else + Message(CHANNEL_COLOR_RED, "Stop trying to cheat!"); + return 0; + } + return 0; +} + +void Client::BankWithdrawal(int64 amount) { + bool cheater = false; + if (GetBanker() && amount > 0) { + string withdrawal = ""; + char withdrawal_data[512] = { 0 }; + int32 tmp = 0; + if (amount >= 1000000) { + tmp = amount / 1000000; + int32 bank_coins_plat = GetPlayer()->GetBankCoinsPlat(); + if (tmp > bank_coins_plat) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->set_bank_coin_plat(bank_coins_plat - tmp); + GetPlayer()->GetInfoStruct()->add_coin_plat(tmp); + amount -= (int64)tmp * 1000000; + sprintf(withdrawal_data, "%u Platinum ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater && amount >= 10000) { + tmp = amount / 10000; + if (tmp > GetPlayer()->GetBankCoinsGold()) + cheater = true; + else { + int32 bank_coins_gold = GetPlayer()->GetInfoStruct()->get_bank_coin_gold(); + bank_coins_gold -= tmp; + if ((GetPlayer()->GetInfoStruct()->get_coin_gold() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_gold((GetPlayer()->GetInfoStruct()->get_coin_gold() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_coin_plat(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_gold(tmp); + amount -= tmp * 10000; + sprintf(withdrawal_data, "%u Gold ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater && amount >= 100) { + tmp = amount / 100; + int32 bank_coins_silver = GetPlayer()->GetBankCoinsSilver(); + if (tmp > bank_coins_silver) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->set_bank_coin_silver(bank_coins_silver - tmp); + if ((GetPlayer()->GetInfoStruct()->get_coin_silver() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_silver((GetPlayer()->GetInfoStruct()->get_coin_silver() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_coin_gold(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_silver(tmp); + amount -= tmp * 100; + sprintf(withdrawal_data, "%u Silver ", tmp); + withdrawal.append(withdrawal_data); + memset(withdrawal_data, 0, sizeof(withdrawal_data)); + } + } + if (!cheater) { + if (amount > 0) { + sprintf(withdrawal_data, "%u Copper ", (int32)amount); + withdrawal.append(withdrawal_data); + int32 bank_coin_copper = GetPlayer()->GetInfoStruct()->get_bank_coin_copper(); + + GetPlayer()->GetInfoStruct()->set_bank_coin_copper(bank_coin_copper - amount); + if ((GetPlayer()->GetInfoStruct()->get_coin_copper() + amount) > 100) { + GetPlayer()->GetInfoStruct()->set_coin_copper((GetPlayer()->GetInfoStruct()->get_coin_copper() + amount) - 100); + GetPlayer()->GetInfoStruct()->add_coin_silver(1); + } + else + GetPlayer()->GetInfoStruct()->add_coin_copper(amount); + } + if (withdrawal.length() > 0) { + withdrawal.append("withdrawn "); + sprintf(withdrawal_data, "(%u Plat %u Gold %u Silver %u Copper in the bank now.)", GetPlayer()->GetInfoStruct()->get_bank_coin_plat(), + GetPlayer()->GetInfoStruct()->get_bank_coin_gold(), GetPlayer()->GetInfoStruct()->get_bank_coin_silver(), GetPlayer()->GetInfoStruct()->get_bank_coin_copper()); + withdrawal.append(withdrawal_data); + SimpleMessage(CHANNEL_NARRATIVE, withdrawal.c_str()); + } + } + else + Message(CHANNEL_COLOR_RED, "Stop trying to cheat!"); + player->SetCharSheetChanged(true); + Bank(banker); + } + +} + +void Client::BankDeposit(int64 amount) { + bool cheater = false; + if (GetBanker() && amount > 0) { + int32 tmp = 0; + char deposit_data[512] = { 0 }; + string deposit = ""; + if (amount >= 1000000) { + tmp = amount / 1000000; + if (tmp > GetPlayer()->GetCoinsPlat()) + cheater = true; + else { + GetPlayer()->GetInfoStruct()->add_bank_coin_plat(tmp); + GetPlayer()->GetInfoStruct()->set_coin_plat(GetPlayer()->GetInfoStruct()->get_coin_plat() - tmp); + amount -= (int64)tmp * 1000000; + sprintf(deposit_data, "%u Platinum ", tmp); + deposit.append(deposit_data); + memset(deposit_data, 0, sizeof(deposit_data)); + } + } + if (!cheater && amount >= 10000) { + tmp = amount / 10000; + if (tmp > GetPlayer()->GetCoinsGold()) + cheater = true; + else { + if ((GetPlayer()->GetInfoStruct()->get_bank_coin_gold() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_bank_coin_gold((GetPlayer()->GetInfoStruct()->get_bank_coin_gold() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_bank_coin_plat(1); + } + else + GetPlayer()->GetInfoStruct()->add_bank_coin_gold(tmp); + GetPlayer()->GetInfoStruct()->set_coin_gold(GetPlayer()->GetInfoStruct()->get_coin_gold() - tmp); + amount -= tmp * 10000; + sprintf(deposit_data, "%u Gold ", tmp); + deposit.append(deposit_data); + memset(deposit_data, 0, sizeof(deposit_data)); + } + } + if (!cheater && amount >= 100) { + tmp = amount / 100; + if (tmp > GetPlayer()->GetCoinsSilver()) + cheater = true; + else { + if ((GetPlayer()->GetInfoStruct()->get_bank_coin_silver() + tmp) > 100) { + GetPlayer()->GetInfoStruct()->set_bank_coin_silver((GetPlayer()->GetInfoStruct()->get_bank_coin_silver() + tmp) - 100); + GetPlayer()->GetInfoStruct()->add_bank_coin_gold(1); + } + else + GetPlayer()->GetInfoStruct()->add_bank_coin_silver(tmp); + GetPlayer()->GetInfoStruct()->set_coin_silver(GetPlayer()->GetInfoStruct()->get_coin_silver() - tmp); + amount -= tmp * 100; + sprintf(deposit_data, "%u Silver ", tmp); + deposit.append(deposit_data); + memset(deposit_data, 0, sizeof(deposit_data)); + } + } + if (!cheater) { + if (amount > 0) { + sprintf(deposit_data, "%u Copper ", (int32)amount); + deposit.append(deposit_data); + if ((GetPlayer()->GetInfoStruct()->get_bank_coin_copper() + amount) > 100) { + GetPlayer()->GetInfoStruct()->set_bank_coin_copper((GetPlayer()->GetInfoStruct()->get_bank_coin_copper() + amount) - 100); + GetPlayer()->GetInfoStruct()->add_bank_coin_silver(1); + } + else + GetPlayer()->GetInfoStruct()->add_bank_coin_copper(amount); + GetPlayer()->GetInfoStruct()->set_coin_copper(GetPlayer()->GetInfoStruct()->get_coin_copper() - amount); + } + if (deposit.length() > 0) { + deposit.append("deposited "); + sprintf(deposit_data, "(%u Plat %u Gold %u Silver %u Copper in the bank now.)", GetPlayer()->GetInfoStruct()->get_bank_coin_plat(), + GetPlayer()->GetInfoStruct()->get_bank_coin_gold(), GetPlayer()->GetInfoStruct()->get_bank_coin_silver(), GetPlayer()->GetInfoStruct()->get_bank_coin_copper()); + deposit.append(deposit_data); + SimpleMessage(CHANNEL_NARRATIVE, deposit.c_str()); + } + } + else + Message(CHANNEL_COLOR_RED, "Stop trying to cheat!"); + player->SetCharSheetChanged(true); + Bank(banker); + } + +} + +void Client::AddPendingQuestAcceptReward(Quest* quest) +{ + std::unique_lock lock(MPendingQuestAccept); + pending_quest_accept.push_back(quest->GetQuestID()); +} + +void Client::AddPendingQuestReward(Quest* quest, bool update, bool is_temporary, std::string description) { + QueueQuestReward(quest->GetQuestID(), is_temporary, false, false, (is_temporary ? quest->GetCoinTmpReward() : 0), + (is_temporary ? quest->GetStatusTmpReward() : 0), description, false, 0); + quest_updates = update; + if(quest_updates) { + SaveQuestRewardData(true); + } + +} + +void Client::QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved, int32 index) { + if(HasQuestRewardQueued(quest_id, is_temporary, is_collection)) + return; + + QuestRewardData* data = new QuestRewardData; + data->quest_id = quest_id; + data->is_temporary = is_temporary; + data->is_collection = is_collection; + data->has_displayed = has_displayed; + data->tmp_coin = tmp_coin; + data->tmp_status = tmp_status; + data->description = std::string(description); + data->db_saved = db_saved; + data->db_index = index; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + quest_pending_reward.push_back(data); + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Client::HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection) { + + bool success = false; + MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); + if (quest_pending_reward.size() > 0) { + vector::iterator itr; + + for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) { + int32 questID = (*itr)->quest_id; + bool temporary = (*itr)->is_temporary; + bool collection = (*itr)->is_collection; + if( questID == quest_id && is_temporary == temporary && is_collection == collection ) { + success = true; + break; + } + } + } + MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); + + return success; +} + +void Client::RemoveQueuedQuestReward() { + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + if(quest_pending_reward.size() > 0) { + QuestRewardData* data = quest_pending_reward.at(0); + if(data->db_saved) { + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_rewards where char_id = %u and indexed = %u", GetCharacterID(), data->db_index); + if(data->is_temporary && data->quest_id) { + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", GetCharacterID(), data->quest_id); + } + } + quest_pending_reward.erase(quest_pending_reward.begin()); + } + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + + SaveQuestRewardData(true); +} + +void Client::AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress) { + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + quest_pending_updates[quest_id][step_id] = progress; + quest_updates = true; + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + +} + +void Client::ProcessQuestUpdates() { + if(!IsReadyForUpdates()) + return; + + if (quest_pending_updates.size() > 0) { + map > tmp_quest_updates; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + tmp_quest_updates.insert(quest_pending_updates.begin(), quest_pending_updates.end()); + quest_pending_updates.clear(); + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + map >::iterator quest_itr; + map::iterator step_itr; + for (quest_itr = tmp_quest_updates.begin(); quest_itr != tmp_quest_updates.end(); quest_itr++) { + for (step_itr = quest_itr->second.begin(); step_itr != quest_itr->second.end(); step_itr++) { + if (step_itr->second == 0xFFFFFFFF) { + SetStepComplete(quest_itr->first, step_itr->first); + player->SendQuestRequiredSpawns(quest_itr->first); + } + else + AddStepProgress(quest_itr->first, step_itr->first, step_itr->second); + } + } + } + MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); + if (quest_pending_reward.size() > 0) { + MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); + + // only able to display one reward at a time + if(GetPlayer()->IsActiveReward()) + return; + + Query query; + vector::iterator itr; + vector tmp_quest_rewards; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + tmp_quest_rewards.insert(tmp_quest_rewards.begin(), quest_pending_reward.begin(), quest_pending_reward.begin()+1); + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + + bool delete_first = false; + for (itr = tmp_quest_rewards.begin(); itr != tmp_quest_rewards.end();) { + int32 questID = (*itr)->quest_id; + if((*itr)->is_collection && GetPlayer()->GetPendingCollectionReward()) { + DisplayCollectionComplete(GetPlayer()->GetPendingCollectionReward()); + GetPlayer()->SetActiveReward(true); + (*itr)->has_displayed = true; + + UpdateCharacterRewardData((*itr)); + break; + } + else if(questID > 0 && GetPlayer()->UpdateQuestReward(questID, (*itr))) { + (*itr)->has_displayed = true; + UpdateCharacterRewardData((*itr)); + // only able to display one reward at a time + break; + } else { + delete_first = true; + LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, deleting quest id from tmp_quest_rewards.", questID, GetPlayer()->GetName()); + break; + } + } + + if(delete_first) { + RemoveQueuedQuestReward(); + } + } else { + MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); + } + + MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__); + if (quest_pending_reward.size() > 0) { + quest_updates = true; + } + else { + quest_updates = false; + } + MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__); + +} + +void Client::CheckQuestQueue() { + MQuestQueue.writelock(); + last_update_time = 0; + vector::iterator itr; + for (itr = quest_queue.begin(); itr != quest_queue.end(); itr++) { + if(!GetPlayer()->SendQuestStepUpdate((*itr)->quest_id, (*itr)->step, (*itr)->display_quest_helper)) { + LogWrite(CCLIENT__ERROR, 0, "Client", "Queued Quest ID %u missing for Player %s, cannot send quest step update.", (*itr)->quest_id, GetPlayer()->GetName()); + } + safe_delete((*itr)); + } + quest_queue.clear(); + MQuestQueue.releasewritelock(); + +} + +void Client::SetStepComplete(int32 quest_id, int32 step) { + Quest* quest = player->SetStepComplete(quest_id, step); + if (quest) { + SendQuestUpdate(quest); + GetCurrentZone()->SendQuestUpdates(this); + } + +} + +void Client::AddStepProgress(int32 quest_id, int32 step, int32 progress) { + Quest* quest = player->AddStepProgress(quest_id, step, progress); + if (quest) { + SendQuestUpdate(quest); + GetCurrentZone()->SendQuestUpdates(this); + } +} + +void Client::CheckPlayerQuestsKillUpdate(Spawn* spawn) { + bool hadUpdates = false; + vector* quest_updates = player->CheckQuestsKillUpdate(spawn); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + { + SendQuestUpdate(quest_updates->at(i)); + hadUpdates = true; + } + } + safe_delete(quest_updates); + vector* quest_failures = player->CheckQuestsFailures(); + if (quest_failures) { + for (int32 i = 0; i < quest_failures->size(); i++) + { + SendQuestFailure(quest_failures->at(i)); + hadUpdates = true; + } + } + safe_delete(quest_failures); + + if (hadUpdates) + GetCurrentZone()->SendAllSpawnsForVisChange(this); +} + +void Client::CheckPlayerQuestsChatUpdate(Spawn* spawn) { + vector* quest_updates = player->CheckQuestsChatUpdate(spawn); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + SendQuestUpdate(quest_updates->at(i)); + GetCurrentZone()->SendQuestUpdates(this); + } + safe_delete(quest_updates); +} + +void Client::CheckPlayerQuestsItemUpdate(Item* item) { + vector* quest_updates = player->CheckQuestsItemUpdate(item); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + SendQuestUpdate(quest_updates->at(i)); + } + safe_delete(quest_updates); + vector* quest_failures = player->CheckQuestsFailures(); + if (quest_failures) { + for (int32 i = 0; i < quest_failures->size(); i++) + SendQuestFailure(quest_failures->at(i)); + } + safe_delete(quest_failures); +} + +void Client::CheckPlayerQuestsLocationUpdate() { + vector* quest_updates = player->CheckQuestsLocationUpdate(); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + SendQuestUpdate(quest_updates->at(i)); + } + safe_delete(quest_updates); +} + +void Client::CheckPlayerQuestsSpellUpdate(Spell* spell) { + vector* quest_updates = player->CheckQuestsSpellUpdate(spell); + if (quest_updates) { + for (int32 i = 0; i < quest_updates->size(); i++) + SendQuestUpdate(quest_updates->at(i)); + } + safe_delete(quest_updates); + vector* quest_failures = player->CheckQuestsFailures(); + if (quest_failures) { + for (int32 i = 0; i < quest_failures->size(); i++) + SendQuestFailure(quest_failures->at(i)); + } + safe_delete(quest_failures); +} + +void Client::AddPendingQuest(Quest* quest, bool forced) { + if (version <= 372 || forced) { //this client doesn't ask if you want the quest, so auto accept + MPendingQuestAccept.lock(); + player->pending_quests[quest->GetQuestID()] = quest; + MPendingQuestAccept.unlock(); + AcceptQuest(quest->GetQuestID()); + } + else { + MPendingQuestAccept.lock(); + player->pending_quests[quest->GetQuestID()] = quest; + MPendingQuestAccept.unlock(); + EQ2Packet* outapp = quest->OfferQuest(GetVersion(), player); + //DumpPacket(outapp); + QueuePacket(outapp); + } +} + +void Client::AcceptQuest(int32 quest_id) { + MPendingQuestAccept.lock_shared(); + if (player->pending_quests.count(quest_id) > 0) { + Quest* quest = player->pending_quests[quest_id]; + if(quest) { + MPendingQuestAccept.unlock_shared(); + MPendingQuestAccept.lock(); + player->pending_quests.erase(quest->GetQuestID()); + MPendingQuestAccept.unlock(); + AddPlayerQuest(quest); + GetCurrentZone()->SendQuestUpdates(this); + + GetPlayer()->UpdateQuestCompleteCount(quest_id); + return; // already unlocked mutex + } + } + MPendingQuestAccept.unlock_shared(); +} + +void Client::RemovePendingQuest(int32 quest_id) { + bool send_updates = false; + MPendingQuestAccept.lock_shared(); + + if (player->pending_quests.count(quest_id) > 0) { + Quest* quest = player->pending_quests[quest_id]; + MPendingQuestAccept.unlock_shared(); + MPendingQuestAccept.lock(); + player->pending_quests.erase(quest_id); + MPendingQuestAccept.unlock(); + + if(lua_interface) { + lua_interface->CallQuestFunction(quest, "Declined", GetPlayer()); + lua_interface->SetLuaUserDataStale(quest); + } + + safe_delete(quest); + + send_updates = true; + } + else { + MPendingQuestAccept.unlock_shared(); + } + + if(send_updates) { + GetCurrentZone()->SendQuestUpdates(this); + } +} + +void Client::SetPlayerQuest(Quest* quest, map* progress) { + if (!quest || !progress) { + return; + } + map::iterator itr; + QuestStep* step = 0; + for (itr = progress->begin(); itr != progress->end(); itr++) { + step = quest->GetQuestStep(itr->first); + if (step && itr->second > 0) { + step->SetStepProgress(itr->second); + if (lua_interface && step->GetQuestCurrentQuantity() >= step->GetQuestNeededQuantity()) + lua_interface->CallQuestFunction(quest, "Reload", player, step->GetStepID()); + } + } + if (lua_interface && step) + lua_interface->CallQuestFunction(quest, "CurrentStep", player, step->GetStepID()); + else if(!step) { + LogWrite(QUEST__ERROR, 0, "Client", "Missing step for quest %s (ID %u), cannot CallQuestFunction for CurrentStep", quest->GetName(), quest->GetQuestID()); + } +} + +void Client::AddPlayerQuest(Quest* quest, bool call_accepted, bool send_packets) { + bool lockCleared = false; + GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); + if (player->player_quests.count(quest->GetQuestID()) > 0 && player->player_quests[quest->GetQuestID()]) { + if (player->player_quests[quest->GetQuestID()]->GetQuestFlags() > 0) + quest->SetQuestFlags(player->player_quests[quest->GetQuestID()]->GetQuestFlags()); + int32 questID = quest->GetQuestID(); + lockCleared = true; + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + RemovePlayerQuest(questID, false, false); + } + player->player_quests[quest->GetQuestID()] = quest; + + if(!lockCleared) + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + + quest->SetPlayer(player); + quest->SetSaveNeeded(true); + + current_quest_id = quest->GetQuestID(); + if (send_packets && quest->GetQuestGiver() > 0) + GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true); + if (lua_interface && call_accepted) + lua_interface->CallQuestFunction(quest, "Accepted", player); + if (send_packets) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + //SendQuestJournal(); + SendQuestJournalUpdate(quest); + + // sent twice to match live + quest->SetTracked(false); + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player)); + quest->SetTracked(true); + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player)); + + GetCurrentZone()->SendAllSpawnsForVisChange(this); + } + //This isn't during a load screen, so update spawns with required quests + if (call_accepted) + player->SendQuestRequiredSpawns(quest->GetQuestID()); + +} + +void Client::RemovePlayerQuest(int32 id, bool send_update, bool delete_quest) { + if (current_quest_id == id) + current_quest_id = 0; + GetPlayer()->MPlayerQuests.writelock(__FUNCTION__, __LINE__); + if (player->player_quests.count(id) > 0 && player->player_quests[id]) { + if (delete_quest) { + player->player_quests[id]->SetDeleted(true); + database.DeleteCharacterQuest(id, GetCharacterID(), player->GetCompletedPlayerQuests()->count(id) > 0); + } + int32 quest_giver = player->player_quests[id]->GetQuestGiver(); + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + + if (send_update && quest_giver > 0) + GetCurrentZone()->SendSpawnChangesByDBID(quest_giver, this, false, true); + if (send_update) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(false, 0, true); + } + player->RemoveQuest(id, delete_quest); + if (send_update) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(false, 0, true); + GetCurrentZone()->SendAllSpawnsForVisChange(this); + } + } + else { + // if we don't have any quests to count then release the write lock + GetPlayer()->MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); + } + +} + +void Client::SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper) { + if (quest) { + QuestStep* quest_step = quest->GetQuestStep(step); + if (quest_step) { + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, quest_step, 1, false, false, display_quest_helper)); + quest_step->WasUpdated(false); + } + } + +} + +void Client::SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper) { + QueuedQuest* item = new QueuedQuest; + item->quest_id = quest->GetQuestID(); + item->step = step; + item->display_quest_helper = display_quest_helper; + MQuestQueue.writelock(); + quest_queue.push_back(item); + last_update_time = Timer::GetCurrentTime2(); + MQuestQueue.releasewritelock(); + +} + +void Client::SendQuestFailure(Quest* quest) { + vector* failures = quest->GetQuestFailures(); + if (failures) { + QuestStep* step = 0; + for (int32 i = 0; i < failures->size(); i++) { + step = failures->at(i); + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, step, 1, false, true)); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(false, 0, true); + } + failures->clear(); + } + +} + +void Client::SendQuestUpdate(Quest* quest) { + vector* updates = quest->GetQuestUpdates(); + if (updates) { + QuestStep* step = 0; + bool updated = false; + for (int32 i = 0; i < updates->size(); i++) { + step = updates->at(i); + if (lua_interface && step->Complete() && quest->GetCompleteAction(step->GetStepID())) + { + lua_interface->CallQuestFunction(quest, quest->GetCompleteAction(step->GetStepID()), player); + SendQuestUpdateStep(quest, step->GetStepID()); + updated = true; + } + if (step->WasUpdated()) { + // reversing the order of SendQuestJournal and QueuePacket QuestJournalReply causes AoM client to crash! + SendQuestJournal(false, 0, true); + if(!updated) + QueuePacket(quest->QuestJournalReply(GetVersion(), GetNameCRC(), player, step)); + updated = true; + } + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + + } + if (lua_interface && quest->GetCompleted() && quest->GetCompleteAction()) { + lua_interface->CallQuestFunction(quest, quest->GetCompleteAction(), player); + SendQuestJournalUpdate(quest, true); + } + if (quest->GetCompleted()) { + if (quest->GetQuestReturnNPC() > 0) + GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestReturnNPC(), this, false, true); + if (quest->GetCompletedFlag()) + quest->SetCompletedFlag(false); + } + + updates->clear(); + } + +} + +void Client::SendQuestJournal(bool all_quests, Client* client, bool updated) { + if (!client) + client = this; + PacketStruct* packet = player->GetQuestJournalPacket(all_quests, GetVersion(), GetNameCRC(), current_quest_id, updated); + if (packet) { + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } +} + +void Client::SendQuestJournalUpdate(Quest* quest, bool updated) { + PacketStruct* packet = player->GetQuestJournalPacket(quest, GetVersion(), GetNameCRC(), updated); + if (packet) { + QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void Client::ReloadQuests() { + vector ids = player->GetQuestIDs(); + Quest* quest = 0; + for (int32 i = 0; i < ids.size(); i++) { + quest = master_quest_list.GetQuest(ids[i]); + if (quest) + AddPlayerQuest(quest, false); + else + RemovePlayerQuest(ids[i]); + } + +} + +Quest* Client::GetPendingQuestAcceptance(int32 item_id) { + std::unique_lock lock(MPendingQuestAccept); + bool found_quest = false; + vector::iterator itr; + int32 questID = 0; + Quest* quest = nullptr; + for (itr = pending_quest_accept.begin(); itr != pending_quest_accept.end();) { + questID = *itr; + + bool quest_exists = false; + quest = GetPlayer()->PendingQuestAcceptance(questID, item_id, &quest_exists); + + if(!quest_exists) { + LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, removing quest id from pending_quest_accept.", questID, GetPlayer()->GetName()); + itr = pending_quest_accept.erase(itr); + quest = nullptr; + continue; + } + else if (quest) { + pending_quest_accept.erase(itr); + break; + } + + itr++; + } + + return quest; +} + +void Client::AcceptQuestReward(Quest* quest, int32 item_id) { + int8 num_slots_needed = 0; + int16 free_slots = player->item_list.GetNumberOfFreeSlots(); + Item* master_item = 0; + if (item_id > 0) { + num_slots_needed++; + master_item = master_item_list.GetItem(item_id); + } + + int32 totalItems = 0; + + vector* items = 0; + vector* tmpItems = 0; + + bool isTempState = quest->GetQuestTemporaryState(); + + if(isTempState) + { + tmpItems = quest->GetTmpRewardItems(); + if (tmpItems && tmpItems->size() > 0) + { + num_slots_needed += tmpItems->size(); + totalItems += tmpItems->size(); + } + } + else + { + items = quest->GetRewardItems(); + if (items && items->size() > 0) + { + num_slots_needed += items->size(); + totalItems += items->size(); + } + } + + RemoveQueuedQuestReward(); + + GetPlayer()->SetActiveReward(false); + + if (free_slots >= num_slots_needed || (player->item_list.HasFreeBagSlot() && master_item && master_item->IsBag() && master_item->bag_info->num_slots >= totalItems)) { + if (master_item) + AddItem(item_id); + if (tmpItems && tmpItems->size() > 0) { + for (int32 i = 0; i < tmpItems->size(); i++) + AddItem(new Item(tmpItems->at(i))); + } + if (items && items->size() > 0) { + for (int32 i = 0; i < items->size(); i++) + AddItem(new Item(items->at(i))); + } + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + map* reward_factions = quest->GetRewardFactions(); + map::iterator itr; + for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) { + int32 faction_id = itr->first; + sint32 amount = itr->second; + if (amount > 0) + player->GetFactions()->IncreaseFaction(faction_id, amount); + else + player->GetFactions()->DecreaseFaction(faction_id, (amount * -1)); + } + + if(quest->GetQuestTemporaryState()) + { + int64 total_coins = quest->GetCoinTmpReward(); + if (total_coins > 0) + AwardCoins(total_coins, std::string("for completing ").append(quest->GetName())); + + player->GetInfoStruct()->add_status_points(quest->GetStatusTmpReward()); + } + else { + player->GetInfoStruct()->add_status_points(quest->GetStatusPoints()); + } + + quest->SetQuestTemporaryState(false); + player->SetCharSheetChanged(true); + } + else { + GetPlayer()->SetActiveReward(true); + AddPendingQuestAcceptReward(quest); + SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots! Free some slots and try again."); + DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription()); + } + +} + +void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector* rewards, vector* selectable_rewards, map* factions, const char* header, int32 status_points, const char* text, bool was_displayed) { + if (coin == 0 && (!rewards || rewards->size() == 0) && (!selectable_rewards || selectable_rewards->size() == 0) && (!factions || factions->size() == 0) && status_points == 0 && text == 0 && (!quest || (quest->GetCoinsReward() == 0 && quest->GetCoinsRewardMax() == 0))) { + /*if (quest) + text = quest->GetName(); + else*/ + return;//nothing to give + } + + GetPlayer()->ClearPendingSelectableItemRewards(0, true); + GetPlayer()->ClearPendingItemRewards(); + + PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion()); + if (packet2) { + int32 source_id = 0; + if (quest) + source_id = quest->GetQuestID(); + int64 rewarded_coin = 0; + if (quest) { + if (quest->GetCoinsReward() > 0) { + if (quest->GetCoinsRewardMax() > 0) + rewarded_coin = MakeRandomInt(quest->GetCoinsReward(), quest->GetCoinsRewardMax()); + else + rewarded_coin = quest->GetCoinsReward(); + } + quest->SetGeneratedCoin(rewarded_coin); + } + if (rewarded_coin > coin) + coin = rewarded_coin; + if (!quest && !was_displayed) { //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + if (coin > 0) { + player->AddCoins(coin); + PlaySound("coin_cha_ching"); + } + } + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", header); + packet2->setSubstructDataByName("reward_data", "max_coin", coin); + if (player->GetGuild() && !was_displayed) { + if (!quest) { //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + player->GetInfoStruct()->add_status_points(status_points); + player->SetCharSheetChanged(true); + } + packet2->setSubstructDataByName("reward_data", "status_points", status_points); + } + if(text) + packet2->setSubstructDataByName("reward_data", "text", text); + + + std::vector items; + quest->GetTmpRewardItemsByID(&items); + if(rewards || items.size() > 0){ + int32 item_count = items.size(); + item_count += rewards ? rewards->size() : 0; + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count); + int i = 0; + if(rewards) { + for (i = 0; i < rewards->size(); i++) { + Item* item = rewards->at(i); + if (item) { + packet2->setArrayDataByName("reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); + } + if(!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it + } + } + + for (int j = 0; j < items.size(); j++) { + Item* item = items.at(j); + if (item) { + packet2->setArrayDataByName("reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); + } + if(!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + player->AddPendingItemReward(item); //item reference will be deleted after the player accepts it + + i++; + } + } + if (selectable_rewards) { + packet2->setSubstructArrayLengthByName("reward_data", "num_select_rewards", selectable_rewards->size()); + for (int i = 0; i < selectable_rewards->size(); i++) { + Item* item = selectable_rewards->at(i); + if (item) { + packet2->setArrayDataByName("select_reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("select_item", item, player, i, 0, GetClientItemPacketOffset()); + if (!quest) //this entire function is either for version <=561 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + player->AddPendingSelectableItemReward(source_id, item); //item reference will be deleted after the player selects one + } + } + } + if (factions) { + map::iterator itr; + map factions_map; + for (itr = factions->begin(); itr != factions->end(); itr++) { + Faction* faction = master_faction_list.GetFaction(itr->first); + if (faction) + factions_map[faction] = itr->second; + } + packet2->setSubstructArrayLengthByName("reward_data", "num_factions", factions_map.size()); + map::iterator faction_itr; + int8 i = 0; + for (faction_itr = factions_map.begin(); faction_itr != factions_map.end(); faction_itr++) { + packet2->setArrayDataByName("faction_name", faction_itr->first->name.c_str(), i); + sint32 amount = faction_itr->second; + packet2->setArrayDataByName("amount", amount, i); + if (!quest) { //this entire function is for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards + if (amount > 0) + player->GetFactions()->IncreaseFaction(faction_itr->first->id, amount); + else + player->GetFactions()->DecreaseFaction(faction_itr->first->id, (amount * -1)); + } + i++; + } + } + QueuePacket(packet2->serialize()); + safe_delete(packet2); + } +} + +void Client::PopulateQuestRewardItems(vector * items, PacketStruct* packet, + std::string num_rewards_str, std::string reward_id_str, std::string item_str) { + if(!items || !packet) + return; + + if (items) { + int32 total_item_count = 0; + for(int s=0;ssize();s++) { + Item* tmpItem = items->at(s); + if(tmpItem) { + if(tmpItem->details.count > 1) { + total_item_count += tmpItem->details.count; + } + else { + total_item_count += 1; + } + } + } + packet->setArrayLengthByName(num_rewards_str.c_str(), total_item_count); + int16 count = 0; + int16 pos = 0; + for (int32 i = 0; i < items->size();) { + packet->setArrayDataByName(reward_id_str.c_str(), items->at(i)->details.item_id, pos); + if (version < 860) + packet->setItemArrayDataByName(item_str.c_str(), items->at(i), player, pos, 0, GetClientItemPacketOffset()); + else if (version < 1193) + packet->setItemArrayDataByName(item_str.c_str(), items->at(i), player, pos); + else + packet->setItemArrayDataByName(item_str.c_str(), items->at(i), player, pos, 0, 2); + + pos++; + + if(count >= items->at(i)->details.count-1) { + count = 0; + } + else if(items->at(i)->details.count > 1) { + count++; + continue; + } + + i++; + } + } +} +void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription, bool was_displayed) { + if (!quest) + return; + + if (GetVersion() <= 561) { + DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints(), tempReward ? customDescription.c_str() : quest->GetCompletedDescription(), was_displayed); + return; + } + PacketStruct* packet = configReader.getStruct("WS_QuestComplete", GetVersion()); + if (packet) { + packet->setDataByName("title", "Quest Reward!"); + packet->setDataByName("name", quest->GetName()); + if(tempReward) + { + packet->setDataByName("description", customDescription.c_str()); + } + else + packet->setDataByName("description", quest->GetCompletedDescription()); + + packet->setDataByName("level", quest->GetLevel()); + packet->setDataByName("encounter_level", quest->GetEncounterLevel()); + int8 difficulty = 0; + if ((string)quest->GetType() == "Tradeskill") + difficulty = player->GetTSArrowColor(quest->GetLevel()); + else + difficulty = player->GetArrowColor(quest->GetLevel()); + packet->setDataByName("difficulty", difficulty); + + if(tempReward) + { + packet->setDataByName("max_coin", quest->GetCoinTmpReward()); + packet->setDataByName("min_coin", quest->GetCoinTmpReward()); + packet->setDataByName("status_points", quest->GetStatusPoints()); + } + else + { + int64 rewarded_coin = 0; + if (quest->GetCoinsReward() > 0) { + if (quest->GetCoinsRewardMax() > 0) + rewarded_coin = MakeRandomInt(quest->GetCoinsReward(), quest->GetCoinsRewardMax()); + else + rewarded_coin = quest->GetCoinsReward(); + } + quest->SetGeneratedCoin(rewarded_coin); + packet->setDataByName("max_coin", rewarded_coin); + packet->setDataByName("min_coin", rewarded_coin); + packet->setDataByName("status_points", quest->GetStatusPoints()); + } + + if(tempReward) { + PopulateQuestRewardItems(quest->GetTmpRewardItems(), packet); + } + else + { + vector* items2 = quest->GetSelectableRewardItems(); + PopulateQuestRewardItems(quest->GetRewardItems(), packet); + PopulateQuestRewardItems(quest->GetSelectableRewardItems(), packet, std::string("num_select_rewards"), + std::string("select_reward_id"), std::string("select_item")); + + map* reward_factions = quest->GetRewardFactions(); + if (reward_factions && reward_factions->size() > 0) { + packet->setArrayLengthByName("num_factions", reward_factions->size()); + map::iterator itr; + int16 index = 0; + for (itr = reward_factions->begin(); itr != reward_factions->end(); itr++) { + int32 faction_id = itr->first; + sint32 amount = itr->second; + const char* faction_name = master_faction_list.GetFactionNameByID(faction_id); + if (faction_name) { + packet->setArrayDataByName("faction_name", const_cast(faction_name), index); + packet->setArrayDataByName("amount", amount, index); + } + index++; + } + } + } + EQ2Packet* outapp = packet->serialize(); + // DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + +} + +void Client::DisplayRandomizeFeatures(int32 flags) { + SimpleMessage(CHANNEL_NARRATIVE, "Showing Active Randomize Features:"); + if (flags > 0) { + if (flags & RANDOMIZE_GENDER) + SimpleMessage(CHANNEL_NARRATIVE, "- Gender"); + if (flags & RANDOMIZE_RACE) + SimpleMessage(CHANNEL_NARRATIVE, "- Race"); + if (flags & RANDOMIZE_MODEL_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Model"); + if (flags & RANDOMIZE_FACIAL_HAIR_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Facial Hair"); + if (flags & RANDOMIZE_HAIR_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair"); + if (flags & RANDOMIZE_WING_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Wing"); + if (flags & RANDOMIZE_CHEEK_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Cheek"); + if (flags & RANDOMIZE_CHIN_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Chin"); + if (flags & RANDOMIZE_EAR_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Ear"); + if (flags & RANDOMIZE_EYE_BROW_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Eyebrow"); + if (flags & RANDOMIZE_EYE_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Eye"); + if (flags & RANDOMIZE_LIP_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Lip"); + if (flags & RANDOMIZE_NOSE_TYPE) + SimpleMessage(CHANNEL_NARRATIVE, "- Nose"); + if (flags & RANDOMIZE_EYE_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Eye Color"); + if (flags & RANDOMIZE_HAIR_COLOR1) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Color1"); + if (flags & RANDOMIZE_HAIR_COLOR2) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Color2"); + if (flags & RANDOMIZE_HAIR_HIGHLIGHT) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Color Highlights"); + if (flags & RANDOMIZE_HAIR_FACE_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Facial Hair Color"); + if (flags & RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Facial Hair Color Highlights"); + if (flags & RANDOMIZE_HAIR_TYPE_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Type Color"); + if (flags & RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Hair Type Highlights"); + if (flags & RANDOMIZE_SKIN_COLOR) + SimpleMessage(CHANNEL_NARRATIVE, "- Skin Color"); + if (flags & RANDOMIZE_WING_COLOR1) + SimpleMessage(CHANNEL_NARRATIVE, "- Wing Color1"); + if (flags & RANDOMIZE_WING_COLOR2) + SimpleMessage(CHANNEL_NARRATIVE, "- Wing Color2"); + } + else + { + SimpleMessage(CHANNEL_NARRATIVE, "- No Randomization Set."); + } + +} + +void Client::GiveQuestReward(Quest* quest, bool has_displayed) { + current_quest_id = 0; + + if(!quest->GetQuestTemporaryState() && !has_displayed) + { + quest->IncrementCompleteCount(); + player->AddCompletedQuest(quest); + } + + AddPendingQuestAcceptReward(quest); + + DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription()); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal..."); + SendQuestJournal(); + + if(quest->GetQuestTemporaryState()) { + return; + } + + if(!has_displayed) { + if (quest->GetExpReward() > 0) { + int32 xp = quest->GetExpReward(); + player->AddXP(xp); + } + if (quest->GetTSExpReward() > 0) { + int8 ts_level = player->GetTSLevel(); + int32 xp = quest->GetTSExpReward(); + if (player->AddTSXP(xp)) { + Message(CHANNEL_REWARD, "You gain %u tradeskill experience!", (int32)xp); + if (player->GetTSLevel() != ts_level) + ChangeTSLevel(ts_level, player->GetTSLevel()); + player->SetCharSheetChanged(true); + } + } + int64 total_coins = quest->GetGeneratedCoin(); + if (total_coins > 0) + AwardCoins(total_coins, std::string("for completing ").append(quest->GetName())); + + player->RemoveQuest(quest->GetQuestID(), false); + } + + if (quest->GetQuestGiver() > 0) + GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true); + + if(!has_displayed) { + RemovePlayerQuest(quest->GetQuestID(), true, false); + } +} + +void Client::DisplayConversation(int32 conversation_id, int32 spawn_id, vector* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) { + std::unique_lock lock(MConversation); + PacketStruct* packet = configReader.getStruct("WS_DialogOpen", GetVersion()); + if (packet) { + packet->setDataByName("conversation_id", conversation_id); + packet->setDataByName("text", text); + packet->setDataByName("language", language); // default 0 + packet->setDataByName("enable_blue_ui", 0); // default 0 + packet->setDataByName("can_close", can_close); // default 1 + conversation_map[conversation_id].clear(); + if (conversations) { + packet->setArrayLengthByName("num_responses", conversations->size()); + for (int32 i = 0; i < conversations->size(); i++) { + packet->setArrayDataByName("response", conversations->at(i).option.c_str(), i); + if (conversations->at(i).function.length() > 0) + conversation_map[conversation_id][i] = conversations->at(i).function; + } + } + packet->setDataByName("spawn_id", spawn_id); + if (mp3) { + packet->setDataByName("voice", mp3); + packet->setDataByName("key1", key1); + packet->setDataByName("key2", key2); + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::DisplayConversation(Item* item, vector* conversations, const char* text, int8 type, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) { + if (!item || !text || !conversations || conversations->size() == 0) { + return; + } + int32 conversation_id = GetConversationID(0, item); + if (conversation_id == 0) { + next_conversation_id++; + conversation_id = next_conversation_id; + } + MConversation.lock(); + conversation_items[conversation_id] = item; + MConversation.unlock(); + if (type == 4) + DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(player), conversations, text, mp3, key1, key2, language, can_close); + else + DisplayConversation(conversation_id, 0xFFFFFFFF, conversations, text, mp3, key1, key2, language, can_close); + +} + +void Client::DisplayConversation(Spawn* src, int8 type, vector* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) { + if (!src || !(type == 1 || type == 2 || type == 3) || !text /*|| !conversations || conversations->size() == 0*/) { + return; + } + int32 conversation_id = GetConversationID(src, 0); + if (conversation_id == 0) { + next_conversation_id++; + conversation_id = next_conversation_id; + } + MConversation.lock(); + conversation_spawns[conversation_id] = src->GetID(); + MConversation.unlock(); + + /* Spawns can start two different types of conversations. + * Type 1: The chat type with bubbles. + * Type 2: The dialog type with the blue box. */ + if (type == 1) + DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(src), conversations, text, mp3, key1, key2, language, can_close); + else if (type == 2) + DisplayConversation(conversation_id, 0xFFFFFFFF, conversations, text, mp3, key1, key2, language, can_close); + else //if (type == 3) + DisplayConversation(conversation_id, player->GetIDWithPlayerSpawn(player), conversations, text, mp3, key1, key2, language, can_close); + +} + +void Client::CloseDialog(int32 conversation_id) { + std::unique_lock lock(MConversation); + PacketStruct* packet = configReader.getStruct("WS_ServerDialogClose", GetVersion()); + if (packet) { + packet->setDataByName("conversation_id", conversation_id); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + std::map::iterator itr; + while((itr = conversation_items.find(conversation_id)) != conversation_items.end()) + { + conversation_items.erase(itr); + } + + std::map::iterator itr2 = conversation_spawns.find(conversation_id); + + while((itr2 = conversation_spawns.find(conversation_id)) != conversation_spawns.end()) + { + conversation_spawns.erase(itr2); + } +} + +int32 Client::GetConversationID(Spawn* spawn, Item* item) { + std::shared_lock lock(MConversation); + int32 conversation_id = 0; + if (spawn) { + map::iterator itr; + for (itr = conversation_spawns.begin(); itr != conversation_spawns.end(); itr++) { + if (itr->second == spawn->GetID()) { + conversation_id = itr->first; + break; + } + } + } + else if (item) { + map::iterator itr; + for (itr = conversation_items.begin(); itr != conversation_items.end(); itr++) { + if (itr->second == item) { + conversation_id = itr->first; + break; + } + } + } + + return conversation_id; +} + +Spawn* Client::GetCombineSpawn() { + return combine_spawn; +} + +bool Client::ShouldTarget() { + return should_target; +} + +void Client::TargetSpawn(Spawn* spawn) { + should_target = false; + PacketStruct* packet = configReader.getStruct("WS_ServerUpdateTarget", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn)); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + GetPlayer()->SetTarget(spawn); + GetPlayer()->info_changed = true; + GetPlayer()->changed = true; + GetPlayer()->AddChangedZoneSpawn(); +} + +void Client::CombineSpawns(float radius, Spawn* spawn) { + combine_spawn = spawn; + spawn->RemoveSpawnFromGroup(true); + if (!GetCurrentZone()->AddCloseSpawnsToSpawnGroup(combine_spawn, radius)) + SimpleMessage(CHANNEL_COLOR_YELLOW, "One or more spawns are in a spawn group and cannot be combined until they are removed from their group."); + GetCurrentZone()->RepopSpawns(this, combine_spawn); + should_target = true; + +} + +void Client::AddCombineSpawn(Spawn* spawn) { + if (combine_spawn && combine_spawn != spawn && spawn) { + combine_spawn->AddSpawnToGroup(spawn); + spawn->AddSpawnToGroup(combine_spawn); + GetCurrentZone()->RepopSpawns(this, combine_spawn); + } + else if (spawn) + combine_spawn = spawn; + should_target = true; + +} + +void Client::RemoveCombineSpawn(Spawn* spawn) { + if (combine_spawn && spawn) + spawn->RemoveSpawnFromGroup(); + if (combine_spawn == spawn) + combine_spawn->RemoveSpawnFromGroup(true); + GetCurrentZone()->RepopSpawns(this, combine_spawn); + if (combine_spawn == spawn) + combine_spawn = 0; + +} + +void Client::SaveCombineSpawns(const char* name) { + if (!combine_spawn) { + return; + } + vector* spawns = combine_spawn->GetSpawnGroup(); + if (!spawns) { + return; + } + int32 count = spawns->size(); + int32 spawnLocationID = 0; + + if (count == 1) + SimpleMessage(CHANNEL_COLOR_YELLOW, "Error: You only have a single Spawn in the group!"); + else if ((spawnLocationID = database.SaveCombinedSpawnLocation(GetCurrentZone(), combine_spawn, name)) > 0) { + Message(CHANNEL_COLOR_YELLOW, "Successfully combined %u spawns into spawn location: %u", count, spawnLocationID); + // we remove the spawn inside SaveCombinedSpawnLocation + //GetCurrentZone()->RemoveSpawn(combine_spawn); + } + else + SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn group, check console for details."); + + safe_delete(spawns); + combine_spawn = 0; +} + +bool Client::AddItem(int32 item_id, int16 quantity, AddItemType type) { + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + if (master_item) + item = new Item(master_item); + if (item) { + if (quantity > 0) + item->details.count = quantity; + + return AddItem(item, nullptr, type); + } + else + Message(CHANNEL_COLOR_RED, "Could not find item with id of: %i", item_id); + + return false; +} + +bool Client::AddItem(Item* item, bool* item_deleted, AddItemType type) { + if (!item) { + return false; + } + if (player->AddItem(item, type)) { + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) { + //DumpPacket(outapp); + QueuePacket(outapp); + //resend bag desc with new item name added + outapp = player->SendBagUpdate(item->details.unique_id, GetVersion()); + if (outapp) { + //DumpPacket(outapp); + QueuePacket(outapp); + } + /*EQ2Packet* app = item->serialize(client->GetVersion(), false); + DumpPacket(app); + client->QueuePacket(app); + */ + } + CheckPlayerQuestsItemUpdate(item); + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player); + } + else { + lua_interface->SetLuaUserDataStale(item); + // likely lore conflict + + if(item_deleted) + *item_deleted = true; + + return false; + } + + return true; +} + +bool Client::AddItemToBank(int32 item_id, int16 quantity) { + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + if (master_item) + item = new Item(master_item); + if (item) { + if (quantity > 0) + item->details.count = quantity; + return AddItemToBank(item); + } + else + Message(CHANNEL_COLOR_RED, "Could not find item with id of: %i", item_id); + + return false; +} +bool Client::AddItemToBank(Item* item) { + if (!item) { + return false; + } + if (player->AddItemToBank(item)) { + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) { + QueuePacket(outapp); + //resend bag desc with new item name added + outapp = player->SendBagUpdate(item->details.inv_slot_id, GetVersion()); + if (outapp) + QueuePacket(outapp); + /*EQ2Packet* app = item->serialize(client->GetVersion(), false); + DumpPacket(app); + client->QueuePacket(app); + */ + } + CheckPlayerQuestsItemUpdate(item); + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player); + } + else { + lua_interface->SetLuaUserDataStale(item); + // likely lore conflict + safe_delete(item); + return false; + } + + return true; +} + +void Client::UnequipItem(int16 index, sint32 bag_id, int8 to_slot, int8 appearance_equip) { + vector packets = GetPlayer()->UnequipItem(index, bag_id, to_slot, GetVersion(), appearance_equip); + EQ2Packet* outapp = 0; + + for(int32 i=0;iUpdateWeapons(); + EQ2Packet* characterSheetPackets = GetPlayer()->GetPlayerInfo()->serialize(GetVersion()); + QueuePacket(characterSheetPackets); +} +bool Client::RemoveItem(Item* item, int16 quantity, bool force_override_no_delete) { + EQ2Packet* outapp; + bool delete_item = false; + + assert(item); + + if (quantity > 0 && !item->IsBag() && item->details.count > quantity) { + item->details.count -= quantity; + item->save_needed = true; + } + else { + database.DeleteItem(character_id, item, 0); + player->GetPlayerItemList()->RemoveItem(item, false); + delete_item = true; + } + + if(force_override_no_delete) + delete_item = false; + + if ((outapp = player->SendInventoryUpdate(version))) { + QueuePacket(outapp); + if (item->GetItemScript() && lua_interface) + lua_interface->RunItemScript(item->GetItemScript(), "removed", item, player); + if (delete_item) + { + PurgeItem(item); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + + GetPlayer()->CalculateApplyWeight(); + return true; + } + + return false; +} + +void Client::SetLuaDebugClient(bool val) { + if (val) + lua_debug_timer.Start(); + lua_debug = val; + if (lua_interface && !val) { + lua_interface->RemoveDebugClients(this); + lua_debug_timer.Disable(); + } + +} + +void Client::SetMerchantTransaction(Spawn* spawn) { + merchant_transaction = spawn; + +} + +Spawn* Client::GetMerchantTransaction() { + return merchant_transaction; +} + +void Client::SetMailTransaction(Spawn* spawn) { + ResetSendMail(spawn ? false : true); + MMailWindowMutex.lock(); + mail_transaction = spawn; + MMailWindowMutex.unlock(); +} + +Spawn* Client::GetMailTransaction() { + return mail_transaction; +} + +void Client::PlaySound(const char* name) { + if (name) { + PacketStruct* packet = configReader.getStruct("WS_PlaySound", GetVersion()); + if (packet) { + packet->setMediumStringByName("name", name); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +float Client::CalculateBuyMultiplier(int32 merchant_id) { + /*MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(merchant_id); + if(multiplier){ + sint32 faction_val = player->GetFactions()->GetFactionValue(multiplier->faction_id); + float diff_low = faction_val - multiplier->faction_min; + if(diff_low < 0) + diff_low*=-1; + float total_diff = multiplier->faction_max - multiplier->faction_min; + if(total_diff < 0) + total_diff*=-1; + float buy_multiplier = multiplier->high_buy_multiplier - multiplier->low_buy_multiplier; + float total1 = (diff_low/total_diff); + float final_buy_multiplier = total1*buy_multiplier + total1*multiplier->low_buy_multiplier; + return final_buy_multiplier; + }*/ + + return 1; +} + +float Client::CalculateSellMultiplier(int32 merchant_id) { + /*MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(merchant_id); + if(multiplier){ + sint32 faction_val = player->GetFactions()->GetFactionValue(multiplier->faction_id); + float diff_low = faction_val - multiplier->faction_min; + if(diff_low < 0) + diff_low*=-1; + float total_diff = multiplier->faction_max - multiplier->faction_min; + if(total_diff < 0) + total_diff*=-1; + float sell_multiplier = multiplier->high_sell_multiplier - multiplier->low_sell_multiplier; + float total1 = (diff_low/total_diff); + float final_sell_multiplier = total1*sell_multiplier + total1*multiplier->low_sell_multiplier; + return final_sell_multiplier; + }*/ + + return 1; +} + +void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) { + Spawn* spawn = GetMerchantTransaction(); + Guild* guild = GetPlayer()->GetGuild(); + if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) && + spawn->IsClientInMerchantLevelRange(this)) { + int32 total_sell_price = 0; + int32 total_status_sell_price = 0; //for status + float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID()); + int32 sell_price = 0; + int32 status_sell_price = 0; //for status + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + if (unique_id == 0) + item = player->item_list.GetItemFromID(item_id, quantity); + else + item = player->item_list.GetItemFromUniqueID(unique_id); + + if (!item) + item = player->item_list.GetItemFromID(item_id); + if (item && master_item) { + if(item->details.item_locked || item->details.equip_slot_id) + { + SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell the item in use."); + return; + } + else if(item->CheckFlag(NO_VALUE)) + { + SimpleMessage(CHANNEL_COLOR_RED, "This item has no value."); + return; + } + else if (item->IsBag()) + { + int32 bagitemcount = player->GetPlayerItemList()->GetItemCountInBag(item); + if (bagitemcount > 0) { + SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell a bag with items inside it."); + return; + } + } + + int32 sell_price = (int32)(master_item->sell_price * multiplier); + if (sell_price > item->sell_price) + sell_price = item->sell_price; + if (quantity > item->details.count) + quantity = item->details.count; + total_sell_price = sell_price * quantity; + + //------------------------------For Selling Status Items + status_sell_price = (int32)(master_item->sell_status * multiplier); + if (status_sell_price > item->sell_status) + status_sell_price = item->sell_status; + if (quantity > item->details.count) + quantity = item->details.count; + + total_status_sell_price = status_sell_price * quantity; + + if(total_status_sell_price > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))) + total_status_sell_price = 0; + + player->GetInfoStruct()->add_status_points(total_status_sell_price); + + int32 guildMaxLevel = 5 + item->details.recommended_level; // client hard codes +5 to the level + + if (player->GetGuild() && guild->GetLevel() < guildMaxLevel) { + guild->UpdateGuildStatus(GetPlayer(), total_status_sell_price / 10); + guild->SendGuildMemberList(); + guild->AddEXPCurrent((total_status_sell_price / 10), true); + } + if (quantity > 1) + { + if(total_status_sell_price) + Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %i %s to %s for %s and %u Status Points.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str(), status_sell_price); + else + Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %i %s to %s for %s.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str()); + } + else + { + if(total_status_sell_price) + Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %s to %s for %s and %u Status Points.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str(), status_sell_price); + else + Message(CHANNEL_MERCHANT_BUY_SELL, "You sell %s to %s for %s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_sell_price).c_str()); + } + player->AddCoins(total_sell_price); + + if(!item->no_buy_back && (total_status_sell_price == 0 || (total_status_sell_price > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))))) + AddBuyBack(unique_id, item_id, quantity, sell_price); + + if (quantity >= item->details.count) { + database.DeleteItem(GetCharacterID(), item, 0); + player->item_list.RemoveItem(item, true); + } + else { + item->details.count -= quantity; + item->save_needed = true; + } + EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + + if (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) + SendBuyBackList(); + } + } + +} + +void Client::BuyBack(int32 item_id, int16 quantity) { + Spawn* spawn = GetMerchantTransaction(); + if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) && + spawn->IsClientInMerchantLevelRange(this)) { + deque::iterator itr; + BuyBackItem* buyback = 0; + BuyBackItem* closest = 0; + MBuyBack.readlock(__FUNCTION__, __LINE__); + for (itr = buy_back_items.begin(); itr != buy_back_items.end(); itr++) { + buyback = *itr; + if (buyback->unique_id == item_id) { + closest = buyback; + quantity = buyback->quantity; + break; + } + } + MBuyBack.releasereadlock(__FUNCTION__, __LINE__); + if (closest) { + Item* item = 0; + Item* master_item = master_item_list.GetItem(closest->item_id); + if (master_item) { + item = new Item(master_item); + if (closest->quantity >= quantity) + item->details.count = quantity; + else + item->details.count = closest->quantity; + } + bool itemDeleted = false; + bool itemAdded = false; + sint64 dispFlags = 0; + if (item && item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buyback_display_flags", item, player, nullptr, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY)) + SimpleMessage(CHANNEL_NARRATIVE, "You do not meet all the requirements to buy this item."); + else if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item)) + SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item."); + else if (player->RemoveCoins(closest->quantity * closest->price)) { + bool removed = false; + if (closest->quantity == quantity) { + MBuyBack.writelock(__FUNCTION__, __LINE__); + for (itr = buy_back_items.begin(); itr != buy_back_items.end(); itr++) { + if (*itr == closest) { + buy_back_items.erase(itr); + removed = true; + break; + } + } + MBuyBack.releasewritelock(__FUNCTION__, __LINE__); + } + else { + closest->quantity -= quantity; + closest->save_needed = true; + } + itemAdded = AddItem(item, &itemDeleted); + + if (removed) { + database.DeleteBuyBack(GetCharacterID(), closest->item_id, closest->quantity, closest->price); + safe_delete(closest); + } + + if (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) + SendBuyBackList(); + } + else + SimpleMessage(CHANNEL_COLOR_RED, "You cannot afford this item."); + + if(!itemAdded && !itemDeleted) { + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + } + } + +} + +void Client::BuyItem(int32 item_id, int16 quantity) { + // Get the merchant we are buying from + Spawn* spawn = GetMerchantTransaction(); + // Make sure the spawn has a merchant list + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { + int32 total_buy_price = 0; + float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID()); + int32 sell_price = 0; + Item* master_item = master_item_list.GetItem(item_id); + Item* item = 0; + int16 total_available = 0; + + vector* temp; + vector::iterator itr; + MerchantItemInfo* ItemInfo = 0; + temp = world.GetMerchantList(spawn->GetMerchantID()); + + for (itr = temp->begin(); itr != temp->end(); itr++) { + if ((*itr).item_id == item_id) { + ItemInfo = &(*itr); + break; + } + } + + if (master_item && ItemInfo) { + if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) { + quantity = 1; + total_available = 0xFFFF; + sell_price = master_item->sell_price; + } + else { + total_available = world.GetMerchantItemQuantity(spawn->GetMerchantID(), item_id); + sell_price = (int32)(master_item->sell_price * multiplier); + if (quantity > total_available) + quantity = total_available; + } + sint64 dispFlags = 0; + if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buy_display_flags", master_item, player, nullptr, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY)) + { + SimpleMessage(CHANNEL_NARRATIVE, "You do not meet all the requirements to buy this item."); + return; + } + if(quantity < 1) + { + SimpleMessage(CHANNEL_COLOR_RED, "Merchant does not have item for purchase (quantity < 1)."); + return; + } + + total_buy_price = sell_price * quantity; + item = new Item(master_item); + item->details.count = quantity; + if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item)) { + SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item."); + lua_interface->SetLuaUserDataStale(item); + safe_delete(item); + } + else { + // Price not set in the merchant_inventory table, use the old method + if (ItemInfo->price_item_id == 0 && ItemInfo->price_item2_id == 0 && ItemInfo->price_status == 0 && ItemInfo->price_stationcash == 0 && ItemInfo->price_coins == 0) { + if (player->RemoveCoins(total_buy_price)) { + item->SetMaxSellValue(sell_price); + if (quantity > 1) + Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %i %s from %s for%s.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_buy_price).c_str()); + else + Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %s from %s for%s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(total_buy_price).c_str()); + bool itemDeleted = false; + AddItem(item, &itemDeleted); + if(!itemDeleted) { + CheckPlayerQuestsItemUpdate(item); + if (item && total_available < 0xFF) { + world.DecreaseMerchantQuantity(spawn->GetMerchantID(), item_id, quantity); + SendBuyMerchantList(); + } + + if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) + PlayLotto(total_buy_price, item->details.item_id); + } + } + else { + Message(CHANNEL_COLOR_RED, "You do not have enough coin to purchase %s.", master_item->CreateItemLink(GetVersion()).c_str()); + GetCurrentZone()->SendSpellFailedPacket(this, SPELL_ERROR_NOT_ENOUGH_COIN); + PlaySound("buy_failed"); + } + } + else { + // Price set in merchant_inventory table + + // Check if the player has enough status, coins and staion cash to buy the item before checking the items + // TODO: need to add support for station cash + if (player->GetInfoStruct()->get_status_points() >= (ItemInfo->price_status * quantity) && player->HasCoins(ItemInfo->price_coins * quantity)) { + // Check items + int16 item_quantity = 0; + // Default these to true in case price_item_id or price_item2_id was never set + bool hasItem1 = true; + bool hasItem2 = true; + Item* tempItem1 = 0; + Item* tempItem2 = 0; + if (ItemInfo->price_item_id != 0) { + // Same item for whatever reason lets add the quantities together + if (ItemInfo->price_item_id == ItemInfo->price_item2_id) + item_quantity = ItemInfo->price_item_qty + ItemInfo->price_item2_qty; + else + item_quantity = ItemInfo->price_item_qty; + + tempItem1 = player->item_list.GetItemFromID(ItemInfo->price_item_id); + if (tempItem1) { + if (tempItem1->details.count < item_quantity) + hasItem1 = false; + } + else { + hasItem1 = false; + } + } + + // Check item2, if item_quantity is greater then item1 quantity then item2 is the same item + // as item1 and we already checked for it so we can skip this check + if (ItemInfo->price_item2_id != 0 && item_quantity <= ItemInfo->price_item_qty) { + tempItem2 = player->item_list.GetItemFromID(ItemInfo->price_item2_id); + if (tempItem2) { + if (tempItem2->details.count < ItemInfo->price_item2_qty) + hasItem2 = false; + } + else { + hasItem2 = false; + } + } + // if we have every thing then remove the price and give the item + if (hasItem1 && hasItem2) { + player->GetInfoStruct()->set_status_points(player->GetInfoStruct()->get_status_points() - (ItemInfo->price_status * quantity)); + // TODO: station cash + + // The update that would normally be sent after modifing the players inventory is automatically sent in AddItem wich is called later + // so there is no need to send it more then that one time + if (tempItem1) { + if (tempItem1->details.count > item_quantity) { + tempItem1->details.count -= item_quantity; + tempItem1->save_needed = true; + } + else { + database.DeleteItem(GetCharacterID(), tempItem1, 0); + player->item_list.DestroyItem(tempItem1->details.index); + } + } + if (tempItem2) { + if (tempItem2->details.count > ItemInfo->price_item2_qty) { + tempItem2->details.count -= ItemInfo->price_item2_qty; + tempItem2->save_needed = true; + } + else { + database.DeleteItem(GetCharacterID(), tempItem2, 0); + player->item_list.DestroyItem(tempItem2->details.index); + } + } + + + // Checked to see if we had enough coins already so don't need to check the return type on RemoveCoins as it will always be true + player->RemoveCoins(ItemInfo->price_coins * quantity); + item->SetMaxSellValue(sell_price); + if (quantity > 1) + Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %i %s from %s for%s.", quantity, master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(ItemInfo->price_coins * quantity).c_str()); + else + Message(CHANNEL_MERCHANT_BUY_SELL, "You buy %s from %s for%s.", master_item->CreateItemLink(GetVersion()).c_str(), spawn->GetName(), GetCoinMessage(ItemInfo->price_coins * quantity).c_str()); + bool itemDeleted = false; + AddItem(item, &itemDeleted); + if(!itemDeleted) { + CheckPlayerQuestsItemUpdate(item); + if (item && total_available < 0xFF) { + world.DecreaseMerchantQuantity(spawn->GetMerchantID(), item_id, quantity); + SendBuyMerchantList(); + } + + SendSellMerchantList(); + if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO) + PlayLotto(total_buy_price, item->details.item_id); + } + + } + else { + Message(CHANNEL_COLOR_RED, "You do not have enough coin to purchase %s.", master_item->CreateItemLink(GetVersion()).c_str()); + GetCurrentZone()->SendSpellFailedPacket(this, SPELL_ERROR_NOT_ENOUGH_COIN); + PlaySound("buy_failed"); + } + } + else { + Message(CHANNEL_COLOR_RED, "You do not have enough coin to purchase %s.", master_item->CreateItemLink(GetVersion()).c_str()); + GetCurrentZone()->SendSpellFailedPacket(this, SPELL_ERROR_NOT_ENOUGH_COIN); + PlaySound("buy_failed"); + } + } + } + } + } + +} + +void Client::RepairItem(int32 item_id) { + Spawn* spawn = GetMerchantTransaction(); + if (spawn) { + Item* item = player->item_list.GetItemFromID(item_id); + if (!item) + item = player->GetEquipmentList()->GetItemFromItemID(item_id); + if (item) { + if(item->CheckFlag2(NO_REPAIR)) { + Message(CHANNEL_MERCHANT, "The mender was unable to repair your items."); + PlaySound("buy_failed"); + } + else { + int32 repair_cost = item->CalculateRepairCost(); + if (player->RemoveCoins((int32)repair_cost)) { + item->generic_info.condition = 100; + item->save_needed = true; + QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player)); + QueuePacket(player->SendInventoryUpdate(GetVersion())); + QueuePacket(item->serialize(version, false, player)); + Message(CHANNEL_MERCHANT, "You give %s %s to repair your %s.", spawn->GetName(), GetCoinMessage(repair_cost).c_str(), item->CreateItemLink(GetVersion()).c_str()); + PlaySound("coin_cha_ching"); + if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR) + SendRepairList(); + } + else { + string popup_text = "You do not have enough coin to repair "; + string popup_item = item->CreateItemLink(GetVersion()).c_str(); + popup_text.append(popup_item); + SendPopupMessage(10, popup_text.c_str(), "", 3, 0xFF, 0xFF, 0xFF); + Message(CHANNEL_MERCHANT, "You do not have enough coin to repair %s.", item->CreateItemLink(GetVersion()).c_str()); + PlaySound("buy_failed"); + } + } + } + } + +} + +void Client::RepairAllItems() { + Spawn* spawn = GetMerchantTransaction(); + if (spawn) { + vector* repairable_items = GetRepairableItems(); + if (repairable_items && repairable_items->size() > 0) { + vector::iterator itr; + int64 total_cost = 0; + for (itr = repairable_items->begin(); itr != repairable_items->end(); itr++) + total_cost += (*itr)->CalculateRepairCost(); + if (player->RemoveCoins((int32)total_cost)) { + Message(CHANNEL_MERCHANT, "You give %s to repair all of your items.", GetCoinMessage((int32)total_cost).c_str()); + for (itr = repairable_items->begin(); itr != repairable_items->end(); itr++) { + Item* item = *itr; + if (item) { + item->generic_info.condition = 100; + item->save_needed = true; + QueuePacket(item->serialize(version, false, player)); + Message(CHANNEL_COLOR_YELLOW, "Repaired: %s.", item->CreateItemLink(GetVersion()).c_str()); + } + } + QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player)); + QueuePacket(player->SendInventoryUpdate(GetVersion())); + PlaySound("coin_cha_ching"); + if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR) + SendRepairList(); + } + else { + string popup_text = "You do not have enough coin to repair all of your items. "; + SendPopupMessage(10, popup_text.c_str(), "", 3, 0xFF, 0xFF, 0xFF); + SimpleMessage(CHANNEL_MERCHANT, "You do not have enough coin to repair all of your items."); + PlaySound("buy_failed"); + } + } + safe_delete(repairable_items); + } + +} + +void Client::SendAchievementsList() +{ + /*map *achievements = player->GetAchievementList()->GetAchievements(); + map::iterator itr; + Achievement *achievement; + vector *requirements = 0; + vector::iterator itr2; + AchievementRequirements *requirement; + vector *rewards = 0; + vector::iterator itr3; + AchievementRewards *reward; + PacketStruct *packet; + int16 i = 0; + int16 j = 0; + int16 k = 0; + + if (!(packet = configReader.getStruct("WS_CharacterAchievements", version))) { + return; + } + + packet->setArrayLengthByName("num_achievements" , achievements->size()); + for (itr = achievements->begin(); itr != achievements->end(); itr++) { + achievement = itr->second; + packet->setArrayDataByName("achievement_id", achievement->GetID(), i); + packet->setArrayDataByName("title", achievement->GetTitle(), i); + packet->setArrayDataByName("uncompleted_text", achievement->GetUncompletedText(), i); + packet->setArrayDataByName("completed_text", achievement->GetCompletedText(), i); + packet->setArrayDataByName("category", achievement->GetCategory(), i); + packet->setArrayDataByName("expansion", achievement->GetExpansion(), i); + packet->setArrayDataByName("icon", achievement->GetIcon(), i); + packet->setArrayDataByName("point_value", achievement->GetPointValue(), i); + packet->setArrayDataByName("qty_req", achievement->GetQtyReq(), i); + packet->setArrayDataByName("hide_achievement", achievement->GetHide(), i); + packet->setArrayDataByName("unknown3", achievement->GetUnknown3a(), i); + packet->setArrayDataByName("unknown3", achievement->GetUnknown3b(), i); + requirements = achievement->GetRequirements(); + rewards = achievement->GetRewards(); + j = 0; + k = 0; + packet->setSubArrayLengthByName("num_items", requirements->size(), i, j); + for (itr2 = requirements->begin(); itr2 != requirements->end(); itr2++) { + requirement = *itr2; + packet->setSubArrayDataByName("item_name", requirement->name.c_str(), i, j); + packet->setSubArrayDataByName("item_qty_req", requirement->qty_req, i, j); + j++; + } + packet->setSubArrayLengthByName("num_rewards", achievement->GetRewards()->size(), i, k); + for (itr3 = rewards->begin(); itr3 != rewards->end(); itr3++) { + reward = *itr3; + packet->setSubArrayDataByName("reward_item", reward->reward.c_str(), i, k); + k++; + } + i++; + } + + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + EQ2Packet* app = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + safe_delete(packet); + safe_delete(data); + //DumpPacket(app); + QueuePacket(app);*/ + + QueuePacket(master_achievement_list.GetAchievementPacket()->Copy()); + SendAchievementUpdate(true); +} + +void Client::SendAchievementUpdate(bool first_login) { + map* updates = player->GetAchievementUpdateList()->GetAchievementUpdates(); + map::iterator itr; + AchievementUpdate* update; + vector* update_items = 0; + vector::iterator itr2; + AchievementUpdateItems* update_item; + + int16 i = 0; + int16 j = 0; + + PacketStruct* packet; + + if (!(packet = configReader.getStruct("WS_AchievementUpdate", version))) { + return; + } + + packet->setDataByName("unknown1", first_login ? 1 : 0); + packet->setArrayLengthByName("num_achievements", updates->size()); + for (itr = updates->begin(); itr != updates->end(); itr++) { + update = itr->second; + packet->setArrayDataByName("achievement_id", update->GetID(), i); + packet->setArrayDataByName("completed_date", update->GetCompletedDate(), i); + update_items = update->GetUpdateItems(); + j = 0; + packet->setSubArrayLengthByName("num_items", update_items->size(), i); + for (itr2 = update_items->begin(); itr2 != update_items->end(); itr2++) { + update_item = *itr2; + packet->setSubArrayDataByName("item_update", update_item->item_update, i, j); + j++; + } + i++; + } + + //packet->PrintPacket(); + EQ2Packet* data = packet->serialize(); + EQ2Packet* app = new EQ2Packet(OP_ClientCmdMsg, data->pBuffer, data->size); + safe_delete(packet); + safe_delete(data); + //DumpPacket(app); + QueuePacket(app); +} + +void Client::SendBuyMerchantList(bool sell) { + Spawn* spawn = GetMerchantTransaction(); + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { + vector* items = world.GetMerchantItemList(spawn->GetMerchantID(), spawn->GetMerchantType(), player); + if (items) { + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID()); + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", items->size()); + vector::iterator itr; + sint8 item_difficulty = 0; + int32 sell_price = 0; + int i = 0; + int tmp_level = 0; + for (itr = items->begin(); itr != items->end(); itr++, i++) { + MerchantItemInfo ItemInfo = *itr; + Item* item = master_item_list.GetItem(ItemInfo.item_id); + if (!item) + continue; + + packet->setArrayDataByName("item_name", item->name.c_str(), i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("stack_size", item->stack_count, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + if (item->generic_info.adventure_default_level > 0) + tmp_level = item->generic_info.adventure_default_level; + else + tmp_level = item->generic_info.tradeskill_default_level; + packet->setArrayDataByName("level", tmp_level, i); + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", item->details.tier, i); + } + packet->setArrayDataByName("item_id2", item->details.item_id, i); + item_difficulty = player->GetArrowColor(tmp_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + + sint64 overrideValue = 0; + if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, nullptr, &overrideValue)) + item_difficulty = (sint8)overrideValue; + + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + + packet->setArrayDataByName("item_difficulty", item_difficulty, i); + packet->setArrayDataByName("quantity", ItemInfo.quantity, i); + packet->setArrayDataByName("unknown5", 255, i); + packet->setArrayDataByName("stack_size2", item->stack_count, i); + + sint64 dispFlags = 0; + if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buy_display_flags", item, player, nullptr, &dispFlags)) + packet->setArrayDataByName("display_flags", (int8)dispFlags, i); + + std::string overrideValueStr; + // classic client isn't properly tracking this field, DoF we don't have it identified yet, but no field to cause any issues (can add later if identified) + if (GetVersion() >= 546 && item->GetItemScript() && lua_interface && lua_interface->RunItemScriptWithReturnString(item->GetItemScript(), "item_description", item, player, &overrideValueStr)) + packet->setArrayDataByName("description", overrideValueStr.c_str(), i); + + // If no price set in the merchant_inventory table then use the old method + if (ItemInfo.price_item_id == 0 && ItemInfo.price_item2_id == 0 && ItemInfo.price_coins == 0 && ItemInfo.price_status == 0 && ItemInfo.price_stationcash == 0) { + sell_price = (int32)(item->sell_price * multiplier); + packet->setArrayDataByName("price", sell_price, i); + } + else { + int8 count = 0; + if (ItemInfo.price_item_id != 0 && ItemInfo.price_item_qty != 0) + count++; + if (ItemInfo.price_item2_id != 0 && ItemInfo.price_item2_qty != 0) + count++; + if (count != 0) { + packet->setSubArrayLengthByName("num_tokens", count, i); + int8 index = 0; + Item* token = 0; + if (ItemInfo.price_item_id != 0) { + token = master_item_list.GetItem(ItemInfo.price_item_id); + if (item) { + packet->setSubArrayDataByName("token_icon", token->GetIcon(GetVersion()), i, index); + packet->setSubArrayDataByName("token_qty", ItemInfo.price_item_qty, i, index); + packet->setSubArrayDataByName("token_id", ItemInfo.price_item_id, i, index); + packet->setSubArrayDataByName("token_name", token->name.c_str(), i, index); + } + token = 0; + index++; + } + if (ItemInfo.price_item2_id != 0) { + token = master_item_list.GetItem(ItemInfo.price_item2_id); + if (item) { + packet->setSubArrayDataByName("token_icon", token->GetIcon(GetVersion()), i, index); + packet->setSubArrayDataByName("token_qty", ItemInfo.price_item2_qty, i, index); + packet->setSubArrayDataByName("token_id", ItemInfo.price_item2_id, i, index); + packet->setSubArrayDataByName("token_name", token->name.c_str(), i, index); + } + } + } + packet->setArrayDataByName("price", ItemInfo.price_coins, i); + packet->setArrayDataByName("status2", ItemInfo.price_status, i); + packet->setArrayDataByName("station_cash", ItemInfo.price_stationcash, i); + } + } + if (GetVersion() < 561) { + //buy is 0 so dont need to set it + if (sell) + packet->setDataByName("type", 1); + } + else if (GetVersion() == 561) { + packet->setDataByName("type", 2); + } + else { + if (sell) + packet->setDataByName("type", 130); + else + packet->setDataByName("type", 2); + } + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + safe_delete(items); + } + else { + // Need to send an empty packet in the event there is no item list, otherwise the + // last item list sent to the player will show for this merchant + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + if (GetVersion() <= 561) { + //buy is 0 so dont need to set it + if (sell) + packet->setDataByName("type", 1); + } + else { + if (sell) + packet->setDataByName("type", 130); + else + packet->setDataByName("type", 2); + } + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + } + } + +} + +void Client::SendSellMerchantList(bool sell) { + Spawn* spawn = GetMerchantTransaction(); + if (!spawn || (spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY) || (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO)) + return; + + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { + map* items = player->GetItemList(); + if (items) { + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + vector sellable_items; + map::iterator test_itr; + for (test_itr = items->begin(); test_itr != items->end(); test_itr++) { + bool isbagwithitems = false; + if (test_itr->second && test_itr->second->IsBag() && (test_itr->second->details.num_slots - test_itr->second->details.num_free_slots != test_itr->second->details.num_slots)) + isbagwithitems = true; + + if (test_itr->second && !test_itr->second->CheckFlag(NO_VALUE) && (isbagwithitems == false) && (test_itr->second->details.inv_slot_id != -3) && (test_itr->second->details.inv_slot_id != -4)) + sellable_items.push_back(test_itr->second); + } + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", sellable_items.size()); + vector::iterator itr; + Item* item = 0; + sint8 item_difficulty = 0; + float multiplier = CalculateSellMultiplier(spawn->GetMerchantID()); + int32 sell_price = 0; + Item* master_item = 0; + int i = 0; + int tmp_level = 0; + for (itr = sellable_items.begin(); itr != sellable_items.end(); itr++, i++) { + item = *itr; + master_item = master_item_list.GetItem(item->details.item_id); + if (master_item) + sell_price = (int32)(master_item->sell_price * multiplier); + else + sell_price = 0; + if (sell_price > item->sell_price) + sell_price = item->sell_price; + packet->setArrayDataByName("item_name", item->name.c_str(), i); + string thename = item->name; + + packet->setArrayDataByName("price", sell_price, i); + packet->setArrayDataByName("status", 0, i);//additive to status 2 maybe for server bonus etc + + int8 dispFlags = 0; + + // only city merchants allow selling for status + if(item->sell_status > 0 && (spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT)) + { + packet->setArrayDataByName("status2", item->sell_status, i); //this one is the main status + int32 guildMaxLevel = 5 + item->details.recommended_level; // client hard codes +5 to the level + if (GetPlayer()->GetGuild() && GetPlayer()->GetGuild()->GetLevel() >= guildMaxLevel) { + dispFlags += DISPLAY_FLAG_NO_GUILD_STATUS; + } + + } + if(item->no_buy_back || (item->sell_status > 0 && (spawn->GetMerchantType() & MERCHANT_TYPE_CITYMERCHANT))) + { + if(GetVersion() < 1188) + dispFlags += DISPLAY_FLAG_RED_TEXT; // for older clients it isn't "no buy back", you can either have 1 for red text or 255 for 'not for sale' to be checked + else + dispFlags += DISPLAY_FLAG_NO_BUYBACK; + } + + if(item->no_sale) + dispFlags += DISPLAY_FLAG_NOT_FOR_SALE; + + packet->setArrayDataByName("display_flags", dispFlags, i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("unique_item_id", item->details.unique_id, i); + packet->setArrayDataByName("stack_size", item->details.count, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + if (item->generic_info.adventure_default_level > 0) + tmp_level = item->generic_info.adventure_default_level; + else + tmp_level = item->generic_info.tradeskill_default_level; + packet->setArrayDataByName("level", item->details.recommended_level, i); + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", item->details.tier, i); + } + packet->setArrayDataByName("item_id2", item->details.item_id, i); + item_difficulty = player->GetArrowColor(tmp_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + + sint64 overrideValue = 0; + if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, nullptr, &overrideValue)) + item_difficulty = (sint8)overrideValue; + + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + + packet->setArrayDataByName("item_difficulty", item_difficulty, i); + if (item->details.count == 1) + packet->setArrayDataByName("quantity", 0xFFFF, i); + else + packet->setArrayDataByName("quantity", item->details.count, i); + packet->setArrayDataByName("stack_size2", item->details.count, i); + if (GetVersion() <= 1096) + packet->setArrayDataByName("description", item->description.c_str(), i); + } + if (GetVersion() < 561) { + packet->setDataByName("type", 1); + } + else if(GetVersion() == 561) { + packet->setDataByName("type", 1); + } + else { + if (sell) + packet->setDataByName("type", 129); + else + packet->setDataByName("type", 1); + } + packet->setDataByName("unknown8a", 16256, 6); + packet->setDataByName("unknown8a", 16256, 10); + //packet->PrintPacket(); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + safe_delete(items); + } + } + +} + +void Client::SendBuyBackList(bool sell) { + if (GetVersion() <= 561) //this wasn't added until LU37 on July 31st 2007, well after the DoF client + return; + Spawn* spawn = GetMerchantTransaction(); + if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { + deque::iterator itr; + int i = 0; + Item* master_item = 0; + BuyBackItem* buyback = 0; + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", buy_back_items.size()); + sint8 item_difficulty = 0; + MBuyBack.readlock(__FUNCTION__, __LINE__); + int tmp_level = 0; + for (itr = buy_back_items.begin(); itr != buy_back_items.end(); itr++, i++) { + buyback = *itr; + master_item = master_item_list.GetItem(buyback->item_id); + if (master_item) { + packet->setArrayDataByName("item_name", master_item->name.c_str(), i); + packet->setArrayDataByName("price", buyback->price, i); + packet->setArrayDataByName("item_id", master_item->details.item_id, i); + packet->setArrayDataByName("unique_item_id", buyback->unique_id, i); + packet->setArrayDataByName("stack_size", buyback->quantity, i); + packet->setArrayDataByName("icon", master_item->GetIcon(GetVersion()), i); + if (master_item->generic_info.adventure_default_level > 0) + tmp_level = master_item->generic_info.adventure_default_level; + else + tmp_level = master_item->generic_info.tradeskill_default_level; + packet->setArrayDataByName("level", tmp_level, i); + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", master_item->details.tier, i); + } + packet->setArrayDataByName("item_id2", master_item->details.item_id, i); + item_difficulty = player->GetArrowColor(tmp_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + + sint64 overrideValue = 0; + if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "item_difficulty", master_item, player, nullptr, &overrideValue)) + item_difficulty = (sint8)overrideValue; + + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + packet->setArrayDataByName("item_difficulty", item_difficulty, i); + + sint64 dispFlags = 0; + if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "buyback_display_flags", master_item, player, nullptr, &dispFlags)) + packet->setArrayDataByName("display_flags", (int8)dispFlags, i); + + if (buyback->quantity == 1) + packet->setArrayDataByName("quantity", 0xFFFF, i); + else + packet->setArrayDataByName("quantity", buyback->quantity, i); + packet->setArrayDataByName("stack_size2", buyback->quantity, i); + if (GetVersion() <= 1096) + packet->setArrayDataByName("description", master_item->description.c_str(), i); + } + } + MBuyBack.releasereadlock(__FUNCTION__, __LINE__); + if (sell) + packet->setDataByName("type", 640); + else + packet->setDataByName("type", 512); + EQ2Packet* outapp = packet->serialize(); + // DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + } + +} + +void Client::SendRepairList() { + Spawn* spawn = GetMerchantTransaction(); + if (spawn) { + vector* repairable_items = GetRepairableItems(); + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", repairable_items->size()); + Item* item = 0; + sint8 item_difficulty = 0; + int32 i = 0; + vector::iterator itr; + for (itr = repairable_items->begin(); itr != repairable_items->end(); itr++, i++) { + item = *itr; + + packet->setArrayDataByName("item_name", item->name.c_str(), i); + packet->setArrayDataByName("price", item->CalculateRepairCost(), i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("stack_size", item->details.count, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + + /*if (item->generic_info.adventure_default_level > 0) + tmp_level = item->generic_info.adventure_default_level; + else + tmp_level = item->generic_info.tradeskill_default_level; + packet->setArrayDataByName("level", tmp_level, i);*/ + packet->setArrayDataByName("level", item->generic_info.adventure_default_level, i); + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", item->details.tier, i); + } + packet->setArrayDataByName("item_id2", item->details.item_id, i); + item_difficulty = player->GetArrowColor(item->generic_info.adventure_default_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + packet->setArrayDataByName("item_difficulty", item_difficulty, i); + if (item->details.count == 1) + packet->setArrayDataByName("quantity", 0xFFFF, i); + else + packet->setArrayDataByName("quantity", item->details.count, i); + packet->setArrayDataByName("stack_size2", item->details.count, i); + if (GetVersion() <= 1096) + packet->setArrayDataByName("description", item->description.c_str(), i); + } + if (GetVersion() <= 561) { + packet->setDataByName("type", 112); + } + else { + packet->setDataByName("type", 96); + } + EQ2Packet* outapp = packet->serialize(); + + //DumpPacket(outapp); + QueuePacket(outapp); + + /*if (GetVersion() <= 561) { + packet->setDataByName("type", 16); + EQ2Packet* outapp2 = packet->serialize(); + QueuePacket(outapp2); + }*/ + + safe_delete(packet); + } + safe_delete(repairable_items); + } + +} + +void Client::ShowLottoWindow() { + if(GetVersion() <= 373) { + SimpleMessage(CHANNEL_COLOR_RED, "This client does not support the gambler UI, only Desert of Flames or later client."); + return; + } + Spawn* spawn = GetMerchantTransaction(); + if (spawn) { + + int32 item_id = rule_manager.GetGlobalRule(R_World, GamblingTokenItemID)->GetInt32(); + if (!item_id) + { + LogWrite(WORLD__ERROR, 0, "World", "No GamblingTokenItemID rule set!"); + SimpleMessage(CHANNEL_COLOR_RED, "The server admin has not setup a lotto item ticket."); + + return; + } + else if (item_id == 0) + { + LogWrite(WORLD__ERROR, 0, "World", "Error! Invalid GamblingTokenItemID value!"); + + return; + } + + Item* item = master_item_list.GetItem(item_id); + if (!item) { + LogWrite(WORLD__ERROR, 0, "World", "The 'GamblingTokenItemID' rule value %u is not a valid item id.", item_id); + + return; + } + + LogWrite(WORLD__DEBUG, 0, "World", "GamblingTokenItemID = '%s' (%u)", item->name.c_str(), item_id); + + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", 1); + packet->setArrayDataByName("item_name", item->name.c_str()); + packet->setArrayDataByName("price", item->sell_price); + packet->setArrayDataByName("item_id", item->details.item_id); + packet->setArrayDataByName("stack_size", item->details.count); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion())); + packet->setArrayDataByName("level", item->generic_info.adventure_default_level); + + if(rule_manager.GetGlobalRule(R_World, DisplayItemTiers)->GetBool()) { + packet->setArrayDataByName("tier", item->details.tier); + } + packet->setArrayDataByName("item_id2", item->details.item_id); + int8 item_difficulty = player->GetArrowColor(item->generic_info.adventure_default_level); + if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY) + item_difficulty = ARROW_COLOR_WHITE; + item_difficulty -= 6; + if (item_difficulty < 0) + item_difficulty *= -1; + packet->setArrayDataByName("item_difficulty", item_difficulty); + //if(item->details.count == 1) + packet->setArrayDataByName("quantity", 0xFFFF); + //else + // packet->setArrayDataByName("quantity", item->details.count); + packet->setArrayDataByName("stack_size2", item->details.count); + packet->setArrayDataByName("description", item->description.c_str()); + if (GetVersion() <= 546) { + packet->setDataByName("type", 128); + } + else { + packet->setDataByName("type", 0x00000102); + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +void Client::PlayLotto(int32 price, int32 ticket_item_id) { + PacketStruct* packet = configReader.getStruct("WS_Lottery", GetVersion()); + if (packet) { + world.AddLottoPlayer(GetCharacterID(), Timer::GetCurrentTime2() + 4500); + int32 rolls[6] = { 0 }; + int32 lottery_digits[6] = { 0 }; + int8 num_matches = 0; + int64 jackpot = 0; + Item* item = GetPlayer()->item_list.GetItemFromID(ticket_item_id); + if (!item) { + return; + } + database.DeleteItem(GetCharacterID(), item, 0); + GetPlayer()->item_list.RemoveItem(item, true); + QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion())); + Variable* winning_numbers = variables.FindVariable("gambling_winning_numbers"); + if (!winning_numbers) { + winning_numbers = new Variable("gambling_winning_numbers", "231205182236", "Current Gigglegibber Gambling Game winning numbers"); + variables.AddVariable(winning_numbers); + database.SaveVariable(winning_numbers->GetName(), winning_numbers->GetValue(), winning_numbers->GetComment()); + } + if (strlen(winning_numbers->GetValue()) != 12) { + winning_numbers->SetValue("231205182236"); + database.SaveVariable(winning_numbers->GetName(), winning_numbers->GetValue(), winning_numbers->GetComment()); + } + try { + for (int32 i = 0; i < 12; i += 2) { + char num[4]; + strncpy(num, winning_numbers->GetValue() + i, 2); + lottery_digits[i / 2] = atoi(num); + } + } + catch (...) { + LogWrite(WORLD__ERROR, 0, "World", "Error parsing 'gambling_winning_numbers' variable"); + + return; + } + Variable* jackpot_var = variables.FindVariable("gambling_current_jackpot"); + if (!jackpot_var) { + jackpot_var = new Variable("gambling_current_jackpot", "10000", "Current Gigglegibber Gambling Game Jackpot"); + variables.AddVariable(jackpot_var); + database.SaveVariable(jackpot_var->GetName(), jackpot_var->GetValue(), jackpot_var->GetComment()); + } + try { + jackpot = atoul(jackpot_var->GetValue()); + if (jackpot < 10000) + jackpot = 10000; + } + catch (...) { + jackpot = 10000; + } + char new_jackpot[128] = { 0 }; + sprintf(new_jackpot, "%llu", jackpot + price); + jackpot_var->SetValue(new_jackpot); + database.SaveVariable(jackpot_var->GetName(), jackpot_var->GetValue(), jackpot_var->GetComment()); + world.PickRandomLottoDigits(rolls); + packet->setDataByName("roll_digit1", rolls[0]); + packet->setDataByName("roll_digit2", rolls[1]); + packet->setDataByName("roll_digit3", rolls[2]); + packet->setDataByName("roll_digit4", rolls[3]); + packet->setDataByName("roll_digit5", rolls[4]); + packet->setDataByName("roll_digit6", rolls[5]); + packet->setDataByName("lottery_digit1", lottery_digits[0]); + packet->setDataByName("lottery_digit2", lottery_digits[1]); + packet->setDataByName("lottery_digit3", lottery_digits[2]); + packet->setDataByName("lottery_digit4", lottery_digits[3]); + packet->setDataByName("lottery_digit5", lottery_digits[4]); + packet->setDataByName("lottery_digit6", lottery_digits[5]); + QueuePacket(packet->serialize()); + safe_delete(packet); + for (int32 i = 0; i < 6; i++) { + for (int32 j = 0; j < 6; j++) { + if (rolls[i] == lottery_digits[j]) { + num_matches++; + break; + } + } + } + char new_jackpot_str[16]; + memset(new_jackpot_str, 0, sizeof(new_jackpot_str)); + world.SetLottoPlayerNumMatches(GetCharacterID(), num_matches); + if (num_matches == 6) { + world.PickRandomLottoDigits(lottery_digits); + for (int32 i = 0; i < 12; i += 2) + sprintf(new_jackpot_str + i, "%02d", lottery_digits[i / 2]); + winning_numbers->SetValue(new_jackpot_str); + jackpot_var->SetValue("10000"); + database.SaveVariable(winning_numbers->GetName(), winning_numbers->GetValue(), winning_numbers->GetComment()); + database.SaveVariable(jackpot_var->GetName(), jackpot_var->GetValue(), jackpot_var->GetComment()); + } + } + +} + +void Client::SendGuildCreateWindow() { + if (GetVersion() <= 561) { + SimpleMessage(0, "Not implemented on this client...yet?"); + } + else { + Spawn* spawn = GetPlayer()->GetTarget(); + if (spawn) { + PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(spawn)); + packet->setArrayLengthByName("num_items", 0); + packet->setDataByName("type", 0x00008000); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void Client::AddBuyBack(int32 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed) { + BuyBackItem* item = new BuyBackItem; + item->item_id = item_id; + item->unique_id = unique_id; + item->price = price; + item->quantity = quantity; + item->save_needed = save_needed; + MBuyBack.writelock(__FUNCTION__, __LINE__); + buy_back_items.push_back(item); + if (buy_back_items.size() > 10) { + safe_delete(buy_back_items.front()); + buy_back_items.pop_front(); + } + MBuyBack.releasewritelock(__FUNCTION__, __LINE__); + +} + +deque* Client::GetBuyBacks() { + return &buy_back_items; +} + +vector* Client::GetRepairableItems() { + vector* repairable_items = new vector; + vector* equipped_items = player->GetEquipmentList()->GetAllEquippedItems(); + map* items = player->GetItemList(); + if (equipped_items && equipped_items->size() > 0) { + for (int32 i = 0; i < equipped_items->size(); i++) { + Item* item = equipped_items->at(i); + if (item && !item->CheckFlag2(NO_REPAIR) && item->generic_info.condition < 100) + repairable_items->push_back(item); + } + } + if (items && items->size() > 0) { + map::iterator itr; + for (itr = items->begin(); itr != items->end(); itr++) { + Item* item = itr->second; + if (item && !item->CheckFlag2(NO_REPAIR) && item->generic_info.condition < 100) + repairable_items->push_back(item); + } + } + safe_delete(equipped_items); + safe_delete(items); + + return repairable_items; +} + + +vector* Client::GetItemsByEffectType(ItemEffectType type, ItemEffectType type2) { + if(type == NO_EFFECT_TYPE) + return nullptr; + + vector* return_items = new vector; + vector* equipped_items = player->GetEquipmentList()->GetAllEquippedItems(); + map* items = player->GetItemList(); + if (equipped_items && equipped_items->size() > 0) { + for (int32 i = 0; i < equipped_items->size(); i++) { + Item* item = equipped_items->at(i); + if (item && (item->effect_type == type || (type2 != NO_EFFECT_TYPE && item->effect_type == type2))) + return_items->push_back(item); + } + } + if (items && items->size() > 0) { + map::iterator itr; + for (itr = items->begin(); itr != items->end(); itr++) { + Item* item = itr->second; + if (item && (item->effect_type == type || (type2 != NO_EFFECT_TYPE && item->effect_type == type2))) + return_items->push_back(item); + } + } + safe_delete(equipped_items); + safe_delete(items); + + return return_items; +} + +void Client::SendMailList() { + int32 kiosk_id = player->GetIDWithPlayerSpawn(GetMailTransaction()); + if (kiosk_id > 0) { + PacketStruct* p = configReader.getStruct("WS_GetMailHeader", GetVersion()); + if (p) { + MutexMap* mail_list = player->GetMail(); + MutexMap::iterator itr = mail_list->begin(); + int32 i = 0; + p->setDataByName("kiosk_id", kiosk_id); + p->setArrayLengthByName("num_messages", (int16)mail_list->size()); + while (itr.Next()) { + Mail* mail = itr->second; + p->setArrayDataByName("mail_id", mail->mail_id, i); + p->setArrayDataByName("player_to_id", mail->player_to_id, i); + p->setArrayDataByName("player_from", mail->player_from.c_str(), i); + p->setArrayDataByName("subject", mail->subject.c_str(), i); + p->setArrayDataByName("already_read", mail->already_read, i); + if(mail->expire_time) + p->setArrayDataByName("mail_deletion", mail->expire_time - mail->time_sent, i); + else + p->setArrayDataByName("mail_deletion", 0, i); + + p->setArrayDataByName("mail_type", mail->mail_type, i); + p->setArrayDataByName("mail_expire", 0xFFFFFFFF, i); + p->setArrayDataByName("unknown1a", 0xFFFFFFFF, i); + p->setArrayDataByName("coin_copper", mail->coin_copper, i); + p->setArrayDataByName("coin_silver", mail->coin_silver, i); + p->setArrayDataByName("coin_gold", mail->coin_gold, i); + p->setArrayDataByName("coin_plat", mail->coin_plat, i); + + //p->setArrayDataByName("unknown2", 0, i); + + bool successItemAdd = false; + if(mail->stack || mail->char_item_id) + { + Item* item = master_item_list.GetItem(mail->char_item_id); + if(item) + { + item->stack_count = mail->stack > 1 ? mail->stack : 0; + if (version < 860) + p->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); + else if (version < 1193) + p->setItemArrayDataByName("item", item, player, i); + else + p->setItemArrayDataByName("item", item, player, i, 0, 2); + + successItemAdd = true; + } + } + + if(!successItemAdd) + { + p->setArrayDataByName("end_tag2", GetItemPacketType(GetVersion()), i); + p->setArrayDataByName("end_tag3", 0xFF, i); + } + i++; + } + + // GMs send mail for free! + if (GetAdminStatus() > 0) + { + p->setDataByName("postage_cost", 0); + p->setDataByName("attachment_cost", 0); + } + else + { + p->setDataByName("postage_cost", 10); + p->setDataByName("attachment_cost", 50); + } + p->setDataByName("unknown3", 0x01F4); + p->setDataByName("unknown4", 0x01000000); + EQ2Packet* pack = p->serialize(); + //DumpPacket(pack); + QueuePacket(pack); + safe_delete(p); + } + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + +} + +void Client::DisplayMailMessage(int32 mail_id) { + Mail* mail = player->GetMail(mail_id); + if (mail) { + int32 kiosk_id = player->GetIDWithPlayerSpawn(GetMailTransaction()); + if (kiosk_id > 0) { + PacketStruct* update = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + if (update) { + update->setDataByName("action", 0x03); + update->setDataByName("packettype", GetItemPacketType(GetVersion())); + update->setDataByName("packetsubtype", 0xFF); + QueuePacket(update->serialize()); + safe_delete(update); + } + if(!mail->already_read) { + mail->already_read = true; + SendMailList(); + } + PacketStruct* packet = configReader.getStruct("WS_MailGetMessage", GetVersion()); + if (packet) { + packet->setDataByName("kiosk_id", kiosk_id); + packet->setDataByName("mail_id", mail->mail_id); + packet->setDataByName("player_to_id", mail->player_to_id); + packet->setDataByName("player_from", mail->player_from.c_str()); + packet->setDataByName("subject", mail->subject.c_str()); + packet->setDataByName("mail_body", mail->mail_body.c_str()); + packet->setDataByName("unknown1", 1); + packet->setDataByName("unknown2", 0); + packet->setDataByName("lock_report_button", 1); + packet->setDataByName("unknown3", 0xFFFFFFFF); + packet->setDataByName("unknown3a", 0xFFFFFFFF); + packet->setDataByName("coin_copper", mail->coin_copper); + packet->setDataByName("coin_silver", mail->coin_silver); + packet->setDataByName("coin_gold", mail->coin_gold); + packet->setDataByName("coin_plat", mail->coin_plat); + if(mail->stack || mail->char_item_id) + { + Item* item = master_item_list.GetItem(mail->char_item_id); + item->stack_count = mail->stack > 1 ? mail->stack : 0; + if (version < 860) + packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemByName("item", item, player, 0, 0); + else + packet->setItemByName("item", item, player, 0, 2); + } + else + { + packet->setDataByName("end_tag2", GetItemPacketType(GetVersion())); + packet->setDataByName("end_tag3", 0xFF); + } + mail->save_needed = true; + EQ2Packet* pack = packet->serialize(); + QueuePacket(pack); + safe_delete(packet); + // trying to update this causes the window not to open + //SendMailList(); + } + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + } + +} + +/* This is called when the client sends a mail message. This determines whether or not the mail can be sent and must send the reply + packet back to the client before the mail actually sent. */ +void Client::HandleSentMail(EQApplicationPacket* app) { + PacketStruct* packet = configReader.getStruct("WS_MailSendMessage", GetVersion()); + if (packet) { + if(packet->LoadPacketData(app->pBuffer, app->size)) { + string player_to = packet->getType_EQ2_16BitString_ByName("player_to").data; + PacketStruct* reply_packet = configReader.getStruct("WS_MailSendMessageReply", GetVersion()); + vector* ids = 0; + MMailWindowMutex.lock(); + if (reply_packet) { + int8 reply_type = MAIL_SEND_RESULT_UNKNOWN_ERROR; + if (player_to.length() == 0) + reply_type = MAIL_SEND_RESULT_EMPTY_TO_LIST; + else if (player_to.compare(string(GetPlayer()->GetName())) == 0) + reply_type = MAIL_SEND_RESULT_CANNOT_SEND_TO_SELF; + else if (GetAdminStatus() == 0 && !player->RemoveCoins(10)) + reply_type = MAIL_SEND_RESULT_NOT_ENOUGH_COIN; + else { + if (GetAdminStatus() > 200 && player_to.compare("") == 0) { + if (mail_window.char_item_id == 0 && (mail_window.coin_copper + mail_window.coin_silver + mail_window.coin_gold + mail_window.coin_plat) == 0) + ids = database.GetAllPlayerIDs(); + else + SimpleMessage(CHANNEL_NARRATIVE, "You may not mail gifts to multiple players."); + } + else { + ids = new vector; + ids->push_back(database.GetCharacterID(player_to.c_str())); + } + if (ids) { + for (int32 i = 0; i < ids->size(); i++) { + int32 player_to_id = ids->at(i); + if (player_to_id > 0) { + reply_type = MAIL_SEND_RESULT_SUCCESS; + Mail* mail = new Mail; + mail->mail_id = 0; + mail->player_to_id = player_to_id; + mail->player_from = string(GetPlayer()->GetName()); + mail->subject = packet->getType_EQ2_16BitString_ByName("subject").data; + mail->mail_body = packet->getType_EQ2_16BitString_ByName("mail_body").data; + mail->already_read = 0; + mail->mail_type = MAIL_TYPE_REGULAR; + mail->coin_copper = mail_window.coin_copper; + mail->coin_silver = mail_window.coin_silver; + mail->coin_gold = mail_window.coin_gold; + mail->coin_plat = mail_window.coin_plat; + mail->char_item_id = mail_window.char_item_id; + mail->stack = mail_window.stack; + + // GM's send mail for free! + if (GetAdminStatus() > 0) + { + mail->postage_cost = 0; + mail->attachment_cost = 0; + } + else + { + mail->postage_cost = 10; + mail->attachment_cost = 50; + } + mail->time_sent = Timer::GetUnixTimeStamp(); + mail->expire_time = mail->time_sent + 2592000; //30 days in seconds + + mail->save_needed = false; + database.SavePlayerMail(mail); + Client* to_client = zone_list.GetClientByCharID(player_to_id); + if (to_client) { + to_client->GetPlayer()->AddMail(mail); + to_client->SimpleMessage(CHANNEL_NARRATIVE, "You have unread mail in your mailbox."); + string popup_text = "You have unread mail!"; + to_client->SendPopupMessage(10, popup_text.c_str(), "", 3, 0xFF, 0xFF, 0xFF); + } + else { + // don't need the pointer the client doesn't exist currently + safe_delete(mail); + } + ResetSendMail(false, false); + } + else + reply_type = MAIL_SEND_RESULT_UNKNOWN_PLAYER; + } + } + } + string players_to = ""; + if (ids) { + for (int32 i = 0; i < ids->size(); i++) { + if (ids->at(i) != 0) + players_to.append(database.GetCharacterName(ids->at(i))); + if (i < (ids->size() - 1)) + players_to.append(", "); + } + } + reply_packet->setDataByName("player_to", players_to.c_str()); + reply_packet->setDataByName("reply_type", reply_type); + QueuePacket(reply_packet->serialize()); + safe_delete(reply_packet); + safe_delete(ids); + } + MMailWindowMutex.unlock(); + } + safe_delete(packet); + } + +} + +void Client::DeleteMail(int32 mail_id, bool from_database) { + + player->DeleteMail(mail_id, from_database); + +} +bool Client::AddMailItem(Item* item) +{ + if(item && (item->CheckFlag(LORE) || item->CheckFlag(STACK_LORE))) { + Message(CHANNEL_COLOR_CHAT_RELATIONSHIP, "Lore items cannot be mailed."); + return false; + } + + bool ret = false; + if (GetMailTransaction()) { + MMailWindowMutex.lock(); + if(mail_window.char_item_id == 0) + { + mail_window.item = item; + mail_window.char_item_id = item->details.item_id; + mail_window.stack = item->details.count; + ret = true; + PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + packet->setDataByName("coin_copper", mail_window.coin_copper); + packet->setDataByName("coin_silver", mail_window.coin_silver); + packet->setDataByName("coin_gold", mail_window.coin_gold); + packet->setDataByName("coin_plat", mail_window.coin_plat); + + if(item) + { + packet->setDataByName("stack", mail_window.stack); + item->stack_count = mail_window.stack; + if (version < 860) + packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemByName("item", item, player, 0, 0); + else + packet->setItemByName("item", item, player, 0, 2); + } + else + { + packet->setDataByName("end_tag2", GetItemPacketType(GetVersion())); + packet->setDataByName("end_tag3", 0xFF); + } + QueuePacket(packet->serialize()); + } + MMailWindowMutex.unlock(); + } + return ret; +} +bool Client::AddMailCoin(int32 copper, int32 silver, int32 gold, int32 plat) { + + bool ret = false; + if (GetMailTransaction()) { + MMailWindowMutex.lock(); + PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + if (packet) { + if (copper > 0) { + if (player->RemoveCoins(copper)) { + mail_window.coin_copper += copper; + Message(CHANNEL_NARRATIVE, "You add %u copper to the mail window.", copper); + ret = true; + } + } + else if (silver > 0) { + if (player->RemoveCoins(silver * 100)) { + mail_window.coin_silver += silver; + Message(CHANNEL_NARRATIVE, "You add %u silver to the mail window.", silver); + ret = true; + } + } + else if (gold > 0) { + if (player->RemoveCoins(gold * 10000)) { + mail_window.coin_gold += gold; + Message(CHANNEL_NARRATIVE, "You add %u gold to the mail window.", gold); + ret = true; + } + } + else if (plat > 0) { + if (player->RemoveCoins(plat * 1000000)) { + mail_window.coin_plat += plat; + Message(CHANNEL_NARRATIVE, "You add %u platinum to the mail window.", plat); + ret = true; + } + } + if (ret) { + packet->setDataByName("coin_copper", mail_window.coin_copper); + packet->setDataByName("coin_silver", mail_window.coin_silver); + packet->setDataByName("coin_gold", mail_window.coin_gold); + packet->setDataByName("coin_plat", mail_window.coin_plat); + Item* item = master_item_list.GetItem(mail_window.char_item_id); + if(item) + { + packet->setDataByName("stack", mail_window.stack); + item->stack_count = mail_window.stack; + if (version < 860) + packet->setItemByName("item", item, player, 0, version <= 373 ? -2 : -1); + else if (version < 1193) + packet->setItemByName("item", item, player, 0, 0); + else + packet->setItemByName("item", item, player, 0, 2); + } + else + { + packet->setDataByName("end_tag2", GetItemPacketType(GetVersion())); + packet->setDataByName("end_tag3", 0xFF); + } + //packet->PrintPacket(); + QueuePacket(packet->serialize()); + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You don't have that much money."); + safe_delete(packet); + } + MMailWindowMutex.unlock(); + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + + return ret; +} + +bool Client::RemoveMailCoin(int32 copper, int32 silver, int32 gold, int32 plat) { + bool ret = false; + if (GetMailTransaction()) { + MMailWindowMutex.lock(); + PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + if (packet) { + if (copper > 0) { + player->AddCoins(copper); + mail_window.coin_copper -= copper; + Message(CHANNEL_NARRATIVE, "You remove %u copper from the mail window.", copper); + ret = true; + } + else if (silver > 0) { + player->AddCoins(silver * 100); + mail_window.coin_silver -= silver; + Message(CHANNEL_NARRATIVE, "You remove %u silver from the mail window.", silver); + ret = true; + } + else if (gold > 0) { + player->AddCoins(gold * 10000); + mail_window.coin_gold -= gold; + Message(CHANNEL_NARRATIVE, "You remove %u gold from the mail window.", gold); + ret = true; + } + else if (plat > 0) { + player->AddCoins(plat * 1000000); + mail_window.coin_plat -= plat; + Message(CHANNEL_NARRATIVE, "You remove %u platinum from the mail window.", plat); + ret = true; + } + if (ret) { + packet->setDataByName("coin_copper", mail_window.coin_copper); + packet->setDataByName("coin_silver", mail_window.coin_silver); + packet->setDataByName("coin_gold", mail_window.coin_gold); + packet->setDataByName("coin_plat", mail_window.coin_plat); + packet->setDataByName("stack", 0); + packet->setDataByName("packettype", 0x2BFE); + packet->setDataByName("packetsubtype", 0xFF); + packet->setDataByName("unknown2", 0); + QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + MMailWindowMutex.unlock(); + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + + return ret; +} + +void Client::TakeMailAttachments(int32 mail_id) { + if (GetMailTransaction()) { + Mail* mail = player->GetMail(mail_id); + if (mail) { + int64 amount = 0; + if (mail->coin_copper > 0) { + amount += mail->coin_copper; + mail->coin_copper = 0; + } + if (mail->coin_silver > 0) { + amount += mail->coin_silver * 100; + mail->coin_silver = 0; + } + if (mail->coin_gold > 0) { + amount += mail->coin_gold * 10000; + mail->coin_gold = 0; + } + if (mail->coin_plat > 0) { + amount += mail->coin_plat * 1000000; + mail->coin_plat = 0; + } + if (mail->char_item_id > 0) { + AddItem(mail->char_item_id, mail->stack); + mail->char_item_id = 0; + mail->stack = 0; + } + /* Can't find the right packet to send to update the player's mail. This packet below updates the mail the player is sending, not + the mail the player is getting attachments from. There is an opcode OP_MailRemoveAttachFromMailMsg with opcode 328 but i can't + find it in any packet logs.*/ + /*PacketStruct* packet = configReader.getStruct("WS_UpdatePlayerMail", GetVersion()); + if (packet) { + packet->setDataByName("unknown", 0x03); + packet->setDataByName("coin_copper", mail->coin_copper); + packet->setDataByName("coin_silver", mail->coin_silver); + packet->setDataByName("coin_gold", mail->coin_gold); + packet->setDataByName("coin_plat", mail->coin_plat); + packet->setDataByName("stack", 0); + packet->setDataByName("packettype", 0x2BFE); + packet->setDataByName("packetsubtype", 0xFF); + packet->setDataByName("unknown2", 0); + packet->setDataByName("unknown3", 0x00000001);//0x00000016 + DumpPacket(packet->serialize()); + QueuePacket(packet->serialize()); + safe_delete(packet); + }*/ + database.SavePlayerMail(mail); + if (amount > 0) + player->AddCoins(amount); + SendMailList(); + } + } + else + SimpleMessage(CHANNEL_NARRATIVE, "You are currently not in a mail transaction."); + +} + +void Client::ResetSendMail(bool cancel, bool needslock) { + if(cancel && mail_transaction) + SimpleMessage(CHANNEL_NARRATIVE, "You cancel sending a letter."); + if(needslock) + MMailWindowMutex.lock(); + if(cancel) + player->AddCoins(mail_window.coin_copper + (mail_window.coin_silver * 100) + (mail_window.coin_gold * 10000) + (mail_window.coin_plat * 1000000)); + if(!cancel) + mail_transaction = 0; + mail_window.coin_copper = 0; + mail_window.coin_silver = 0; + mail_window.coin_gold = 0; + mail_window.coin_plat = 0; + mail_window.char_item_id = 0; + mail_window.stack = 0; + + if(mail_window.item){ + if(cancel) + AddItem(mail_window.item); + else + safe_delete(mail_window.item); + } + mail_window.item = nullptr; + if(needslock) + MMailWindowMutex.unlock(); +} + +bool Client::GateAllowed() { + ZoneServer* zone = GetCurrentZone(); + if (zone){ + bool cangate = zone->GetCanGate(); + return cangate; + } + + return false; +} + +bool Client::BindAllowed() { + ZoneServer* zone = GetCurrentZone(); + if (zone){ + bool canbind = zone->GetCanBind(); + return canbind; + } + return false; +} + +bool Client::Bind() { + int canbind = BindAllowed(); + + if(canbind == 0) { + Message(CHANNEL_MERCHANT, "You cannot bind at this location."); + return false; + } + player->GetPlayerInfo()->SetBindZone(GetCurrentZone()->GetZoneID()); + player->GetPlayerInfo()->SetBindX(player->GetX()); + player->GetPlayerInfo()->SetBindY(player->GetY()); + player->GetPlayerInfo()->SetBindZ(player->GetZ()); + player->GetPlayerInfo()->SetBindHeading(player->GetHeading()); + Message(CHANNEL_MERCHANT, "Your spirit has been bound to this location."); + return true; +} + +bool Client::Gate(bool is_spell) { + if (player->GetPlayerInfo()->GetBindZoneID() == 0) { + SimpleMessage(CHANNEL_MERCHANT, "You can not cast recall spells. You have no bind location set."); + return false; + } + + ZoneServer* zone = zone_list.Get(player->GetPlayerInfo()->GetBindZoneID()); + if (zone) { + int cangate = GateAllowed(); + if(cangate == 0) { + SimpleMessage(CHANNEL_MERCHANT, "You can not cast recall spells in this zone."); + return false; + } + player->SetX(player->GetPlayerInfo()->GetBindZoneX()); + player->SetY(player->GetPlayerInfo()->GetBindZoneY()); + player->SetZ(player->GetPlayerInfo()->GetBindZoneZ()); + player->SetHeading(player->GetPlayerInfo()->GetBindZoneHeading()); + Zone(zone, false, is_spell); + + return true; + } + + return false; +} + +void Client::ProcessTeleport(Spawn* spawn, vector* destinations, int32 transport_id, bool is_spell) { + if (!destinations || !spawn) { + return; + } + + bool has_map = false; + if (transport_id > 0) + has_map = GetCurrentZone()->TransportHasMap(transport_id); + + transport_spawn = spawn; + vector transport_list; + vector::iterator itr; + TransportDestination* destination = 0; + for (itr = destinations->begin(); itr != destinations->end(); itr++) { + destination = *itr; + if (has_map || (destination->type == TRANSPORT_TYPE_ZONE && ((destination->destination_zone_id != GetCurrentZone()->GetZoneID()) || GetPlayer()->GetDistance(destination->destination_x, destination->destination_y, destination->destination_z) > 100))) + transport_list.push_back(destination); + } + if (transport_list.size() == 0 && destination) { + if (destination->destination_zone_id == 0 || destination->destination_zone_id == GetCurrentZone()->GetZoneID()) { + + if (destination->type == TRANSPORT_TYPE_FLIGHT) + SendFlightAutoMount(destination->flight_path_id, destination->mount_id, destination->mount_red_color, destination->mount_green_color, destination->mount_blue_color); + else + { + EQ2Packet* app = GetPlayer()->Move(destination->destination_x, destination->destination_y, destination->destination_z, GetVersion()); + if (app) + QueuePacket(app); + } + } + else { + ZoneServer* new_zone = zone_list.Get(destination->destination_zone_id); + + // determine if this is an instanced zone that already exists + ZoneServer* instance_zone = GetPlayer()->GetGroupMemberInZone(destination->destination_zone_id); + + if (instance_zone || new_zone) { + GetPlayer()->SetX(destination->destination_x); + GetPlayer()->SetY(destination->destination_y); + GetPlayer()->SetZ(destination->destination_z); + GetPlayer()->SetHeading(destination->destination_heading); + if (instance_zone) + Zone(instance_zone->GetInstanceID(), false, true, is_spell); + else + Zone(new_zone, false, is_spell); + } + } + if (destination->message.length() > 0) + SimpleMessage(CHANNEL_COLOR_YELLOW, destination->message.c_str()); + } + else if (transport_list.size() > 0) { + if (!spawn->IsSoundsDisabled()) + PlaySound("mariner_bell"); + + PacketStruct* packet = configReader.getStruct("WS_TeleportList", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn)); + + // Put all the destinations the player can go in a new vector + vector destinations; + for (int32 i = 0; i < transport_list.size(); i++) { + destination = transport_list.at(i); + + // Check min level + if (destination->min_level > 0 && GetPlayer()->GetLevel() < destination->min_level) + continue; + // Check max level + if (destination->max_level > 0 && GetPlayer()->GetLevel() > destination->max_level) + continue; + // Check quest complete + if (destination->req_quest_complete > 0 && GetPlayer()->HasQuestBeenCompleted(destination->req_quest_complete) == 0) + continue; + // Check req quest and step + if (destination->req_quest > 0 && destination->req_quest_step > 0 && GetPlayer()->GetQuestStep(destination->req_quest) != destination->req_quest_step) + continue; + // If we have a map and our current location is the same as the detination and player is within 20 units from the transport set the "current" elements but don't addt to the destination list + if (has_map && (destination->destination_zone_id == GetCurrentZone()->GetZoneID() && GetPlayer()->GetDistance(destination->destination_x, destination->destination_y, destination->destination_z) < 20)) { + packet->setDataByName("current_zone", destination->display_name.c_str()); + packet->setDataByName("current_map_x", destination->map_x); + packet->setDataByName("current_map_y", destination->map_y); + } + else { + destinations.push_back(destination); + } + } + + // Use the new vector to create the packet + destination = 0; + packet->setArrayLengthByName("num_destinations", destinations.size()); + for (int32 i = 0; i < destinations.size(); i++) { + destination = destinations.at(i); + + packet->setArrayDataByName("unique_id", destination->unique_id, i); + packet->setArrayDataByName("display_name", destination->display_name.c_str(), i); + packet->setArrayDataByName("zone_name", destination->display_name.c_str(), i); + packet->setArrayDataByName("zone_file_name", destination->display_name.c_str(), i); + packet->setArrayDataByName("cost", destination->cost, i); + + if (has_map) { + packet->setArrayDataByName("map_x", destination->map_x, i); + packet->setArrayDataByName("map_y", destination->map_y, i); + } + + } + + if (has_map) + packet->setDataByName("map_name", GetCurrentZone()->GetTransportMap(transport_id).c_str()); + EQ2Packet* app = packet->serialize(); + //DumpPacket(app); + if (destinations.size() > 0) + QueuePacket(app); + safe_delete(packet); + } + } + +} + +void Client::ProcessTeleportLocation(EQApplicationPacket* app) { + PacketStruct* packet = configReader.getStruct("WS_TeleportDestination", GetVersion()); + if (packet) { + if (packet->LoadPacketData(app->pBuffer, app->size)) { + Spawn* spawn = GetPlayer()->GetSpawnWithPlayerID(packet->getType_int32_ByName("spawn_id")); + int32 unique_id = packet->getType_int32_ByName("unique_id"); + string zone_name = packet->getType_EQ2_16BitString_ByName("zone_name").data; + int32 cost = packet->getType_int32_ByName("cost"); + vector destinations; + TransportDestination* destination = 0; + if (this->GetTemporaryTransportID() || (spawn && spawn == transport_spawn && spawn->GetTransporterID())) + GetCurrentZone()->GetTransporters(&destinations, this, this->GetTemporaryTransportID() ? this->GetTemporaryTransportID() : spawn->GetTransporterID()); + vector::iterator itr; + for (itr = destinations.begin(); itr != destinations.end(); itr++) { + if ((*itr)->unique_id == unique_id && (*itr)->display_name == zone_name && (*itr)->cost == cost) { + destination = *itr; + break; + } + } + + SetTemporaryTransportID(0); + + if (!destination) + SimpleMessage(CHANNEL_COLOR_RED, "Error processing transport."); + else { + if (cost == 0 || player->RemoveCoins(cost)) { + if (destination->destination_zone_id == 0 || destination->destination_zone_id == GetCurrentZone()->GetZoneID()) { + + if (destination->type == TRANSPORT_TYPE_FLIGHT) + SendFlightAutoMount(destination->flight_path_id, destination->mount_id, destination->mount_red_color, destination->mount_green_color, destination->mount_blue_color); + else + { + EQ2Packet* outapp = GetPlayer()->Move(destination->destination_x, destination->destination_y, destination->destination_z, GetVersion()); + if (outapp) + QueuePacket(outapp); + } + } + else { + GetPlayer()->SetX(destination->destination_x); + GetPlayer()->SetY(destination->destination_y); + GetPlayer()->SetZ(destination->destination_z); + GetPlayer()->SetHeading(destination->destination_heading); + + // Test if where we're going is an Instanced zone + if (!TryZoneInstance(destination->destination_zone_id, false)) { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Attempting to zone normally"); + ZoneServer* new_zone = zone_list.Get(destination->destination_zone_id); + Zone(new_zone, false); + } + } + if (destination->message.length() > 0) + SimpleMessage(CHANNEL_COLOR_YELLOW, destination->message.c_str()); + } + else + SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough money to use this transport."); + } + } + safe_delete(packet); + } + +} + +void Client::SendNewSpells(int8 class_id) { + if (class_id > 0) { + vector* spells = master_spell_list.GetSpellListByAdventureClass(class_id, player->GetLevel(), 1); + AddSendNewSpells(spells); + safe_delete(spells); + } + +} + +void Client::SendNewTSSpells(int8 class_id) { + if (class_id > 0) { + vector* spells = master_spell_list.GetSpellListByTradeskillClass(class_id, player->GetLevel(), 1); + AddSendNewSpells(spells); + safe_delete(spells); + } +} + +void Client::AddSendNewSpells(vector* spells) { + Spell* spell = 0; + bool send_updates = false; + vector::iterator itr; + for (itr = spells->begin(); itr != spells->end(); itr++) { + spell = *itr; + if (spell && !player->HasSpell(spell->GetSpellID(), spell->GetSpellTier(), true) && spell->GetSpellData()->lua_script.length() > 0) { + send_updates = true; + SendSpellUpdate(spell); + player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), player->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); + player->UnlockSpell(spell); + } + } + if (send_updates) { + EQ2Packet* outapp = player->GetSpellBookUpdatePacket(GetVersion()); + if (outapp) + QueuePacket(outapp); + } +} + +void Client::SetItemSearch(vector* items) { + if (items) { + safe_delete(search_items); + search_items = items; + } + +} + +vector* Client::GetSearchItems() { + return search_items; +} + +void Client::SearchStore(int32 page) { + if (search_items) { + PacketStruct* packet = configReader.getStruct("WS_BrokerItems", GetVersion()); + if (packet) { + int32 x = page * 8; + if (search_items->size() > 8) { + if ((search_items->size() - x) > 8) + packet->setArrayLengthByName("num_items", 8); + else + packet->setArrayLengthByName("num_items", search_items->size() - x); + } + else + packet->setArrayLengthByName("num_items", search_items->size()); + if (search_items->size() > 0) { + packet->setArrayLengthByName("num_sellers", 1); + packet->setArrayDataByName("seller_seller_id", 1); + packet->setDataByName("per_page", 8); + packet->setDataByName("num_pages", search_items->size() / 8 + 1); + packet->setDataByName("page", page); + Item* item = 0; + int32 limit = search_items->size() > 8 ? 8 : search_items->size(); + for (int32 i = 0; i < limit; i++, x++) { + if (x >= search_items->size()) + break; + + item = search_items->at(x); + std::string teststr("test "); + teststr.append(std::to_string(i)); + packet->setArrayDataByName("string_one", teststr.c_str(), i); + packet->setArrayDataByName("string_two", "testtwo", i); + packet->setArrayDataByName("seller_name", "EQ2EMuDev", i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + packet->setArrayDataByName("item_id2", item->details.item_id, i); + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + //packet->setArrayDataByName("unknown2b", i, i); + packet->setArrayDataByName("item_seller_id", 1, i); + if (item->stack_count == 0) + packet->setArrayDataByName("quantity", 1, i); + else + packet->setArrayDataByName("quantity", item->stack_count, i); + packet->setArrayDataByName("stack_size", item->stack_count, i); + + packet->setArrayDataByName("sell_price", item->sell_price, i); + + + std::string tmpStr(""); + tmpStr.append(item->name.c_str()); + tmpStr.append(" ("); + tmpStr.append(std::to_string(item->details.item_id)); + tmpStr.append(")"); + + packet->setArrayDataByName("item_name", tmpStr.c_str(), i); + packet->setArrayDataByName("req_level", item->generic_info.adventure_default_level, i); + //QueuePacket(item->serialize(GetVersion(), false, GetPlayer())); + } + } + EQ2Packet* outapp = packet->serialize(); + DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } + } + +} + +void Client::SetReadyForUpdates() { + if (!ready_for_updates) { + database.loadCharacterProperties(this); + SendDefaultGroupOptions(); + } + + ready_for_updates = true; + + if(GetVersion() <= 561) { + SendRecipeList(); + } +} + +void Client::SetReadyForSpawns(bool val) { + ready_for_spawns = val; + if (GetPlayer()->GetActivityStatus() > 0) { + GetPlayer()->SetActivityStatus(0); + if (GetPlayer()->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupMessage(GetPlayer()->GetGroupMemberInfo()->group_id, "%s has returned from Linkdead.", GetPlayer()->GetName()); + } + } + GetPlayer()->SetActiveReward(false); + zone_list.CheckFriendZoned(this); + +} + +void Client::SendChatRelationship(int8 type, const char* name) { + if (!name) { + return; + } + PacketStruct* packet = configReader.getStruct("WS_ChatRelationship", GetVersion()); + if (packet) { + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("type", type); + packet->setArrayLengthByName("num_names", 1); + packet->setArrayDataByName("name", name); + if (type == 0) { + Client* client = zone_list.GetClientByCharName(name); + if (client) { + packet->setArrayDataByName("location", client->GetCurrentZone()->GetZoneName()); + packet->setArrayDataByName("class_name", classes.GetClassName(client->GetPlayer()->GetAdventureClass())); + } + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::SendFriendList() { + PacketStruct* packet = configReader.getStruct("WS_ChatRelationship", GetVersion()); + if (packet) { + packet->setDataByName("account_id", GetAccountID()); + map::iterator itr; + map* friends = player->GetFriends(); + if (friends && friends->size() > 0) { + Client* client = 0; + vector names; + for (itr = friends->begin(); itr != friends->end(); itr++) { + if (itr->second == 2) + continue; + names.push_back(itr->first); + } + packet->setArrayLengthByName("num_names", names.size()); + for (int32 i = 0; i < names.size(); i++) { + client = zone_list.GetClientByCharName(names[i]); + packet->setArrayDataByName("name", names[i].c_str(), i); + if (client) { + packet->setArrayDataByName("location", client->GetCurrentZone()->GetZoneName(), i); + packet->setArrayDataByName("class_name", classes.GetClassName(client->GetPlayer()->GetAdventureClass()), i); + } + } + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::SendIgnoreList() { + PacketStruct* packet = configReader.getStruct("WS_ChatRelationship", GetVersion()); + if (packet) { + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("type", 2); + map::iterator itr; + map* ignored = player->GetIgnoredPlayers(); + if (ignored && ignored->size() > 0) { + vector names; + for (itr = ignored->begin(); itr != ignored->end(); itr++) { + if (itr->second == 2) + continue; + names.push_back(itr->first); + } + packet->setArrayLengthByName("num_names", names.size()); + for (int32 i = 0; i < names.size(); i++) + packet->setArrayDataByName("name", names[i].c_str(), i); + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::AddWaypoint(string name, int8 type) { + waypoint_id++; + WaypointInfo info; + info.id = waypoint_id; + info.type = type; + waypoints[name] = info; +} + +void Client::SendWaypoints() { + PacketStruct* packet = configReader.getStruct("WS_WaypointUpdate", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_updates", waypoints.size()); + map::iterator itr; + int16 i = 0; + for (itr = waypoints.begin(); itr != waypoints.end(); itr++) { + packet->setArrayDataByName("waypoint_name", itr->first.c_str(), i); + packet->setArrayDataByName("waypoint_category", itr->second.type, i); + packet->setArrayDataByName("spawn_id", itr->second.id, i); + i++; + } + packet->setDataByName("unknown", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void Client::SelectWaypoint(int32 id) { + string found_name = ""; + map::iterator itr; + for (itr = waypoints.begin(); itr != waypoints.end(); itr++) { + if (itr->second.id == id) { + found_name = itr->first; + break; + } + } + if (found_name.length() > 0) { + Spawn* spawn = current_zone->FindSpawn(player, found_name.c_str()); + ShowPathToTarget(spawn); + } +} + +void Client::AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id) { + if (waypoint_name) { + PacketStruct* packet = configReader.getStruct("WS_WaypointUpdate", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_updates", 1); + packet->setArrayDataByName("waypoint_name", waypoint_name, 0); + packet->setArrayDataByName("waypoint_category", waypoint_category, 0); + packet->setArrayDataByName("spawn_id", spawn_id, 0); + packet->setArrayDataByName("waypoint_category2", waypoint_category, 0); + packet->setArrayDataByName("spawn_id2", spawn_id, 0); + packet->setDataByName("unknown", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void Client::ClearWaypoint() { + PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion()); + if (packet) { + QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +bool Client::ShowPathToTarget(float x, float y, float z, float y_offset) { + if (current_zone->pathing) { + if (GetPlayer()->GetMap()) { + if (x < GetPlayer()->GetMap()->GetMinX() || x > GetPlayer()->GetMap()->GetMaxX()) + return false; + if (z < GetPlayer()->GetMap()->GetMinZ() || z > GetPlayer()->GetMap()->GetMaxZ()) + return false; + auto loc = glm::vec3(x, z, y); + float new_z = GetPlayer()->FindBestZ(loc, nullptr); + if (new_z != BEST_Z_INVALID) //this is actually y + y = new_z; + } + bool partial = false; + bool stuck = false; + PathfinderOptions opts; + opts.smooth_path = true; + opts.step_size = 100.0f;//RuleR(Pathing, NavmeshStepSize); + opts.offset = y_offset + 1.0f; + opts.flags = PathingNotDisabled ^ PathingZoneLine; + PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion()); + if (packet) { + auto path = current_zone->pathing->FindPath(glm::vec3(player->GetX(), player->GetZ(), player->GetY()), glm::vec3(x, z, y), partial, stuck, opts); + packet->setArrayLengthByName("num_points", path.size()); + int i = 0; + for (auto& node : path) + { + packet->setArrayDataByName("x", node.pos.x, i); + packet->setArrayDataByName("y", node.pos.z, i); + packet->setArrayDataByName("z", node.pos.y, i); + packet->setDataByName("waypoint_x", x); + packet->setDataByName("waypoint_y", y); + packet->setDataByName("waypoint_z", z); + i++; + } + if (i > 0) + QueuePacket(packet->serialize()); + safe_delete(packet); + return (i > 0); + } + } + return false; +} + +bool Client::ShowPathToTarget(Spawn* spawn) { + if (spawn) { + return ShowPathToTarget(spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetYOffset()); + } + return false; +} + +void Client::BeginWaypoint(const char* waypoint_name, float x, float y, float z) { + if (waypoint_name) { + PacketStruct* packet = configReader.getStruct("WS_GlowPath", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_points", 1); + packet->setArrayDataByName("x", x, 0); + packet->setArrayDataByName("y", y, 0); + packet->setArrayDataByName("z", z, 0); + packet->setDataByName("waypoint_x", x); + packet->setDataByName("waypoint_y", y); + packet->setDataByName("waypoint_z", z); + packet->setDataByName("waypoint_name", waypoint_name); + packet->setDataByName("unknown", 0); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +void Client::InspectPlayer(Player* player_to_inspect) { + int source_pvp_alignment = GetPlayer()->GetPVPAlignment(); + int target_pvp_alignment = player_to_inspect->GetPVPAlignment(); + bool pvp_allowed = rule_manager.GetGlobalRule(R_PVP, AllowPVP)->GetBool(); + + if(pvp_allowed == true){ + if(source_pvp_alignment != target_pvp_alignment){ + Message(CHANNEL_COLOR_RED, "You can not inspect players of different alignments."); + return; + } + } + + if (player_to_inspect && player_to_inspect->GetClient()) { + PacketStruct* packet = configReader.getStruct("WS_InspectPlayer", GetVersion()); + if (packet) { + packet->setDataByName("unknown", 0); + packet->setSmallStringByName("name", player_to_inspect->GetName()); + packet->setDataByName("race", player_to_inspect->GetRace()); + packet->setDataByName("gender", player_to_inspect->GetGender()); + packet->setDataByName("adventure_level", player_to_inspect->GetLevel()); + + int16 effective_level = player_to_inspect->GetInfoStruct()->get_effective_level() != 0 ? player_to_inspect->GetInfoStruct()->get_effective_level() : player_to_inspect->GetLevel(); + packet->setDataByName("adventure_level_effective", effective_level); + packet->setDataByName("adventure_class", player_to_inspect->GetAdventureClass()); + packet->setDataByName("tradeskill_level", player_to_inspect->GetTSLevel()); + packet->setDataByName("tradeskill_class", player_to_inspect->GetTradeskillClass()); + packet->setDataByName("health", player_to_inspect->GetHP()); + packet->setDataByName("health_max", player_to_inspect->GetTotalHP()); + packet->setDataByName("health_base", player_to_inspect->GetTotalHPBase()); + packet->setDataByName("power", player_to_inspect->GetPower()); + packet->setDataByName("power_max", player_to_inspect->GetTotalPower()); + packet->setDataByName("power_base", player_to_inspect->GetTotalPowerBase()); + packet->setDataByName("mitigation", player_to_inspect->GetInfoStruct()->get_cur_mitigation()); + packet->setDataByName("unknown1", 0); + packet->setDataByName("avoidance", player_to_inspect->GetInfoStruct()->get_avoidance_display()*10.0f); + packet->setDataByName("unknown2", 0); + packet->setDataByName("mitigation_percentage", 0); + packet->setDataByName("strength", player_to_inspect->GetStr()); + packet->setDataByName("strength_base", player_to_inspect->GetStrBase()); + packet->setDataByName("stamina", player_to_inspect->GetSta()); + packet->setDataByName("stamina_base", player_to_inspect->GetStaBase()); + packet->setDataByName("agility", player_to_inspect->GetAgi()); + packet->setDataByName("agility_base", player_to_inspect->GetAgiBase()); + packet->setDataByName("wisdom", player_to_inspect->GetWis()); + packet->setDataByName("wisdom_base", player_to_inspect->GetWisBase()); + packet->setDataByName("intelligence", player_to_inspect->GetInt()); + packet->setDataByName("intelligence_base", player_to_inspect->GetIntBase()); + packet->setDataByName("unknown4", 0); + packet->setDataByName("unknown5", 0); + packet->setDataByName("unknown6", 0); + packet->setDataByName("unknown7", 0); + packet->setDataByName("unknown8", 0); + packet->setDataByName("unknown9", 0); + packet->setDataByName("unknown10", 0); + packet->setDataByName("unknown11", 0); + packet->setDataByName("unknown12", 0); + packet->setDataByName("heat_resist", player_to_inspect->GetHeatResistance()); + packet->setDataByName("heat_resist_base", player_to_inspect->GetHeatResistanceBase()); + packet->setDataByName("heat_resist_percentage", 0); + packet->setDataByName("cold_resist", player_to_inspect->GetColdResistance()); + packet->setDataByName("cold_resist_base", player_to_inspect->GetColdResistanceBase()); + packet->setDataByName("cold_resist_percentage", 0); + packet->setDataByName("magic_resist", player_to_inspect->GetMagicResistance()); + packet->setDataByName("magic_resist_base", player_to_inspect->GetMagicResistanceBase()); + packet->setDataByName("magic_resist_percentage", 0); + packet->setDataByName("mental_resist", player_to_inspect->GetMentalResistance()); + packet->setDataByName("mental_resist_base", player_to_inspect->GetMentalResistanceBase()); + packet->setDataByName("mental_resist_percentage", 0); + packet->setDataByName("divine_resist", player_to_inspect->GetDivineResistance()); + packet->setDataByName("divine_resist_base", player_to_inspect->GetDivineResistanceBase()); + packet->setDataByName("divine_resist_percentage", 0); + packet->setDataByName("disease_resist", player_to_inspect->GetDiseaseResistance()); + packet->setDataByName("disease_resist_base", player_to_inspect->GetDiseaseResistanceBase()); + packet->setDataByName("disease_resist_percentage", 0); + packet->setDataByName("poison_resist", player_to_inspect->GetPoisonResistance()); + packet->setDataByName("poison_resist_base", player_to_inspect->GetPoisonResistanceBase()); + packet->setDataByName("poison_resist_percentage", 0); + packet->setArrayLengthByName("num_chars", 0x01FF); + string biography = player_to_inspect->GetBiography(); + for (size_t i = 0; i < biography.length(); i++) + packet->setArrayDataByName("biography_char", biography[i], i); + + if(GetVersion() <= 373) { + for(int32 s=0;s<20;s++) { + int32 slot = s; + + char item_slot_name[64], item_slot_name_appearance[64]; + _snprintf(item_slot_name,64,"slot_%u",slot); + Item* pw = player_to_inspect->GetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, GetVersion())); + packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 7, true, true, true); + } + } + else if(GetVersion() <= 561) { + for(int32 s=0;s<22;s++) { + int32 slot = s; + + char item_slot_name[64], item_slot_name_appearance[64]; + _snprintf(item_slot_name,64,"slot_%u",slot); + Item* pw = player_to_inspect->GetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, GetVersion())); + packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 5, true, true); + } + } + else { + for(int32 s=0;sGetEquipmentList()->GetItem(GetPlayer()->ConvertSlotFromClient(s, player_to_inspect->GetClient()->GetVersion())); + packet->setItemByName(item_slot_name, pw, this->GetPlayer(), 0, 13, false, true); + if(s <= EQ2_FEET_SLOT || s == EQ2_RANGE_SLOT || s == EQ2_CLOAK_SLOT) { + pw = player_to_inspect->GetAppearanceEquipmentList()->GetItem(s); + packet->setItemByName(item_slot_name_appearance, pw, this->GetPlayer(), 0, 13, false, true); + } + else { + packet->setItemByName(item_slot_name_appearance, nullptr, this->GetPlayer(), 0, 13, false, true); + } + } + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + +} + +void Client::SetPendingGuildInvite(Guild* guild, Player* invited_by) { + pending_guild_invite.guild = guild; + pending_guild_invite.invited_by = invited_by; +} + +void Client::ShowClaimWindow() { + PacketStruct* packet = configReader.getStruct("WS_PromoFlagsDetails", GetVersion()); + if (packet) { + + int16 loaded = database.CountCharClaimItems(GetCharacterID()); + vector claim = database.LoadCharacterClaimItems(GetCharacterID()); + int32 account_age = database.GetAccountAge(GetAccountID()); + + //not sure if there is a message or not, but adding this and a return, so if we have nothing do nothing rather than display an empty window. + if (loaded == 0 || claim.empty()) { + Message(CHANNEL_COLOR_RED, "You have nothing to claim."); + return; + } + + packet->setArrayLengthByName("num_claim_items", loaded); + + int j = 0; //use this to track skipped vet items. + for (int i = 0; i < claim.size(); i++) + { + if (j == claim.size()) { + Message(CHANNEL_COLOR_RED, "You have nothing to claim."); + return; + } + + Item* item = master_item_list.GetItem(claim[i].item_id); + int16 claimed = 0; + + if (claim[i].curr_claim < claim[i].max_claim) { + claimed = claim[i].max_claim - claim[i].curr_claim; + } + else { + claimed = 0; + } + + //dont display vet rewards until they reach the age required + if (account_age < claim[i].vet_reward_time) { + j++; + continue; + } + + packet->setArrayDataByName("id", i, i); + packet->setArrayDataByName("not_yet_claimed", 1, i); + packet->setArrayDataByName("num_remaining", claim[i].curr_claim, i); + packet->setArrayDataByName("one_per_character", claim[i].one_per_char, i); + packet->setArrayDataByName("claimed_on_this_char", 0, i); + packet->setArrayDataByName("item_name", item->name.c_str(), i); + packet->setArrayDataByName("text", "If you ever see this text, let a developer know!", i); //I've not seen this! + //packet->setArrayDataByName("category", "Scott's Shit", i); //devn00b: not using so commenting out, leaving in case we ever do implement categories. + packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i); + packet->setArrayDataByName("item_id", item->details.item_id, i); + j++; + } + } + packet->setDataByName("unknown3", 1); + QueuePacket(packet->serialize()); + safe_delete(packet); +} + + +void Client::ShowGuildSearchWindow() { + PacketStruct* packet = configReader.getStruct("WS_GuildRecruiting", GetVersion()); + if (packet) { + MutexMap* guilds = guild_list.GetGuilds(); + MutexMap::iterator itr = guilds->begin(); + packet->setArrayLengthByName("num_guilds", guilds->size()); + int32 i = 0; + while (itr.Next()) { + Guild* guild = itr.second; + packet->setArrayDataByName("guild_id", guild->GetID(), i); + packet->setArrayDataByName("guild_name", guild->GetName(), i); + packet->setArrayDataByName("recruiting_short_description", guild->GetRecruitingShortDesc().c_str(), i); + packet->setArrayDataByName("descriptive_tag1", guild->GetRecruitingDescTag(0), i); + packet->setArrayDataByName("descriptive_tag2", guild->GetRecruitingDescTag(1), i); + packet->setArrayDataByName("descriptive_tag3", guild->GetRecruitingDescTag(2), i); + packet->setArrayDataByName("descriptive_tag4", guild->GetRecruitingDescTag(3), i); + packet->setArrayDataByName("playstyle", guild->GetRecruitingPlayStyle(), i); + packet->setArrayDataByName("looking_for", guild->GetRecruitingLookingForPacketValue(), i); //tradeskillers, fighters, new + packet->setArrayDataByName("unknown7", 0x02, i); + packet->setArrayDataByName("min_level", guild->GetRecruitingMinLevel(), i); + i++; + } + QueuePacket(packet->serialize()); + safe_delete(packet); + } + +} + +void Client::ShowDressingRoom(Item* item, sint32 crc) { + PacketStruct* packet; + vector* slot_data; + vector::iterator itr; + int32 slots = 0; + + assert(item); + + if (!(packet = configReader.getStruct("WS_DressingRoom", GetVersion()))) { + return; + } + + slot_data = &item->slot_data; + for (itr = slot_data->begin(); itr != slot_data->end(); itr++) { + if (version >= 1188) + slots = *itr; + else + slots += (int8)pow(2.0, *itr); + } + + packet->setDataByName("slot", slots); + packet->setDataByName("appearance_id", item->generic_info.appearance_id); + if (slots == 2) { + packet->setDataByName("rgb", item->generic_info.appearance_red, 2); + packet->setDataByName("rgb", item->generic_info.appearance_blue, 0); + } + else { + packet->setDataByName("rgb", item->generic_info.appearance_red, 0); + packet->setDataByName("rgb", item->generic_info.appearance_blue, 2); + } + packet->setDataByName("rgb", item->generic_info.appearance_green, 1); + if (slots == 4) { + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_red, 2); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_green, 1); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_blue, 0); + } + else if (slots == 7) { + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_red, 1); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_green, 0); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_blue, 2); + } + else { + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_red, 0); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_green, 1); + packet->setDataByName("highlight_rgb", item->generic_info.appearance_highlight_blue, 2); + } + packet->setDataByName("icon", item->GetIcon(GetVersion())); + packet->setDataByName("item_id", item->details.item_id); + packet->setDataByName("item_crc", crc); + packet->setDataByName("unknown2", 0xFFFFFFFF); + packet->setDataByName("unknown4", 0xFFFFFFFF); + packet->setDataByName("unknown5", 0xFF, 9); + packet->setDataByName("unknown5", 0xFF, 10); + QueuePacket(packet->serialize()); + safe_delete(packet); + +} + +void Client::SendCollectionList() { + map* collections = player->GetCollectionList()->GetCollections(); + map::iterator itr; + vector* collection_items; + Collection* collection; + struct CollectionItem* collection_item; + PacketStruct* packet = 0; + int16 i = 0, j; + + if (!(packet = configReader.getStruct("WS_CollectionUpdate", version))) + return; + + packet->setArrayLengthByName("num_collections", collections->size()); + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + collection_items = collection->GetCollectionItems(); + + packet->setArrayDataByName("unknown", 1, i); + packet->setArrayDataByName("collection_name", collection->GetName(), i); + packet->setArrayDataByName("collection_category", collection->GetCategory(), i); + packet->setArrayDataByName("completed", collection->GetCompleted(), i); + packet->setArrayDataByName("collection_id", collection->GetID(), i); + packet->setArrayDataByName("level", collection->GetLevel(), i); + packet->setArrayDataByName("ready_to_turn_in", collection->GetIsReadyToTurnIn(), i); + packet->setSubArrayLengthByName("num_items", collection_items->size(), i); + + for (j = 0; j < collection_items->size(); j++) { + collection_item = collection_items->at(j); + Item* item = master_item_list.GetItem(collection_item->item); + if (item) { + packet->setSubArrayDataByName("item_icon", item->GetIcon(GetVersion()), i, j); + if (version < 955) + packet->setSubArrayDataByName("item_name", item->name.c_str(), i, j); + else + packet->setSubArrayDataByName("item_id", item->details.item_id, i, j); + } + packet->setSubArrayDataByName("item_flag", collection_item->found, i, j); + } + i++; + } + + packet->setDataByName("new_collection_flag", 1); + + QueuePacket(packet->serialize()); + safe_delete(packet); + +} + +bool Client::SendCollectionsForItem(Item* item) { + map collections_to_send; + map* collections; + map::iterator itr; + vector* collection_items; + Collection* collection; + struct CollectionItem* collection_item; + PacketStruct* packet; + int16 i; + + assert(item); + + /* get any collections required by this item that the player already has */ + collections = player->GetCollectionList()->GetCollections(); + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + if (!collection->GetCompleted() && !collection->GetIsReadyToTurnIn() && collection->NeedsItem(item)) { + LogWrite(COLLECTION__DEBUG, 0, "Collect", "Adding collection from player list %u\n", collection->GetID()); + collections_to_send[collection->GetID()] = collection; + } + } + + /* get any collections required by this item that the player does not have and send it to the client */ + collections = master_collection_list.GetCollections(); + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + if (collection->NeedsItem(item) && !player->GetCollectionList()->GetCollection(collection->GetID())) { + if (!(packet = configReader.getStruct("WS_CollectionUpdate", version))) { + return false; + } + + packet->setArrayLengthByName("num_collections", 1); + packet->setArrayDataByName("unknown", 1, 0); + packet->setArrayDataByName("collection_name", collection->GetName(), 0); + packet->setArrayDataByName("collection_category", collection->GetCategory(), 0); + packet->setArrayDataByName("completed", 0, 0); + packet->setArrayDataByName("collection_id", collection->GetID(), 0); + packet->setArrayDataByName("level", collection->GetLevel(), 0); + packet->setArrayDataByName("ready_to_turn_in", 0, 0); + packet->setArrayDataByName("unknown3", 0x28, 0); + + collection_items = collection->GetCollectionItems(); + packet->setSubArrayLengthByName("num_items", collection_items->size(), 0); + for (i = 0; i < collection_items->size(); i++) { + collection_item = collection_items->at(i); + Item* item2 = master_item_list.GetItem(collection_item->item); + if (item2) { + packet->setSubArrayDataByName("item_icon", item2->GetIcon(GetVersion()), 0, i); + if (version < 955) + packet->setSubArrayDataByName("item_name", item2->name.c_str(), 0, i); + else + packet->setSubArrayDataByName("item_id", item2->details.item_id, 0, i); + packet->setSubArrayDataByName("item_flag", collection_item->found, 0, i); + } + } + packet->setDataByName("new_collection_flag", 0); + + QueuePacket(packet->serialize()); + safe_delete(packet); + + LogWrite(COLLECTION__DEBUG, 0, "Collect", "Adding collection from master list %u\n", collection->GetID()); + collections_to_send[collection->GetID()] = collection; + } + } + + /* send the client a list of collections that should be filtered for this item */ + if (collections_to_send.size() > 0) { + if (!(packet = configReader.getStruct("WS_CollectionFilter", version))) { + return false; + } + + i = 0; + packet->setArrayLengthByName("num_filters", collections_to_send.size()); + for (itr = collections_to_send.begin(); itr != collections_to_send.end(); itr++) { + collection = itr->second; + + packet->setArrayDataByName("collection_id", collection->GetID(), i); + packet->setArrayDataByName("collection_item_num", collection->GetCollectionItemByItemID(item->details.item_id)->index, i); + i++; + } + + packet->setDataByName("item_icon", item->GetIcon(GetVersion())); + packet->setDataByName("item_name", item->name.c_str()); + packet->setDataByName("item_id", item->details.item_id); + packet->setDataByName("unknown3", player->GetCollectionList()->Size()); + + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + return collections_to_send.size() > 0; +} + +void Client::HandleCollectionAddItem(int32 collection_id, Item* item) { + Collection* collection; + struct CollectionItem* collection_item; + PacketStruct* packet; + char tmp[200] = { 0 }; + + assert(item); + + /* first try to get the collection from the player's collection list. if it's not found, get it from the master list */ + if ((collection = player->GetCollectionList()->GetCollection(collection_id))) { + + /* get the item struct that represents the item for this collection */ + if (!(collection_item = collection->GetCollectionItemByItemID(item->details.item_id))) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Could not find item '%s' required by collection '%s'", item->name.c_str(), collection->GetName()); + return; + } + + /* sanity check */ + if (collection_item->found) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Player '%s' has already found item '%s' for collection '%s'", player->GetName(), item->name.c_str(), collection->GetName()); + return; + } + } + else if ((collection = master_collection_list.GetCollection(collection_id))) { + collection = new Collection(collection); + + /* get the item struct that represents the item for this collection */ + if (!(collection_item = collection->GetCollectionItemByItemID(item->details.item_id))) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Could not find item '%s' required by collection '%s'", item->name.c_str(), collection->GetName()); + safe_delete(collection); + return; + } + + /* add the new collection to the player's collection list */ + if (!player->GetCollectionList()->AddCollection(collection)) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Player '%s' already has collection '%s'", player->GetName(), collection->GetName()); + safe_delete(collection); + return; + } + } + else { + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Could not find collection with id %u", collection_id); + return; + } + + collection_item->found = 1; + collection->SetSaveNeeded(true); + + if (!(packet = configReader.getStruct("WS_CollectionItem", version))) { + SendCollectionList(); + LogWrite(COLLECTION__ERROR, 0, "Collect", "Error in Client::HandleCollectionAddItem. Could not find struct 'WS_CollectionItem'"); + return; + } + + packet->setDataByName("collection_id", collection_id); + packet->setDataByName("collection_item_num", collection_item->index); + packet->setDataByName("add", 1); + QueuePacket(packet->serialize()); + Item* item2 = master_item_list.GetItem(collection_item->item); + if (item2) { + Message(CHANNEL_COLOR_YELLOW, "You added: %s to %s", item2->name.c_str(), collection->GetName()); + sprintf(tmp, "You added: %s to %s", item2->name.c_str(), collection->GetName()); + SendPopupMessage(5, tmp, "quest_item", 3.5, 0x64, 0xFF, 0xFF); + } + safe_delete(packet); + + RemoveItem(item, 1); + SendCollectionList(); + +} + +void Client::DisplayCollectionComplete(Collection* collection) { + vector* reward_items; + vector* selectable_reward_items; + struct CollectionRewardItem* reward_item; + PacketStruct* packet; + int32 i; + + assert(collection); + + reward_items = collection->GetRewardItems(); + selectable_reward_items = collection->GetSelectableRewardItems(); + + if (GetVersion() <= 561) { + int32 source_id = collection->GetID(); + PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion()); + if (packet2) { + packet2->setSubstructDataByName("reward_data", "unknown1", 255); + packet2->setSubstructDataByName("reward_data", "reward", "Quest Reward!"); + packet2->setSubstructDataByName("reward_data", "max_coin", collection->GetRewardCoin()); + packet2->setSubstructDataByName("reward_data", "exp_bonus", collection->GetRewardXP()); + + if(reward_items){ + int32 item_count = reward_items->size(); + packet2->setSubstructArrayLengthByName("reward_data", "num_rewards", item_count); + i = 0; + if(reward_items) { + for (i = 0; i < reward_items->size(); i++) { + Item* item = reward_items->at(i)->item; + if (item) { + packet2->setArrayDataByName("reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("item", item, player, i, 0, GetClientItemPacketOffset()); + } + } + } + } + if (selectable_reward_items) { + packet2->setSubstructArrayLengthByName("reward_data", "num_select_rewards", selectable_reward_items->size()); + for (i = 0; i < selectable_reward_items->size(); i++) { + Item* item = selectable_reward_items->at(i)->item; + if (item) { + packet2->setArrayDataByName("select_reward_id", item->details.item_id, i); + packet2->setItemArrayDataByName("select_item", item, player, i, 0, GetClientItemPacketOffset()); + } + } + } + } + + EQ2Packet* app = packet2->serialize(); + QueuePacket(app); + safe_delete(packet2); + return; + } + + if (!(packet = configReader.getStruct("WS_QuestComplete", version))) { + return; + } + + packet->setDataByName("title", "Quest Reward!"); + packet->setDataByName("name", collection->GetName()); + packet->setDataByName("description", collection->GetCategory()); + packet->setDataByName("level", collection->GetLevel()); + packet->setDataByName("max_coin", collection->GetRewardCoin()); + packet->setDataByName("min_coin", collection->GetRewardCoin()); + //packet->setDataByName("status_points", quest->GetStatusPoints()); + packet->setArrayLengthByName("num_rewards", reward_items->size()); + for (i = 0; i < reward_items->size(); i++) { + reward_item = reward_items->at(i); + if(!reward_item->item) + { + LogWrite(DATABASE__ERROR, 0, "Database", "DisplayCollectionComplete Collection %s (%u) num_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); + Message(CHANNEL_COLOR_RED, "DisplayCollectionComplete Collection %s (%u) num_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); + continue; + } + + packet->setArrayDataByName("reward_id", reward_item->item->details.item_id, i); + if (version < 860) + packet->setItemArrayDataByName("item", reward_item->item, player, i, 0, GetClientItemPacketOffset()); + else if (version < 1193) + packet->setItemArrayDataByName("item", reward_item->item, player, i); + else + packet->setItemArrayDataByName("item", reward_item->item, player, i, 0, 2); + } + packet->setArrayLengthByName("num_select_rewards", selectable_reward_items->size()); + for (i = 0; i < selectable_reward_items->size(); i++) { + reward_item = selectable_reward_items->at(i); + if(!reward_item->item) + { + LogWrite(DATABASE__ERROR, 0, "Database", "DisplayCollectionComplete Collection %s (%u) num_select_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); + Message(CHANNEL_COLOR_RED, "DisplayCollectionComplete Collection %s (%u) num_select_rewards has missing item in slot %u", collection->GetName(), collection->GetID(), i); + continue; + } + + packet->setArrayDataByName("select_reward_id", reward_item->item->details.item_id, i); + if (version < 860) + packet->setItemArrayDataByName("select_item", reward_item->item, player, i, 0, GetClientItemPacketOffset()); + else if (version < 1193) + packet->setItemArrayDataByName("select_item", reward_item->item, player, i); + else + packet->setItemArrayDataByName("select_item", reward_item->item, player, i, 0, 2); + } + + QueuePacket(packet->serialize()); + safe_delete(packet); + +} + +void Client::HandInCollections() { + map* collections; + map::iterator itr; + Collection* collection; + + /* only show 1 collection reward dialog at a time */ + if (player->GetPendingCollectionReward()) { + return; + } + + collections = player->GetCollectionList()->GetCollections(); + + /* we only want to display 1 collection reward dialog at a time. so once we find one to display, send it and return. once the player accepts the reward + for this collection, this function gets called again and the process repeats until there are no more collections to hand in */ + for (itr = collections->begin(); itr != collections->end(); itr++) { + collection = itr->second; + if (collection->GetIsReadyToTurnIn()) { + player->SetPendingCollectionReward(collection); + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + QuestRewardData* data = new QuestRewardData; + data->quest_id = 0; + data->is_temporary = false; + data->description = std::string(""); + data->is_collection = true; + data->has_displayed = false; + data->tmp_coin = 0; + data->tmp_status = 0; + data->db_saved = false; + data->db_index = 0; + quest_pending_reward.push_back(data); + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); + quest_updates = true; + break; + } + } + if(quest_updates) { + SaveQuestRewardData(true); + } +} + +void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_item_id) { + vector* reward_items; + vector::iterator itr; + struct CollectionRewardItem* reward_item; + int16 num_slots_needed; + int16 num_slots; + + assert(collection); + + reward_items = collection->GetRewardItems(); + num_slots_needed = (int16)reward_items->size(); + if (selectable_item_id > 0) + num_slots_needed++; + + num_slots = player->GetPlayerItemList()->GetNumberOfFreeSlots(); + if (num_slots < num_slots_needed) { + SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots. Free up some slots and try again"); + DisplayCollectionComplete(collection); + return; + } + + /* add manditory items */ + for (itr = reward_items->begin(); itr != reward_items->end(); itr++) { + reward_item = *itr; + AddItem(reward_item->item->details.item_id, reward_item->quantity); + } + + /* find and add the selectable item if there's one */ + if (selectable_item_id > 0) { + reward_items = collection->GetSelectableRewardItems(); + for (itr = reward_items->begin(); itr != reward_items->end(); itr++) { + reward_item = *itr; + if (reward_item->item->details.item_id == selectable_item_id) { + AddItem(reward_item->item->details.item_id, reward_item->quantity); + break; + } + } + } + if (collection->GetRewardXP() > 0) { + player->AddXP((int32)collection->GetRewardXP()); + } + if (collection->GetRewardCoin() > 0) { + player->AddCoins(collection->GetRewardCoin()); + Message(CHANNEL_COLOR_YELLOW, "You receive %s", GetCoinMessage(collection->GetRewardCoin()).c_str()); + } + + collection->SetCompleted(true); + //update achievements for completeing collection here + collection->SetSaveNeeded(true); + SendCollectionList(); + + /* reset the pending collection reward and check for my collections that the player needs to hand in */ + player->SetPendingCollectionReward(0); + + RemoveQueuedQuestReward(); + + GetPlayer()->SetActiveReward(false); + + HandInCollections(); + + GetPlayer()->GetZone()->SendSubSpawnUpdates(SUBSPAWN_TYPES::COLLECTOR); +} + +void Client::SendRecipeList() { + if(GetRecipeListSent()) { + return; + } + + PacketStruct* packet = 0; + map* recipes = player->GetRecipeList()->GetRecipes(); + map::iterator itr; + int16 i = 0; + Recipe* recipe; + + if(version <= 561) { + PacketStruct* packet = 0; + if (!(packet = configReader.getStruct("WS_UpdateRecipeBook", GetVersion()))) { + return; + } + packet->setArrayLengthByName("recipe_count", recipes->size()); + for (itr = recipes->begin(); itr != recipes->end(); itr++) { + recipe = itr->second; + int32 recipe_id = recipe->GetID(); + packet->setArrayDataByName("recipe_id", recipe_id, i); + packet->setArrayDataByName("recipe_data_crc", GetRecipeCRC(recipe), i); + packet->setArrayDataByName("unknown", 0x7005BE3, i); //0x7005BE3 + i++; + } + EQ2Packet* ret = packet->serializeCountPacket(GetVersion(), 0, nullptr, nullptr); + QueuePacket(ret); + safe_delete(packet); + SetRecipeListSent(true); + return; + } + else if (!(packet = configReader.getStruct("WS_RecipeList", version))) { + return; + } + int8 level = player->GetTSLevel(); + int index = 0; + for (itr = recipes->begin(); itr != recipes->end(); itr++) { + recipe = itr->second; + auto res = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + + if (res != devices.end()) + index = res - devices.begin(); + else + devices.push_back(recipe->GetDevice()); + } + + packet->setDataByName("command_type", 0); + packet->setArrayLengthByName("num_recipes", recipes->size()); + int stringsize = 0; + for (itr = recipes->begin(); itr != recipes->end(); itr++) { + recipe = itr->second; + int32 myid = recipe->GetID(); + int8 rlevel = recipe->GetLevel(); + int8 even = level - level * .05 + .5; + int8 easymin = level - level * .25 + .5; + int8 veasymin = level - level * .35 + .5; + if (rlevel > level ) + packet->setArrayDataByName("tier", 4, i); + else if ((rlevel <= level) & (rlevel >= even)) + packet->setArrayDataByName("tier", 3, i); + else if ((rlevel <= even) & (rlevel >= easymin)) + packet->setArrayDataByName("tier", 2, i); + else if ((rlevel <= easymin) & (rlevel >= veasymin)) + packet->setArrayDataByName("tier", 1, i); + else if ((rlevel <= veasymin) & (rlevel >= 0)) + packet->setArrayDataByName("tier", 0, i); + if (rlevel == 2) + int xxx = 1; + packet->setArrayDataByName("recipe_id", myid, i); + packet->setArrayDataByName("level", recipe->GetLevel(), i); + packet->setArrayDataByName("unknown1", recipe->GetLevel(), i); + packet->setArrayDataByName("icon", recipe->GetIcon(), i); + packet->setArrayDataByName("classes", recipe->GetClasses(), i); + packet->setArrayDataByName("technique", recipe->GetTechnique(), i); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), i); + + + auto recipe_device = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + if (recipe_device != devices.end()) + packet->setArrayDataByName("device_type", recipe_device - devices.begin(), i); + else + {//TODO error should never get here + } + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), i); + packet->setArrayDataByName("recipe_name", recipe->GetName(), i); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), i); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), i); + i++; + } + //packet->PrintPacket(); + EQ2Packet* pack = packet->serialize(); + QueuePacket(pack); + safe_delete(packet); + SetRecipeListSent(true); +} + +void Client::ShowRecipeBook() { + PacketStruct* packet = 0; + Spawn* target = 0; + int index = 0; + if (!(target = player->GetTarget())) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have a tradeskill device targeted"); + return; + } + if (!target->IsObject() || !((Object*)target)->GetDeviceID()) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "You do not have a tradeskill device targeted"); + return; + } + + if (!(packet = configReader.getStruct("WS_ShowRecipeBook", version))) { + return; + } + + packet->setDataByName("device", target->GetName()); + packet->setDataByName("unknown1", 1); + auto res = std::find(devices.begin(), devices.end(), target->GetName()); + if (res != devices.end()){ + index = res - devices.begin(); + int32 deviceID = 0; + deviceID |= 1UL << index; + //LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "GetDeviceID() = %u, deviceID = %u", ((Object*)target)->GetDeviceID(), deviceID); + packet->setDataByName("unknown2", devices.size()); + packet->setDataByName("unknown3", deviceID); + } + else + packet->setDataByName("unknown2", devices.size()); + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void Client::SendTitleUpdate() { + // must call release read lock before leaving function on GetPlayerTitles + vector* titles = player->GetPlayerTitles()->GetAllTitles(); + vector::iterator itr; + Title* title; + sint32 i = 0; + sint32 prefix_index = database.GetCharPrefixIndex(GetCharacterID(), player); + sint32 suffix_index = database.GetCharSuffixIndex(GetCharacterID(), player); + PacketStruct* packet = configReader.getStruct("WS_TitleUpdate", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_titles", titles->size()); + for (itr = titles->begin(); itr != titles->end(); itr++) { + title = *itr; + packet->setArrayDataByName("title", title->GetName(), i); + packet->setArrayDataByName("prefix", title->GetPrefix(), i); + i++; + } + packet->setDataByName("current_prefix", prefix_index); + packet->setDataByName("current_suffix", suffix_index); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + + QueuePacket(packet->serialize()); + safe_delete(packet); + SendUpdateTitles(prefix_index, suffix_index); + } + player->GetPlayerTitles()->ReleaseReadLock(); +} + +void Client::SendUpdateTitles(sint32 prefix, sint32 suffix) { + Title* suffix_title = 0; + Title* prefix_title = 0; + if (suffix != -1) { + suffix_title = player->GetPlayerTitles()->GetTitle(suffix); + if(suffix_title) + strcpy(player->appearance.suffix_title, suffix_title->GetName()); + } + else + memset(player->appearance.suffix_title, 0, strlen(player->appearance.suffix_title)); + if (prefix != -1) { + prefix_title = player->GetPlayerTitles()->GetTitle(prefix); + if(prefix_title) + strcpy(player->appearance.prefix_title, prefix_title->GetName()); + } + else + memset(player->appearance.prefix_title, 0, strlen(player->appearance.prefix_title)); + + current_zone->SendUpdateTitles(this, suffix_title, prefix_title); +} + +void Client::SendLanguagesUpdate(int32 id, bool setlang) { + list* languages = player->GetPlayerLanguages()->GetAllLanguages(); + list::iterator itr; + Language* language; + int32 i = 0; + + if(setlang==1){ + GetPlayer()->SetCurrentLanguage(id); + } + + PacketStruct* packet = configReader.getStruct("WS_Languages", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_languages", languages->size()); + for (itr = languages->begin(); itr != languages->end(); itr++) { + language = *itr; + packet->setArrayDataByName("language_id", language->GetID(), i); + i++; + } + packet->setDataByName("current_language", id); + + LogWrite(CCLIENT__PACKET, 0, "Client", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__); +#if EQDEBUG >= 9 + packet->PrintPacket(); +#endif + QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void Client::SendPetOptionsWindow(const char* pet_name, int8 type) { + PacketStruct* packet = configReader.getStruct("WS_PetOptions", GetVersion()); + if (packet) { + if (pet_name) + packet->setDataByName("pet_name", pet_name); + if (player->GetInfoStruct()->get_pet_behavior() & 1) + packet->setDataByName("protect_master", 1); + if (player->GetInfoStruct()->get_pet_behavior() & 2) + packet->setDataByName("protect_self", 1); + if (player->GetInfoStruct()->get_pet_movement() == 2) + packet->setDataByName("stay_follow_toggle", 1); + + packet->setDataByName("pet_type", type); + + QueuePacket(packet->serialize()); + } + + safe_delete(packet); +} + +bool Client::IsCrafting() { + return current_zone->GetTradeskillMgr()->IsClientCrafting(this); +} + +void Client::SendBiography() { + PacketStruct* packet = configReader.getStruct("WS_BioUpdate", GetVersion()); + if (packet) { + char biography[512]; + if(player->GetInfoStruct()->get_biography().size() < 1) + { + safe_delete(packet); + return; + } + else + { + int16 size = player->GetInfoStruct()->get_biography().size(); + if(size>511) + size = 511; + + strncpy(biography, player->GetInfoStruct()->get_biography().c_str(), player->GetInfoStruct()->get_biography().size()); + biography[player->GetInfoStruct()->get_biography().size()] = '\0'; + } + packet->setDataByName("biography", biography); + + QueuePacket(packet->serialize()); + } + + safe_delete(packet); +} + +PendingResurrection* Client::GetCurrentRez() { + return ¤t_rez; +} + +Mutex* Client::GetResurrectMutex() { + return &m_resurrect; +} + +void Client::SendResurrectionWindow() { + Spawn* caster = current_rez.caster; + if (!caster || !player) + return; + + PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", GetVersion()); + if (!packet) + return; + + char* tmp = new char[512]; + sprintf(tmp, "%s would like to cast '%s' on you. Do you accept?", caster->GetName(), current_rez.spell_name.c_str()); + + packet->setMediumStringByName("text", tmp); + packet->setMediumStringByName("accept_text", "Yes"); + packet->setMediumStringByName("cancel_text", "No"); + + sprintf(tmp, "accept_resurrection %u", player->GetID()); + packet->setMediumStringByName("accept_command", tmp); + + sprintf(tmp, "decline_resurrection %u", player->GetID()); + packet->setMediumStringByName("cancel_command", tmp); + packet->setDataByName("time", current_rez.expire_timer->GetRemainingTime() / 1000); + QueuePacket(packet->serialize()); + safe_delete(packet); + safe_delete_array(tmp); +} + +void Client::AcceptResurrection() { + Spawn* caster = current_rez.caster; + if (!player || !caster) + return; + + if (player->Alive()) + return; + + if ((caster->GetZone() != player->GetZone()) || (current_rez.range > 0 && player->GetDistance(caster) > current_rez.range)) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "The caster must be nearby to complete the spell."); + SendResurrectionWindow(); + return; + } + + player->GetZone()->ResurrectSpawn(player, this); + current_rez.should_delete = true; +} + +void Client::SetPendingLastName(string last_name) { + pending_last_name = new string; + pending_last_name->assign(last_name); +} + +string* Client::GetPendingLastName() { + return pending_last_name; +} + +void Client::RemovePendingLastName() { + safe_delete(pending_last_name); +} + +void Client::SendLastNameConfirmation() { + if (!pending_last_name) + return; + + PacketStruct* packet = configReader.getStruct("WS_ChoiceWindow", GetVersion()); + if (packet) { + char* text = new char[128]; + sprintf(text, "Are you sure you want your last name to be \"%s\"?", pending_last_name->c_str()); + packet->setDataByName("text", text); + packet->setDataByName("accept_text", "Yes"); + packet->setDataByName("accept_command", "confirmedlastname"); + packet->setDataByName("cancel_text", "No"); + packet->setDataByName("max_length", 50); + packet->setDataByName("unknown4", 1); + packet->setDataByName("unknown5", 1); + QueuePacket(packet->serialize()); + safe_delete(packet); + safe_delete_array(text); + } +} + +void Client::AddQuestTimer(int32 quest_id) { + MQuestTimers.writelock(__FUNCTION__, __LINE__); + quest_timers.push_back(quest_id); + MQuestTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void Client::RemoveQuestTimer(int32 quest_id) { + MQuestTimers.writelock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = quest_timers.begin(); itr != quest_timers.end(); itr++) { + if ((*itr) == quest_id) { + quest_timers.erase(itr); + break; + } + } + MQuestTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void Client::SavePlayerImages() { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Saving %s image for player %s (%u)", (incoming_paperdoll.image_type == PAPERDOLL_TYPE_FULL ? "paperdoll" : "headshot"), GetPlayer()->GetName(), GetCharacterID()); + + // Save the paperdoll image if the server allows it + if (incoming_paperdoll.image_type == PAPERDOLL_TYPE_FULL && rule_manager.GetGlobalRule(R_World, SavePaperdollImage)->GetBool()) + database.SaveCharacterPicture(GetCharacterID(), incoming_paperdoll.image_type, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + + if (incoming_paperdoll.image_type == PAPERDOLL_TYPE_HEAD) { + // Save the head shot if the server allows it + if (rule_manager.GetGlobalRule(R_World, SaveHeadshotImage)->GetBool()) + database.SaveCharacterPicture(GetCharacterID(), incoming_paperdoll.image_type, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + + // Send the head shot to the login server + if (rule_manager.GetGlobalRule(R_World, SendPaperdollImagesToLogin)->GetBool()) { + int32 size = incoming_paperdoll.current_size_bytes + CHARPICSTRUCT_MINSIZE; + ServerPacket* packet = new ServerPacket(ServerOP_CharacterPicture, size); + memset(packet->pBuffer, 0, size); + + CharPictureUpdate_Struct* pic = (CharPictureUpdate_Struct*)packet->pBuffer; + pic->account_id = GetAccountID(); + pic->char_id = GetCharacterID(); + pic->pic_size = (int16)incoming_paperdoll.current_size_bytes; + memcpy(pic->pic, incoming_paperdoll.image_bytes, incoming_paperdoll.current_size_bytes); + + loginserver.SendPacket(packet); + safe_delete(packet); + } + } + safe_delete_array(incoming_paperdoll.image_bytes); + incoming_paperdoll.current_size_bytes = 0; +} + +void Client::EndAutoMount() { + PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", GetVersion()); + if (packet) { + packet->setDataByName("parameter1", 128); + packet->setDataByName("parameter2", 64); + packet->setDataByName("value", 1); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + packet = configReader.getStruct("WS_ServerControlFlags", GetVersion()); + if (packet) { + packet->setDataByName("parameter3", 4); + packet->setDataByName("parameter5", 2); + packet->setDataByName("value", 0); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + packet = configReader.getStruct("WS_ClearForLanding", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(GetPlayer())); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + on_auto_mount = false; + + player->SetMount(((Player*)player)->GetTempMount()); + EQ2_Color mount_color = player->GetTempMountColor(); + EQ2_Color saddle_color = player->GetTempMountSaddleColor(); + player->SetMountColor(&mount_color); + player->SetMountSaddleColor(&saddle_color); + player->SetTempMount(0); +} + +bool Client::EntityCommandPrecheck(Spawn* spawn, const char* command) { + const char* spawn_script = spawn->GetSpawnScript(); + bool should_use_spawn = true; + if (spawn_script) { + lua_State* state = lua_interface->GetSpawnScript(spawn_script); + if (state) { + Mutex* state_mutex = lua_interface->GetSpawnScriptMutex(spawn_script); + if (state_mutex) + state_mutex->writelock(__FUNCTION__, __LINE__); + lua_getglobal(state, "can_use_command"); + if (lua_isfunction(state, -1)) { + lua_interface->SetSpawnValue(state, spawn); + lua_interface->SetSpawnValue(state, GetPlayer()); + lua_interface->SetStringValue(state, command ? command : ""); + if (lua_pcall(state, 3, 1, 0) == 0) { + should_use_spawn = lua_interface->GetBooleanValue(state, 1); + } + } + lua_interface->ResetFunctionStack(state); + if (state_mutex) + state_mutex->releasewritelock(__FUNCTION__, __LINE__); + } + } + return should_use_spawn; +} + +bool Client::IsCurrentTransmuteID(int32 req_id) { + return req_id == transmuteID; +} + +void Client::SetTransmuteID(int32 trans_id) { + transmuteID = trans_id; +} + +int32 Client::GetTransmuteID() { + return transmuteID; +} + +bool Client::HandleNewLogin(int32 account_id, int32 access_code) +{ + printf("HandleNewLogin: AcctID: %i AccessCode: %i\n", account_id, access_code); + ZoneAuthRequest* zar = zone_auth.GetAuth(account_id, access_code); + + if (zar) + { + int32 charID = database.GetCharacterID(zar->GetCharacterName()); + if (database.IsActiveQuery(charID)) + { + delayedLogin = true; + delayedAccountID = account_id; + delayedAccessKey = access_code; + delayTimer.Start(500); + LogWrite(ZONE__INFO, 0, "ZoneAuth", "Attempt to Login must be delayed, async character save in progress! ... Access Key: %u, Character Name: %s, Account ID: %u, Client Data Version: %u", zar->GetAccessKey(), zar->GetCharacterName(), zar->GetAccountID(), version); + return true; + } + + delayedLogin = false; + delayTimer.Disable(); + + firstlogin = zar->isFirstLogin(); + SetReadyForSpawns(false); + ready_for_updates = false; + LogWrite(ZONE__INFO, 0, "ZoneAuth", "Access Key: %u, Character Name: %s, Account ID: %u, Client Data Version: %u", zar->GetAccessKey(), zar->GetCharacterName(), zar->GetAccountID(), version); + Client* client = zone_list.GetClientByCharName(zar->GetCharacterName()); + if (client || database.loadCharacter(zar->GetCharacterName(), zar->GetAccountID(), this)) { + GetPlayer()->CalculateOfflineDebtRecovery(GetLastSavedTimeStamp()); + GetPlayer()->vis_changed = false; + GetPlayer()->info_changed = false; + + bool pvp_allowed = rule_manager.GetGlobalRule(R_PVP, AllowPVP)->GetBool(); + if (pvp_allowed) + this->GetPlayer()->SetAttackable(1); + MDeletePlayer.writelock(__FUNCTION__, __LINE__); + if (client) { + if (client->getConnection()) + client->getConnection()->SendDisconnect(true); + + bool restore_ld_success = false; + if (client->GetCurrentZone() && !client->IsZoning()) { + //swap players, allowing the client to resume his LD'd player (ONLY if same version of the client) + if (client->GetVersion() == version) { + client->DisableSave(); + client->ReplaceGroupClient(this); + Player* current_player = GetPlayer(); + SetPlayer(client->GetPlayer()); + GetPlayer()->SetClient(this); + GetPlayer()->SetReturningFromLD(true); + + SetCharacterID(client->GetCharacterID()); + SetAccountID(client->GetAccountID()); + SetAdminStatus(client->GetAdminStatus()); + SetCurrentZone(GetPlayer()->GetZone()); + client->SetPlayer(current_player); + GetPlayer()->ResetSavedSpawns(); + restore_ld_success = true; + + char tmpldname[128]; + snprintf(tmpldname, 128, "%s Linkdead",GetPlayer()->GetName()); + client->GetPlayer()->SetName(tmpldname, false); + } + ZoneServer* tmpZone = client->GetCurrentZone(); + tmpZone->RemoveClientImmediately(client); + } + if(!restore_ld_success && !database.loadCharacter(zar->GetCharacterName(), zar->GetAccountID(), this)) { + LogWrite(ZONE__ERROR, 0, "Zone", "Error reloading LD character and loading DB character: %s", player->GetName()); + ClientPacketFunctions::SendLoginDenied(this); + Disconnect(); + return false; + } + } + MDeletePlayer.releasewritelock(__FUNCTION__, __LINE__); + if (!GetCurrentZone()) { + LogWrite(ZONE__ERROR, 0, "Zone", "Error loading zone for character: %s", player->GetName()); + ClientPacketFunctions::SendLoginDenied(this); + Disconnect(); + return false; + } + else if (EQOpcodeManager.count(GetOpcodeVersion(version)) > 0 && getConnection()) { + getConnection()->SetClientVersion(version); + GetCurrentZone()->SetSpawnStructs(this); + connected_to_zone = true; + client_list.Remove(this); //remove from master client list + GetCurrentZone()->AddClient(this); //add to zones client list + zone_list.AddClientToMap(player->GetName(), this); + // this initiates additional DB loading and client offloading within the Zone the player resides, need the client already added in zone and to the map above. + if(GetCurrentZone()->IsLoading()) { + new_client_login = NewLoginState::LOGIN_DELAYED; + } + else { + new_client_login = NewLoginState::LOGIN_ALLOWED; + } + + const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "new_client", GetCurrentZone(), GetPlayer()); + } + else { + LogWrite(WORLD__ERROR, 0, "World", "Incompatible version: %i", version); + version = 0; + ClientPacketFunctions::SendLoginDenied(this); + Disconnect(); + return false; + } + } + else { + LogWrite(WORLD__ERROR, 0, "World", "Could not load character '%s' with account id of: %u", zar->GetCharacterName(), zar->GetAccountID()); + ClientPacketFunctions::SendLoginDenied(this); + Disconnect(); + return false; + } + zone_auth.RemoveAuth(zar); + } + else + { + LogWrite(WORLD__ERROR, 0, "World", "Invalid ZoneAuthRequest, disconnecting client."); + Disconnect(); + return false; + } + + return true; +} + + +void Client::SendSpawnChanges(set& spawns) { + if (!IsReadyForUpdates()) + return; + + map info_changes; + map pos_changes; + map vis_changes; + + map empty_changes; + + int32 info_size = 0; + int32 pos_size = 0; + int32 vis_size = 0; + + int count = 0; + + for (const auto& spawn : spawns) { + int16 index = GetPlayer()->GetIndexForSpawn(spawn); + if (index == 0 || !GetPlayer()->WasSentSpawn(spawn->GetID())) + continue; + + if(GetPlayer() == spawn && GetVersion() <= 284) { // stopped self client/player warping in the EQ2 release disc (Beta), don't send yourself in bulk spawn updates + continue; + } + + int16 tmp_info_size = 0; + int16 tmp_pos_size = 0; + int16 tmp_vis_size = 0; + if (spawn->vis_changed) + { + auto vis_change = spawn->spawn_vis_changes_ex(GetPlayer(), GetVersion(), &tmp_vis_size); + if (vis_change) { + SpawnData data; + data.spawn = spawn; + data.data = vis_change; + data.size = tmp_vis_size; + map tmp_vis_changes; + tmp_vis_changes[index] = data; + + map tmp_info_changes; + map tmp_pos_changes; + if (spawn->info_changed) { + auto info_change = spawn->spawn_info_changes_ex(GetPlayer(), GetVersion(), &tmp_info_size); + + if (info_change) { + SpawnData data; + data.spawn = spawn; + data.data = info_change; + data.size = tmp_info_size; + tmp_info_changes[index] = data; + } + } + + if (spawn->position_changed) { + auto pos_change = spawn->spawn_pos_changes_ex(GetPlayer(), GetVersion(), &tmp_pos_size); + + if (pos_change) { + SpawnData data; + data.spawn = spawn; + data.data = pos_change; + data.size = tmp_pos_size; + tmp_pos_changes[index] = data; + } + } + + MakeSpawnChangePacket(tmp_info_changes, tmp_pos_changes, tmp_vis_changes, tmp_info_size, tmp_pos_size, data.size); + + for (auto& kv : tmp_info_changes) { + safe_delete_array(kv.second.data); + } + + for (auto& kv : tmp_pos_changes) { + safe_delete_array(kv.second.data); + } + + for (auto& kv : tmp_vis_changes) { + safe_delete_array(kv.second.data); + } + continue; + } + } + + if (spawn->info_changed) { + auto info_change = spawn->spawn_info_changes_ex(GetPlayer(), GetVersion(), &tmp_info_size); + + if (info_change) { + SpawnData data; + data.spawn = spawn; + data.data = info_change; + data.size = tmp_info_size; + info_size += tmp_info_size; + + info_changes[index] = data; + } + count++; + } + + if (spawn->position_changed) { + auto pos_change = spawn->spawn_pos_changes_ex(GetPlayer(), GetVersion(), &tmp_pos_size); + + if (pos_change) { + SpawnData data; + data.spawn = spawn; + data.data = pos_change; + data.size = tmp_pos_size; + pos_size += tmp_pos_size; + + pos_changes[index] = data; + } + count++; + } + + if (spawn->vis_changed) { + auto vis_change = spawn->spawn_vis_changes_ex(GetPlayer(), GetVersion(), &tmp_vis_size); + + if (vis_change) { + SpawnData data; + data.spawn = spawn; + data.data = vis_change; + data.size = tmp_vis_size; + vis_size += tmp_vis_size; + + vis_changes[index] = data; + } + count++; + } + + } + + if (info_size == 0 && pos_size == 0 && vis_size == 0) { + return; + } + + MakeSpawnChangePacket(info_changes, pos_changes, vis_changes, info_size, pos_size, vis_size); + + for (auto& kv : info_changes) { + safe_delete_array(kv.second.data); + } + + for (auto& kv : pos_changes) { + safe_delete_array(kv.second.data); + } + + for (auto& kv : vis_changes) { + safe_delete_array(kv.second.data); + } +} + +void Client::MakeSpawnChangePacket(map info_changes, map pos_changes, map vis_changes, int32 info_size, int32 pos_size, int32 vis_size) +{ + static const int8 oversized = 255; + int16 opcode_val = 0; + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) { + opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); + } + int32 size = info_size + pos_size + vis_size + 8; + size += CheckOverLoadSize(info_size); + size += CheckOverLoadSize(pos_size); + size += CheckOverLoadSize(vis_size); + if (version <= 373 && size >= 255) {//1 byte to 3 for overloaded val + size += 2; + } + else if (version >= 374) + size += 3; + uchar* tmp = new uchar[size]; + uchar* ptr = tmp; + + memset(tmp, 0, size); + if (version <= 373) { + if (size >= 255) { + size -= 3; + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + memcpy(ptr, &size, sizeof(int16)); + ptr += sizeof(int16); + size += 3; + } + else { + size -= 1; + memcpy(ptr, &size, sizeof(int8)); + ptr += sizeof(int8); + size += 1; + } + } + else { + size -= 4; + memcpy(ptr, &size, sizeof(int32)); + ptr += sizeof(int32); + size += 4; + } + memcpy(ptr, &oversized, sizeof(int8)); + ptr += sizeof(int8); + + memcpy(ptr, &opcode_val, sizeof(int16)); + ptr += sizeof(int16); + + int32 current_time = Timer::GetCurrentTime2(); + memcpy(ptr, ¤t_time, sizeof(int32)); + ptr += sizeof(int32); + + ptr += DoOverLoad(info_size, ptr); + + for (const auto& kv : info_changes) { + auto info = kv.second; + memcpy(ptr, info.data, info.size); + ptr += info.size; + } + + ptr += DoOverLoad(pos_size, ptr); + + for (const auto& kv : pos_changes) { + auto pos = kv.second; + memcpy(ptr, pos.data, pos.size); + ptr += pos.size; + } + + ptr += DoOverLoad(vis_size, ptr); + + for (const auto& kv : vis_changes) { + auto vis = kv.second; + memcpy(ptr, vis.data, vis.size); + ptr += vis.size; + } + + EQ2Packet* packet = new EQ2Packet(OP_ClientCmdMsg, tmp, size); + + if (packet) { + /*char blah[64]; + snprintf(blah, 64, "Sending %i", current_time); + SimpleMessage(4, blah);*/ + QueuePacket(packet); + } + + delete[] tmp; +} + +void Client::SendHailCommand(Spawn* spawn) +{ + // hardcoded 'hail' entity commands + switch (spawn->secondary_command_list_id) + { + case 9: + case 1262: + case 1265: + case 1267: + { + EQ2_16BitString* command = new EQ2_16BitString(); + command->data = ""; + command->size = 0; + commands.Process(COMMAND_HAIL, command, this, spawn); + safe_delete(command); + break; + } + } +} + +void Client::SendDefaultCommand(Spawn* spawn, const char* command, float distance) +{ + if (GetPlayer()->WasSentSpawn(spawn->GetID())) { + PacketStruct* packet = configReader.getStruct("WS_SetDefaultCommand", GetVersion()); + if (packet) { + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setMediumStringByName("command_name", command); + packet->setDataByName("distance", distance); + QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +bool Client::HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command) +{ + if (GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE) + return false; + + if (command == "house_spawn_examine") + { + uint32 itemID = spawn->GetPickupItemID(); + if (itemID) + { + + Item* item = master_item_list.GetItem(itemID); + if (item) + { + EQ2Packet* app = item->serialize(GetVersion(), true, GetPlayer()); + //DumpPacket(app); + QueuePacket(app); + } + + } + return true; + } + + return false; +} + +bool Client::PopulateHouseSpawn(PacketStruct* place_object) +{ + if (GetTempPlacementSpawn()) + { + Spawn* tmp = GetTempPlacementSpawn(); + + int32 spawn_group_id = database.GetNextSpawnLocation(); + tmp->SetSpawnLocationID(spawn_group_id); + + float newHeading = place_object->getType_float_ByName("heading") + 180; + + int32 spawnDBID = 0; + if (GetCurrentZone()->house_object_database_lookup.count(tmp->GetModelType()) > 0) + { + spawnDBID = GetCurrentZone()->house_object_database_lookup.Get(tmp->GetModelType()); + tmp->SetDatabaseID(spawnDBID); + } + else + { + spawnDBID = database.FindHouseInstanceSpawn(tmp); + if (spawnDBID) + { + GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), spawnDBID); + tmp->SetDatabaseID(spawnDBID); + } + } + + tmp->SetX(place_object->getType_float_ByName("x")); + tmp->SetY(place_object->getType_float_ByName("y")); + tmp->SetZ(place_object->getType_float_ByName("z")); + tmp->SetHeading(newHeading); + tmp->SetSpawnOrigX(tmp->GetX()); + tmp->SetSpawnOrigY(tmp->GetY()); + tmp->SetSpawnOrigZ(tmp->GetZ()); + tmp->SetSpawnOrigHeading(tmp->GetHeading()); + + database.SaveSpawnInfo(tmp); + database.SaveSpawnEntry(tmp, "houseplacement", 100, 0, 0, 0); + + if (!spawnDBID) + { + GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), tmp->GetDatabaseID()); + // we need to copy as to not delete the ZoneServer object_list entry this on house item pickup + GetCurrentZone()->AddObject(tmp->GetDatabaseID(), ((Object*)tmp)->Copy()); + } + + return true; + } + + return false; +} + +bool Client::PopulateHouseSpawnFinalize() +{ + if (GetTempPlacementSpawn()) + { + Spawn* tmp = GetTempPlacementSpawn(); + GetCurrentZone()->AddSpawn(tmp); + GetCurrentZone()->SendSpawnChanges(tmp, this); + SetTempPlacementSpawn(nullptr); + int32 uniqueID = GetPlacementUniqueItemID(); + Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); + tmp->SetPickupItemID(uniqueItem->details.item_id); + tmp->SetPickupUniqueItemID(uniqueID); + + if (uniqueItem) + { + if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE) + { + Query query; + query.RunQuery2(Q_INSERT, "insert into spawn_instance_data set spawn_id = %u, spawn_location_id = %u, pickup_item_id = %u, pickup_unique_item_id = %u", tmp->GetDatabaseID(), tmp->GetSpawnLocationID(), tmp->GetPickupItemID(), uniqueID); + } + + if(uniqueItem->GetItemScript() && + lua_interface->RunItemScript(uniqueItem->GetItemScript(), "placed", uniqueItem, GetPlayer(), tmp)) + { + uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); + } + + if(uniqueItem) { + database.DeleteItem(GetCharacterID(), uniqueItem, 0); + GetPlayer()->item_list.RemoveItem(uniqueItem, true); + QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion())); + } + + SetPlacementUniqueItemID(0); + } + return true; + } + + return false; +} + +void Client::SendMoveObjectMode(Spawn* spawn, uint8 placementMode, float unknown2_3) +{ + PacketStruct* packet = configReader.getStruct("WS_MoveObjectMode", GetVersion()); + packet->setDataByName("placement_mode", placementMode); + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("model_type", spawn->GetModelType()); + packet->setDataByName("unknown", 1); //size + packet->setDataByName("unknown2", 1); //size 2 + packet->setDataByName("unknown2", .5, 1); //size 3 + packet->setDataByName("unknown2", 3, 2); + packet->setDataByName("unknown2", unknown2_3, 3); + packet->setDataByName("max_distance", 500); + packet->setDataByName("CoEunknown", 0xFFFFFFFF); + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void Client::SendFlightAutoMount(int32 path_id, int16 mount_id, int8 mount_red_color, int8 mount_green_color, int8 mount_blue_color) +{ + SetPendingFlightPath(path_id); + + ((Player*)player)->SetTempMount(((Entity*)player)->GetMount()); + ((Player*)player)->SetTempMountColor(((Entity*)player)->GetMountColor()); + ((Player*)player)->SetTempMountSaddleColor(((Entity*)player)->GetMountSaddleColor()); + + PacketStruct* packet = configReader.getStruct("WS_ReadyForTakeOff", GetVersion()); + if (packet) { + QueuePacket(packet->serialize()); + safe_delete(packet); + } + + if (mount_id) + ((Entity*)GetPlayer())->SetMount(mount_id, mount_red_color, mount_green_color, mount_blue_color); +} + +void Client::SendShowBook(Spawn* sender, string title, int8 language, int8 num_pages, ...) +{ + if (!sender) + { + LogWrite(CCLIENT__ERROR, 0, "Client", "SendShowBook missing sender for Player %s, book title %s", GetPlayer()->GetName(), title); + return; + } + + PacketStruct* packet = configReader.getStruct("WS_EqShowBook", GetVersion()); + if (!packet) { + LogWrite(CCLIENT__ERROR, 0, "Client", "WS_EqShowBook missing for version %u", GetVersion()); + return; + } + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(sender)); + packet->setDataByName("book_title", title.c_str()); + packet->setDataByName("book_type", "simple"); + packet->setDataByName("unknown2", 1); + if(language > 0 && !GetPlayer()->HasLanguage(language)) + packet->setDataByName("language", language); + + if (GetVersion() > 561) + packet->setDataByName("unknown5", 1, 4); + + packet->setArrayLengthByName("num_pages", num_pages); + + va_list args; + va_start(args, num_pages); + std::string endString(""); + for (int8 p = 0; p < num_pages; p++) + { + std::string page = va_arg(args, string); + switch (GetVersion()) + { + // release client + case 283: + case 373: // trial isle client + { + endString.append(page); + break; + } + // DoF trial + case 546: + case 561: + { + if (p == 0) + packet->setDataByName("cover_page", page.c_str()); + else + packet->setArrayDataByName("page_text", page.c_str(), p - 1); + break; + } + // all other clients + default: + { + packet->setArrayDataByName("page_text", page.c_str(), p); + break; + } + } + } + + if (GetVersion() <= 373) + { + packet->setDataByName("page_text", endString.c_str()); + } + + va_end(args); + + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void Client::SendShowBook(Spawn* sender, string title, int8 language, vector pages) +{ + if (!sender) + { + LogWrite(CCLIENT__ERROR, 0, "Client", "SendShowBook missing sender for Player %s, book title %s", GetPlayer()->GetName(), title); + return; + } + + PacketStruct* packet = configReader.getStruct("WS_EqShowBook", GetVersion()); + if (!packet) { + LogWrite(CCLIENT__ERROR, 0, "Client", "WS_EqShowBook missing for version %u", GetVersion()); + return; + } + packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(sender)); + packet->setDataByName("book_title", title.c_str()); + packet->setDataByName("book_type", "simple"); + packet->setDataByName("unknown2", 1); + + if(language > 0 && !GetPlayer()->HasLanguage(language)) + packet->setDataByName("language", language); + + if (GetVersion() > 561) + packet->setDataByName("unknown5", 1, 4); + + packet->setArrayLengthByName("num_pages", pages.size()); + + std::string endString(""); + for (int8 p = 0; p < pages.size(); p++) + { + Item::BookPage* page = pages[p]; + std::string pageText = string(page->page_text.data); + switch (GetVersion()) + { + // release client + case 283: + case 373: // trial isle client + { + endString.append(pageText); + break; + } + // DoF trial + case 546: + case 561: + { + if (p == 0) + packet->setDataByName("cover_page", pageText.c_str()); + else + packet->setArrayDataByName("page_text", pageText.c_str(), p - 1); + break; + } + // all other clients + default: + { + int8 valign = int8(page->valign); + int8 halign = int8(page->halign); + packet->setArrayDataByName("page_text", pageText.c_str(), p); + packet->setArrayDataByName("page_text_valign", valign, p); + packet->setArrayDataByName("page_text_halign", halign, p); + break; + } + } + } + + if (GetVersion() == 283) + { + packet->setDataByName("page_text", endString.c_str()); + } + + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +void Client::ReplaceGroupClient(Client* new_client) +{ + if (this->GetPlayer()->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); + if(group) + { + group->MGroupMembers.writelock(); + rejoin_group_id = this->GetPlayer()->GetGroupMemberInfo()->group_id; + this->GetPlayer()->GetGroupMemberInfo()->client = new_client; + this->GetPlayer()->GetGroupMemberInfo()->member = GetPlayer(); + group->MGroupMembers.releasewritelock(); + } + else + { + this->GetPlayer()->GetGroupMemberInfo()->client = 0; + this->GetPlayer()->GetGroupMemberInfo()->member = 0; + this->GetPlayer()->SetGroupMemberInfo(0); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } +} + +void Client::TempRemoveGroup() +{ + if (this->GetPlayer()->GetGroupMemberInfo()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(this->GetPlayer()->GetGroupMemberInfo()->group_id); + if(group) + { + group->MGroupMembers.writelock(); + rejoin_group_id = this->GetPlayer()->GetGroupMemberInfo()->group_id; + this->GetPlayer()->GetGroupMemberInfo()->client = 0; + this->GetPlayer()->GetGroupMemberInfo()->member = 0; + this->GetPlayer()->SetGroupMemberInfo(0); + group->MGroupMembers.releasewritelock(); + group->RemoveClientReference(this); + } + else + { + this->GetPlayer()->GetGroupMemberInfo()->client = 0; + this->GetPlayer()->GetGroupMemberInfo()->member = 0; + this->GetPlayer()->SetGroupMemberInfo(0); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } +} + +void Client::CreateMail(int32 charID, std::string fromName, std::string subjectName, std::string mailBody, +int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time) +{ + Mail mail; + mail.mail_id = 0; + mail.already_read = 0; + mail.postage_cost = 0; + mail.attachment_cost = 0; + mail.save_needed = 1; + //uhh...mail has std::strings so + //memset(&mail,0,sizeof(Mail)); + mail.player_to_id = charID; + mail.player_from = fromName; + mail.subject = subjectName; + mail.mail_body = mailBody; + + mail.mail_type = mailType; + + mail.coin_copper = copper; + mail.coin_silver = silver; + mail.coin_gold = gold; + mail.coin_plat = platinum; + + mail.char_item_id = item_id; + mail.stack = stack_size; + + mail.time_sent = time_sent; + mail.expire_time = expire_time; + + database.SavePlayerMail(&mail); +} + +void Client::CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, +int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time) +{ + Mail* mail = new Mail(); + mail->player_to_id = GetCharacterID(); + mail->player_from = fromName; + mail->subject = subjectName; + mail->mail_body = mailBody; + + mail->mail_type = mailType; + + mail->coin_copper = copper; + mail->coin_silver = silver; + mail->coin_gold = gold; + mail->coin_plat = platinum; + + mail->char_item_id = item_id; + mail->stack = stack_size; + + mail->time_sent = time_sent; + mail->expire_time = expire_time; + + mail->postage_cost = 0; + mail->save_needed = 1; + mail->already_read = 0; + database.SavePlayerMail(mail); + GetPlayer()->AddMail(mail); +} + +void Client::SendEquipOrInvUpdateBySlot(int8 slot) +{ + if(slot < NUM_SLOTS) + { + EQ2Packet* app = GetPlayer()->GetEquipmentList()->serialize(GetVersion(), GetPlayer()); + if (app) + QueuePacket(app); + } + else + { + EQ2Packet* outapp = GetPlayer()->SendInventoryUpdate(GetVersion()); + if (outapp) + QueuePacket(outapp); + } +} + +void Client::QueueStateCommand(int32 spawn_player_id, int32 state) +{ + if(spawn_player_id < 1) + return; + + MQueueStateCmds.writelock(); + queued_state_commands.insert(make_pair(spawn_player_id, state)); + MQueueStateCmds.releasewritelock(); +} + +void Client::ProcessStateCommands() +{ + if(!IsReadyForUpdates()) + return; + + MQueueStateCmds.writelock(); + map::iterator itr = queued_state_commands.begin(); + for(; itr != queued_state_commands.end(); itr++) + ClientPacketFunctions::SendStateCommand(this, itr->first, itr->second); + + queued_state_commands.clear(); + MQueueStateCmds.releasewritelock(); +} + +void Client::PurgeItem(Item* item) +{ + std::unique_lock lock(MConversation); + map::iterator itr; + for(itr = conversation_items.begin(); itr != conversation_items.end(); itr++) + { + if ( itr->second == item ) + { + conversation_items.erase(itr); + break; + } + } +} + +void Client::ConsumeFoodDrink(Item* item, int32 slot) +{ + if(GetPlayer()->StopSaveSpellEffects()) + return; + + if(item) { + LogWrite(MISC__INFO, 1, "Command", "ItemID: %u, ItemName: %s ItemCount: %i ", item->details.item_id, item->name.c_str(), item->details.count); + if(item->GetItemScript() && lua_interface){ + lua_interface->RunItemScript(item->GetItemScript(), "cast", item, GetPlayer()); + if (slot == 22) { + Message(CHANNEL_NARRATIVE, "You eat a %s.", item->name.c_str()); + GetPlayer()->SetActiveFoodUniqueID(item->details.unique_id); + } + else { + Message(CHANNEL_NARRATIVE, "You drink a %s.", item->name.c_str()); + GetPlayer()->SetActiveDrinkUniqueID(item->details.unique_id); + } + } + else { + Message(CHANNEL_NARRATIVE, "SERVER BUG! Item Script not assigned for consuming '%s'.", item->name.c_str()); + return; + } + + if (item->details.count > 1) { + item->details.count -= 1; + item->save_needed = true; + } + else { + database.DeleteItem(GetPlayer()->GetCharacterID(), item, "EQUIPPED"); + GetPlayer()->GetEquipmentList()->RemoveItem(slot, true); + + } + GetPlayer()->SetCharSheetChanged(true); + QueuePacket(player->GetEquipmentList()->serialize(GetVersion(), player)); + if(GetVersion() <= 373) { + EQ2Packet* outapp = GetPlayer()->SendInventoryUpdate(GetVersion()); + QueuePacket(outapp); + } + } +} + +void Client::AwardCoins(int64 total_coins, std::string reason) +{ + if (total_coins > 0) { + player->AddCoins(total_coins); + PlaySound("coin_cha_ching"); + char tmp[64] = { 0 }; + string message = "You receive "; + int32 val = 0; + if (total_coins >= 1000000) { + val = total_coins / 1000000; + total_coins -= 1000000 * val; + sprintf(tmp, "%u Platinum ", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 10000) { + val = total_coins / 10000; + total_coins -= 10000 * val; + sprintf(tmp, "%u Gold ", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins >= 100) { + val = total_coins / 100; + total_coins -= 100 * val; + sprintf(tmp, "%u Silver ", val); + message.append(tmp); + memset(tmp, 0, 64); + } + if (total_coins > 0) { + sprintf(tmp, "%u Copper ", (int32)total_coins); + message.append(tmp); + } + message.append(reason); + int8 type = CHANNEL_LOOT; + SimpleMessage(type, message.c_str()); + } +} + +void Client::TriggerSpellSave() +{ + int32 interval = rule_manager.GetGlobalRule(R_Spells, PlayerSpellSaveStateWaitInterval)->GetInt32(); + // default to not have some bogus value in the rule + if(interval < 1) + interval = 100; + + MSaveSpellStateMutex.lock(); + if(!save_spell_state_timer.Enabled()) + { + save_spell_state_time_bucket = 0; + save_spell_state_timer.Start(interval, true); + } + else + { + int32 elapsed_time = save_spell_state_timer.GetElapsedTime(); + save_spell_state_time_bucket += elapsed_time; + + int32 save_wait_cap = rule_manager.GetGlobalRule(R_Spells, PlayerSpellSaveStateCap)->GetInt32(); + + // default to not have some bogus value in the rule + if(save_wait_cap < interval) + save_wait_cap = interval+1; + + if(save_spell_state_time_bucket >= save_wait_cap) + { + save_spell_state_timer.Trigger(); + } + } + MSaveSpellStateMutex.unlock(); +} + +void Client::UpdateSentSpellList() { + MSpellDetails.readlock(__FUNCTION__, __LINE__); + std::map::iterator itr; + for(itr = sent_spell_details.begin(); itr != sent_spell_details.end(); itr++) { + Spell* spell = master_spell_list.GetSpell(itr->first, itr->second); + EQ2Packet* app = spell->SerializeSpell(this, false, false); + QueuePacket(app); + } + MSpellDetails.releasereadlock(__FUNCTION__, __LINE__); +} + +void Client::SetTempPlacementSpawn(Spawn* tmp) { + tempPlacementSpawn = tmp; + hasSentTempPlacementSpawn = false; + if(tempPlacementSpawn) + temp_placement_timer.Start(); + else + temp_placement_timer.Disable(); +} + +void Client::SetPlayer(Player* new_player) { + if (player && player != new_player) + zone_list.RemoveClientFromMap(player->GetName(), this); + + player = new_player; + player->SetClient(this); +} + +bool Client::UseItem(Item* item, Spawn* target) { + if (item && item->GetItemScript()) { + int16 item_index = item->details.index; + if(!item->CheckFlag2(INDESTRUCTABLE) && item->generic_info.condition == 0) { + SimpleMessage(CHANNEL_COLOR_RED, "This item is broken and must be repaired at a mender before it can be used"); + } + else if (item->CheckFlag(EVIL_ONLY) && GetPlayer()->GetAlignment() != ALIGNMENT_EVIL) { + Message(0, "%s requires an evil race.", item->name.c_str()); + } + else if (item->CheckFlag(GOOD_ONLY) && GetPlayer()->GetAlignment() != ALIGNMENT_GOOD) { + Message(0, "%s requires a good race.", item->name.c_str()); + } + else if (item->generic_info.max_charges == 0 || item->generic_info.max_charges == 0xFFFF) { + lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target); + return true; + } + else { + if (item->details.count > 0) { + std::string itemName = string(item->name); + int32 item_id = item->details.item_id; + sint64 flags = 0; + if(lua_interface->RunItemScript(item->GetItemScript(), "used", item, player, target, &flags) && flags >= 0) + { + //reobtain item make sure it wasn't removed + item = player->item_list.GetItemFromIndex(item_index); + if(!item) { + LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, however after the item looks to be removed.", GetPlayer()->GetName(), itemName.c_str(), item_id); + return true; + } + else if(!item->generic_info.display_charges && item->generic_info.max_charges == 1) { + Message(CHANNEL_NARRATIVE, "%s is out of charges. It has been removed.", item->name.c_str()); + RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity + return true; + } + else + { + item->details.count--; // charges + item->save_needed = true; + QueuePacket(item->serialize(GetVersion(), false, GetPlayer())); + if(!item->details.count) { + Message(CHANNEL_NARRATIVE, "%s is out of charges. It has been removed.", item->name.c_str()); + RemoveItem(item, 1); // end of a set of charges OR an item that uses a stack count of actual item quantity + } + return true; + } + } + else { + LogWrite(PLAYER__WARNING, 0, "Command", "%s: Item %s (%i) was used, after it returned %i, bypassing any removal/update of items.", GetPlayer()->GetName(), itemName.c_str(), item_id, flags); + return true; + } + } + else + { + //reobtain item make sure it wasn't removed + item = player->item_list.GetItemFromIndex(item_index); + SimpleMessage(CHANNEL_COLOR_YELLOW, "Item is out of charges."); + if(item) { + LogWrite(PLAYER__ERROR, 0, "Command", "%s: Item %s (%i) attempted to be used, however details.count is 0.", GetPlayer()->GetName(), item->name.c_str(), item->details.item_id); + } + } + } + } + return false; +} + +void Client::SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, + VoiceOverStruct* garble, bool success, bool garble_success) { + VoiceOverStruct* resStruct = nullptr; + + if(language == 0 || GetPlayer()->HasLanguage(language)) { + if(success) { + resStruct = non_garble; + } + } + else if(garble_success) { + resStruct = garble; + } + + if(resStruct) { + GetPlayer()->GetZone()->PlayFlavor(this, spawn, resStruct->mp3_string.c_str(), resStruct->text_string.c_str(), resStruct->emote_string.c_str(), resStruct->key1, resStruct->key2, language); + } +} + +void Client::SaveQuestRewardData(bool force_refresh) { + Query query; + if(force_refresh) { + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_rewards where char_id = %u", + GetCharacterID()); + + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_temporary_rewards where char_id = %u", + GetCharacterID()); + } + vector::iterator itr; + vector tmp_quest_rewards; + MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__); + int index = 0; + for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) { + int32 questID = (*itr)->quest_id; + if(!(*itr)->db_saved || force_refresh) { + query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_rewards (char_id, indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description) values(%u, %u, %u, %u, %u, %u, %llu, %u, '%s')", + GetCharacterID(), index, questID, (*itr)->is_temporary, (*itr)->is_collection, (*itr)->has_displayed, (*itr)->tmp_coin, (*itr)->tmp_status, database.getSafeEscapeString((*itr)->description.c_str()).c_str()); + (*itr)->db_saved = true; + (*itr)->db_index = index; + if(questID && (*itr)->is_temporary) { + std::vector items; + GetPlayer()->GetQuestTemporaryRewards(questID, &items); + if(!force_refresh && items.size() > 0) { + query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "delete from character_quest_temporary_rewards where char_id = %u and quest_id = %u", + GetCharacterID(), questID); + } + for(int i=0;idetails.item_id, items[i]->details.count); + } + } + } + index++; + } + MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__); +} + +void Client::UpdateCharacterRewardData(QuestRewardData* data) { + + if(!data) + return; + if(data->db_saved) { + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "update character_quest_rewards set is_temporary = %u, is_collection = %u, has_displayed = %u, tmp_coin = %llu, tmp_status = %u, description = '%s' where char_id=%u and indexed=%u and quest_id=%u", + data->is_temporary, data->is_collection, data->has_displayed, data->tmp_coin, data->tmp_status, database.getSafeEscapeString(data->description.c_str()).c_str(), GetCharacterID(), data->db_index, data->quest_id); + } +} + +void Client::AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i) { + + + int index = 0; + if(recipe == nullptr) + return; + + PlayerRecipeList* prl = GetPlayer()->GetRecipeList(); + if (prl->GetRecipe(recipe->GetID())) { + delete recipe; + return; + } + auto res = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + + if (res != devices.end()) + index = res - devices.begin(); + else + devices.push_back(recipe->GetDevice()); + + + prl->AddRecipe(recipe); + database.SavePlayerRecipe(GetPlayer(), recipe->GetID()); + Message(CHANNEL_NARRATIVE, "Recipe: \"%s\" put in recipe book.", recipe->GetName()); + + if (packet && GetRecipeListSent()) { + packet->setArrayDataByName("id", recipe->GetID(), *i); + packet->setArrayDataByName("tier", recipe->GetTier(), *i); + packet->setArrayDataByName("level", recipe->GetLevel(), *i); + packet->setArrayDataByName("icon", recipe->GetIcon(), *i); + packet->setArrayDataByName("classes", recipe->GetClasses(), *i); + //packet->setArrayDataByName("skill", recipe->GetSkill(), *i); + packet->setArrayDataByName("technique", recipe->GetTechnique(), *i); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), *i); + auto recipe_device = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + if (recipe_device != devices.end()) + packet->setArrayDataByName("device_type", recipe_device - devices.begin(), *i); + else + {//TODO error should never get here + } + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), *i); + packet->setArrayDataByName("recipe_name", recipe->GetName(), *i); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), *i); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), *i); + if(i) { + (*i)++; + } + } +} + +bool Client::SetPlayerPOVGhost(Spawn* spawn) { + if(!spawn) { + pov_ghost_spawn_id = 0; + SendCharPOVGhost(); + return true; + } + + int32 ghost_id = player->GetIDWithPlayerSpawn(spawn); + if(ghost_id) { + pov_ghost_spawn_id = spawn->GetID(); + SendCharPOVGhost(); + return true; + } + + return false; +} + +void Client::HandleDialogSelectMsg(int32 conversation_id, int32 response_index) { + std::string conversation = ""; + bool conv_established = false; + MConversation.lock_shared(); + if (conversation_map.count(conversation_id) > 0 && conversation_map[conversation_id].count(response_index) > 0) { + conversation = std::string(conversation_map[conversation_id][response_index].c_str()); + conv_established = true; + } + + int32 spawn_id = conversation_spawns[conversation_id]; + + Item* item = conversation_items[conversation_id]; + MConversation.unlock_shared(); + + if (GetCurrentZone()) { + Spawn* spawn = nullptr; + if(spawn_id) { + spawn = GetCurrentZone()->GetSpawnByID(spawn_id); + } + + if (conv_established) { + if (spawn) { + if(conversation == "CloseItemConversation") { + LogWrite(LUA__ERROR, 0, "LUA", "CloseItemConversation is an invalid function call for this conversation with spawn id %u", spawn_id); + } + else { + GetCurrentZone()->CallSpawnScript(spawn, SPAWN_SCRIPT_CONVERSATION, player, conversation.c_str()); + } + } + else if (item && lua_interface && item->GetItemScript()) + lua_interface->RunItemScript(item->GetItemScript(), conversation.c_str(), item, player); + else + CloseDialog(conversation_id); + } + else + CloseDialog(conversation_id); + } +} + + +bool Client::SetPetName(const char* petName) { + int8 result = database.CheckNameFilter(petName,4,31); + if (result == BADNAMELENGTH_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Name length is invalid, must be greater then 3 characters and less then 31."); + return false; + } + else if (result == NAMEINVALID_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is invalid, can only contain letters."); + return false; + } + else if (result == NAMETAKEN_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is already taken, please choose another."); + return false; + } + else if (result == NAMEFILTER_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Name failed the filter check."); + return false; + } + else if (result == UNKNOWNERROR_REPLY) { + SimpleMessage(CHANNEL_COLOR_YELLOW, "Unknown error while checking the name."); + return false; + } + + GetPlayer()->GetInfoStruct()->set_pet_name(petName); + return true; +} + +bool Client::CheckConsumptionAllowed(int16 slot, bool send_message) { + switch(slot) { + case EQ2_FOOD_SLOT: { + if(GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_FOOD)) { + if(send_message) { + Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!"); + } + return false; + } + break; + } + case EQ2_DRINK_SLOT: { + if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_DRINK)) + { + if(send_message) { + Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!"); + } + return false; + } + break; + } + default: { + if (GetVersion() <= 373) { + Item* item = GetPlayer()->item_list.GetItemFromIndex(slot); + if(item->IsFood()) { + if(item->IsFoodFood()) { + if(GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_FOOD)) { + if(send_message) { + Message(CHANNEL_NARRATIVE, "If you ate anymore you would explode!"); + } + return false; + } + return true; + } + else if(item->IsFoodDrink()) { + if (GetPlayer()->GetSpellEffectBySpellType(SPELL_TYPE_DRINK)) + { + if(send_message) { + Message(CHANNEL_NARRATIVE, "If you drank anymore you would explode!"); + } + return false; + } + return true; + } + } + } + return false; + break; + } + } + + return true; +} + +void Client::StartLinkdeadTimer() { + if(!linkdead_timer) { + int32 LD_Timer = rule_manager.GetGlobalRule(R_World, LinkDeadTimer)->GetInt32(); + LogWrite(CCLIENT__DEBUG, 0, "Client", "Starting linkdead timer for %s (timer %u seconds)", GetPlayer()->GetName(), (LD_Timer/1000)); + linkdead_timer = new Timer(LD_Timer); + linkdead_timer->Enable(); + + if(GetPlayer()->GetGroupMemberInfo()) { + LogWrite(CCLIENT__DEBUG, 0, "Client", "Telling player %s group they are disconnecting", GetPlayer()->GetName()); + world.GetGroupManager()->GroupMessage(GetPlayer()->GetGroupMemberInfo()->group_id, "%s has gone Linkdead.", GetPlayer()->GetName()); + } + } +} + +bool Client::IsLinkdeadTimerEnabled() { + if(linkdead_timer) { + return linkdead_timer->Enabled(); + } + + return false; +} + +void Client::SendNewAdventureSpells() { + SendNewSpells(player->GetAdventureClass()); + int8 base_class = classes.GetBaseClass(player->GetAdventureClass()); + int secondary_class = classes.GetSecondaryBaseClass(player->GetAdventureClass()); + if(base_class != player->GetAdventureClass()) { + SendNewSpells(base_class); + } + if(secondary_class != player->GetAdventureClass() && secondary_class != base_class) { + SendNewSpells(secondary_class); + } +} + +void Client::SendNewTradeskillSpells() { + SendNewTSSpells(player->GetTradeskillClass()); + int8 secondary_class = classes.GetSecondaryTSBaseClass(player->GetTradeskillClass()); + if(secondary_class != player->GetTradeskillClass()) { + SendNewTSSpells(secondary_class); + } +} + +bool Client::AddRecipeBookToPlayer(int32 recipe_book_id, Item* item) { + Recipe* master_recipe = master_recipebook_list.GetRecipeBooks(recipe_book_id); + + if(master_recipe) { + Recipe* recipe_book = new Recipe(master_recipe); + // if valid recipe book and the player doesn't have it + if (recipe_book && recipe_book->GetLevel() > GetPlayer()->GetTSLevel()) { + if(item) { + Message(CHANNEL_NARRATIVE, "Your tradeskill level is not high enough to scribe this book."); + } + safe_delete(recipe_book); + } + else if(recipe_book && item && !recipe_book->CanUseRecipeByClass(item, GetPlayer()->GetTradeskillClass())) { + if(item) { + Message(CHANNEL_NARRATIVE, "Your tradeskill class cannot use this recipe."); + } + safe_delete(recipe_book); + } + else if (recipe_book && (!item || !(GetPlayer()->GetRecipeBookList()->HasRecipeBook(recipe_book_id)))){ + LogWrite(PLAYER__DEBUG, 0, "Recipe", "Valid recipe book that the player doesn't have"); + // Add recipe book to the players list + if(!GetPlayer()->GetRecipeBookList()->HasRecipeBook(recipe_book_id)) { + GetPlayer()->GetRecipeBookList()->AddRecipeBook(recipe_book); + } + + std::vector recipes; + // Get a list of all recipes this book contains + if (item && item->recipebook_info) { + //Backup I guess if the recipe book is empty for whatever reason? + for (auto& itr : item->recipebook_info->recipes) { + Recipe* r = master_recipe_list.GetRecipeByCRC(itr); //GetRecipeByName(itr.c_str()); + if (r) { + recipes.push_back(r); + } + } + LogWrite(PLAYER__DEBUG, 0, "Recipe", "%i recipes found for %s book", recipes.size(), recipe_book->GetBookName()); + } + else { + LogWrite(PLAYER__ERROR, 0, "Recipe", "no recipes found for %s book", recipe_book->GetBookName()); + } + + //Filter out duplicate recipes the player already has + for (auto itr = recipes.begin(); itr != recipes.end();) { + Recipe* recipe = *itr; + if (GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())) { + itr = recipes.erase(itr); + } + else itr++; + } + + int16 i = 0; + // Create the packet to send to update the players recipe list + PacketStruct* packet = 0; + if (!recipes.empty() && GetRecipeListSent()) { + packet = configReader.getStruct("WS_RecipeList", GetVersion()); + if (packet) { + packet->setArrayLengthByName("num_recipes", recipes.size()); + } + } + + for (int32 r = 0; r < recipes.size(); r++) { + Recipe* recipe = recipes[r]; + if (recipe) { + Recipe* player_recipe = new Recipe(recipe); + AddRecipeToPlayerPack(player_recipe, packet, &i); + } + } + + LogWrite(TRADESKILL__DEBUG, 0, "Recipe", "Done adding recipes"); + database.SavePlayerRecipeBook(GetPlayer(), recipe_book->GetBookID()); + if(item) { + database.DeleteItem(GetCharacterID(), item, 0); + GetPlayer()->item_list.RemoveItem(item, true); + } + QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion())); + + SetRecipeListSent(false); + SendRecipeList(); + safe_delete(packet); + return true; + } + else { + if (recipe_book && item) { + Message(CHANNEL_NARRATIVE, "You have already learned all you can from this item."); + } + safe_delete(recipe_book); + } + } + else { + LogWrite(PLAYER__ERROR, 0, "Player", "%u recipe book id does not exist. Cannot AddRecipeToPlayer.", recipe_book_id); + } + return false; +} + + +bool Client::RemoveRecipeFromPlayer(int32 recipe_id) { + PlayerRecipeList* prl = GetPlayer()->GetRecipeList(); + + PacketStruct* packet = configReader.getStruct("WS_RecipeList", version); + Recipe* recipe = prl->GetRecipe(recipe_id); + int8 level = player->GetTSLevel(); + if(packet && recipe) { + packet->setDataByName("command_type", 1); + packet->setArrayLengthByName("num_recipes", 1); + int32 myid = recipe->GetID(); + int8 rlevel = recipe->GetLevel(); + int8 even = level - level * .05 + .5; + int8 easymin = level - level * .25 + .5; + int8 veasymin = level - level * .35 + .5; + if (rlevel > level ) + packet->setArrayDataByName("tier", 4, 0); + else if ((rlevel <= level) & (rlevel >= even)) + packet->setArrayDataByName("tier", 3, 0); + else if ((rlevel <= even) & (rlevel >= easymin)) + packet->setArrayDataByName("tier", 2, 0); + else if ((rlevel <= easymin) & (rlevel >= veasymin)) + packet->setArrayDataByName("tier", 1, 0); + else if ((rlevel <= veasymin) & (rlevel >= 0)) + packet->setArrayDataByName("tier", 0, 0); + if (rlevel == 2) + int xxx = 1; + packet->setArrayDataByName("recipe_id", myid, 0); + packet->setArrayDataByName("level", recipe->GetLevel(), 0); + packet->setArrayDataByName("unknown1", recipe->GetLevel(), 0); + packet->setArrayDataByName("icon", recipe->GetIcon(), 0); + packet->setArrayDataByName("classes", recipe->GetClasses(), 0); + packet->setArrayDataByName("technique", recipe->GetTechnique(), 0); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), 0); + + + auto recipe_device = std::find(devices.begin(), devices.end(), recipe->GetDevice()); + if (recipe_device != devices.end()) + packet->setArrayDataByName("device_type", recipe_device - devices.begin(), 0); + else + {//TODO error should never get here + } + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), 0); + packet->setArrayDataByName("recipe_name", recipe->GetName(), 0); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), 0); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), 0); + QueuePacket(packet->serialize()); + } + safe_delete(packet); + + bool res = prl->RemoveRecipe(recipe_id); + if(res) { + Query query; + query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "DELETE FROM character_recipes where char_id=%u and recipe_id=%u", GetCharacterID(), recipe_id); + } + return res; +} + +void Client::SaveSpells() { + MSaveSpellStateMutex.lock(); + player->SaveSpellEffects(); + player->SetSaveSpellEffects(true); + MSaveSpellStateMutex.unlock(); +} + +void Client::SendReplaceWidget(int32 widget_id, bool delete_widget, float x, float y, float z, int32 grid_id) { + Widget* new_spawn = new Widget(); + new_spawn->SetWidgetID(widget_id); + new_spawn->SetLocation(grid_id); + new_spawn->SetWidgetX(x); + new_spawn->SetWidgetY(y); + new_spawn->SetWidgetZ(z); + new_spawn->SetX(x); + new_spawn->SetY(y); + new_spawn->SetZ(z); + + EQ2Packet* ret = new_spawn->serialize(GetPlayer(), GetVersion()); + QueuePacket(ret); + + // we have to delete spawn* references anyway, we don't keep this widget live in the spawn list + GetPlayer()->RemoveSpawn(new_spawn, delete_widget); + + safe_delete(new_spawn); +} + +void Client::ProcessZoneIgnoreWidgets() { + GetPlayer()->MIgnoredWidgets.lock_shared(); + std::map::iterator itr; + for(itr = GetPlayer()->ignored_widgets.begin(); itr != GetPlayer()->ignored_widgets.end(); itr++) { + SendReplaceWidget(itr->first, true); + } + GetPlayer()->MIgnoredWidgets.unlock_shared(); +} + +void Client::PopulateRecipeData(Recipe* recipe, PacketStruct* packet, int i) { + if(!recipe || !packet) + return; + + int8 level = player->GetTSLevel(); + int32 myid = recipe->GetID(); + int8 rlevel = recipe->GetLevel(); + int8 even = level - level * .05 + .5; + int8 easymin = level - level * .25 + .5; + int8 veasymin = level - level * .35 + .5; + if (rlevel > level ) + packet->setArrayDataByName("tier", 4, i); + else if ((rlevel <= level) & (rlevel >= even)) + packet->setArrayDataByName("tier", 3, i); + else if ((rlevel <= even) & (rlevel >= easymin)) + packet->setArrayDataByName("tier", 2, i); + else if ((rlevel <= easymin) & (rlevel >= veasymin)) + packet->setArrayDataByName("tier", 1, i); + else if ((rlevel <= veasymin) & (rlevel >= 0)) + packet->setArrayDataByName("tier", 0, i); + if (rlevel == 2) + int xxx = 1; + packet->setArrayDataByName("recipe_id", myid, i); + packet->setArrayDataByName("level", recipe->GetLevel(), i); + packet->setArrayDataByName("icon", recipe->GetIcon(), i); + packet->setArrayDataByName("classes", recipe->GetClasses(), i); + packet->setArrayDataByName("technique", recipe->GetTechnique(), i); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), i); + packet->setArrayDataByName("device", recipe->GetDevice(), i); + packet->setArrayDataByName("device_sub_type", recipe->GetDevice_Sub_Type(), i); + packet->setArrayDataByName("recipe_name", recipe->GetName(), i); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), i); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), i); + + packet->setArrayDataByName("book_volume", 0x01, i); + packet->setArrayDataByName("device_id", 0x01, i); +} + +int32 Client::GetRecipeCRC(Recipe* recipe) { + + PacketStruct* packet = 0; + if (!(packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()))) { + return 0; + } + packet->setArrayLengthByName("num_recipes", 1); + + PopulateRecipeData(recipe, packet); + + string* generic_string_data = packet->serializeString(); + int32 size = generic_string_data->length(); + + if(size < 5) + return 0; + + uchar* out_data = new uchar[size+1]; + uchar* out_ptr = out_data; + memcpy(out_ptr, (uchar*)generic_string_data->c_str()+4, generic_string_data->length()-4); + uint32 out_crc = GenerateCRCRecipe(0, (void*)out_ptr, size-4); + safe_delete(packet); + safe_delete_array(out_data); + + return out_crc; +} + +void Client::SendRecipeDetails(vector* recipes) { + if(!recipes || recipes->size() == 0) + return; + + PacketStruct* packet = 0; + if (!(packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()))) { + return; + } + int32 recipe_size = player->GetRecipeList()->Size(); + packet->setArrayLengthByName("num_recipes", recipe_size > 100 ? 100 : recipe_size); + int16 i = 0; + int32 count = 0; + vector::iterator recipe_itr; + for(recipe_itr = recipes->begin(); recipe_itr != recipes->end(); recipe_itr++) { + Recipe* recipe = player->GetRecipeList()->GetRecipe(*recipe_itr); + if(!recipe) { + continue; + } + else if(i > 99) { + QueuePacket(packet->serialize()); + safe_delete(packet); + packet = configReader.getStruct("WS_RecipeDetailList", GetVersion()); + recipe_size -= i; + packet->setArrayLengthByName("num_recipes", recipe_size > 100 ? 100 : recipe_size); + i = 0; + } + count++; + PopulateRecipeData(recipe, packet, i); + i++; + } + + //packet->PrintPacket(); + + QueuePacket(packet->serialize()); + safe_delete(packet); +} + +ZoneServer* Client::GetHouseZoneServer(int32 spawn_id, int64 house_id) { + PlayerHouse* ph = nullptr; + HouseZone* hz = nullptr; + if(spawn_id) { + Spawn* houseWidget = GetPlayer()->GetSpawnByIndex(spawn_id); + if(houseWidget && houseWidget->IsWidget() && ((Widget*)houseWidget)->GetHouseID()) { + hz = world.GetHouseZone(((Widget*)houseWidget)->GetHouseID()); + if (hz) { + ph = world.GetPlayerHouseByHouseID(GetPlayer()->GetCharacterID(), hz->id); + } else { + Message(CHANNEL_COLOR_YELLOW, "HouseWidget spawn index %u house zone could not be found.", spawn_id); + } + } + } + + if(!ph && house_id) { + ph = world.GetPlayerHouseByUniqueID(house_id); + if (ph) { + hz = world.GetHouseZone(ph->house_id); + } + } + + if (ph && hz) { + ZoneServer* house = zone_list.GetByInstanceID(ph->instance_id, hz->zone_id, false, true); + return house; + } + + return nullptr; +} + +void Client::SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time) { + PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", GetVersion()); + if (packet) { + int32 caster_id = GetPlayer()->GetIDWithPlayerSpawn(caster); + int32 target_id = GetPlayer()->GetIDWithPlayerSpawn(target); + + packet->setDataByName("spawn_id", caster_id); + packet->setArrayLengthByName("num_targets", 1); + packet->setArrayDataByName("target", target_id); + packet->setDataByName("num_targets", 1); + + int32 visual = GetSpellVisualOverride(spell_visual); + + packet->setDataByName("spell_visual", visual); //result + packet->setDataByName("cast_time", cast_time*.01f); //delay + packet->setDataByName("spell_id", 1); + packet->setDataByName("spell_level", 1); + packet->setDataByName("spell_tier", 1); + EQ2Packet* outapp = packet->serialize(); + + DumpPacket(outapp); + QueuePacket(outapp); + safe_delete(packet); + } +} + +int32 Client::GetSpellVisualOverride(int32 spell_visual) { + int32 visual = spell_visual; + if(GetVersion() <= 561) { // spell's spell_visual field is based on newer clients, DoF has to convert + Emote* spellVisualEmote = visual_states.FindSpellVisualByID(visual, 60085); + if(spellVisualEmote != nullptr && spellVisualEmote->GetMessageString().size() > 0) { + spellVisualEmote = visual_states.FindSpellVisual(spellVisualEmote->GetMessageString(), GetVersion()); + if(spellVisualEmote) { + visual = (int32)spellVisualEmote->GetVisualState(); + } + } + } + + return visual; +} \ No newline at end of file diff --git a/source/WorldServer/client.h b/source/WorldServer/client.h new file mode 100644 index 0000000..805a35b --- /dev/null +++ b/source/WorldServer/client.h @@ -0,0 +1,767 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef CLIENT_H +#define CLIENT_H + +#include +#include +#include +#include + +#include "../common/EQStream.h" +#include "../common/timer.h" +#include "Items/Items.h" +#include "zoneserver.h" +#include "Player.h" +#include "Quests.h" + +using namespace std; +#define CLIENT_TIMEOUT 60000 +struct TransportDestination; +struct ConversationOption; +struct VoiceOverStruct; + +#define MAIL_SEND_RESULT_SUCCESS 0 +#define MAIL_SEND_RESULT_UNKNOWN_PLAYER 1 +#define MAIL_SEND_RESULT_CANNOT_SEND_TO_PLAYER 2 +#define MAIL_SEND_RESULT_GIFT_WRONG_SERVER 3 /* Cannot send gifts across worlds */ +#define MAIL_SEND_RESULT_CANNOT_SEND_TO_SELF 4 +#define MAIL_SEND_RESULT_MAILBOX_FULL 5 +#define MAIL_SEND_RESULT_NOT_ENOUGH_COIN 6 +#define MAIL_SEND_RESULT_ITEM_IN_BAG 7 /* Cannot send non-empty bags as gifts */ +#define MAIL_SEND_RESULT_NOT_IN_GUILD 8 +#define MAIL_SEND_RESULT_GUILD_ACCESS_DENIED 9 +#define MAIL_SEND_RESULT_GIFTS_TO_GUILD 10 /* Cannot send gifts to entire guild */ +#define MAIL_SEND_RESULT_EMPTY_TO_LIST 11 /* Empty recipient list */ +#define MAIL_SEND_RESULT_TRIAL_PLAYERS 12 /* Cannot send mail to trial players */ +#define MAIL_SEND_RESULT_MAIL_WRONG_SERVER 13 /* Cannot send mail across worlds */ +#define MAIL_SEND_RESULT_UNKNOWN_ERROR 14 + +#define MAIL_TYPE_REGULAR 0 +#define MAIL_TYPE_SPAM 1 +#define MAIL_TYPE_GM 2 + +struct QueuedQuest{ + int32 quest_id; + int32 step; + bool display_quest_helper; +}; + +struct BuyBackItem{ + int32 item_id; + int32 unique_id; + int16 quantity; + int32 price; + bool save_needed; +}; + +struct MacroData{ + string name; + string text; + int16 icon; +}; + +struct Mail { + int32 mail_id; + int32 player_to_id; + string player_from; + string subject; + string mail_body; + int8 already_read; + int8 mail_type; + int32 coin_copper; + int32 coin_silver; + int32 coin_gold; + int32 coin_plat; + int16 stack; + int32 postage_cost; + int32 attachment_cost; + int32 char_item_id; + int32 time_sent; + int32 expire_time; + int8 save_needed; +}; + +struct MailWindow { + int32 coin_copper; + int32 coin_silver; + int32 coin_gold; + int32 coin_plat; + Item* item; + int32 char_item_id; + int32 stack; +}; + +struct PendingGuildInvite { + Guild* guild; + Player* invited_by; +}; + +struct PendingResurrection { + Spawn* caster; + Timer* expire_timer; + string spell_name; + string heal_name; + bool active; + float hp_perc; + float mp_perc; + float range; + int8 crit_mod; + bool no_calcs; + int32 subspell; + bool crit; + bool should_delete; + int32 spell_visual; +}; + +#define PAPERDOLL_TYPE_FULL 0 +#define PAPERDOLL_TYPE_HEAD 1 + +struct IncomingPaperdollImage { + uchar* image_bytes; + int32 current_size_bytes; + int8 image_num_packets; + int8 last_received_packet_index; + int8 image_type; +}; +struct WaypointInfo { + int32 id; + int8 type; +}; + + +class Client { +public: + Client(EQStream* ieqs); + ~Client(); + + void RemoveClientFromZone(); + bool Process(bool zone_process = false); + void Disconnect(bool send_disconnect = true); + void SetConnected(bool val){ connected = val; } + bool IsConnected(){ return connected; } + bool IsReadyForSpawns(){ return ready_for_spawns; } + bool IsReadyForUpdates() { return ready_for_updates; } + bool IsZoning(){ return client_zoning; } + void SetReadyForUpdates(); + void SetReadyForSpawns(bool val); + void QueuePacket(EQ2Packet* app, bool attemptedCombine=false); + void SendLoginInfo(); + int8 GetMessageChannelColor(int8 channel_type); + void HandleTellMessage(Client* from, const char* message, const char* to, int32 current_language_id); + void SimpleMessage(int8 color, const char* message); + void Message(int8 type, const char* message, ...); + void SendSpellUpdate(Spell* spell, bool add_silently = false, bool add_to_hotbar = true); + void Zone(ZoneServer* new_zone, bool set_coords = true, bool is_spell = false); + void Zone(const char* new_zone, bool set_coords = true, bool is_spell = false); + void Zone(int32 instanceid, bool set_coords = true, bool byInstanceID=false, bool is_spell = false); + void SendZoneInfo(); + void SendZoneSpawns(); + void HandleVerbRequest(EQApplicationPacket* app); + void SendControlGhost(int32 send_id=0xFFFFFFFF, int8 unknown2=0); + void SendCharInfo(); + void SendLoginDeniedBadVersion(); + void SendCharPOVGhost(); + void SendPlayerDeathWindow(); + float DistanceFrom(Client* client); + void SendDefaultGroupOptions(); + bool HandleLootItemByID(Spawn* entity, int32 item_id, Spawn* target); + bool HandleLootItem(Spawn* entity, Item* item, Spawn* target=nullptr, bool overrideLootRestrictions = false); + void HandleLootItemRequestPacket(EQApplicationPacket* app); + void HandleSkillInfoRequest(EQApplicationPacket* app); + void HandleExamineInfoRequest(EQApplicationPacket* app); + void HandleQuickbarUpdateRequest(EQApplicationPacket* app); + void SendPopupMessage(int8 unknown, const char* text, const char* type, float size, int8 red, int8 green, int8 blue); + void PopulateSkillMap(); + void ChangeLevel(int16 old_level, int16 new_level); + void ChangeTSLevel(int16 old_level, int16 new_level); + bool Summon(const char* search_name); + std::string IdentifyInstanceLockout(int32 zoneID, bool displayClient = true); + ZoneServer* IdentifyInstance(int32 zoneID); + bool TryZoneInstance(int32 zoneID, bool zone_coords_valid=false); + bool GotoSpawn(const char* search_name, bool forceTarget=false); + void DisplayDeadWindow(); + void HandlePlayerRevive(int32 point_id); + void Bank(Spawn* banker, bool cancel = false); + void BankWithdrawal(int64 amount); + bool BankWithdrawalNoBanker(int64 amount); + bool BankHasCoin(int64 amount); + void BankDeposit(int64 amount); + Spawn* GetBanker(); + void SetBanker(Spawn* in_banker); + bool AddItem(int32 item_id, int16 quantity = 0, AddItemType type = AddItemType::NOT_SET); + bool AddItem(Item* item, bool* item_deleted = 0, AddItemType type = AddItemType::NOT_SET); + bool AddItemToBank(int32 item_id, int16 quantity = 0); + bool AddItemToBank(Item* item); + void UnequipItem(int16 index, sint32 bag_id = -999, int8 to_slot = 255, int8 appearance_equip = 0); + bool RemoveItem(Item *item, int16 quantity, bool force_override_no_delete = false); + void ProcessTeleport(Spawn* spawn, vector* destinations, int32 transport_id = 0, bool is_spell = false); + void ProcessTeleportLocation(EQApplicationPacket* app); + + void UpdateCharacterInstances(); + void SetLastSavedTimeStamp(int32 unixts) { last_saved_timestamp = unixts; } + int32 GetLastSavedTimeStamp() { return last_saved_timestamp; } + + bool CheckZoneAccess(const char* zoneName); + + ZoneServer* GetCurrentZone(); + void SetCurrentZoneByInstanceID(int32 id, int32 zoneid); + //void SetCurrentZoneByInstanceID(instanceid, zoneid); + void SetCurrentZone(int32 id); + void SetCurrentZone(ZoneServer* zone); + void SetZoningDestination(ZoneServer* zone) { + zoning_destination = zone; + } + ZoneServer* GetZoningDestination() { return zoning_destination; } + Player* GetPlayer(){ return player; } + EQStream* getConnection(){ return eqs; } + void setConnection(EQStream* ieqs){ eqs = ieqs; } + + inline int32 GetIP() { return ip; } + inline int16 GetPort() { return port; } + inline int32 WaitingForBootup() { return pwaitingforbootup; } + inline int32 GetCharacterID() { return character_id; } + inline int32 GetAccountID() { return account_id; } + inline const char* GetAccountName() { return account_name; } + inline sint16 GetAdminStatus() { return admin_status; } + inline int16 GetVersion() { return version; } + void SetNameCRC(int32 val){ name_crc = val; } + int32 GetNameCRC(){ return name_crc; } + + + void SetVersion(int16 new_version){ version = new_version; } + void SetAccountID(int32 in_accountid) { account_id = in_accountid; } + void SetCharacterID(int32 in_characterid) { character_id = in_characterid; } + void SetAdminStatus(sint16 in_status) { admin_status = in_status; } + + + void DetermineCharacterUpdates ( ); + + void UpdateTimeStampFlag ( int8 flagType ) + { + if(! (timestamp_flag & flagType ) ) + timestamp_flag |= flagType; + } + + int8 GetTimeStampFlag ( ) { return timestamp_flag; } + bool UpdateQuickbarNeeded(); + void Save(); + bool remove_from_list; + void CloseLoot(int32 spawn_id); + void SendLootResponsePacket(int32 total_coins, vector* items, Spawn* entity, bool ignore_loot_tier = false); + void LootSpawnRequest(Spawn* entity, bool attemptDisarm=true); + bool LootSpawnByMethod(Spawn* entity); + void OpenChest(Spawn* entity, bool attemptDisarm=true); + void CastGroupOrSelf(Entity* source, uint32 spellID, uint32 spellTier=1, float restrictiveRadius=0.0f); + void CheckPlayerQuestsKillUpdate(Spawn* spawn); + void CheckPlayerQuestsChatUpdate(Spawn* spawn); + void CheckPlayerQuestsItemUpdate(Item* item); + void CheckPlayerQuestsSpellUpdate(Spell* spell); + void CheckPlayerQuestsLocationUpdate(); + void AddPendingQuest(Quest* quest, bool forced = false); + void AcceptQuest(int32 quest_id); + void RemovePendingQuest(int32 quest_id); + void SetPlayerQuest(Quest* quest, map* progress); + void AddPlayerQuest(Quest* quest, bool call_accepted = true, bool send_packets = true); + void RemovePlayerQuest(int32 id, bool send_update = true, bool delete_quest = true); + void SendQuestJournal(bool all_quests = false, Client* client = 0, bool updated = true); + void SendQuestUpdate(Quest* quest); + void SendQuestFailure(Quest* quest); + void SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper = true); + void SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper = true); + void DisplayQuestRewards(Quest* quest, int64 coin, vector* rewards=0, vector* selectable_rewards=0, map* factions=0, const char* header="Quest Reward!", int32 status_points=0, const char* text=0, bool was_displayed = false); + void PopulateQuestRewardItems(vector * items, PacketStruct* packet, std::string num_rewards_str = "num_rewards", std::string reward_id_str = "reward_id" , std::string item_str = "item"); + void DisplayQuestComplete(Quest* quest, bool tempReward = false, std::string customDescription = string(""), bool was_displayed = false); + void DisplayRandomizeFeatures(int32 features); + void AcceptQuestReward(Quest* quest, int32 item_id); + Quest* GetPendingQuestAcceptance(int32 item_id); + void DisplayConversation(int32 conversation_id, int32 spawn_id, vector* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language = 0, int8 can_close = 1); + void DisplayConversation(Item* item, vector* conversations, const char* text, int8 type, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0, int8 language = 0, int8 can_close = 1); + void DisplayConversation(Spawn* src, int8 type, vector* conversations, const char* text, const char* mp3 = 0, int32 key1 = 0, int32 key2 = 0, int8 language = 0, int8 can_close = 1); + void CloseDialog(int32 conversation_id); + int32 GetConversationID(Spawn* spawn, Item* item); + void CombineSpawns(float radius, Spawn* spawn); + void AddCombineSpawn(Spawn* spawn); + void RemoveCombineSpawn(Spawn* spawn); + void SaveCombineSpawns(const char* name = 0); + Spawn* GetCombineSpawn(); + bool ShouldTarget(); + void TargetSpawn(Spawn* spawn); + void ReloadQuests(); + int32 GetCurrentQuestID(){ return current_quest_id; } + void SetLuaDebugClient(bool val); + void SetMerchantTransaction(Spawn* spawn); + Spawn* GetMerchantTransaction(); + void SetMailTransaction(Spawn* spawn); + Spawn* GetMailTransaction(); + void PlaySound(const char* name); + void SendBuyMerchantList(bool sell = false); + void SendSellMerchantList(bool sell = false); + void SendBuyBackList(bool sell = false); + void SendRepairList(); + void ShowLottoWindow(); + void PlayLotto(int32 price, int32 ticket_item_id); + void SendGuildCreateWindow(); + float CalculateBuyMultiplier(int32 merchant_id); + float CalculateSellMultiplier(int32 merchant_id); + void BuyItem(int32 item_id, int16 quantity); + void SellItem(int32 item_id, int16 quantity, int32 unique_id = 0); + void BuyBack(int32 item_id, int16 quantity); + void RepairItem(int32 item_id); + void RepairAllItems(); + void AddBuyBack(int32 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed = true); + deque* GetBuyBacks(); + vector* GetRepairableItems(); + vector* GetItemsByEffectType(ItemEffectType type, ItemEffectType secondary_effect = NO_EFFECT_TYPE); + void SendMailList(); + void DisplayMailMessage(int32 mail_id); + void HandleSentMail(EQApplicationPacket* app); + void DeleteMail(int32 mail_id, bool from_database = false); + bool AddMailItem(Item* item); + bool AddMailCoin(int32 copper, int32 silver = 0, int32 gold = 0, int32 plat = 0); + bool RemoveMailCoin(int32 copper, int32 silver = 0, int32 gold = 0, int32 plat = 0); + void TakeMailAttachments(int32 mail_id); + void ResetSendMail(bool cancel = true, bool needslock = true); + bool GateAllowed(); + bool BindAllowed(); + bool Bind(); + bool Gate(bool is_spell = false); + void SendChatRelationship(int8 type, const char* name); + void SendFriendList(); + void SendIgnoreList(); + void SendNewAdventureSpells(); + void SendNewTradeskillSpells(); + string GetCoinMessage(int32 total_coins); + void SetItemSearch(vector* items); + vector* GetSearchItems(); + void SearchStore(int32 page); + void SetPlayer(Player* new_player); + + void AddPendingQuestAcceptReward(Quest* quest); + void AddPendingQuestReward(Quest* quest, bool update=true, bool is_temporary = false, std::string description = std::string("")); + bool HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection); + void QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved=false, int32 index=0); + void RemoveQueuedQuestReward(); + void AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress = 0xFFFFFFFF); + void ProcessQuestUpdates(); + void AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id); + void BeginWaypoint(const char* waypoint_name, float x, float y, float z); + void InspectPlayer(Player* player_to_inspect); + void SetPendingGuildInvite(Guild* guild, Player* invited_by = 0); + PendingGuildInvite* GetPendingGuildInvite() {return &pending_guild_invite;} + void ShowClaimWindow(); + void ShowGuildSearchWindow(); + void CheckQuestQueue(); + void ShowDressingRoom(Item *item, sint32 crc); + void SendCollectionList(); + bool SendCollectionsForItem(Item *item); + void HandleCollectionAddItem(int32 collection_id, Item *item); + void DisplayCollectionComplete(Collection *collection); + void HandInCollections(); + void AcceptCollectionRewards(Collection *collection, int32 selectable_item_id = 0); + void SendRecipeList(); + void PopulateRecipeData(Recipe* recipe, PacketStruct* packet, int i=0); + int32 GetRecipeCRC(Recipe* recipe); + void SendRecipeDetails(vector* recipes); + void SendTitleUpdate(); + void SendUpdateTitles(sint32 prefix, sint32 suffix); + void SendLanguagesUpdate(int32 id, bool setlang = 1); + void SendAchievementsList(); + void SendAchievementUpdate(bool first_login = false); + + ///Send the pet options window to the client + ///Type of pet, 1 = combat 0 = non combat + void SendPetOptionsWindow(const char* pet_name, int8 type = 1); + void SendBiography(); + + bool IsCrafting(); + + void SetRecipeListSent(bool val) {m_recipeListSent = val; } + bool GetRecipeListSent() { return m_recipeListSent; } + void ShowRecipeBook(); + PendingResurrection* GetCurrentRez(); + void SendResurrectionWindow(); + void AcceptResurrection(); + Mutex m_resurrect; + Mutex* GetResurrectMutex(); + void SetPendingLastName(string last_name); + void RemovePendingLastName(); + string* GetPendingLastName(); + void SendLastNameConfirmation(); + + void SetInitialSpawnsSent(bool val) { initial_spawns_sent = val; } + + bool GetInitialSpawnsSent() { return initial_spawns_sent; } + + void SendQuestJournalUpdate(Quest* quest, bool updated=true); + + void AddQuestTimer(int32 quest_id); + + void RemoveQuestTimer(int32 quest_id); + + void SetPendingFlightPath(int32 val) { pending_flight_path = val; } + int32 GetPendingFlightPath() { return pending_flight_path; } + + void EndAutoMount(); + bool GetOnAutoMount() { return on_auto_mount; } + + bool IsCurrentTransmuteID(int32 trans_id); + void SetTransmuteID(int32 trans_id); + int32 GetTransmuteID(); + + enum ServerSpawnPlacementMode { DEFAULT, OPEN_HEADING, CLOSE_HEADING }; + void SetSpawnPlacementMode(ServerSpawnPlacementMode mode) { spawnPlacementMode = mode; } + ServerSpawnPlacementMode GetSpawnPlacementMode() { return spawnPlacementMode; } + + bool HandleNewLogin(int32 account_id, int32 access_code); + void SendSpawnChanges(set& spawns); + void MakeSpawnChangePacket(map info_changes, map pos_changes, map vis_changes, int32 info_size, int32 pos_size, int32 vis_size); + + bool IsZonedIn() { return connected_to_zone; } + + void SendHailCommand(Spawn* target); + void SendDefaultCommand(Spawn* spawn, const char* command, float distance); + + void SetTempPlacementSpawn(Spawn* tmp); + + Spawn* GetTempPlacementSpawn() { return tempPlacementSpawn; } + + void SetPlacementUniqueItemID(int32 id) { placement_unique_item_id = id; } + int32 GetPlacementUniqueItemID() { return placement_unique_item_id; } + + void SetHasOwnerOrEditAccess(bool val) { hasOwnerOrEditAccess = val; } + bool HasOwnerOrEditAccess() { return hasOwnerOrEditAccess; } + + bool HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command); + // find an appropriate spawn to use for the house object, save spawn location/entry data to DB + bool PopulateHouseSpawn(PacketStruct* place_object); + + // finalize the spawn-in of the object in world, remove the item from player inventory, set the spawned in object item id (for future pickup) + bool PopulateHouseSpawnFinalize(); + + void SendMoveObjectMode(Spawn* spawn, uint8 placementMode, float unknown2_3=0.0f); + + void SendFlightAutoMount(int32 path_id, int16 mount_id = 0, int8 mount_red_color = 0xFF, int8 mount_green_color = 0xFF, int8 mount_blue_color=0xFF); + + void SendShowBook(Spawn* sender, string title, int8 language, int8 num_pages, ...); + void SendShowBook(Spawn* sender, string title, int8 language, vector pages); + + void SetTemporaryTransportID(int32 id) { temporary_transport_id = id; } + int32 GetTemporaryTransportID() { return temporary_transport_id; } + + void SetRejoinGroupID(int32 id) { rejoin_group_id = id; } + + void TempRemoveGroup(); + void ReplaceGroupClient(Client* new_client); + + void SendWaypoints(); + + void AddWaypoint(string name, int8 type); + void RemoveWaypoint(string name) { + if (waypoints.count(name) > 0){ + waypoints.erase(name); + } + } + void SelectWaypoint(int32 id); + void ClearWaypoint(); + bool ShowPathToTarget(float x, float y, float z, float y_offset); + bool ShowPathToTarget(Spawn* spawn); + + void SetRegionDebug(bool val) { regionDebugMessaging = val; } + + static void CreateMail(int32 charID, std::string fromName, std::string subjectName, std::string mailBody, + int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time); + void CreateAndUpdateMail(std::string fromName, std::string subjectName, std::string mailBody, + int8 mailType, int32 copper, int32 silver, int32 gold, int32 platinum, int32 item_id, int16 stack_size, int32 time_sent, int32 expire_time); + + void SendEquipOrInvUpdateBySlot(int8 slot); + + void SetReloadingZone(bool val) { client_reloading_zone = val; } + bool IsReloadingZone() { return client_reloading_zone; } + + void QueueStateCommand(int32 spawn_player_id, int32 state); + void ProcessStateCommands(); + void PurgeItem(Item* item); + void ConsumeFoodDrink(Item* item, int32 slot); + void AwardCoins(int64 total_coins, std::string reason = string("")); + + void TriggerSpellSave(); + + void ClearSentItemDetails() { + MItemDetails.writelock(__FUNCTION__, __LINE__); + sent_item_details.clear(); + MItemDetails.releasewritelock(__FUNCTION__, __LINE__); + } + + bool IsPlayerLoadingComplete() { return player_loading_complete; } + + int32 GetRejoinGroupID() { return rejoin_group_id; } + + void ClearSentSpellList() { + MSpellDetails.writelock(__FUNCTION__, __LINE__); + sent_spell_details.clear(); + MSpellDetails.releasewritelock(__FUNCTION__, __LINE__); + } + + void UpdateSentSpellList(); + + bool CountSentSpell(int32 id, int32 tier) { + bool res = false; + MSpellDetails.readlock(__FUNCTION__, __LINE__); + std::map::iterator itr = sent_spell_details.find(id); + if(itr != sent_spell_details.end() && itr->second == tier) + res = true; + MSpellDetails.releasereadlock(__FUNCTION__, __LINE__); + return res; + } + + void SetSentSpell(int32 id, int32 tier) { + MSpellDetails.writelock(__FUNCTION__, __LINE__); + sent_spell_details[id] = tier; + MSpellDetails.releasewritelock(__FUNCTION__, __LINE__); + } + + void DisableSave() { disable_save = true; } + bool IsSaveDisabled() { return disable_save; } + void ResetZoningCoords() { + zoning_x = 0; + zoning_y = 0; + zoning_z = 0; + zoning_h = 0; + } + void SetZoningCoords(float x, float y, float z, float h) { + zoning_x = x; + zoning_y = y; + zoning_z = z; + zoning_h = h; + } + + bool UseItem(Item* item, Spawn* target = nullptr); + + void SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, VoiceOverStruct* garble, bool success = false, bool garble_success = false); + void SaveQuestRewardData(bool force_refresh = false); + void UpdateCharacterRewardData(QuestRewardData* data); + void SetQuestUpdateState(bool val) { quest_updates = val; } + + + bool SetPlayerPOVGhost(Spawn* spawn); + + int32 GetPlayerPOVGhostSpawnID() { return pov_ghost_spawn_id; } + + void HandleDialogSelectMsg(int32 conversation_id, int32 response_index); + bool SetPetName(const char* name); + + bool CheckConsumptionAllowed(int16 slot, bool send_message = true); + + void StartLinkdeadTimer(); + bool IsLinkdeadTimerEnabled(); + + bool AddRecipeBookToPlayer(int32 recipe_id, Item* item = nullptr); + bool RemoveRecipeFromPlayer(int32 recipe_id); + + void SaveSpells(); + + void GiveQuestReward(Quest* quest, bool has_displayed = false); + + void SendReplaceWidget(int32 widget_id, bool delete_widget, float x=0.0f, float y=0.0f, float z=0.0f, int32 grid_id=0); + void ProcessZoneIgnoreWidgets(); + + void SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time); + int32 GetSpellVisualOverride(int32 spell_visual); + + sint16 GetClientItemPacketOffset() { sint16 offset = -1; if(GetVersion() <= 373) { offset = -2; } return offset; } +private: + void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i); + void SavePlayerImages(); + void SkillChanged(Skill* skill, int16 previous_value, int16 new_value); + void SetStepComplete(int32 quest_id, int32 step); + void AddStepProgress(int32 quest_id, int32 step, int32 progress); + + void SendNewSpells(int8 class_id); + void SendNewTSSpells(int8 class_id); + void AddSendNewSpells(vector* spells); + + map > quest_pending_updates; + vector quest_queue; + vector quest_pending_reward; + volatile bool quest_updates; + Mutex MQuestPendingUpdates; + Mutex MQuestQueue; + Mutex MDeletePlayer; + vector* search_items; + int32 waypoint_id = 0; + map waypoints; + Spawn* transport_spawn; + Mutex MBuyBack; + deque buy_back_items; + Spawn* merchant_transaction; + Spawn* mail_transaction; + mutable std::shared_mutex MPendingQuestAccept; + vector pending_quest_accept; + bool lua_debug; + bool should_target; + Spawn* combine_spawn; + int8 num_active_failures; + int32 next_conversation_id; + map conversation_spawns; + map conversation_items; + mutable std::shared_mutex MConversation; + map > conversation_map; + int32 current_quest_id; + Spawn* banker; + map sent_spell_details; + map sent_item_details; + Player* player; + int16 version; + int8 timestamp_flag; + int32 ip; + int16 port; + int32 account_id; + int32 character_id; + sint16 admin_status; // -2 Banned, -1 Suspended, 0 User, etc. + char account_name[64]; + char zone_name[64]; + int32 zoneID; + int32 instanceID; + Timer* autobootup_timeout; + int32 pwaitingforbootup; + int32 last_update_time; + + int32 last_saved_timestamp; + + Timer* CLE_keepalive_timer; + Timer* connect; + Timer* camp_timer; + Timer* linkdead_timer; + bool connected; + std::atomic ready_for_spawns; + std::atomic ready_for_updates; + + bool seencharsel; + bool connected_to_zone; + bool client_zoning; + int32 zoning_id; + int32 zoning_instance_id; + ZoneServer* zoning_destination; + float zoning_x; + float zoning_y; + float zoning_z; + float zoning_h; + bool firstlogin; + + enum NewLoginState { LOGIN_NONE, LOGIN_DELAYED, LOGIN_ALLOWED, LOGIN_INITIAL_LOAD, LOGIN_SEND }; + NewLoginState new_client_login; // 1 = delayed state, 2 = let client in + Timer underworld_cooldown_timer; + Timer pos_update; + Timer quest_pos_timer; + Timer lua_debug_timer; + Timer temp_placement_timer; + Timer spawn_removal_timer; + std::atomic player_pos_changed; + std::atomic player_pos_change_count; + int32 player_pos_timer; + bool enabled_player_pos_timer; + bool HandlePacket(EQApplicationPacket *app); + EQStream* eqs; + bool quickbar_changed; + ZoneServer* current_zone; + int32 name_crc; + MailWindow mail_window; + std::mutex MMailWindowMutex; + PendingGuildInvite pending_guild_invite; + PendingResurrection current_rez; + string* pending_last_name; + IncomingPaperdollImage incoming_paperdoll; + int32 transmuteID; + ZoneServer* GetHouseZoneServer(int32 spawn_id, int64 house_id); + + std::atomic m_recipeListSent; + bool initial_spawns_sent; + bool should_load_spells; + + // int32 = quest id + vector quest_timers; + Mutex MQuestTimers; + + int32 pending_flight_path; + + ServerSpawnPlacementMode spawnPlacementMode; + bool on_auto_mount; + bool EntityCommandPrecheck(Spawn* spawn, const char* command); + bool delayedLogin; + int32 delayedAccountID; + int32 delayedAccessKey; + Timer delayTimer; + Spawn* tempPlacementSpawn; + int32 placement_unique_item_id; + bool hasOwnerOrEditAccess; + bool hasSentTempPlacementSpawn; + + int32 temporary_transport_id; + + int32 rejoin_group_id; + + int32 lastRegionRemapTime; + + bool regionDebugMessaging; + + bool client_reloading_zone; + + map queued_state_commands; + Mutex MQueueStateCmds; + Timer save_spell_state_timer; // will be the 're-trigger' to delay + int32 save_spell_state_time_bucket; // bucket as we collect over time when timer is reset by new spell effects being casted + std::mutex MSaveSpellStateMutex; + bool player_loading_complete; + Mutex MItemDetails; + Mutex MSpellDetails; + bool disable_save; + vector< string > devices; + + std::atomic pov_ghost_spawn_id; + Timer delay_msg_timer; + + uchar* recipe_orig_packet; + uchar* recipe_xor_packet; + int recipe_packet_count; + int recipe_orig_packet_size; +}; + +class ClientList { +public: + ClientList(); + ~ClientList(); + bool ContainsStream(EQStream* eqs); + void Add(Client* client); + Client* Get(int32 ip, int16 port); + Client* FindByAccountID(int32 account_id); + Client* FindByName(char* charname); + void Remove(Client* client, bool delete_data = false); + void RemoveConnection(EQStream* eqs); + void Process(); + int32 Count(); + void ReloadQuests(); + void CheckPlayersInvisStatus(Client* owner); + void RemovePlayerFromInvisHistory(int32 spawnID); +private: + Mutex MClients; + list client_list; +}; +#endif diff --git a/source/WorldServer/makefile b/source/WorldServer/makefile new file mode 100644 index 0000000..7c963f2 --- /dev/null +++ b/source/WorldServer/makefile @@ -0,0 +1,100 @@ + + +# Programs +CC = gcc +CXX = g++ +LINKER = g++ + + +# Configuration +Build_Dir = build +Source_Dir = .. +#Conf_Dir = ../../conf +#Content_Dir = ../../../../vgocontent +APP = eq2world + + +# LUA flags +Lua_C_Flags = -DLUA_COMPAT_ALL -DLUA_USE_LINUX +Lua_W_Flags = -Wall + +C_Flags = -I/eq2emu/fmt/include -I/eq2emu/recastnavigation/Detour/Include -I/usr/include/mariadb -I/usr/local/include/boost -I/usr/include/glm -march=native -pipe -pthread -std=c++17 +LD_Flags = -L/usr/lib/x86_64-linux-gnu -lmariadb -lz -lpthread -L/recastnavigation/RecastDemo/Build/gmake/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lssl -lcrypto -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex + +# World flags +W_Flags = -Wall -Wno-reorder +D_Flags = -DEQ2 -DWORLD -D_GNU_SOURCE + +# Setup Debug or Release build +ifeq ($(BUILD),debug) + # "Debug" build - minimum optimization, and debugging symbols + C_Flags += -O -ggdb + D_Flags += -DDEBUG + Current_Build_Dir := $(Build_Dir)/debug + App_Filename = $(APP)_debug +else + # "Release" build - optimization, and no debug symbols + C_Flags += -O2 -s -DNDEBUG + Current_Build_Dir := $(Build_Dir)/release + App_Filename = $(APP) +endif + + +# File lists +World_Source = $(wildcard $(Source_Dir)/WorldServer/*.cpp) $(wildcard $(Source_Dir)/WorldServer/*/*.cpp) +World_Objects = $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(World_Source))) +Common_Source = $(wildcard $(Source_Dir)/common/*.cpp) $(wildcard $(Source_Dir)/common/*/*.cpp) +Common_Objects = $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Common_Source))) +Lua_Source = $(wildcard $(Source_Dir)/LUA/*.c) +Lua_Objects = $(patsubst %.c,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Lua_Source))) + + +# Receipes +all: $(APP) + +$(APP): $(Common_Objects) $(World_Objects) $(Lua_Objects) + @echo Linking... + @$(LINKER) $(W_Flags) $^ $(LD_Flags) -o $(App_Filename) + @test -e $(APP) || /bin/true + #@ln -s $(App_Filename) $(APP) || /bin/true + @echo Finished building world. + +$(Current_Build_Dir)/LUA/%.o: $(Source_Dir)/LUA/%.c + @mkdir -p $(dir $@) + $(CC) -c $(Lua_C_Flags) $(Lua_W_Flags) $< -o $@ + +$(Current_Build_Dir)/%.o: $(Source_Dir)/%.cpp + @mkdir -p $(dir $@) + $(CXX) -c $(C_Flags) $(D_Flags) $(W_Flags) $< -o $@ + +#setup: +# @test ! -e volumes.phys && ln -s $(Conf_Dir)/volumes.phys . || /bin/true +# @test ! -e vgemu-structs.xml && ln -s $(Conf_Dir)/vgemu-structs.xml . || /bin/true +# @$(foreach folder,$(wildcard $(Content_Dir)/scripts/*),test -d $(Content_Dir)/scripts && test ! -e $(notdir $(folder)) && ln -s $(folder) . || /bin/true) +# @echo "Symlinks have been created." +# @cp -n $(Conf_Dir)/vgemu-world.xml . +# @echo "You need to edit your config file: vgemu-world.xml" + +release: + @$(MAKE) "BUILD=release" + +debug: + @$(MAKE) "BUILD=debug" + +clean: + rm -rf $(filter-out %Lua,$(foreach folder,$(wildcard $(Current_Build_Dir)/*),$(folder))) $(App_Filename) $(APP) + +cleanlua: + rm -rf $(Current_Build_Dir)/Lua + +cleanall: + rm -rf $(Build_Dir) $(App_Filename) $(APP) + + +#cleansetup: +# rm volumes.phys vgemu-structs.xml $(foreach folder,$(wildcard $(Content_Dir)/scripts/*),$(notdir $(folder))) + +#docs: docs-world + +#docs-world: +# @cd ../../doc; doxygen Doxyfile-World diff --git a/source/WorldServer/makefile.discord b/source/WorldServer/makefile.discord new file mode 100644 index 0000000..739f4e0 --- /dev/null +++ b/source/WorldServer/makefile.discord @@ -0,0 +1,97 @@ +# Programs +CC = gcc +CXX = g++ +LINKER = g++ + + +# Configuration +Build_Dir = build +Source_Dir = .. +APP = eq2world + + +# LUA flags +Lua_C_Flags = -DLUA_COMPAT_ALL -DLUA_USE_LINUX +Lua_W_Flags = -Wall + + +# World flags +C_Flags = -I/usr/include/mariadb -I../depends/fmt/include -I../depends/recastnavigation/Detour/Include -I/usr/local/include/boost -I../depends/glm/ -march=native -pipe -pthread -std=c++17 +LD_Flags = -L/usr/lib/x86_64-linux-gnu -lz -lpthread -lmariadbclient -L../depends/recastnavigation/RecastDemo/Build/gmake/lib/Debug -lDebugUtils -lDetour -lDetourCrowd -lDetourTileCache -lRecast -L/usr/local/lib -rdynamic -lm -Wl,-E -ldl -lreadline -lboost_system -lboost_filesystem -lboost_iostreams -lboost_regex +W_Flags = -Wall -Wno-reorder +D_Flags = -DEQ2 -DWORLD -D_GNU_SOURCE + + +# Setup Debug or Release build +ifeq ($(BUILD),debug) + # "Debug" build - minimum optimization, and debugging symbols + C_Flags += -O -ggdb + D_Flags += -DDEBUG -DDISCORD + LD_Flags += -ldpp + Current_Build_Dir := $(Build_Dir)/debug + App_Filename = $(APP)_debug +else + # "Release" build - optimization, and no debug symbols + C_Flags += -O2 -s -DNDEBUG + Current_Build_Dir := $(Build_Dir)/release + App_Filename = $(APP) +endif + + +# File lists +World_Source = $(wildcard $(Source_Dir)/WorldServer/*.cpp) $(wildcard $(Source_Dir)/WorldServer/*/*.cpp) +World_Objects = $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(World_Source))) +Common_Source = $(wildcard $(Source_Dir)/common/*.cpp) +Common_Objects = $(patsubst %.cpp,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Common_Source))) +Lua_Source = $(wildcard $(Source_Dir)/LUA/*.c) +Lua_Objects = $(patsubst %.c,$(Current_Build_Dir)/%.o,$(subst $(Source_Dir)/,,$(Lua_Source))) + + +# Receipes +all: $(APP) + +$(APP): $(Common_Objects) $(World_Objects) $(Lua_Objects) + @echo Linking... + @$(LINKER) $(W_Flags) $^ $(LD_Flags) -o $(App_Filename) + @test -e $(APP) || /bin/true + #@ln -s $(App_Filename) $(APP) || /bin/true + @echo Finished building world. + +$(Current_Build_Dir)/LUA/%.o: $(Source_Dir)/LUA/%.c + @mkdir -p $(dir $@) + $(CC) -c $(Lua_C_Flags) $(Lua_W_Flags) $< -o $@ + +$(Current_Build_Dir)/%.o: $(Source_Dir)/%.cpp + @mkdir -p $(dir $@) + $(CXX) -c $(C_Flags) $(D_Flags) $(W_Flags) $< -o $@ + +#setup: +# @test ! -e volumes.phys && ln -s $(Conf_Dir)/volumes.phys . || /bin/true +# @test ! -e vgemu-structs.xml && ln -s $(Conf_Dir)/vgemu-structs.xml . || /bin/true +# @$(foreach folder,$(wildcard $(Content_Dir)/scripts/*),test -d $(Content_Dir)/scripts && test ! -e $(notdir $(folder)) && ln -s $(folder) . || /bin/true) +# @echo "Symlinks have been created." +# @cp -n $(Conf_Dir)/vgemu-world.xml . +# @echo "You need to edit your config file: vgemu-world.xml" + +release: + @$(MAKE) "BUILD=release" + +debug: + @$(MAKE) "BUILD=debug" + +clean: + rm -rf $(filter-out %Lua,$(foreach folder,$(wildcard $(Current_Build_Dir)/*),$(folder))) $(App_Filename) $(APP) + +cleanlua: + rm -rf $(Current_Build_Dir)/Lua + +cleanall: + rm -rf $(Build_Dir) $(App_Filename) $(APP) + +#cleansetup: +# rm volumes.phys vgemu-structs.xml $(foreach folder,$(wildcard $(Content_Dir)/scripts/*),$(notdir $(folder))) + +#docs: docs-world + +#docs-world: +# @cd ../../doc; doxygen Doxyfile-World diff --git a/source/WorldServer/net.cpp b/source/WorldServer/net.cpp new file mode 100644 index 0000000..c18082f --- /dev/null +++ b/source/WorldServer/net.cpp @@ -0,0 +1,948 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +#include "../common/Log.h" + +#include +using namespace std; +#include +#include +#include +#include +#include + +#include + +#include "../common/queue.h" +#include "../common/timer.h" +#include "../common/EQStreamFactory.h" +#include "../common/EQStream.h" +#include "net.h" + +#include "Variables.h" +#include "WorldDatabase.h" +#include "../common/seperator.h" +#include "../common/version.h" +#include "../common/EQEMuError.h" +#include "../common/opcodemgr.h" +#include "../common/Common_Defines.h" +#include "../common/JsonParser.h" +#include "../common/Common_Defines.h" + +#include "LoginServer.h" +#include "Commands/Commands.h" +#include "Factions.h" +#include "World.h" +#include "../common/ConfigReader.h" +#include "Skills.h" +#include "LuaInterface.h" +#include "Guilds/Guild.h" +#include "Commands/ConsoleCommands.h" +#include "Traits/Traits.h" +#include "Transmute.h" +#include "Zone/ChestTrap.h" + +//devn00b +#ifdef DISCORD + //linux only for the moment. + #ifndef WIN32 + #include + #include "Chat/Chat.h" + extern Chat chat; + #endif +#endif + +double frame_time = 0.0; + +#ifdef WIN32 + #include + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include + #include "../common/unix.h" +#endif + +#ifdef PROFILER +#define SHINY_PROFILER TRUE +#include "../Profiler/src/Shiny.h" +#endif + +NetConnection net; +World world; +EQStreamFactory eqsf(LoginStream); +LoginServer loginserver; +LuaInterface* lua_interface = new LuaInterface(); +#include "MutexList.h" + +#include "Rules/Rules.h" +#include "Titles.h" +#include "Languages.h" +#include "Achievements/Achievements.h" + +volatile bool RunLoops = true; +sint32 numclients = 0; +sint32 numzones = 0; +extern ClientList client_list; +extern ZoneList zone_list; +extern MasterFactionList master_faction_list; +extern WorldDatabase database; +extern MasterSpellList master_spell_list; +extern MasterTraitList master_trait_list; +extern MasterSkillList master_skill_list; +extern MasterItemList master_item_list; +extern GuildList guild_list; +extern Variables variables; +ConfigReader configReader; +int32 MasterItemList::next_unique_id = 0; +int last_signal = 0; +RuleManager rule_manager; +MasterTitlesList master_titles_list; +MasterLanguagesList master_languages_list; +ChestTrapList chest_trap_list; +extern MasterAchievementList master_achievement_list; +extern map EQOpcodeVersions; + + +ThreadReturnType ItemLoad (void* tmp); +ThreadReturnType AchievmentLoad (void* tmp); +ThreadReturnType SpellLoad (void* tmp); +//devn00b +#ifdef DISCORD + #ifndef WIN32 + ThreadReturnType StartDiscord (void* tmp); + #endif +#endif + +int main(int argc, char** argv) { +#ifdef PROFILER + PROFILE_FUNC(); +#endif + int32 t_total = Timer::GetUnixTimeStamp(); + + LogStart(); + + LogParseConfigs(); + net.WelcomeHeader(); + + LogWrite(INIT__INFO, 0, "Init", "Starting EQ2Emulator WorldServer..."); + //int32 server_startup = time(NULL); + + //remove this when all database calls are using the new database class + if (!database.Init()) { + LogStop(); + return EXIT_FAILURE; + } + + if (!database.ConnectNewDatabase()) + return EXIT_FAILURE; + + #ifdef _DEBUG + _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + #endif + + if (signal(SIGINT, CatchSignal) == SIG_ERR) { + LogWrite(INIT__ERROR, 0, "Init", "Could not set signal handler"); + return 0; + } + if (signal(SIGSEGV, CatchSignal) == SIG_ERR) { + LogWrite(INIT__ERROR, 0, "Init", "Could not set signal handler"); + return 0; + } + if (signal(SIGILL, CatchSignal) == SIG_ERR) { + LogWrite(INIT__ERROR, 0, "Init", "Could not set signal handler"); + return 0; + } + #ifndef WIN32 + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { + LogWrite(INIT__ERROR, 0, "Init", "Could not set signal handler"); + return 0; + } + #endif + + LogWrite(WORLD__DEBUG, 0, "World", "Randomizing World..."); + srand(time(NULL)); + + net.ReadLoginINI(); + + // JA: Grouping all System (core) data loads together for timing purposes + LogWrite(WORLD__INFO, 0, "World", "Loading System Data..."); + int32 t_now = Timer::GetUnixTimeStamp(); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading opcodes..."); + EQOpcodeVersions = database.GetVersions(); + map::iterator version_itr; + int16 version1 = 0; + int16 prevVersion = 0; + std::string prevString = std::string(""); + std::string builtString = std::string(""); + for (version_itr = EQOpcodeVersions.begin(); version_itr != EQOpcodeVersions.end(); version_itr++) { + version1 = version_itr->first; + EQOpcodeManager[version1] = new RegularOpcodeManager(); + map eq = database.GetOpcodes(version1); + std::string missingOpcodesList = std::string(""); + if(!EQOpcodeManager[version1]->LoadOpcodes(&eq, &missingOpcodesList)) { + LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!"); + return false; + } + + if(version1 == 0) // we don't need to display version 0 + continue; + + if(prevString.size() > 0) { + if(prevString == missingOpcodesList) { + builtString += ", " + std::to_string(version1); + } + else { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcodes %s.", builtString.c_str()); + builtString = std::string(""); + prevString = std::string(""); + } + } + if(prevString.size() < 1) { + prevString = std::string(missingOpcodesList); + builtString = std::string(missingOpcodesList + " are missing from the opcodes table for version(s) " + std::to_string(version1)); + } + } + + if(builtString.size() > 0) { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcodes %s.", builtString.c_str()); + } + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading structs..."); + if(!configReader.LoadFile("CommonStructs.xml") || !configReader.LoadFile("WorldStructs.xml") || !configReader.LoadFile("SpawnStructs.xml") || !configReader.LoadFile("ItemStructs.xml")) { + LogWrite(INIT__ERROR, 0, "Init", "Loading structs failed. Make sure you have CommonStructs.xml, WorldStructs.xml, SpawnStructs.xml, and ItemStructs.xml in the working directory!"); + return false; + } + + world.init(net.GetWebWorldAddress(), net.GetWebWorldPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword()); + + bool threadedLoad = rule_manager.GetGlobalRule(R_World, ThreadedLoad)->GetBool(); + + LogWrite(WORLD__DEBUG, 1, "World", "-Loading EQ2 time of day..."); + loginserver.InitLoginServerVariables(); + + LogWrite(WORLD__INFO, 0, "World", "Loaded System Data (took %u seconds)", Timer::GetUnixTimeStamp() - t_now); + // JA: End System Data loading functions + + if (threadedLoad) { + LogWrite(WORLD__WARNING, 0, "Threaded", "Using Threaded loading of static data..."); +#ifdef WIN32 + _beginthread(ItemLoad, 0, &world); + _beginthread(SpellLoad, 0, &world); + //_beginthread(AchievmentLoad, 0, &world); +#else + pthread_t thread; + pthread_create(&thread, NULL, ItemLoad, &world); + pthread_detach(thread); + pthread_t thread2; + pthread_create(&thread2, NULL, SpellLoad, &world); + pthread_detach(thread2); + //devn00b + #ifdef DISCORD + pthread_t thread3; + pthread_create(&thread3, NULL, StartDiscord, &world); + pthread_detach(thread3); + #endif +#endif + } + + // JA temp logger + LogWrite(MISC__TODO, 0, "Reformat", "JA: This is as far as I got reformatting the console logs."); + + if (!threadedLoad) { + // JA: Load all Item info + LogWrite(ITEM__INFO, 0, "Items", "Loading Items..."); + database.LoadItemList(); + MasterItemList::ResetUniqueID(database.LoadNextUniqueItemID()); + + LogWrite(SPELL__INFO, 0, "Spells", "Loading Spells..."); + database.LoadSpells(); + + LogWrite(SPELL__INFO, 0, "Spells", "Loading Spell Errors..."); + database.LoadSpellErrors(); + + // Jabantiz: Load traits + LogWrite(WORLD__INFO, 0, "Traits", "Loading Traits..."); + database.LoadTraits(); + + // JA: Load all Quest info + LogWrite(QUEST__INFO, 0, "Quests", "Loading Quests..."); + database.LoadQuests(); + + LogWrite(COLLECTION__INFO, 0, "Collect", "Loading Collections..."); + database.LoadCollections(); + + LogWrite(MISC__TODO, 1, "TODO", "TODO loading achievements\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + //LogWrite(ACHIEVEMENT__INFO, 0, "Achievements", "Loading Achievements..."); + //database.LoadAchievements(); + //master_achievement_list.CreateMasterAchievementListPacket(); + + LogWrite(MERCHANT__INFO, 0, "Merchants", "Loading Merchants..."); + database.LoadMerchantInformation(); + } + + LogWrite(GUILD__INFO, 0, "Guilds", "Loading Guilds..."); + database.LoadGuilds(); + + LogWrite(TRADESKILL__INFO, 0, "Recipes", "Loading Recipe Books..."); + database.LoadRecipeBooks(); + LogWrite(TRADESKILL__INFO, 0, "Recipes", "Loading Recipes..."); + database.LoadRecipes(); + LogWrite(TRADESKILL__INFO, 0, "Tradeskills", "Loading Tradeskill Events..."); + database.LoadTradeskillEvents(); + + LogWrite(SPELL__INFO, 0, "AA", "Loading Alternate Advancements..."); + database.LoadAltAdvancements(); + LogWrite(SPELL__INFO, 0, "AA", "Loading AA Tree Nodes..."); + database.LoadTreeNodes(); + LogWrite(WORLD__INFO, 0, "Titles", "Loading Titles..."); + database.LoadTitles(); + LogWrite(WORLD__INFO, 0, "Languages", "Loading Languages..."); + database.LoadLanguages(); + + LogWrite(CHAT__INFO, 0, "Chat", "Loading channels..."); + database.LoadChannels(); + + LogWrite(LUA__INFO, 0, "LUA", "Loading Spawn Scripts..."); + database.LoadSpawnScriptData(); + + LogWrite(LUA__INFO, 0, "LUA", "Loading Zone Scripts..."); + database.LoadZoneScriptData(); + + LogWrite(WORLD__INFO, 0, "World", "Loading House Zone Data..."); + database.LoadHouseZones(); + database.LoadPlayerHouses(); + + LogWrite(WORLD__INFO, 0, "World", "Loading Heroic OP Data..."); + database.LoadHOStarters(); + database.LoadHOWheel(); + + LogWrite(WORLD__INFO, 0, "World", "Loading Race Types Data..."); + database.LoadRaceTypes(); + + LogWrite(WORLD__INFO, 0, "World", "Loading Transmuting Data..."); + database.LoadTransmuting(); + + LogWrite(WORLD__INFO, 0, "World", "Loading Chest Trap Data..."); + database.LoadChestTraps(); + + LogWrite(WORLD__INFO, 0, "World", "Loading NPC Spells..."); + database.LoadNPCSpells(); + + if (threadedLoad) { + LogWrite(WORLD__INFO, 0, "World", "Waiting for load threads to finish."); + while (!world.items_loaded || !world.spells_loaded /*|| !world.achievments_loaded*/) + Sleep(10); + LogWrite(WORLD__INFO, 0, "World", "Load threads finished."); + } + + LogWrite(WORLD__INFO, 0, "World", "Total World startup time: %u seconds.", Timer::GetUnixTimeStamp() - t_total); + int ret_code = 0; + if (eqsf.Open(net.GetWorldPort())) { + if (strlen(net.GetWorldAddress()) == 0) + LogWrite(NET__INFO, 0, "Net", "World server listening on port %i", net.GetWorldPort()); + else + LogWrite(NET__INFO, 0, "Net", "World server listening on: %s:%i", net.GetWorldAddress(), net.GetWorldPort()); + + if(strlen(net.GetInternalWorldAddress())>0) + LogWrite(NET__INFO, 0, "Net", "World server listening on: %s:%i", net.GetInternalWorldAddress(), net.GetWorldPort()); + + world.world_loaded = true; + world.world_uptime = getCurrentTimestamp(); + } + else { + LogWrite(NET__ERROR, 0, "Net", "Failed to open port %i.", net.GetWorldPort()); + ret_code = 1; + } + Timer* TimeoutTimer = 0; + if (ret_code == 0) { + Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect + InterserverTimer.Trigger(); + TimeoutTimer = new Timer(5000); + TimeoutTimer->Start(); + EQStream* eqs = 0; + UpdateWindowTitle(0); + LogWrite(ZONE__INFO, 0, "Zone", "Starting static zones..."); + database.LoadSpecialZones(); + map connecting_clients; + map::iterator cc_itr; + + LogWrite(WORLD__DEBUG, 0, "Thread", "Starting console command thread..."); +#ifdef WIN32 + _beginthread(EQ2ConsoleListener, 0, NULL); +#else + /*pthread_t thread; + pthread_create(&thread, NULL, &EQ2ConsoleListener, NULL); + pthread_detach(thread);*/ +#endif + // + + // just before starting loops, announce how to get console help (only to windows) +#ifdef WIN32 + LogWrite(WORLD__INFO, 0, "Console", "Type 'help' or '?' and press enter for menu options."); +#endif + + + std::chrono::time_point frame_prev = std::chrono::system_clock::now(); + while (RunLoops) { + Timer::SetCurrentTime(); + std::chrono::time_point frame_now = std::chrono::system_clock::now(); + frame_time = std::chrono::duration_cast>(frame_now - frame_prev).count(); + frame_prev = frame_now; + +#ifndef NO_CATCH + try + { +#endif + while ((eqs = eqsf.Pop())) { + struct in_addr in; + in.s_addr = eqs->GetRemoteIP(); + LogWrite(NET__DEBUG, 0, "Net", "New client from ip: %s port: %i", inet_ntoa(in), ntohs(eqs->GetRemotePort())); + + // JA: Check for BannedIPs + if (rule_manager.GetGlobalRule(R_World, UseBannedIPsTable)->GetInt8() == 1) + { + LogWrite(WORLD__DEBUG, 0, "World", "Checking inbound connection %s against BannedIPs table", inet_ntoa(in)); + if (database.CheckBannedIPs(inet_ntoa(in))) + { + LogWrite(WORLD__DEBUG, 0, "World", "Connection from %s FAILED banned IPs check. Closing connection.", inet_ntoa(in)); + eqs->Close(); // JA: If the inbound IP is on the banned table, close the EQStream. + } + } + + if (eqs && eqs->CheckActive() && client_list.ContainsStream(eqs) == false) { + LogWrite(NET__DEBUG, 0, "Net", "Adding new client..."); + Client* client = new Client(eqs); + client_list.Add(client); + } + else if (eqs && !client_list.ContainsStream(eqs)) { + LogWrite(NET__DEBUG, 0, "Net", "Adding client to waiting list..."); + connecting_clients[eqs] = Timer::GetCurrentTime2(); + } + } + if (connecting_clients.size() > 0) { + for (cc_itr = connecting_clients.begin(); cc_itr != connecting_clients.end(); cc_itr++) { + if (cc_itr->first && cc_itr->first->CheckActive() && client_list.ContainsStream(cc_itr->first) == false) { + LogWrite(NET__DEBUG, 0, "Net", "Removing client from waiting list..."); + Client* client = new Client(cc_itr->first); + client_list.Add(client); + connecting_clients.erase(cc_itr); + break; + } + else if (Timer::GetCurrentTime2() >= (cc_itr->second + 10000)) { + connecting_clients.erase(cc_itr); + break; + } + } + } + world.Process(); + client_list.Process(); + loginserver.Process(); + if (TimeoutTimer->Check()) { + eqsf.CheckTimeout(); + } + if (InterserverTimer.Check()) { + InterserverTimer.Start(); + database.ping(); + database.PingNewDB(); + database.PingAsyncDatabase(); + + if (net.LoginServerInfo && loginserver.Connected() == false && loginserver.CanReconnect()) { + LogWrite(WORLD__DEBUG, 0, "Thread", "Starting autoinit loginserver thread..."); +#ifdef WIN32 + _beginthread(AutoInitLoginServer, 0, NULL); +#else + pthread_t thread; + pthread_create(&thread, NULL, &AutoInitLoginServer, NULL); + pthread_detach(thread); +#endif + } + } +#ifndef NO_CATCH + } + catch (...) { + LogWrite(WORLD__ERROR, 0, "World", "Exception caught in net main loop!"); + } +#endif + if (numclients == 0) { + Sleep(10); + continue; + } + Sleep(1); + } + } + LogWrite(WORLD__DEBUG, 0, "World", "The world is ending!"); + + LogWrite(WORLD__DEBUG, 0, "World", "Shutting down zones..."); + zone_list.ShutDownZones(); + + LogWrite(WORLD__DEBUG, 0, "World", "Shutting down LUA interface..."); + safe_delete(lua_interface); + safe_delete(TimeoutTimer); + eqsf.Close(); + map::iterator opcode_itr; + for(opcode_itr=EQOpcodeManager.begin();opcode_itr!=EQOpcodeManager.end();opcode_itr++){ + safe_delete(opcode_itr->second); + } + CheckEQEMuErrorAndPause(); + +#ifdef PROFILER + PROFILER_UPDATE(); + PROFILER_OUTPUT(); +#endif + + LogWrite(WORLD__INFO, 0, "World", "Exiting... we hope you enjoyed your flight."); + LogStop(); + return ret_code; +} + +ThreadReturnType ItemLoad (void* tmp) +{ + LogWrite(WORLD__WARNING, 0, "Thread", "Item Loading Thread started."); +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ItemLoad(): tmp = 0!"); + THREAD_RETURN(NULL); + } + World* world = (World*) tmp; + WorldDatabase db; + db.Init(); + db.ConnectNewDatabase(); + + LogWrite(ITEM__INFO, 0, "Items", "Loading Items..."); + db.LoadItemList(); + MasterItemList::ResetUniqueID(db.LoadNextUniqueItemID()); + + // Relies on the item list so needs to be in the item thread + LogWrite(COLLECTION__INFO, 0, "Collect", "Loading Collections..."); + db.LoadCollections(); + + LogWrite(MERCHANT__INFO, 0, "Merchants", "Loading Merchants..."); + db.LoadMerchantInformation(); + + LogWrite(QUEST__INFO, 0, "Quests", "Loading Quests..."); + db.LoadQuests(); + + world->items_loaded = true; + LogWrite(WORLD__WARNING, 0, "Thread", "Item Loading Thread completed."); + + mysql_thread_end(); + THREAD_RETURN(NULL); +} + +ThreadReturnType SpellLoad (void* tmp) +{ + LogWrite(WORLD__WARNING, 0, "Thread", "Spell Loading Thread started."); +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ItemLoad(): tmp = 0!"); + THREAD_RETURN(NULL); + } + World* world = (World*) tmp; + WorldDatabase db; + db.Init(); + db.ConnectNewDatabase(); + LogWrite(SPELL__INFO, 0, "Spells", "Loading Spells..."); + db.LoadSpells(); + + LogWrite(SPELL__INFO, 0, "Spells", "Loading Spell Errors..."); + db.LoadSpellErrors(); + + LogWrite(WORLD__INFO, 0, "Traits", "Loading Traits..."); + db.LoadTraits(); + + world->spells_loaded = true; + LogWrite(WORLD__WARNING, 0, "Thread", "Spell Loading Thread completed."); + + mysql_thread_end(); + THREAD_RETURN(NULL); +} + +ThreadReturnType AchievmentLoad (void* tmp) +{ + LogWrite(WORLD__WARNING, 0, "Thread", "Achievement Loading Thread started."); +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ItemLoad(): tmp = 0!"); + THREAD_RETURN(NULL); + } + World* world = (World*) tmp; + WorldDatabase db; + db.Init(); + db.ConnectNewDatabase(); + + LogWrite(ACHIEVEMENT__INFO, 0, "Achievements", "Loading Achievements..."); + int32 t_now = Timer::GetUnixTimeStamp(); + db.LoadAchievements(); + master_achievement_list.CreateMasterAchievementListPacket(); + LogWrite(ACHIEVEMENT__INFO, 0, "Achievements", "Achievements loaded (took %u seconds)", Timer::GetUnixTimeStamp() - t_now); + + world->achievments_loaded = true; + LogWrite(WORLD__WARNING, 0, "Thread", "Achievement Loading Thread completed."); + + mysql_thread_end(); + THREAD_RETURN(NULL); +} + +ThreadReturnType EQ2ConsoleListener(void* tmp) +{ + char cmd[300]; + size_t i = 0; + size_t len; + + while( RunLoops ) + { + // Read in single line from "stdin" + memset( cmd, 0, sizeof( cmd ) ); + if( fgets( cmd, 300, stdin ) == NULL ) + continue; + + if( !RunLoops ) + break; + + len = strlen(cmd); + for( i = 0; i < len; ++i ) + { + if(cmd[i] == '\n' || cmd[i] == '\r') + cmd[i] = '\0'; + } + + ProcessConsoleInput(cmd); + } + THREAD_RETURN(NULL); +} + +#include +void CatchSignal(int sig_num) { + // In rare cases this can be called after the log system is shut down causing a deadlock or crash + // when the world shuts down, if this happens again comment out the LogWrite and uncomment the printf + if (last_signal != sig_num){ + static Mutex lock; + static std::ofstream signal_out; + + lock.lock(); + if (!signal_out.is_open()) + signal_out.open("signal_catching.log", ios::trunc); + if (signal_out){ + signal_out << "Caught signal " << sig_num << "\n"; + signal_out.flush(); + } + printf("Caught signal %i\n", sig_num); + lock.unlock(); + + last_signal = sig_num; + RunLoops = false; + } +} + +bool NetConnection::ReadLoginINI() { + JsonParser parser(MAIN_CONFIG_FILE); + if(!parser.IsLoaded()) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE); + return false; + } + std::string worldname_str = parser.getValue("loginserver.worldname"); + if(worldname_str.size() < 4) { + LogWrite(INIT__ERROR, 0, "Init", "loginserver.worldname was invalid or less than 4 characters.."); + return false; + } + + std::string worldaccount_str = parser.getValue("loginserver.account"); + std::string worldpassword_str = parser.getValue("loginserver.password"); + std::string worldaddress_str = parser.getValue("loginserver.worldaddress"); + + snprintf(worldname, sizeof(worldname), "%s", worldname_str.c_str()); + snprintf(worldaccount, sizeof(worldaccount), "%s", worldaccount_str.c_str()); + snprintf(worldpassword, sizeof(worldpassword), "%s", worldpassword_str.c_str()); + snprintf(worldaddress, sizeof(worldaddress), "%s", worldaddress_str.c_str()); + + std::string logstats_str = parser.getValue("loginserver.logstats"); + int16 logstats = 0; + parser.convertStringToUnsignedShort(logstats_str, logstats); + net.UpdateStats = logstats > 0 ? true : false; + + std::string locked_str = parser.getValue("loginserver.locked"); + int16 locked = 0; + parser.convertStringToUnsignedShort(locked_str, locked); + world_locked = locked > 0 ? true : false; + + std::string worldport_str = parser.getValue("loginserver.worldport"); + parser.convertStringToUnsignedShort(worldport_str, worldport); + + for(int i=-1;i<=3;i++) { + + std::string loginport_str = ""; + std::string loginaddress_str = ""; + + if(i==-1) { + loginport_str = parser.getValue("loginserver.loginport"); + loginaddress_str = parser.getValue("loginserver.loginserver"); + } + else { + loginport_str = parser.getValue("loginserver.loginport" + std::to_string(i)); + loginaddress_str = parser.getValue("loginserver.loginserver" + std::to_string(i)); + } + + if(loginport_str.size() < 1 || loginaddress_str.size() < 1) + continue; + + parser.convertStringToUnsignedShort(loginport_str, loginport[i+1]); + snprintf(loginaddress[i+1], sizeof(loginaddress[i+1]), "%s", loginaddress_str.c_str()); + LogWrite(INIT__INFO, 0, "Init", "Login Server %s:%u...", loginaddress[i+1], loginport[i+1]); + } + + if(!loginaddress[0][0]) { + LogWrite(INIT__ERROR, 0, "Init", "loginserver.loginserver was missing.."); + return false; + } + + web_worldaddress = parser.getValue("worldserver.webaddress"); + web_certfile = parser.getValue("worldserver.webcertfile"); + web_keyfile = parser.getValue("worldserver.webkeyfile"); + web_keypassword = parser.getValue("worldserver.webkeypassword"); + web_hardcodeuser = parser.getValue("worldserver.webhardcodeuser"); + web_hardcodepassword = parser.getValue("worldserver.webhardcodepassword"); + + std::string webloginport_str = parser.getValue("worldserver.webport"); + parser.convertStringToUnsignedShort(webloginport_str, web_worldport); + + std::string defaultstatus_str = parser.getValue("worldserver.defaultstatus"); + parser.convertStringToUnsignedChar(defaultstatus_str, DEFAULTSTATUS); + + LogWrite(INIT__DEBUG, 0, "Init", "%s read...", MAIN_CONFIG_FILE); + LoginServerInfo=1; + return true; +} + + +char* NetConnection::GetLoginInfo(int16* oPort) { + if (oPort == 0) + return 0; + if (loginaddress[0][0] == 0) + return 0; + + int8 tmp[4] = { 0, 0, 0 }; + int8 count = 0; + + for (int i=0; i<4; i++) { + if (loginaddress[i][0]) + tmp[count++] = i; + } + + int x = rand() % count; + + *oPort = loginport[tmp[x]]; + return loginaddress[tmp[x]]; +} + +void UpdateWindowTitle(char* iNewTitle) { + + char tmp[500]; + if (iNewTitle) { + snprintf(tmp, sizeof(tmp), "World: %s", iNewTitle); + } + else { + string servername = net.GetWorldName(); + snprintf(tmp, sizeof(tmp), "%s (%s), Version: %s: %i Clients(s) in %i Zones(s)", EQ2EMU_MODULE, servername.c_str(), CURRENT_VERSION, numclients, numzones); + } + // Zero terminate ([max - 1] = 0) the string to prevent a warning + tmp[499] = 0; + #ifdef WIN32 + SetConsoleTitle(tmp); + #else + printf("%c]0;%s%c", '\033', tmp, '\007'); + #endif +} + +ZoneAuthRequest::ZoneAuthRequest(int32 account_id, char* name, int32 access_key) { +accountid = account_id; +character_name = string(name); +accesskey = access_key; +timestamp = Timer::GetUnixTimeStamp(); +firstlogin = false; +} + +ZoneAuthRequest::~ZoneAuthRequest ( ) +{ +} + +void ZoneAuth::AddAuth(ZoneAuthRequest *zar) { + LogWrite(NET__DEBUG, 0, "Net", "AddAuth: %u Key: %u", zar->GetAccountID(), zar->GetAccessKey()); + list.Insert(zar); +} + +ZoneAuthRequest* ZoneAuth::GetAuth(int32 account_id, int32 access_key) { + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData()->GetAccountID() == account_id && iterator.GetData()->GetAccessKey() == access_key) { + ZoneAuthRequest* tmp = iterator.GetData(); + return tmp; + } + iterator.Advance(); + } + return 0; + +} + +void ZoneAuth::PurgeInactiveAuth() { + LinkedListIterator iterator(list); + + iterator.Reset(); + int32 current_time = Timer::GetUnixTimeStamp(); + while(iterator.MoreElements()) { + if ((iterator.GetData()->GetTimeStamp()+60) < current_time) { + iterator.RemoveCurrent(); + } + iterator.Advance(); + } +} + +void ZoneAuth::RemoveAuth(ZoneAuthRequest *zar) { + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData() == zar) { + iterator.RemoveCurrent(); + break; + } + iterator.Advance(); + } +} + +void NetConnection::WelcomeHeader() +{ +#ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); +#endif + printf("\n\nCopyright (C) 2007-2022 EQ2Emulator. https://www.eq2emu.com \n\n"); + printf("EQ2Emulator is free software: you can redistribute it and/or modify\n"); + printf("it under the terms of the GNU General Public License as published by\n"); + printf("the Free Software Foundation, either version 3 of the License, or\n"); + printf("(at your option) any later version.\n\n"); + printf("EQ2Emulator is distributed in the hope that it will be useful,\n"); + printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); + printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); + printf("GNU General Public License for more details.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_GREEN_BOLD); +#endif + printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n"); + printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n"); + printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n"); + printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n"); + printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n"); + printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n"); + printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n"); + printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n"); + printf(" \\__/ \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_MAGENTA_BOLD); +#endif + printf(" Website : https://eq2emu.com \n"); + printf(" Wiki : https://wiki.eq2emu.com \n"); + printf(" Git : https://git.eq2emu.com \n"); + printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE); +#endif + + fflush(stdout); +} + +#ifdef DISCORD +ThreadReturnType StartDiscord(void* tmp) +{ +#ifndef DISCORD + THREAD_RETURN(NULL); +#endif + if (tmp == 0) { + ThrowError("StartDiscord: tmp = 0!"); + THREAD_RETURN(NULL); + } + +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + + bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool(); + + if(enablediscord == false) { + LogWrite(INIT__INFO, 0,"Discord","Bot Disabled By Rule..."); + THREAD_RETURN(NULL); + } + + LogWrite(INIT__INFO, 0, "Discord", "Starting Discord Bridge..."); + const char* bottoken = rule_manager.GetGlobalRule(R_Discord, DiscordBotToken)->GetString(); + + if(strlen(bottoken)== 0) { + LogWrite(INIT__INFO, 0,"Discord","Bot Token Was Empty..."); + THREAD_RETURN(NULL); + } + + dpp::cluster bot(bottoken, dpp::i_default_intents | dpp::i_message_content); + + //if we have debug on, go ahead and show DPP logs. + #ifdef DEBUG + bot.on_log([&bot](const dpp::log_t & event) { + std::cout << "[" << dpp::utility::loglevel(event.severity) << "] " << event.message << "\n"; + }); + #endif + + bot.on_message_create([&bot](const dpp::message_create_t& event) { + if (event.msg.author.is_bot() == false) { + std::string chanid = event.msg.channel_id.str(); + std::string listenchan = rule_manager.GetGlobalRule(R_Discord, DiscordListenChan)->GetString(); + + if(chanid.compare(listenchan) != 0 || !chanid.size() || !listenchan.size()) { + return; + } + chat.TellChannel(NULL, listenchan.c_str(), event.msg.content.c_str(), event.msg.author.username.c_str()); + } + }); + + while(true) { + bot.start(dpp::st_wait); + //wait 30s for reconnect. prevents hammering discord and a potential ban. + std::this_thread::sleep_for(std::chrono::milliseconds(30000)); + } + + THREAD_RETURN(NULL); +} +#endif \ No newline at end of file diff --git a/source/WorldServer/net.h b/source/WorldServer/net.h new file mode 100644 index 0000000..d3bad05 --- /dev/null +++ b/source/WorldServer/net.h @@ -0,0 +1,143 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_NET__ +#define __EQ2_NET__ +#ifndef WIN32 + #include + #include + #include + #include + #include + #include + #include +#else + #include + #include + #include + #include +#endif + +#include "../common/linked_list.h" +#include "../common/types.h" + +ThreadReturnType EQ2ConsoleListener(void *tmp); +void CatchSignal(int sig_num); +void UpdateWindowTitle(char* iNewTitle); + +#define PORT 9000 +#define LOGIN_PORT 9100 + +class NetConnection +{ +public: + NetConnection() { + world_locked = false; + for (int i=0; i<4; i++) { + memset(loginaddress[i], 0, sizeof(loginaddress[i])); + loginport[i] = LOGIN_PORT; + } + listening_socket = 0; + memset(worldname, 0, sizeof(worldname)); + memset(worldaccount, 0, sizeof(worldaccount)); + memset(worldpassword, 0, sizeof(worldpassword)); + memset(worldaddress, 0, sizeof(worldaddress)); + memset(internalworldaddress, 0, sizeof(internalworldaddress)); + worldport = PORT; + DEFAULTSTATUS=0; + LoginServerInfo = 0;//ReadLoginINI(); + UpdateStats = false; + web_worldport = 0; + } + ~NetConnection() { } + + bool ReadLoginINI(); + void WelcomeHeader(); + + bool LoginServerInfo; + bool UpdateStats; + char* GetLoginInfo(int16* oPort); + inline char* GetLoginAddress(int8 i) { return loginaddress[i]; } + inline int16 GetLoginPort(int8 i) { return loginport[i]; } + inline char* GetWorldName() { return worldname; } + inline char* GetWorldAccount() { return worldaccount; } + inline char* GetWorldPassword() { return worldpassword; } + inline char* GetWorldAddress() { return worldaddress; } + inline char* GetInternalWorldAddress() { return internalworldaddress; } + inline int16 GetWorldPort() { return worldport; } + inline int8 GetDefaultStatus() { return DEFAULTSTATUS; } + std::string GetWebWorldAddress() { return web_worldaddress; } + inline int16 GetWebWorldPort() { return web_worldport; } + std::string GetWebCertFile() { return web_certfile; } + std::string GetWebKeyFile() { return web_keyfile; } + std::string GetWebKeyPassword() { return web_keypassword; } + std::string GetWebHardcodeUser() { return web_hardcodeuser; } + std::string GetWebHardcodePassword() { return web_hardcodepassword; } + bool world_locked; +private: + int listening_socket; + char loginaddress[4][255]; + int16 loginport[4]; + char worldname[201]; + char worldaccount[31]; + char worldpassword[31]; + char worldaddress[255]; + char internalworldaddress[21]; + int16 worldport; + int8 DEFAULTSTATUS; + std::string web_worldaddress; + std::string web_certfile; + std::string web_keyfile; + std::string web_keypassword; + std::string web_hardcodeuser; + std::string web_hardcodepassword; + int16 web_worldport; + +}; + +class ZoneAuthRequest +{ +public: + ZoneAuthRequest(int32 account_id, char* name, int32 access_key); + ~ZoneAuthRequest( ); + int32 GetAccountID() { return accountid; } + const char* GetCharacterName() { return character_name.c_str(); } + int32 GetAccessKey() { return accesskey; } + int32 GetTimeStamp() { return timestamp; } + void setFirstLogin(bool value) { firstlogin = value; } + bool isFirstLogin() { return firstlogin; } +private: + int32 accountid; + string character_name; + int32 accesskey; + int32 timestamp; + bool firstlogin; +}; + +class ZoneAuth +{ +public: + void AddAuth(ZoneAuthRequest* zar); + ZoneAuthRequest* GetAuth(int32 account_id, int32 access_key); + void PurgeInactiveAuth(); + void RemoveAuth(ZoneAuthRequest* zar); +private: + LinkedList list; +}; +#endif diff --git a/source/WorldServer/races.cpp b/source/WorldServer/races.cpp new file mode 100644 index 0000000..d2c7eb3 --- /dev/null +++ b/source/WorldServer/races.cpp @@ -0,0 +1,147 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +#include "races.h" +#include "../common/MiscFunctions.h" + +Races::Races(){ + race_map["BARBARIAN"] = 0; + race_map["DARKELF"] = 1; + race_map["DWARF"] = 2; + race_map["ERUDITE"] = 3; + race_map["FROGLOK"] = 4; + race_map["GNOME"] = 5; + race_map["HALFELF"] = 6; + race_map["HALFLING"] = 7; + race_map["HIGHELF"] = 8; + race_map["HUMAN"] = 9; + race_map["IKSAR"] = 10; + race_map["KERRA"] = 11; + race_map["OGRE"] = 12; + race_map["RATONGA"] = 13; + race_map["TROLL"] = 14; + race_map["WOODELF"] = 15; + race_map["FAE_LIGHT"] = 16; + race_map["FAE_DARK"] = 17; + race_map["SARNAK"] = 18; + race_map["VAMPIRE"] = 19; + race_map["AERAKYN"] = 20; + + race_map_friendly[0] = "Barbarian"; + race_map_friendly[1] = "Dark Elf"; + race_map_friendly[2] = "Dwarf"; + race_map_friendly[3] = "Erudite"; + race_map_friendly[4] = "Froglok"; + race_map_friendly[5] = "Gnome"; + race_map_friendly[6] = "Half Elf"; + race_map_friendly[7] = "Halfling"; + race_map_friendly[8] = "High Elf"; + race_map_friendly[9] = "Human"; + race_map_friendly[10] = "Iksar"; + race_map_friendly[11] = "Kerra"; + race_map_friendly[12] = "Ogre"; + race_map_friendly[13] = "Ratonga"; + race_map_friendly[14] = "Troll"; + race_map_friendly[15] = "Wood Elf"; + race_map_friendly[16] = "Fae"; + race_map_friendly[17] = "Arasai"; + race_map_friendly[18] = "Sarnak"; + race_map_friendly[19] = "Vampire"; + race_map_friendly[20] = "Aerakyn"; + + // "Neutral" races are in both lists - this is for /randomize RACE + race_map_good[0] = "DWARF"; + race_map_good[1] = "FAE_LIGHT"; + race_map_good[2] = "FROGLOK"; + race_map_good[3] = "HALFLING"; + race_map_good[4] = "HIGHELF"; + race_map_good[5] = "WOODELF"; + race_map_good[6] = "BARBARIAN"; + race_map_good[7] = "ERUDITE"; + race_map_good[8] = "GNOME"; + race_map_good[9] = "HALFELF"; + race_map_good[10] = "HUMAN"; + race_map_good[11] = "KERRA"; + race_map_good[12] = "VAMPIRE"; + race_map_good[13] = "AERAKYN"; + + race_map_evil[0] = "FAE_DARK"; + race_map_evil[1] = "DARKELF"; + race_map_evil[2] = "IKSAR"; + race_map_evil[3] = "OGRE"; + race_map_evil[4] = "RATONGA"; + race_map_evil[5] = "SARNAK"; + race_map_evil[6] = "TROLL"; + race_map_evil[7] = "BARBARIAN"; + race_map_evil[8] = "ERUDITE"; + race_map_evil[9] = "GNOME"; + race_map_evil[10] = "HALFELF"; + race_map_evil[11] = "HUMAN"; + race_map_evil[12] = "KERRA"; + race_map_evil[13] = "VAMPIRE"; + race_map_evil[14] = "AERAKYN"; +} + +sint8 Races::GetRaceID(const char* name){ + string race_name = string(name); + race_name = ToUpper(race_name); + if(race_map.count(race_name) == 1) + return race_map[race_name]; + else + return -1; +} + +const char* Races::GetRaceName(int8 race_id){ + map::iterator itr; + for(itr = race_map.begin(); itr != race_map.end(); itr++){ + if(itr->second == race_id) + return itr->first.c_str(); + } + return 0; +} + +const char* Races::GetRaceNameCase(int8 race_id) { + map::iterator itr; + for(itr = race_map_friendly.begin(); itr != race_map_friendly.end(); itr++){ + if(itr->first == race_id) + return itr->second.c_str(); + } + return 0; +} + +int8 Races::GetRaceNameGood() { + int8 random = MakeRandomInt(0,13); // 12 good races + map::iterator itr; + for(itr = race_map_good.begin(); itr != race_map_good.end(); itr++){ + if(itr->first == random) + return GetRaceID(itr->second.c_str()); + } + return 9; // default to Human race if error finding another +} + +int8 Races::GetRaceNameEvil() { + int8 random = MakeRandomInt(0,14); // 13 evil races + map::iterator itr; + for(itr = race_map_evil.begin(); itr != race_map_evil.end(); itr++){ + if(itr->first == random) + return GetRaceID(itr->second.c_str()); + } + return 9; // default to Human race if error finding another +} diff --git a/source/WorldServer/races.h b/source/WorldServer/races.h new file mode 100644 index 0000000..1764959 --- /dev/null +++ b/source/WorldServer/races.h @@ -0,0 +1,62 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef RACES_H +#define RACES_H +#include "../common/types.h" +#include +using namespace std; + +#define BARBARIAN 0 +#define DARK_ELF 1 +#define DWARF 2 +#define ERUDITE 3 +#define FROGLOK 4 +#define GNOME 5 +#define HALF_ELF 6 +#define HALFLING 7 +#define HIGH_ELF 8 +#define HUMAN 9 +#define IKSAR 10 +#define KERRA 11 +#define OGRE 12 +#define RATONGA 13 +#define TROLL 14 +#define WOOD_ELF 15 +#define FAE 16 +#define ARASAI 17 +#define SARNAK 18 +#define VAMPIRE 19 +#define AERAKYN 20 + +class Races { +public: + Races(); + const char* GetRaceName(int8 race_id); + const char* GetRaceNameCase(int8 race_id); + int8 GetRaceNameGood(); + int8 GetRaceNameEvil(); + sint8 GetRaceID(const char* name); +private: + map race_map; + map race_map_friendly; + map race_map_good; + map race_map_evil; +}; +#endif diff --git a/source/WorldServer/zoneserver.cpp b/source/WorldServer/zoneserver.cpp new file mode 100644 index 0000000..61b1200 --- /dev/null +++ b/source/WorldServer/zoneserver.cpp @@ -0,0 +1,9104 @@ +/* +EQ2Emulator: Everquest II Server Emulator +Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + +This file is part of EQ2Emulator. +EQ2Emulator is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +EQ2Emulator 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with EQ2Emulator. If not, see . +*/ + +#include "../common/debug.h" +#include +using namespace std; +#include +#include "../common/misc.h" +#include +#include +#include +#include +#include +#include "Commands/Commands.h" +#include "Zone/pathfinder_interface.h" +#include "NPC_AI.h" + +#ifdef WIN32 +#include +#include +#include +#pragma comment(lib,"imagehlp.lib") +#else +#include +#include +#ifdef FREEBSD //Timothy Whitman - January 7, 2003 +#include +#endif +#include +#include +#include +#include +#include + +#include "../common/unix.h" + +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 + +extern int errno; +#endif + +#include "../common/servertalk.h" +#include "../common/packet_dump.h" +#include "WorldDatabase.h" +#include "races.h" +#include "classes.h" +#include "../common/seperator.h" +#include "../common/EQStream.h" +#include "../common/EQStreamFactory.h" +#include "../common/opcodemgr.h" +#include "zoneserver.h" +#include "client.h" +#include "LoginServer.h" +#include "World.h" +#include +#include +#include "LuaInterface.h" +#include "Factions.h" +#include "VisualStates.h" +#include "ClientPacketFunctions.h" +#include "SpellProcess.h" +#include "../common/Log.h" +#include "Rules/Rules.h" +#include "Chat/Chat.h" +#include "Tradeskills/Tradeskills.h" +#include "RaceTypes/RaceTypes.h" +#include +#include + +#include "Bots/Bot.h" + +#ifdef WIN32 +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +// int32 numplayers = 0; // never used? +// extern bool GetZoneLongName(char* short_name, char** long_name); // never used? +// extern bool holdzones; // never used? +// extern volatile bool RunLoops; // never used in the zone server? +// extern Classes classes; // never used in the zone server? + +#define NO_CATCH 1 + +extern WorldDatabase database; +extern sint32 numzones; +extern ClientList client_list; +extern LoginServer loginserver; +extern ZoneList zone_list; +extern World world; +extern ConfigReader configReader; +extern Commands commands; +extern LuaInterface* lua_interface; +extern MasterFactionList master_faction_list; +extern VisualStates visual_states; +extern RuleManager rule_manager; +extern Chat chat; +extern MasterRaceTypeList race_types_list; +extern MasterSpellList master_spell_list; // temp - remove later +extern MasterSkillList master_skill_list; + + +int32 MinInstanceID = 1000; + +// JA: Moved most default values to Rules and risky initializers to ZoneServer::Init() - 2012.12.07 +ZoneServer::ZoneServer(const char* name) { + incoming_clients = 0; + default_zone_map = nullptr; + + MIncomingClients.SetName("ZoneServer::MIncomingClients"); + + depop_zone = false; + repop_zone = false; + respawns_allowed = true; + instanceID = 0; + strcpy(zone_name, name); + zoneID = 0; + rain = 0; + cityzone = false; + always_loaded = false; + locked = false; // JA: implementing /zone lock|unlock commands + pNumPlayers = 0; + LoadingData = true; + zoneShuttingDown = false; + ++numzones; + revive_points = 0; + zone_motd = ""; + finished_depop = true; + initial_spawn_threads_active = 0; + minimumStatus = 0; + minimumLevel = 0; + maximumLevel = 0; + minimumVersion = 0; + weather_current_severity = 0; + weather_signaled = false; + xp_mod = 0; + isDusk = false; + dusk_hour = 0; + dusk_minute = 0; + dawn_hour = 0; + dawn_minute = 0; + reloading_spellprocess = false; + expansion_flag = 0; + holiday_flag = 0; + can_bind = 1; + can_gate = 1; + MMasterZoneLock = new CriticalSection(MUTEX_ATTRIBUTE_RECURSIVE); + + pathing = nullptr; + strcpy(zonesky_file,""); + + reloading = true; + watchdogTimestamp = Timer::GetCurrentTime2(); + + MPendingSpawnRemoval.SetName("ZoneServer::MPendingSpawnRemoval"); + + lifetime_client_count = 0; +} + +typedef map ChangedSpawnMapType; +ZoneServer::~ZoneServer() { + zoneShuttingDown = true; //ensure other threads shut down too + //allow other threads to properly shut down + LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name); + int32 disp_count = 0; + int32 next_disp_count = 100; + while (spawnthread_active || initial_spawn_threads_active > 0){ + bool disp = false; + if ( disp_count == 0 ) { + disp = true; + } + else if(disp_count >= next_disp_count) { + disp_count = 0; + disp = true; + } + + disp_count++; + if (spawnthread_active && disp) + LogWrite(ZONE__DEBUG, 7, "Zone", "Zone shutdown waiting on spawn thread"); + if (initial_spawn_threads_active > 0 && disp) + LogWrite(ZONE__DEBUG, 7, "Zone", "Zone shutdown waiting on initial spawn thread"); + Sleep(10); + } + + MChangedSpawns.lock(); + changed_spawns.clear(); + MChangedSpawns.unlock(); + + transport_spawns.clear(); + safe_delete(tradeskillMgr); + MMasterZoneLock->lock(); + MMasterSpawnLock.writelock(__FUNCTION__, __LINE__); + DeleteData(true); + RemoveLocationProximities(); + RemoveLocationGrids(); + DeleteSpawns(true); + + DeleteGlobalSpawns(); + + DeleteFlightPaths(); + + MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__); + MMasterZoneLock->unlock(); + world.UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, -1); + + // If lockout, public, tradeskill, or quest instance delete from db when zone shuts down + if (InstanceType == SOLO_LOCKOUT_INSTANCE || InstanceType == GROUP_LOCKOUT_INSTANCE || InstanceType == RAID_LOCKOUT_INSTANCE || + InstanceType == PUBLIC_INSTANCE || InstanceType == TRADESKILL_INSTANCE || InstanceType == QUEST_INSTANCE) { + LogWrite(INSTANCE__DEBUG, 0, "Instance", "Non persistent instance shutting down, deleting instance"); + database.DeleteInstance(instanceID); + } + + if (pathing != nullptr) + delete pathing; + + if (movementMgr != nullptr) + delete movementMgr; + + // moved to the bottom as we want spawns deleted first, this used to be above Spawn deletion which is a big no no + safe_delete(spellProcess); + + + MGridMaps.lock(); + std::map::iterator grids; + for(grids = grid_maps.begin(); grids != grid_maps.end(); grids++) { + GridMap* gm = grids->second; + safe_delete(gm); + } + grid_maps.clear(); + MGridMaps.unlock(); + + LogWrite(ZONE__INFO, 0, "Zone", "Completed zone shutdown of '%s'", zone_name); + --numzones; + UpdateWindowTitle(0); + zone_list.Remove(this); + zone_list.RemoveClientZoneReference(this); + safe_delete(MMasterZoneLock); +} + +void ZoneServer::IncrementIncomingClients() { + MIncomingClients.writelock(__FUNCTION__, __LINE__); + incoming_clients++; + LogWrite(ZONE__INFO, 0, "Zone", "Increment incoming clients of '%s' zoneid %u (instance id: %u). Current incoming client count: %u", zone_name, zoneID, instanceID, incoming_clients); + MIncomingClients.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DecrementIncomingClients() { + MIncomingClients.writelock(__FUNCTION__, __LINE__); + bool zeroed = false; + if(incoming_clients) + incoming_clients--; + else + zeroed = true; + LogWrite(ZONE__INFO, 0, "Zone", "Decrement incoming clients of '%s' zoneid %u (instance id: %u). Current incoming client count: %u (was client count previously zero: %u)", zone_name, zoneID, instanceID, incoming_clients, zeroed); + MIncomingClients.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::Init() +{ + LogWrite(ZONE__INFO, 0, "Zone", "Loading new Zone '%s'", zone_name); + zone_list.Add(this); + + spellProcess = new SpellProcess(); + tradeskillMgr = new TradeskillMgr(); + + /* Dynamic Timers */ + regenTimer.Start(rule_manager.GetGlobalRule(R_Zone, RegenTimer)->GetInt32()); + client_save.Start(rule_manager.GetGlobalRule(R_Zone, ClientSaveTimer)->GetInt32()); + shutdownTimer.Disable(); + spawn_range.Start(rule_manager.GetGlobalRule(R_Zone, CheckAttackPlayer)->GetInt32()); + aggro_timer.Start(rule_manager.GetGlobalRule(R_Zone, CheckAttackNPC)->GetInt32()); + /* Weather stuff */ + InitWeather(); + + /* Static Timers */ + // JA - haven't decided yet if these should remain hard-coded. Changing them could break EQ2Emu functionality + spawn_check_add.Start(1000); + spawn_check_remove.Start(30000); + spawn_expire_timer.Start(10000); + respawn_timer.Start(10000); + // there was never a starter for these? + widget_timer.Start(5000); + + tracking_timer.Start(5000); + + movement_timer.Start(100); + location_prox_timer.Start(1000); + location_grid_timer.Start(1000); + + charsheet_changes.Start(500); + + // Send game time packet every in game hour (180 sec) + sync_game_time_timer.Start(180000); + + // Get the dusk and dawn time from the rule manager and store it in the correct variables + sscanf (rule_manager.GetGlobalRule(R_World, DuskTime)->GetString(), "%d:%d", &dusk_hour, &dusk_minute); + sscanf (rule_manager.GetGlobalRule(R_World, DawnTime)->GetString(), "%d:%d", &dawn_hour, &dawn_minute); + + spawn_update.Start(rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + LogWrite(ZONE__DEBUG, 0, "Zone", "SpawnUpdateTimer: %ims", rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + + queue_updates.Start(rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + LogWrite(ZONE__DEBUG, 0, "Zone", "QueueUpdateTimer(inherits SpawnUpdateTimer): %ims", rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + + spawn_delete_timer = rule_manager.GetGlobalRule(R_Zone, SpawnDeleteTimer)->GetInt32(); + LogWrite(ZONE__DEBUG, 0, "Zone", "SpawnDeleteTimer: %ums", spawn_delete_timer); + + LogWrite(ZONE__DEBUG, 0, "Zone", "Loading zone flight paths"); + database.LoadZoneFlightPaths(this); + + world.UpdateServerStatistic(STAT_SERVER_NUM_ACTIVE_ZONES, 1); + UpdateWindowTitle(0); + + string zoneName(GetZoneFile()); + + world.LoadRegionMaps(zoneName); + + world.LoadMaps(zoneName); + + pathing = IPathfinder::Load(zoneName); + movementMgr = new MobMovementManager(); + + MMasterSpawnLock.SetName("ZoneServer::MMasterSpawnLock"); + m_npc_faction_list.SetName("ZoneServer::npc_faction_list"); + m_enemy_faction_list.SetName("ZoneServer::enemy_faction_list"); + m_reverse_enemy_faction_list.SetName("ZoneServer::reverse_enemy_faction_list"); + MDeadSpawns.SetName("ZoneServer::dead_spawns"); + MTransportSpawns.SetName("ZoneServer::transport_spawns"); + MSpawnList.SetName("ZoneServer::spawn_list"); + MTransporters.SetName("ZoneServer::m_transportMaps"); + MSpawnGroupAssociation.SetName("ZoneServer::spawn_group_associations"); + MSpawnGroupLocations.SetName("ZoneServer::spawn_group_locations"); + MSpawnLocationGroups.SetName("ZoneServer::spawn_location_groups"); + MSpawnGroupChances.SetName("ZoneServer::spawn_group_chances"); + MTransportLocations.SetName("ZoneServer::transporter_locations"); + MSpawnLocationList.SetName("ZoneServer::spawn_location_list"); + MSpawnDeleteList.SetName("ZoneServer::spawn_delete_list"); + MSpawnScriptTimers.SetName("ZoneServer::spawn_script_timers"); + MRemoveSpawnScriptTimersList.SetName("ZoneServer::remove_spawn_script_timers_list"); + MClientList.SetName("ZoneServer::clients"); + MWidgetTimers.SetName("ZoneServer::widget_timers"); +#ifdef WIN32 + _beginthread(ZoneLoop, 0, this); + _beginthread(SpawnLoop, 0, this); +#else + pthread_create(&ZoneThread, NULL, ZoneLoop, this); + pthread_detach(ZoneThread); + pthread_create(&SpawnThread, NULL, SpawnLoop, this); + pthread_detach(SpawnThread); +#endif +} + +void ZoneServer::CancelThreads() { +#ifdef WIN32 + LogWrite(WORLD__ERROR, 1, "World", "Zone %s is hung, however CancelThreads is unsupported for WIN32.", GetZoneName()); +#else + pthread_cancel(ZoneThread); + pthread_cancel(SpawnThread); +#endif +} + +void ZoneServer::InitWeather() +{ + weather_enabled = rule_manager.GetGlobalRule(R_Zone, WeatherEnabled)->GetBool(); + if( weather_enabled && isWeatherAllowed()) + { + string tmp; + // set up weather system when zone starts up + weather_type = rule_manager.GetGlobalRule(R_Zone, WeatherType)->GetInt8(); + switch(weather_type) + { + case 3: tmp = "Chaotic"; break; + case 2: tmp = "Random"; break; + case 1: tmp = "Dynamic"; break; + default: tmp = "Normal"; break; + } + LogWrite(ZONE__DEBUG, 0, "Zone", "%s: Setting up '%s' weather", zone_name, tmp.c_str()); + + weather_frequency = rule_manager.GetGlobalRule(R_Zone, WeatherChangeFrequency)->GetInt32(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Change weather every %u seconds", zone_name, weather_frequency); + + weather_change_chance = rule_manager.GetGlobalRule(R_Zone, WeatherChangeChance)->GetInt8(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Chance of weather change: %i%%", zone_name, weather_change_chance); + + weather_min_severity = rule_manager.GetGlobalRule(R_Zone, MinWeatherSeverity)->GetFloat(); + weather_max_severity = rule_manager.GetGlobalRule(R_Zone, MaxWeatherSeverity)->GetFloat(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Severity min/max is %.2f - %.2f", zone_name, weather_min_severity, weather_max_severity); + // Allow a random roll to determine if weather should start out severe or calm + if( MakeRandomInt(1, 100) > 50) + { + weather_pattern = 1; // default weather to increase in severity initially + weather_current_severity = weather_min_severity; + } + else + { + weather_pattern = 0; // default weather to decrease in severity initially + weather_current_severity = weather_max_severity; + } + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Severity set to %.2f, pattern: %i", zone_name, weather_current_severity, weather_pattern); + + weather_change_amount = rule_manager.GetGlobalRule(R_Zone, WeatherChangePerInterval)->GetFloat(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather change by %.2f each interval", zone_name, weather_change_amount); + + if( weather_type > 0 ) + { + weather_dynamic_offset = rule_manager.GetGlobalRule(R_Zone, WeatherDynamicMaxOffset)->GetFloat(); + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather Max Offset changes no more than %.2f each interval", zone_name, weather_dynamic_offset); + } + else + weather_dynamic_offset = 0; + + SetRain(weather_current_severity); + weather_last_changed_time = Timer::GetUnixTimeStamp(); + weatherTimer.Start(rule_manager.GetGlobalRule(R_Zone, WeatherTimer)->GetInt32()); + } +} +void ZoneServer::DeleteSpellProcess(){ + //Just get a lock to make sure we aren't already looping the spawnprocess or clientprocess if this is different than the calling thread + MMasterSpawnLock.writelock(__FUNCTION__, __LINE__); + MMasterZoneLock->lock(); + reloading_spellprocess = true; + // Remove spells from NPC's + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && spawn->IsNPC()) + ((NPC*)spawn)->SetSpells(0); + + if(spawn->IsEntity()) + ((Entity*)spawn)->RemoveSpellBonus(nullptr, true); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + MMasterZoneLock->unlock(); + MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__); + DismissAllPets(); + spellProcess->RemoveAllSpells(true); + safe_delete(spellProcess); +} + +void ZoneServer::LoadSpellProcess(){ + spellProcess = new SpellProcess(); + reloading_spellprocess = false; + + // Reload NPC's spells + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && spawn->IsNPC()) + ((NPC*)spawn)->SetSpells(world.GetNPCSpells(((NPC*)spawn)->GetPrimarySpellList(), ((NPC*)spawn)->GetSecondarySpellList())); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::LockAllSpells(Player* player) { + if (player && spellProcess) { + Client* client = ((Player*)player)->GetClient(); + if (client) + spellProcess->LockAllSpells(client); + } +} + +void ZoneServer::UnlockAllSpells(Player* player) { + if (player && spellProcess) { + Client* client = ((Player*)player)->GetClient(); + if (client) + spellProcess->UnlockAllSpells(client); + } +} + +void ZoneServer::DeleteFactionLists() { + map *>::iterator faction_itr; + map *>::iterator spawn_itr; + + m_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + for (faction_itr = enemy_faction_list.begin(); faction_itr != enemy_faction_list.end(); faction_itr++) + safe_delete(faction_itr->second); + enemy_faction_list.clear(); + m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__); + + m_reverse_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + for (faction_itr = reverse_enemy_faction_list.begin(); faction_itr != reverse_enemy_faction_list.end(); faction_itr++) + safe_delete(faction_itr->second); + reverse_enemy_faction_list.clear(); + m_reverse_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__); + + m_npc_faction_list.writelock(__FUNCTION__, __LINE__); + for (spawn_itr = npc_faction_list.begin(); spawn_itr != npc_faction_list.end(); spawn_itr++) + safe_delete(spawn_itr->second); + npc_faction_list.clear(); + m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DeleteData(bool boot_clients){ + Spawn* spawn = 0; + vector tmp_player_list; // changed to a vector from a MutexList as this is a local variable and don't need mutex stuff for the list + + // Clear spawn groups + spawn_group_map.clear(); + + // Loop through the spawn list and set the spawn for deletion + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn){ + if(!boot_clients && (spawn->IsPlayer() || spawn->IsBot())) + tmp_player_list.push_back(spawn); + else if(spawn->IsPlayer()){ + Client* client = ((Player*)spawn)->GetClient(); + if(client) + client->Disconnect(); + } + else{ + RemoveSpawnSupportFunctions(spawn, boot_clients, true); + RemoveSpawnFromGrid(spawn, spawn->GetLocation()); + AddPendingDelete(spawn); + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + // Quick hack to prevent a deadlock, RemoveSpawnSupportFunctions() will cancel spells and result in zone->GetSpawnByID() + // being called which read locks the spawn list and caused a dead lock as the above mutex's were write locked + MSpawnList.writelock(__FUNCTION__, __LINE__); + // Clear the spawn list, this was in the mutex above, moved it down so the above mutex could be a read lock + spawn_list.clear(); + + // Moved this up so we only read lock the list once in this list + vector::iterator spawn_iter2; + for (spawn_iter2 = tmp_player_list.begin(); spawn_iter2 != tmp_player_list.end(); spawn_iter2++) { + spawn_list[(*spawn_iter2)->GetID()] = (*spawn_iter2); + } + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + + // Clear player proximities + RemovePlayerProximity(0, true); + + spawn_range_map.clear(true); + if(boot_clients) { + // Refactor + vector::iterator itr; + + MClientList.writelock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) + safe_delete(*itr); + + clients.clear(); + MClientList.releasewritelock(__FUNCTION__, __LINE__); + } + + // Clear and delete spawn locations + MSpawnLocationList.writelock(__FUNCTION__, __LINE__); + map::iterator spawn_location_iter; + for (spawn_location_iter = spawn_location_list.begin(); spawn_location_iter != spawn_location_list.end(); spawn_location_iter++) + safe_delete(spawn_location_iter->second); + + spawn_location_list.clear(); + MSpawnLocationList.releasewritelock(__FUNCTION__, __LINE__); + + // If we allow clients to stay in the zone we need to preserve the revive_points, otherwise if the player dies they will crash + if(revive_points && boot_clients){ + vector::iterator revive_iter; + for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++){ + safe_delete(*revive_iter); + } + safe_delete(revive_points); + } + + MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__); + map*>::iterator assoc_itr; + for (assoc_itr = spawn_group_associations.begin(); assoc_itr != spawn_group_associations.end(); assoc_itr++) + safe_delete(assoc_itr->second); + + spawn_group_associations.clear(); + MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__); + map*>::iterator loc_itr; + for (loc_itr = spawn_group_locations.begin(); loc_itr != spawn_group_locations.end(); loc_itr++) + safe_delete(loc_itr->second); + + spawn_group_locations.clear(); + MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__); + map*>::iterator group_itr; + for (group_itr = spawn_location_groups.begin(); group_itr != spawn_location_groups.end(); group_itr++) + safe_delete(group_itr->second); + + spawn_location_groups.clear(); + MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__); + + // Clear lists that need more then just a Clear() + DeleteFactionLists(); + DeleteSpawnScriptTimers(0, true); + DeleteSpawnScriptTimers(); + ClearDeadSpawns(); + + // Clear lists + movement_spawns.clear(); + respawn_timers.clear(); + transport_spawns.clear(); + quick_database_id_lookup.clear(); + + MWidgetTimers.writelock(__FUNCTION__, __LINE__); + widget_timers.clear(); + MWidgetTimers.releasewritelock(__FUNCTION__, __LINE__); + + map::iterator struct_itr; + for (struct_itr = versioned_info_structs.begin(); struct_itr != versioned_info_structs.end(); struct_itr++) + safe_delete(struct_itr->second); + versioned_info_structs.clear(); + + for (struct_itr = versioned_pos_structs.begin(); struct_itr != versioned_pos_structs.end(); struct_itr++) + safe_delete(struct_itr->second); + versioned_pos_structs.clear(); + + for (struct_itr = versioned_vis_structs.begin(); struct_itr != versioned_vis_structs.end(); struct_itr++) + safe_delete(struct_itr->second); + versioned_vis_structs.clear(); +} + +void ZoneServer::RemoveLocationProximities() { + MutexList::iterator itr = location_proximities.begin(); + while(itr.Next()){ + safe_delete(itr->value); + } + location_proximities.clear(); +} + +RevivePoint* ZoneServer::GetRevivePoint(int32 id){ + vector::iterator revive_iter; + for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++){ + if((*revive_iter)->id == id) + return *revive_iter; + } + return 0; +} + +vector* ZoneServer::GetRevivePoints(Client* client) +{ + vector* points = new vector; + RevivePoint* closest_point = 0; + + // we should not check for revive points if this is null + if ( revive_points != NULL ) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Got revive point in %s!", __FUNCTION__); + + float closest = 100000; + float test_closest = 0; + RevivePoint* test_point = 0; + vector::iterator revive_iter; + for(revive_iter=revive_points->begin(); revive_iter != revive_points->end(); revive_iter++) + { + test_point = *revive_iter; + if(test_point) + { + test_closest = client->GetPlayer()->GetDistance(test_point->x, test_point->y, test_point->z); + + // should this be changed to list all revive points within max distance or just the closest + if(test_closest < closest) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "test_closest: %.2f, closest: %.2f", test_closest, closest); + closest = test_closest; + closest_point = test_point; + } + if(test_point->always_included ) { + points->push_back(test_point); + if(closest_point == test_point) { + closest_point = nullptr; + closest = 100000; + } + } + } + } + if(closest_point) { + points->push_back(closest_point); + } + } + + if(closest_point && points->size() == 0 && closest_point->zone_id == GetZoneID()) + { + LogWrite(ZONE__WARNING, 0, "Zone", "Nearest Revive Point too far away. Add another!"); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "The closest revive point is quite far away, you might want to ask the server admin for a closer one."); + points->push_back(closest_point); + } + else if(points->size() == 0) + { + LogWrite(ZONE__WARNING, 0, "Zone", "No Revive Points set for zoneID %u. Add some!", GetZoneID()); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "There are no revive points for this zone, you might want to ask the server admin for one."); + closest_point = new RevivePoint; + closest_point->heading = GetSafeHeading(); + closest_point->id = 0xFFFFFFFF; + closest_point->location_name = "Zone Safe Point"; + closest_point->zone_id = GetZoneID(); + closest_point->x = GetSafeX(); + closest_point->y = GetSafeY(); + closest_point->z = GetSafeZ(); + closest_point->always_included = true; + points->push_back(closest_point); + } + return points; +} + +void ZoneServer::TriggerCharSheetTimer(){ + charsheet_changes.Trigger(); +} + +void ZoneServer::RegenUpdate(){ + if(damaged_spawns.size(true) == 0) + return; + + Spawn* spawn = 0; + MutexList::iterator spawn_iter = damaged_spawns.begin(); + while(spawn_iter.Next()){ + spawn = GetSpawnByID(spawn_iter->value); + if(spawn && (((spawn->GetHP() < spawn->GetTotalHP()) && spawn->GetHP()>0) || (spawn->GetPower() < spawn->GetTotalPower()))){ + if(spawn->IsEntity()) + ((Entity*)spawn)->DoRegenUpdate(); + if(spawn->IsPlayer()){ + Client* client = ((Player*)spawn)->GetClient(); + if(client && client->IsReadyForUpdates()) + client->QueuePacket(client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion())); + } + } + else + RemoveDamagedSpawn(spawn); + //Spawn no longer valid, remove it from the list + if (!spawn) + damaged_spawns.Remove(spawn_iter->value); + } +} + +void ZoneServer::ClearDeadSpawns(){ + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + dead_spawns.clear(); + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ProcessDepop(bool respawns_allowed, bool repop) { + vector::iterator client_itr; + Client* client = 0; + Spawn* spawn = 0; + PacketStruct* packet = 0; + int16 packet_version = 0; + spawn_expire_timers.clear(); + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client) + continue; + client->GetPlayer()->SetTarget(0); + if(repop) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone Repop in progress..."); + else{ + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Zone Depop in progress..."); + if(respawns_allowed) + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Spawns will respawn according to their respawn timers."); + } + if(!packet || packet_version != client->GetVersion()){ + safe_delete(packet); + packet_version = client->GetVersion(); + packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version); + } + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && !spawn->IsPlayer() && !spawn->IsBot()){ + bool dispatched = false; + if(spawn->IsPet()) + { + Entity* owner = ((Entity*)spawn)->GetOwner(); + if(owner) + { + owner->DismissPet((Entity*)spawn); + dispatched = true; + } + } + + spawn->SetDeletedSpawn(true); + + if(!dispatched) + SendRemoveSpawn(client, spawn, packet); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + DeleteTransporters(); + safe_delete(packet); + if(!repop && respawns_allowed){ + spawn_range_map.clear(true); + MutexList tmp_player_list; // Local variable, never be on another thread so probably don't need the extra mutex code that comes with a MutexList + ClearDeadSpawns(); + + map::iterator itr; + MSpawnList.writelock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn) { + if(spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0) + respawn_timers.Put(spawn->GetSpawnLocationID(), Timer::GetCurrentTime2() + spawn->GetRespawnTime()*1000); + if(spawn->IsPlayer() || spawn->IsBot()) + tmp_player_list.Add(spawn); + else { + RemoveSpawnSupportFunctions(spawn, true); + RemoveSpawnFromGrid(spawn, spawn->GetLocation()); + AddPendingDelete(spawn); + } + } + } + spawn_list.clear(); + //add back just the clients + MutexList::iterator spawn_iter2 = tmp_player_list.begin(); + while(spawn_iter2.Next()) { + spawn_list[spawn_iter2->value->GetID()] = spawn_iter2->value; + } + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + } + else { + DeleteData(false); + } + + if(repop) + { + // reload spirit shards for the current zone + database.LoadSpiritShards(this); + + LoadingData = true; + } +} + +void ZoneServer::Depop(bool respawns, bool repop) { + respawns_allowed = respawns; + repop_zone = repop; + finished_depop = false; + depop_zone = true; +} + +bool ZoneServer::AddCloseSpawnsToSpawnGroup(Spawn* spawn, float radius){ + if(!spawn) + return false; + + Spawn* close_spawn = 0; + bool ret = true; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + close_spawn = itr->second; + if(close_spawn && close_spawn != spawn && !close_spawn->IsPlayer() && close_spawn->GetDistance(spawn) <= radius){ + if((spawn->IsNPC() && close_spawn->IsNPC()) || (spawn->IsGroundSpawn() && close_spawn->IsGroundSpawn()) || (spawn->IsObject() && close_spawn->IsObject()) || (spawn->IsWidget() && close_spawn->IsWidget()) || (spawn->IsSign() && close_spawn->IsSign())){ + if(close_spawn->GetSpawnGroupID() == 0){ + spawn->AddSpawnToGroup(close_spawn); + close_spawn->AddSpawnToGroup(spawn); + } + else + ret = false; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void ZoneServer::RepopSpawns(Client* client, Spawn* in_spawn){ + vector* spawns = in_spawn->GetSpawnGroup(); + PacketStruct* packet = configReader.getStruct("WS_DestroyGhostCmd", client->GetVersion()); + if(spawns){ + if(!packet) + return; + + Spawn* spawn = 0; + vector::iterator itr; + for(itr = spawns->begin(); itr != spawns->end(); itr++){ + spawn = *itr; + spawn->SetDeletedSpawn(true); + SendRemoveSpawn(client, spawn, packet); + } + } + safe_delete(spawns); + if(in_spawn) + in_spawn->SetDeletedSpawn(true); + + SendRemoveSpawn(client, in_spawn, packet); + spawn_check_add.Trigger(); + safe_delete(packet); +} + +bool ZoneServer::AggroVictim(NPC* npc, Spawn* victim, Client* client) +{ + bool isEntity = victim->IsEntity(); + if(isEntity && !npc->AttackAllowed((Entity*)victim)) + return false; + + if (npc->HasSpawnGroup()) { + vector* groupVec = npc->GetSpawnGroup(); + for (int32 i = 0; i < groupVec->size(); i++) { + Spawn* group_member = groupVec->at(i); + if (group_member && !group_member->EngagedInCombat() && group_member->Alive()) { + CallSpawnScript(group_member, SPAWN_SCRIPT_AGGRO, victim); + if (isEntity) + ((NPC*)group_member)->AddHate((Entity*)victim, 50); + else + ((NPC*)group_member)->InCombat(true); + } + } + safe_delete(groupVec); + } + else + { + if (isEntity) + { + CallSpawnScript(victim, SPAWN_SCRIPT_AGGRO, victim); + npc->AddHate((Entity*)victim, 50); + } + else + npc->InCombat(true); + } + + victim->CheckEncounterState((Entity*)npc, true); + return true; +} + +bool ZoneServer::CheckNPCAttacks(NPC* npc, Spawn* victim, Client* client){ + if(!npc || !victim) + return true; + + if (client) { + int8 arrow = 0; + if (client->IsReadyForUpdates() && npc->CanSeeInvis(client->GetPlayer()) && client->GetPlayer()->GetFactions()->ShouldAttack(npc->GetFactionID()) && npc->AttackAllowed((Entity*)victim, false)) { + if (!npc->EngagedInCombat()) { + if(client->GetPlayer()->GetArrowColor(npc->GetLevel()) != ARROW_COLOR_GRAY) { + AggroVictim(npc, victim, client); + } + else if(npc->IsScaredByStrongPlayers() && + !client->GetPlayer()->IsSpawnInRangeList(npc->GetID())) { + SendSpawnChanges(npc, client, true, true); + client->GetPlayer()->SetSpawnInRangeList(npc->GetID(), true); + } + } + } + } + else{ + AggroVictim(npc, victim, client); + } + return true; +} + +bool ZoneServer::CheckEnemyList(NPC* npc) { + vector *factions; + vector::iterator faction_itr; + vector *spawns; + vector::iterator spawn_itr; + map attack_spawns; + map reverse_attack_spawns; + map::iterator itr; + int32 faction_id = npc->GetFactionID(); + float distance; + + if (faction_id == 0) + return true; + + m_enemy_faction_list.readlock(__FUNCTION__, __LINE__); + if (enemy_faction_list.count(faction_id) > 0) { + factions = enemy_faction_list[faction_id]; + + for (faction_itr = factions->begin(); faction_itr != factions->end(); faction_itr++) { + m_npc_faction_list.readlock(__FUNCTION__, __LINE__); + if (npc_faction_list.count(*faction_itr) > 0) { + spawns = npc_faction_list[*faction_itr]; + spawn_itr = spawns->begin(); + + for (spawn_itr = spawns->begin(); spawn_itr != spawns->end(); spawn_itr++) { + Spawn* spawn = GetSpawnByID(*spawn_itr); + if (spawn) { + if ((!npc->IsPrivateSpawn() || npc->AllowedAccess(spawn)) && (distance = spawn->GetDistance(npc)) <= npc->GetAggroRadius() && npc->CheckLoS(spawn)) + attack_spawns[distance] = spawn; + } + } + } + m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__); + } + } + m_enemy_faction_list.releasereadlock(__FUNCTION__, __LINE__); + + m_reverse_enemy_faction_list.readlock(__FUNCTION__, __LINE__); + if (reverse_enemy_faction_list.count(faction_id) > 0) { + factions = reverse_enemy_faction_list[faction_id]; + + for (faction_itr = factions->begin(); faction_itr != factions->end(); faction_itr++) { + m_npc_faction_list.readlock(__FUNCTION__, __LINE__); + if (npc_faction_list.count(*faction_itr) > 0) { + spawns = npc_faction_list[*faction_itr]; + spawn_itr = spawns->begin(); + + for (spawn_itr = spawns->begin(); spawn_itr != spawns->end(); spawn_itr++) { + Spawn* spawn = GetSpawnByID(*spawn_itr); + if (spawn) { + if ((!npc->IsPrivateSpawn() || npc->AllowedAccess(spawn)) && (distance = spawn->GetDistance(npc)) <= npc->GetAggroRadius() && npc->CheckLoS(spawn)) + reverse_attack_spawns[distance] = spawn; + } + } + } + m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__); + } + } + m_reverse_enemy_faction_list.releasereadlock(__FUNCTION__, __LINE__); + + if (attack_spawns.size() > 0) { + for (itr = attack_spawns.begin(); itr != attack_spawns.end(); itr++) + CheckNPCAttacks(npc, itr->second); + } + if (reverse_attack_spawns.size() > 0) { + for (itr = reverse_attack_spawns.begin(); itr != reverse_attack_spawns.end(); itr++) + CheckNPCAttacks((NPC*)itr->second, npc); + } + + return attack_spawns.size() == 0; +} + +void ZoneServer::RemoveDeadEnemyList(Spawn *spawn) +{ + int32 faction_id = spawn->GetFactionID(); + vector *spawns; + vector::iterator itr; + + m_npc_faction_list.writelock(__FUNCTION__, __LINE__); + if (npc_faction_list.count(faction_id) > 0) { + spawns = npc_faction_list[faction_id]; + + for (itr = spawns->begin(); itr != spawns->end(); itr++) { + if (*itr == spawn->GetID()) { + spawns->erase(itr); + break; + } + } + } + m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddEnemyList(NPC* npc){ + int32 faction_id = npc->GetFactionID(); + vector *hostile_factions; + vector::iterator itr; + + if(faction_id <= 9) + return; + + if(!rule_manager.GetGlobalRule(R_Faction, AllowFactionBasedCombat)->GetBool()) { + LogWrite(FACTION__WARNING, 0, "Faction", "Faction Combat is DISABLED via R_Faction::AllowFactionBasedCombat rule!"); + return; + } + + m_npc_faction_list.readlock(__FUNCTION__, __LINE__); + if (npc_faction_list.count(faction_id) == 0) { + if(faction_id > 10) { + if ((hostile_factions = master_faction_list.GetHostileFactions(faction_id)) != NULL) { + itr = hostile_factions->begin(); + + for (itr = hostile_factions->begin(); itr != hostile_factions->end(); itr++) { + m_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + if (enemy_faction_list.count(faction_id) == 0) + enemy_faction_list[faction_id] = new vector; + enemy_faction_list[faction_id]->push_back(*itr); + m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__); + + m_reverse_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + if(reverse_enemy_faction_list.count(*itr) == 0) + reverse_enemy_faction_list[*itr] = new vector; + reverse_enemy_faction_list[*itr]->push_back(faction_id); + m_reverse_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__); + } + } + } + + /*m_enemy_faction_list.writelock(__FUNCTION__, __LINE__); + if(enemy_faction_list.count(1) == 0) + enemy_faction_list[1] = new vector; + enemy_faction_list[1]->push_back(faction_id); + m_enemy_faction_list.releasewritelock(__FUNCTION__, __LINE__);*/ + } + m_npc_faction_list.releasereadlock(__FUNCTION__, __LINE__); + + m_npc_faction_list.writelock(__FUNCTION__, __LINE__); + if(npc_faction_list.count(faction_id) == 0) + npc_faction_list[faction_id] = new vector; + npc_faction_list[faction_id]->push_back(npc->GetID()); + m_npc_faction_list.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::CheckSpawnRange(Client* client, Spawn* spawn, bool initial_login){ + if(client && spawn && (initial_login || client->IsConnected())) { + if(spawn != client->GetPlayer()) { + if(spawn_range_map.count(client) == 0) + spawn_range_map.Put(client, new MutexMap()); + float curDist = spawn->GetDistance(client->GetPlayer()); + + int32 ghost_spawn_id = client->GetPlayerPOVGhostSpawnID(); + Spawn* otherSpawn = GetSpawnByID(ghost_spawn_id); + + if (!client->GetPlayer()->WasSentSpawn(spawn->GetID()) + && (!otherSpawn || otherSpawn->GetDistance(spawn) > SEND_SPAWN_DISTANCE) && curDist > SEND_SPAWN_DISTANCE) + { + return; + } + + spawn_range_map.Get(client)->Put(spawn->GetID(), curDist); + + if(!initial_login && client && spawn->IsNPC() && (!spawn->IsPrivateSpawn() || spawn->AllowedAccess(client->GetPlayer())) + && curDist <= ((NPC*)spawn)->GetAggroRadius() && !client->GetPlayer()->GetInvulnerable()) + CheckNPCAttacks((NPC*)spawn, client->GetPlayer(), client); + } + + if(!initial_login) + CheckPlayerProximity(spawn, client); + } +} + +void ZoneServer::CheckSpawnRange(Spawn* spawn){ + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsReadyForSpawns()) + CheckSpawnRange(client, spawn); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::PrepareSpawnID(Player* player, Spawn* spawn){ + return player->SetSpawnMap(spawn); +} + +void ZoneServer::CheckSendSpawnToClient(Client* client, bool initial_login) { + if (!client) { + LogWrite(ZONE__ERROR, 0, "Zone", "CheckSendSpawnToClient called with an invalid client"); + return; + } + + if (!initial_login && !client->GetInitialSpawnsSent() || (!initial_login && !client->IsReadyForSpawns())) + return; + + Spawn* spawn = 0; + map* > closest_spawns; + if (spawn_range_map.count(client) > 0) { + if (initial_login || client->IsConnected()) { + MutexMap::iterator spawn_iter = spawn_range_map.Get(client)->begin(); + while (spawn_iter.Next()) { + spawn = GetSpawnByID(spawn_iter->first, true); + if (spawn && spawn->GetPrivateQuestSpawn()) { + if (!spawn->IsPrivateSpawn()) + spawn->AddAllowAccessSpawn(spawn); + if (spawn->MeetsSpawnAccessRequirements(client->GetPlayer())) { + if (spawn->IsPrivateSpawn() && !spawn->AllowedAccess(client->GetPlayer())) + spawn->AddAllowAccessSpawn(client->GetPlayer()); + } + else if (spawn->AllowedAccess(client->GetPlayer())) + spawn->RemoveSpawnAccess(client->GetPlayer()); + } + if (spawn && spawn != client->GetPlayer() && client->GetPlayer()->ShouldSendSpawn(spawn)) { + if ((!initial_login && spawn_iter->second <= SEND_SPAWN_DISTANCE) || (initial_login && (spawn_iter->second <= (SEND_SPAWN_DISTANCE / 2) || spawn->IsWidget()))) { + if(PrepareSpawnID(client->GetPlayer(), spawn)) { + if (closest_spawns.count(spawn_iter->second) == 0) + closest_spawns[spawn_iter->second] = new vector(); + closest_spawns[spawn_iter->second]->push_back(spawn); + } + } + } + } + } + vector::iterator spawn_iter2; + map* >::iterator itr; + for (itr = closest_spawns.begin(); itr != closest_spawns.end(); ) { + for (spawn_iter2 = itr->second->begin(); spawn_iter2 != itr->second->end(); spawn_iter2++) { + spawn = *spawn_iter2; + + if(!client->IsReloadingZone() || (client->IsReloadingZone() && spawn != client->GetPlayer())) + SendSpawn(spawn, client); + + if (client->ShouldTarget() && client->GetCombineSpawn() == spawn) + client->TargetSpawn(spawn); + } + vector* vect = itr->second; + map* >::iterator tmpitr = itr; + itr++; + closest_spawns.erase(tmpitr); + safe_delete(vect); + } + } + + if (initial_login) + client->SetInitialSpawnsSent(true); +} + +void ZoneServer::CheckSendSpawnToClient(){ + vector::iterator itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + if(client->IsReadyForSpawns()) + CheckSendSpawnToClient(client); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::CheckRemoveSpawnFromClient(Spawn* spawn) { + vector::iterator itr; + Client* client = 0; + PacketStruct* packet = 0; + int16 packet_version = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + if(client){ + int32 ghost_spawn_id = client->GetPlayerPOVGhostSpawnID(); + Spawn* otherSpawn = GetSpawnByID(ghost_spawn_id); + if(!packet || packet_version != client->GetVersion()){ + safe_delete(packet); + packet_version = client->GetVersion(); + packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version); + } + + if(spawn && spawn != client->GetPlayer() && + client->GetPlayer()->WasSentSpawn(spawn->GetID()) && + !client->GetPlayer()->IsRemovingSpawn(spawn->GetID()) && + client->GetPlayer()->WasSpawnRemoved(spawn) == false && + (ghost_spawn_id == 0 || (ghost_spawn_id != spawn->GetID() && otherSpawn && otherSpawn->GetDistance(spawn) > REMOVE_SPAWN_DISTANCE)) && + (spawn_range_map.Get(client)->Get(spawn->GetID()) > REMOVE_SPAWN_DISTANCE && + !spawn->IsSign() && !spawn->IsObject() && !spawn->IsWidget() && !spawn->IsTransportSpawn())){ + SendRemoveSpawn(client, spawn, packet); + spawn_range_map.Get(client)->erase(spawn->GetID()); + } + + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); +} + +bool ZoneServer::CombatProcess(Spawn* spawn) { + bool ret = true; + + if (spawn && spawn->IsEntity()) + ((Entity*)spawn)->ProcessCombat(); + if (spawn && !spawn->Alive() && !spawn->IsLootDispensed()) { + LootProcess(spawn); + } + + return ret; +} + +void ZoneServer::LootProcess(Spawn* spawn) { + if (spawn->GetLootMethod() == GroupLootMethod::METHOD_ROUND_ROBIN) { + spawn->LockLoot(); + if (spawn->CheckLootTimer() || spawn->IsLootWindowComplete()) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: Dispensing loot, loot window was completed? %s.", spawn->GetName(), spawn->IsLootWindowComplete() ? "YES" : "NO"); + spawn->DisableLootTimer(); + spawn->SetLootDispensed(); + Spawn* looter = nullptr; + if (spawn->GetLootGroupID() < 1 && spawn->GetLootWindowList()->size() > 0) { + std::map::iterator itr; + + for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) { + Spawn* entry = GetSpawnByID(itr->first, true); + if (entry->IsPlayer()) { + looter = entry; + break; + } + } + + int32 item_id = 0; + std::vector item_list; + spawn->GetLootItemsList(&item_list); + spawn->UnlockLoot(); + + std::vector::iterator item_itr; + + for (item_itr = item_list.begin(); item_itr != item_list.end(); item_itr++) { + int32 item_id = *item_itr; + Item* tmpItem = master_item_list.GetItem(item_id); + + bool skipItem = spawn->IsItemInLootTier(tmpItem); + + if (skipItem) + continue; + + if (looter) { + if (looter->IsPlayer()) { + + Item* item = spawn->LootItem(item_id); + bool success = false; + success = ((Player*)looter)->GetClient()->HandleLootItem(spawn, item, ((Player*)looter)); + + if (!success) + spawn->AddLootItem(item); + } + else { + Item* item = spawn->LootItem(item_id); + safe_delete(item); + } + } + } + } + else if (spawn->GetLootGroupID() > 0) { + int32 item_id = 0; + std::vector item_list; + spawn->GetLootItemsList(&item_list); + spawn->UnlockLoot(); + spawn->DistributeGroupLoot_RoundRobin(&item_list); + } + + if (!spawn->HasLoot()) { + if (spawn->IsNPC()) + RemoveDeadSpawn(spawn); + } + else { + spawn->LockLoot(); + spawn->SetLootMethod(GroupLootMethod::METHOD_FFA, 0, 0); + spawn->SetLooterSpawnID(0); + spawn->UnlockLoot(); + } + } + else { + spawn->UnlockLoot(); + } + } + else if ((spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || spawn->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && spawn->IsLootTimerRunning()) { + spawn->LockLoot(); + if (spawn->CheckLootTimer() || spawn->IsLootWindowComplete()) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: Dispensing loot, loot window was completed? %s.", spawn->GetName(), spawn->IsLootWindowComplete() ? "YES" : "NO"); + spawn->DisableLootTimer(); + spawn->SetLootDispensed(); + + // identify any clients that still have the loot window open, close it out + CloseSpawnLootWindow(spawn); + + // lotto items while we have loot items in the list + int32 item_id = 0; + std::vector item_list; + spawn->GetLootItemsList(&item_list); + spawn->UnlockLoot(); + + std::vector::iterator item_itr; + + for (item_itr = item_list.begin(); item_itr != item_list.end(); item_itr++) { + int32 item_id = *item_itr; + Item* tmpItem = master_item_list.GetItem(item_id); + + bool skipItem = spawn->IsItemInLootTier(tmpItem); + + if (skipItem) + continue; + + std::map out_entries; + std::map::iterator out_itr; + bool itemNeed = true; + switch (spawn->GetLootMethod()) { + case GroupLootMethod::METHOD_LOTTO: { + spawn->GetSpawnLottoEntries(item_id, &out_entries); + break; + } + case GroupLootMethod::METHOD_NEED_BEFORE_GREED: { + spawn->GetSpawnNeedGreedEntries(item_id, true, &out_entries); + if (out_entries.size() < 1) { + spawn->GetSpawnNeedGreedEntries(item_id, false, &out_entries); + itemNeed = false; + } + break; + } + } + if (out_entries.size() < 1) { + LogWrite(LOOT__INFO, 0, "Loot", "%s: No spawns matched for loot attempt of %s (%u), skip item.", spawn->GetName(), tmpItem ? tmpItem->name.c_str() : "Unknown", item_id); + continue; + } + Spawn* looter = nullptr; + int32 curWinValue = 0; + for (out_itr = out_entries.begin(); out_itr != out_entries.end(); out_itr++) { + Spawn* entry = GetSpawnByID(out_itr->first, true); + if ((out_itr->second > curWinValue) || looter == nullptr) { + curWinValue = out_itr->second; + looter = entry; + } + if (spawn->GetLootMethod() == GroupLootMethod::METHOD_LOTTO) { + world.GetGroupManager()->SendGroupMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %u on %s.", entry->GetName(), out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown"); + } + else { + world.GetGroupManager()->SendGroupMessage(spawn->GetLootGroupID(), CHANNEL_LOOT_ROLLS, "%s rolled %s (%u) on %s.", entry->GetName(), itemNeed ? "NEED" : "GREED", out_itr->second, tmpItem ? tmpItem->name.c_str() : "Unknown"); + } + } + + if (looter) { + if (looter->IsPlayer()) { + Item* item = spawn->LootItem(item_id); + bool success = false; + success = ((Player*)looter)->GetClient()->HandleLootItem(spawn, item, ((Player*)looter)); + + if (!success) + spawn->AddLootItem(item); + } + else { + Item* item = spawn->LootItem(item_id); + safe_delete(item); + } + } + } + + if (!spawn->HasLoot()) { + if (spawn->IsNPC()) + RemoveDeadSpawn(spawn); + } + else { + spawn->LockLoot(); + spawn->SetLootMethod(GroupLootMethod::METHOD_FFA, 0, 0); + spawn->SetLooterSpawnID(0); + spawn->UnlockLoot(); + } + } + else { + spawn->UnlockLoot(); + } + } +} + +void ZoneServer::CloseSpawnLootWindow(Spawn* spawn) { + if (spawn->GetLootWindowList()->size() > 0) { + std::map::iterator itr; + for (itr = spawn->GetLootWindowList()->begin(); itr != spawn->GetLootWindowList()->end(); itr++) { + if (itr->second) + continue; + + itr->second = true; + Spawn* looter = GetSpawnByID(itr->first, true); + if (looter && looter->IsPlayer() && ((Player*)looter)->GetClient()) { + LogWrite(LOOT__DEBUG, 0, "Loot", "%s: Close loot for player %s.", spawn->GetName(), looter->GetName()); + ((Player*)looter)->GetClient()->CloseLoot(spawn->GetID()); + } + } + } +} +void ZoneServer::AddPendingDelete(Spawn* spawn) { + MSpawnDeleteList.writelock(__FUNCTION__, __LINE__); + spawn->SetDeletedSpawn(true); + if (spawn_delete_list.count(spawn) == 0) + spawn_delete_list.insert(make_pair(spawn, Timer::GetCurrentTime2() + spawn_delete_timer)); //give other threads up to 30 seconds to stop using this spawn reference + MSpawnDeleteList.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DeleteSpawns(bool delete_all) { + MSpawnDeleteList.writelock(__FUNCTION__, __LINE__); + MPendingSpawnRemoval.readlock(__FUNCTION__, __LINE__); + if(spawn_delete_list.size() > 0){ + map::iterator itr; + map::iterator erase_itr; + int32 current_time = Timer::GetCurrentTime2(); + Spawn* spawn = 0; + for (itr = spawn_delete_list.begin(); itr != spawn_delete_list.end(); ) { + if (delete_all || current_time >= itr->second){ + // we haven't removed it from the spawn list yet.. + if(!delete_all && m_pendingSpawnRemove.count(itr->first->GetID())) + continue; + + spawn = itr->first; + if(movementMgr != nullptr) { + movementMgr->RemoveMob((Entity*)spawn); + } + + // delete brain if it has one + if(spawn->IsNPC()) { + NPC* tmpNPC = (NPC*)spawn; + if(tmpNPC->Brain()) + tmpNPC->SetBrain(nullptr); + } + + erase_itr = itr++; + spawn_delete_list.erase(erase_itr); + + MSpawnList.writelock(__FUNCTION__, __LINE__); + lua_interface->SetLuaUserDataStale(spawn); + + std::map::iterator sitr = spawn_list.find(spawn->GetID()); + if(sitr != spawn_list.end()) { + spawn_list.erase(sitr); + } + + if(spawn->IsCollector()) { + std::map::iterator subitr = subspawn_list[SUBSPAWN_TYPES::COLLECTOR].find(spawn->GetID()); + if(subitr != subspawn_list[SUBSPAWN_TYPES::COLLECTOR].end()) { + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(subitr); + } + } + + if(spawn->GetPickupItemID()) { + std::map::iterator subitr = subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].find(spawn->GetPickupItemID()); + if(subitr != subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].end() && subitr->second == spawn) { + subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].erase(subitr); + } + housing_spawn_map.erase(spawn->GetID()); + } + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + spellProcess->RemoveCaster(spawn); + safe_delete(spawn); + } + else + itr++; + } + } + MPendingSpawnRemoval.releasereadlock(__FUNCTION__, __LINE__); + MSpawnDeleteList.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddDamagedSpawn(Spawn* spawn){ + if (spawn) + damaged_spawns.Add(spawn->GetID()); +} + +void ZoneServer::RemoveDamagedSpawn(Spawn* spawn){ + if (spawn) + damaged_spawns.Remove(spawn->GetID()); +} + +bool ZoneServer::Process() +{ + MMasterZoneLock->lock(); //Changing this back to a recursive lock to fix a possible /reload spells crash with multiple zones running - Foof + SetWatchdogTime(Timer::GetCurrentTime2()); +#ifndef NO_CATCH + try + { +#endif + while (zoneID == 0) { //this is loaded by world + SetWatchdogTime(Timer::GetCurrentTime2()); + Sleep(10); + } + + if (LoadingData) { + if (lua_interface) { + string tmpScript("ZoneScripts/"); + tmpScript.append(GetZoneName()); + tmpScript.append(".lua"); + struct stat buffer; + bool fileExists = (stat(tmpScript.c_str(), &buffer) == 0); + if (fileExists) + lua_interface->RunZoneScript(tmpScript.c_str(), "preinit_zone_script", this); + } + + if (reloading) { + LogWrite(COMMAND__DEBUG, 0, "Command", "-Loading Entity Commands..."); + database.LoadEntityCommands(this); + LogWrite(NPC__INFO, 0, "NPC", "-Loading Spirit Shard data..."); + database.LoadSpiritShards(this); + LogWrite(NPC__INFO, 0, "NPC", "-Load Spirit Shard data complete!"); + + LogWrite(NPC__INFO, 0, "NPC", "-Loading NPC data..."); + database.LoadNPCs(this); + LogWrite(NPC__INFO, 0, "NPC", "-Load NPC data complete!"); + + LogWrite(OBJECT__INFO, 0, "Object", "-Loading Object data..."); + database.LoadObjects(this); + LogWrite(OBJECT__INFO, 0, "Object", "-Load Object data complete!"); + + LogWrite(SIGN__INFO, 0, "Sign", "-Loading Sign data..."); + database.LoadSigns(this); + LogWrite(SIGN__INFO, 0, "Sign", "-Load Sign data complete!"); + + LogWrite(WIDGET__INFO, 0, "Widget", "-Loading Widget data..."); + database.LoadWidgets(this); + LogWrite(WIDGET__INFO, 0, "Widget", "-Load Widget data complete!"); + + LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Loading Groundspawn data..."); + database.LoadGroundSpawns(this); + database.LoadGroundSpawnEntries(this); + LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Load Groundspawn data complete!"); + + LogWrite(PET__INFO, 0, "Pet", "-Loading Pet data..."); + database.GetPetNames(this); + LogWrite(PET__INFO, 0, "Pet", "-Load Pet data complete!"); + + LogWrite(LOOT__INFO, 0, "Loot", "-Loading Spawn loot data..."); + database.LoadLoot(this); + LogWrite(LOOT__INFO, 0, "Loot", "-Loading Spawn loot data complete!"); + + LogWrite(TRANSPORT__INFO, 0, "Transport", "-Loading Transporters..."); + database.LoadTransporters(this); + LogWrite(TRANSPORT__INFO, 0, "Transport", "-Loading Transporters complete!"); + reloading = false; + world.RemoveReloadingSubSystem("Spawns"); + } + + MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__); + spawn_group_associations.clear(); + MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__); + spawn_group_locations.clear(); + MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__); + spawn_location_groups.clear(); + MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnGroupChances.writelock(__FUNCTION__, __LINE__); + spawn_group_chances.clear(); + MSpawnGroupChances.releasewritelock(__FUNCTION__, __LINE__); + Map* zonemap = world.GetMap(std::string(GetZoneFile()),0); + while (zonemap != nullptr && zonemap->IsMapLoading()) + { + SetWatchdogTime(Timer::GetCurrentTime2()); + // Client loop + ClientProcess(true); + Sleep(10); + } + + default_zone_map = world.GetMap(std::string(GetZoneFile()),0); + + DeleteTransporters(); + ReloadTransporters(); + + database.LoadSpawns(this); + ProcessSpawnLocations(); + + if (!revive_points) + revive_points = new vector; + else { + while (!revive_points->empty()) { + safe_delete(revive_points->back()); + revive_points->pop_back(); + } + } + database.LoadRevivePoints(revive_points, GetZoneID()); + + RemoveLocationGrids(); + database.LoadLocationGrids(this); + + + MMasterZoneLock->unlock(); + + while(true) { + ProcessPendingSpawns(); + Sleep(20); + MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__); + int32 count = pending_spawn_list_add.size(); + MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__); + if(count < 1) + break; + } + + startupDelayTimer.Start(60000); // this is hard coded for 60 seconds after the zone is loaded to allow a client to at least add itself to the list before we start zone shutdown timer + + MMasterZoneLock->lock(); + + LoadingData = false; + + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + if (lua_interface && zone_script) { + RemoveLocationProximities(); + lua_interface->RunZoneScript(zone_script, "init_zone_script", this); + } + + spawn_range.Trigger(); + spawn_check_add.Trigger(); + } + + if (reloading_spellprocess){ + MMasterZoneLock->unlock(); + return !zoneShuttingDown; + } + + if(shutdownTimer.Enabled() && shutdownTimer.Check() && connected_clients.size(true) == 0) { + //if(lifetime_client_count) + zoneShuttingDown = true; + /*else { // allow up to 120 seconds then timeout + LogWrite(ZONE__WARNING, 0, "Zone", "No clients have connected to zone '%s' and the shutdown timer has counted down -- will delay shutdown for 120 seconds.", GetZoneName()); + shutdownTimer.Start(120000, true); + lifetime_client_count = 1; + }*/ + MMasterZoneLock->unlock(); + return false; + } + + // client loop + if(charsheet_changes.Check()) + SendCharSheetChanges(); + + // Client loop + ClientProcess(startupDelayTimer.Enabled()); + if(startupDelayTimer.Check()) + startupDelayTimer.Disable(); + + if(!reloading && spellProcess) + spellProcess->Process(); + if (tradeskillMgr) + tradeskillMgr->Process(); + + // Client loop + if(client_save.Check()) + SaveClients(); + + // Possibility to do a client loop + if(weather_enabled && weatherTimer.Check()) + ProcessWeather(); + + // client related loop, move to main thread? + if(!zoneShuttingDown) + ProcessDrowning(); + + // client than location_proximities loop, move to main thread + if (location_prox_timer.Check() && !zoneShuttingDown) + CheckLocationProximity(); + + // client than location_grid loop, move to main thread + if (location_grid_timer.Check() && !zoneShuttingDown) + CheckLocationGrids(); + + if (sync_game_time_timer.Check() && !zoneShuttingDown) + SendTimeUpdateToAllClients(); + + world.MWorldTime.readlock(__FUNCTION__, __LINE__); + int hour = world.GetWorldTimeStruct()->hour; + int minute = world.GetWorldTimeStruct()->minute; + world.MWorldTime.releasereadlock(__FUNCTION__, __LINE__); + + if (!isDusk && (hour >= 19 || hour < 8)) {//((hour > dusk_hour || hour < dawn_hour) || ((dusk_hour == hour && minute >= dusk_minute) || (hour == dawn_hour && minute < dawn_minute)))) { + isDusk = true; + const char* zone_script = world.GetZoneScript(GetZoneID()); + if (lua_interface && zone_script) + lua_interface->RunZoneScript(zone_script, "dusk", this); + + ProcessSpawnConditional(SPAWN_CONDITIONAL_NIGHT); + } + else if (isDusk && hour >= 8 && hour < 19) {//((hour > dawn_hour && hour < dusk_hour) || ((hour == dawn_hour && minute >= dawn_minute) || (hour == dusk_hour && minute < dusk_minute)))) { + isDusk = false; + const char* zone_script = world.GetZoneScript(GetZoneID()); + if (lua_interface && zone_script) + lua_interface->RunZoneScript(zone_script, "dawn", this); + + ProcessSpawnConditional(SPAWN_CONDITIONAL_DAY); + } + + // damaged spawns loop, spawn related, move to spawn thread? + if(regenTimer.Check()) + RegenUpdate(); + + // respawn_timers loop + if(respawn_timer.Check() && !zoneShuttingDown) + CheckRespawns(); + + // spawn_expire_timers loop + if (spawn_expire_timer.Check() && !zoneShuttingDown) + CheckSpawnExpireTimers(); + + // widget_timers loop + if(widget_timer.Check() && !zoneShuttingDown) + CheckWidgetTimers(); + + // spawn_script_timers loop + if(!reloading && !zoneShuttingDown) + CheckSpawnScriptTimers(); + + // Check to see if a dead spawn needs to be removed + CheckDeadSpawnRemoval(); +#ifndef NO_CATCH + } + catch(...) + { + LogWrite(ZONE__ERROR, 0, "Zone", "Exception while running '%s'", GetZoneName()); + zoneShuttingDown = true; + MMasterZoneLock->unlock(); + return false; + } +#endif + MMasterZoneLock->unlock(); + return (zoneShuttingDown == false); +} + +bool ZoneServer::SpawnProcess(){ + if(depop_zone) { + depop_zone = false; + ProcessDepop(respawns_allowed, repop_zone); + finished_depop = true; + } + + MMasterSpawnLock.writelock(__FUNCTION__, __LINE__); + // If the zone is loading data or shutting down don't do anything + if(!LoadingData && !zoneShuttingDown && !reloading_spellprocess) { + // Set some bool's for timers + bool movement = movement_timer.Check(); + bool spawnRange = spawn_range.Check(); + bool checkRemove = spawn_check_remove.Check(); + bool aggroCheck = aggro_timer.Check(); + vector pending_spawn_list_remove; + + // Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them all + ProcessSpawnRemovals(); + + map::iterator itr; + if (spawnRange || checkRemove) + { + // Loop through the spawn list + MSpawnList.readlock(__FUNCTION__, __LINE__); + // Loop throught the list to set up spawns to be sent to clients + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + // if zone is shutting down kill the loop + if (zoneShuttingDown) + break; + + Spawn* spawn = itr->second; + if (spawn) { + // Checks the range to all clients in the zone + if (spawnRange) + CheckSpawnRange(spawn); + + // Checks to see if the spawn needs to be removed from a client + if (checkRemove) + CheckRemoveSpawnFromClient(spawn); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + + // Broke the spawn loop into 2 so spawns are sent to the client faster, send the spawns to clients now then resume the spawn loop + + // client loop, move to main thread? + // moved this back to the spawn thread as on the main thread during a depop, done on the spawn thread, spawns would start to pop again + // might be an issue with other functions moved from the spawn thread to the main thread? + if(spawn_check_add.Check() && !zoneShuttingDown) + CheckSendSpawnToClient(); + + + // send spawn changes, changed_spawns loop + if (spawn_update.Check() && !zoneShuttingDown) { //check for changed spawns every {Rule:SpawnUpdateTimer} milliseconds (default: 200ms) + SendSpawnChanges(); + } + + if (movement || aggroCheck) + { + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + // Break the loop if the zone is shutting down + if (zoneShuttingDown) + break; + + Spawn* spawn = itr->second; + if (spawn) { + // Process spawn movement + if (movement) { + spawn->ProcessMovement(true); + // update last_movement_update for all spawns (used for time_step) + spawn->last_movement_update = Timer::GetCurrentTime2(); + if (!aggroCheck) + CombatProcess(spawn); + } + + // Makes NPC's KOS to other NPC's or players + if (aggroCheck) + { + ProcessAggroChecks(spawn); + CombatProcess(spawn); + } + } + else { + // unable to get a valid spawn, lets add the id to another list to remove from the spawn list after this loop is finished + pending_spawn_list_remove.push_back(itr->first); + } + + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + + // Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them all + if (pending_spawn_list_remove.size() > 0) { + MSpawnList.writelock(__FUNCTION__, __LINE__); + vector::iterator itr2; + for (itr2 = pending_spawn_list_remove.begin(); itr2 != pending_spawn_list_remove.end(); itr2++) { + spawn_list.erase(*itr2); + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(*itr2); + + std::map::iterator hsmitr = housing_spawn_map.find(*itr2); + if(hsmitr != housing_spawn_map.end()) { + subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].erase(hsmitr->second); + housing_spawn_map.erase(hsmitr); + } + } + + pending_spawn_list_remove.clear(); + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + } + + // Double Check to see if there are any spawn id's that need to be removed from the spawn list, if so remove them before we replace with pending spawns + // and also potentially further down when we delete the Spawn* in DeleteSpawns(false) + ProcessSpawnRemovals(); + + // Check to see if there are spawns waiting to be added to the spawn list, if so add them all + if (pending_spawn_list_add.size() > 0) { + ProcessPendingSpawns(); + } + + MSpawnList.readlock(__FUNCTION__, __LINE__); + if (movementMgr != nullptr) + movementMgr->Process(); + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + if(queue_updates.Check()) + ProcessQueuedStateCommands(); + // Do other loops for spawns + // tracking, client loop with spawn loop for each client that is tracking, change to a spawn_range_map loop instead of using the main spawn list? + //if (tracking_timer.Check()) + //ProcessTracking(); // probably doesn't work as spawn loop iterator is never set + + + // Delete unused spawns, do this last + if(!zoneShuttingDown) + DeleteSpawns(false); + + // Nothing should come after this + + + //LogWrite(PLAYER__ERROR, 0, "Debug", "Spawn loop time %u", Timer::GetCurrentTime2() - time); + } + + MMasterSpawnLock.releasewritelock(__FUNCTION__, __LINE__); + + return (zoneShuttingDown == false); +} + +void ZoneServer::CheckDeadSpawnRemoval() { + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + if(dead_spawns.size() > 0){ + vector tmp_dead_list; + int32 current_time = Timer::GetCurrentTime2(); + Spawn* spawn = 0; + map::iterator itr = dead_spawns.begin(); + map::iterator itr_delete; + while (itr != dead_spawns.end()) { + spawn = GetSpawnByID(itr->first); + if (spawn) { + if(current_time >= itr->second) + tmp_dead_list.push_back(spawn); + itr++; + } + else { + itr_delete = itr++; + dead_spawns.erase(itr_delete); + } + } + for(int i=tmp_dead_list.size()-1;i>=0;i--){ + spawn = tmp_dead_list[i]; + if (!spawn->IsPlayer()) + { + dead_spawns.erase(spawn->GetID()); + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); + RemoveSpawn(spawn, true, true, true, true, true); + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + } + } + } + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::CheckRespawns(){ + vector tmp_respawn_list; + MutexMap::iterator itr = respawn_timers.begin(); + while(itr.Next()){ + if(Timer::GetCurrentTime2() >= itr->second) + tmp_respawn_list.push_back(itr->first); + } + for(int i=tmp_respawn_list.size()-1;i>=0;i--){ + if ( IsInstanceZone() ) + { + if ( database.DeleteInstanceSpawnRemoved(GetInstanceID(),tmp_respawn_list[i]) ) + { + } + else + { + } + } + + ProcessSpawnLocation(tmp_respawn_list[i], true); + respawn_timers.erase(tmp_respawn_list[i]); + } +} + +void ZoneServer::CheckSpawnExpireTimers() { + MutexMap::iterator itr = spawn_expire_timers.begin(); + while (itr.Next()) { + Spawn* spawn = GetSpawnByID(itr->first); + if (spawn) { + if (Timer::GetCurrentTime2() >= itr.second) { + spawn_expire_timers.erase(itr.first); + Despawn(spawn, spawn->GetRespawnTime()); + } + } + else + spawn_expire_timers.erase(itr->first); + } +} + +void ZoneServer::AddSpawnExpireTimer(Spawn* spawn, int32 expire_time, int32 expire_offset) { + if (spawn) { + int32 actual_expire_time = expire_time; + if (expire_offset > 0) { + int32 low = expire_time; + int32 high = expire_time + expire_offset; + if (expire_offset < expire_time) + low = expire_time - expire_offset; + int32 range = (high - low) + 1; + actual_expire_time = (low + (int32)((range * rand()) / (RAND_MAX + 1.0))); + } + actual_expire_time *= 1000; + spawn_expire_timers.Put(spawn->GetID(), Timer::GetCurrentTime2() + actual_expire_time); + } +} + +void ZoneServer::SaveClient(Client* client){ + client->Save(); +} + +void ZoneServer::SaveClients(){ + vector::iterator itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + if(client->IsConnected() && client->IsReadyForUpdates()){ + SaveClient(client); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSpawnVisualState(Spawn* spawn, int16 type){ + if(!spawn) + return; + + vector::iterator itr; + spawn->SetTempVisualState(type); + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + if(client && client->GetPlayer() != spawn) + AddChangedSpawn(spawn); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSpawnChangesByDBID(int32 db_id, Client* client, bool override_changes, bool override_vis_changes){ + Spawn* spawn = GetSpawnByDatabaseID(db_id); + if(spawn && (spawn->changed || override_changes || override_vis_changes)) + SendSpawnChanges(spawn, client, override_changes, override_vis_changes); +} + +void ZoneServer::SendSpawnChanges(Spawn* spawn, Client* client, bool override_changes, bool override_vis_changes){ + if(client && client->IsConnected() && client->IsReadyForUpdates() && client->GetPlayer()->WasSentSpawn(spawn->GetID()) && (spawn->IsTransportSpawn() || client->GetPlayer()->GetDistance(spawn) < SEND_SPAWN_DISTANCE)){ + EQ2Packet* outapp = spawn->spawn_update_packet(client->GetPlayer(), client->GetVersion(), override_changes, override_vis_changes); + if(outapp) + client->QueuePacket(outapp); + } +} + +void ZoneServer::SendSpawnChanges(Spawn* spawn){ + MClientList.readlock(__FUNCTION__, __LINE__); + MSpawnList.readlock(); + if(spawn && spawn->changed){ + if(!spawn->IsPlayer() || (spawn->IsPlayer() && (spawn->info_changed || spawn->vis_changed))){ + vector::iterator itr; + Client* client = 0; + + // MClientList locked at a higher level + for (itr = clients.begin(); itr != clients.end(); itr++) { + client = *itr; + SendSpawnChanges(spawn, client); + } + } + spawn->changed = false; + spawn->info_changed = false; + if(spawn->IsPlayer() == false) + spawn->position_changed = false; + spawn->vis_changed = false; + } + MSpawnList.releasereadlock(); + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +Spawn* ZoneServer::FindSpawn(Player* searcher, const char* name){ + if(!searcher || !name) + return 0; + + Spawn* spawn = 0; + vector find_spawn_list; + vector::iterator fspawn_iter; + int8 name_size = strlen(name); + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && !strncasecmp(spawn->GetName(), name, name_size)) + find_spawn_list.push_back(spawn); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + Spawn* closest = 0; + float distance = 0; + float test_distance = 0; + for(fspawn_iter=find_spawn_list.begin(); fspawn_iter!=find_spawn_list.end(); fspawn_iter++){ + spawn = *fspawn_iter; + if(spawn && ((((test_distance = searcher->GetDistance(spawn)) < distance)) || !closest)){ + distance = test_distance; + closest = spawn; + } + } + return closest; +} + +void ZoneServer::AddChangedSpawn(Spawn* spawn) { + if (!spawn || (spawn->IsPlayer() && !spawn->info_changed && !spawn->vis_changed) || (spawn->IsPlayer() && ((Player*)spawn)->GetClient() && ((Player*)spawn)->GetClient()->IsReadyForUpdates() == false)) + return; + + MChangedSpawns.lock_shared(); + ChangedSpawnMapType::iterator it = changed_spawns.find(spawn->GetID()); + if (it != changed_spawns.end()) { + it->second = true; + MChangedSpawns.unlock_shared(); + } + else { + MChangedSpawns.unlock_shared(); + MChangedSpawns.lock(); + changed_spawns.insert(make_pair(spawn->GetID(),true)); + MChangedSpawns.unlock(); + } +} + +void ZoneServer::RemoveChangedSpawn(Spawn* spawn){ + if(!spawn) + return; + + MChangedSpawns.lock(); + ChangedSpawnMapType::iterator it = changed_spawns.find(spawn->GetID()); + if (it != changed_spawns.end()) { + it->second = false; + } + MChangedSpawns.unlock(); +} + +void ZoneServer::AddDrowningVictim(Player* player){ + Client* client = ((Player*)player)->GetClient(); + if(client && drowning_victims.count(client) == 0) + drowning_victims.Put(client, Timer::GetCurrentTime2()); +} + +void ZoneServer::RemoveDrowningVictim(Player* player){ + Client* client = ((Player*)player)->GetClient(); + if(client) + drowning_victims.erase(client); +} + +Client* ZoneServer::GetDrowningVictim(Player* player){ + Client* client = ((Player*)player)->GetClient(); + if(client && drowning_victims.count(client) > 0) + return(client); + return 0; +} + +void ZoneServer::ProcessDrowning(){ + vector dead_list; + if(drowning_victims.size(true) > 0){ + sint32 damage = 0; + int32 current_time = Timer::GetCurrentTime2(); + MutexMap::iterator itr = drowning_victims.begin(); + while(itr.Next()){ + if(current_time >= itr->second) { + Client* client = itr->first; + Player* player = client->GetPlayer(); + drowning_victims.Get(client) = Timer::GetCurrentTime2() + 2000; + damage = player->GetTotalHP()/20 + player->GetInfoStruct()->get_hp_regen(); + player->TakeDamage(damage); + if(!player->Alive()) + dead_list.push_back(client); + player->SetCharSheetChanged(true); + SendCharSheetChanges(client); + SendDamagePacket(0, player, DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, DAMAGE_PACKET_RESULT_SUCCESSFUL, DAMAGE_PACKET_DAMAGE_TYPE_DROWN, damage, 0); + client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are drowning!"); + } + } + } + if(dead_list.size() > 0){ + vector::iterator itr; + for(itr = dead_list.begin(); itr != dead_list.end(); itr++){ + RemoveDrowningVictim((*itr)->GetPlayer()); + KillSpawn(false, (*itr)->GetPlayer(), nullptr, true, 0, 0, 10); // kill blow type 10 means death by WATER! (glug glug!) + } + } +} + +void ZoneServer::SendSpawnChanges(){ + std::shared_lock lock(MChangedSpawns); + if (changed_spawns.size() < 1) + return; + + set spawns_to_send; + Spawn* spawn = 0; + + int count = 0; + + MSpawnList.readlock(__FUNCTION__, __LINE__); + + for( ChangedSpawnMapType::iterator it = changed_spawns.begin(); it != changed_spawns.end(); ++it ) { + if(!it->second) + continue; + + spawn = GetSpawnByID(it->first); + if(spawn){ + spawns_to_send.insert(spawn); + count++; + } + } + + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + if(clients.size()) + { + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client) + client->SendSpawnChanges(spawns_to_send); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + for (const auto& spawn : spawns_to_send) { + spawn->changed = false; + spawn->position_changed = false; + spawn->vis_changed = false; + spawn->info_changed = false; + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendPlayerPositionChanges(Player* player){ + if(player){ + player->position_changed = false; + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(player != client->GetPlayer() && client->GetPlayer()->WasSentSpawn(player->GetID())){ + if(client->GetVersion() > 373) { + EQ2Packet* outapp = player->player_position_update_packet(client->GetPlayer(), client->GetVersion(), true); + if(outapp) + client->QueuePacket(outapp); + } + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } +} + +void ZoneServer::SendCharSheetChanges(){ + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) + SendCharSheetChanges(*client_itr); + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendCharSheetChanges(Client* client){ + if(client && client->IsConnected() && client->GetPlayer()->GetCharSheetChanged()){ + client->GetPlayer()->SetCharSheetChanged(false); + ClientPacketFunctions::SendCharacterSheet(client); + } +} + +int32 ZoneServer::CalculateSpawnGroup(SpawnLocation* spawnlocation, bool respawn) +{ + int32 group = 0; + list* groups_at_location = GetSpawnGroupsByLocation(spawnlocation->placement_id); + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + if(groups_at_location){ + list::iterator group_location_itr; + float chance = 0; + float total_chance = 0; + map tmp_chances; + set* associated_groups = 0; + for (group_location_itr = groups_at_location->begin(); group_location_itr != groups_at_location->end(); group_location_itr++) { + if(tmp_chances.count(*group_location_itr) > 0) + continue; + associated_groups = GetAssociatedGroups(*group_location_itr); + if(associated_groups){ + set::iterator group_itr; + for (group_itr = associated_groups->begin(); group_itr != associated_groups->end(); group_itr++) { + chance = GetSpawnGroupChance(*group_itr); + if(chance > 0){ + total_chance += chance; + tmp_chances[*group_itr] = chance; + } + else + tmp_chances[*group_itr] = 0; + } + } + else{ //single group, no associations + chance = GetSpawnGroupChance(*group_location_itr); + total_chance += chance; + tmp_chances[*group_location_itr] = chance; + } + } + if(tmp_chances.size() > 1){ + //set the default for any chances not set + map::iterator itr2; + for(itr2 = tmp_chances.begin(); itr2 != tmp_chances.end(); itr2++){ + if(itr2->second == 0){ + total_chance += 100/tmp_chances.size(); + tmp_chances[itr2->first] = (float)(100 / tmp_chances.size()); + } + } + } + if(tmp_chances.size() > 1){ + float roll = (float)(rand()%((int32)total_chance)); + map::iterator itr3; + for (itr3 = tmp_chances.begin(); itr3 != tmp_chances.end(); itr3++){ + if(itr3->second >= roll){ + group = itr3->first; + break; + } + else + roll -= itr3->second; + } + } + else if(tmp_chances.size() == 1) + group = tmp_chances.begin()->first; + } + if(group > 0){ + map* locations = GetSpawnLocationsByGroup(group); + if(locations){ + map::iterator itr; + Spawn* spawn = 0; + Spawn* leader = 0; + + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + for (itr = locations->begin(); itr != locations->end(); itr++) { + if(spawn_location_list.count(itr->second) > 0){ + spawn = ProcessSpawnLocation(spawn_location_list[itr->second], respawn); + if(!leader && spawn) + leader = spawn; + if(leader) + leader->AddSpawnToGroup(spawn); + if(spawn){ + //if(spawn_group_map.count(group) == 0) + // spawn_group_map.Put(group, new MutexList()); + MutexList* groupList = &spawn_group_map.Get(group); + groupList->Add(spawn->GetID()); + spawn->SetSpawnGroupID(group); + } + } + } + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); + } + } + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + return group; +} + +void ZoneServer::ProcessSpawnLocation(int32 location_id, bool respawn) +{ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + + if(spawn_location_list.count(location_id) > 0) + { + if(respawn) //see if there are any spawns still in game associated with this spawn's group, if so, dont spawn this + { + list* groups = GetSpawnGroupsByLocation(spawn_location_list[location_id]->placement_id); + + if(groups) + { + set* associated_groups = 0; + bool should_spawn = true; + list::iterator itr; + for (itr = groups->begin(); itr != groups->end(); itr++) { + associated_groups = GetAssociatedGroups(*itr); + + if(associated_groups) + { + set::iterator assoc_itr; + for (assoc_itr = associated_groups->begin(); assoc_itr != associated_groups->end(); assoc_itr++) { + if(spawn_group_map.count(*assoc_itr) > 0 && spawn_group_map.Get(*assoc_itr).size() > 0) + should_spawn = false; + } + } + } + + if(should_spawn) + CalculateSpawnGroup(spawn_location_list[location_id]); + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + // need to unlock the list before we exit the function + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); + return; + } + } + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + ProcessSpawnLocation(spawn_location_list[location_id], respawn); + } + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); +} + +Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respawn) +{ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + if(!spawnlocation) + return 0; + + Spawn* spawn = 0; + float rand_number = MakeRandomFloat(0, spawnlocation->total_percentage); + + for(int32 i=0;ientities.size();i++) + { + if(spawnlocation->entities[i]->spawn_percentage == 0) + continue; + + if (spawnlocation->conditional > 0) { + if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_DAY && isDusk) + continue; + + if ((spawnlocation->conditional & SPAWN_CONDITIONAL_NIGHT) == SPAWN_CONDITIONAL_NIGHT && !isDusk) + continue; + + if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_NOT_RAINING && rain >= 0.75f) + continue; + + if ((spawnlocation->conditional & SPAWN_CONDITIONAL_DAY) == SPAWN_CONDITIONAL_RAINING && rain < 0.75f) + continue; + } + + if (spawnlocation->entities[i]->spawn_percentage >= rand_number) { + if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC) + spawn = AddNPCSpawn(spawnlocation, spawnlocation->entities[i]); + else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN) + spawn = AddGroundSpawn(spawnlocation, spawnlocation->entities[i]); + else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT) + spawn = AddObjectSpawn(spawnlocation, spawnlocation->entities[i]); + else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET) + spawn = AddWidgetSpawn(spawnlocation, spawnlocation->entities[i]); + else if (spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN) + spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]); + + if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE) + database.GetHouseSpawnInstanceData(this, spawn); + + if(spawn && spawn->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) in spawn location id %u was skipped due to a missing expansion / holiday flag being met (ZoneServer::ProcessSpawnLocation)", spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_location_id); + safe_delete(spawn); + spawn = 0; + continue; + } + else if (!spawn) + { + LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by spawn location to zone %s with location id %u, spawn id %u, spawn type %u.", GetZoneName(), spawnlocation->entities[i]->spawn_location_id, spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_type); + continue; + } + + if (spawn) + { + if(respawn) + CallSpawnScript(spawn, SPAWN_SCRIPT_RESPAWN); + else + CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + } + break; + } + else + rand_number -= spawnlocation->entities[i]->spawn_percentage; + } + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + return spawn; +} + + +Spawn* ZoneServer::ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn) +{ + if(!spawnlocation) + return 0; + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + Spawn* spawn = 0; + float rand_number = MakeRandomFloat(0, spawnlocation->total_percentage); + + for(int32 i=0;ientities.size();i++) + { + if(spawnlocation->entities[i]->spawn_percentage == 0) + continue; + + int32 spawnTime = 0; + + if(spawnlocation->entities[i]->spawn_percentage >= rand_number) + { + if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC && + (spawnTime = database.CheckSpawnRemoveInfo(instNPCs,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddNPCSpawn(spawnlocation, spawnlocation->entities[i]); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN && + (spawnTime = database.CheckSpawnRemoveInfo(instGroundSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddGroundSpawn(spawnlocation, spawnlocation->entities[i]); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT && + (spawnTime = database.CheckSpawnRemoveInfo(instObjSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddObjectSpawn(spawnlocation, spawnlocation->entities[i]); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET && + (spawnTime = database.CheckSpawnRemoveInfo(instWidgetSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddWidgetSpawn(spawnlocation, spawnlocation->entities[i]); + else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN && + (spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0) + spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]); + + if(spawn && spawn->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) in spawn location id %u was skipped due to a missing expansion / holiday flag being met (ZoneServer::ProcessInstanceSpawnLocation)", spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_location_id); + safe_delete(spawn); + spawn = 0; + continue; + } + + if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE) + database.GetHouseSpawnInstanceData(this, spawn); + + const char* script = 0; + + for(int x=0;x<3;x++) + { + switch(x) + { + case 0: + script = world.GetSpawnEntryScript(spawnlocation->entities[i]->spawn_entry_id); + break; + case 1: + script = world.GetSpawnLocationScript(spawnlocation->entities[i]->spawn_location_id); + break; + case 2: + script = world.GetSpawnScript(spawnlocation->entities[i]->spawn_id); + break; + } + + if(spawn && script && lua_interface->GetSpawnScript(script) != 0) + { + spawn->SetSpawnScript(string(script)); + break; + } + } + + if(spawn) + { + if (respawn) + CallSpawnScript(spawn, SPAWN_SCRIPT_RESPAWN); + else + CallSpawnScript(spawn, SPAWN_SCRIPT_SPAWN); + + if ( spawnTime > 1 ) + { + spawn->SetRespawnTime(spawnTime); + } + } + break; + } + else + rand_number -= spawnlocation->entities[i]->spawn_percentage; + } + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + return spawn; +} + +void ZoneServer::ProcessSpawnLocations() +{ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + + map* instNPCs = NULL; + map* instGroundSpawns = NULL; + map* instObjSpawns = NULL; + map* instWidgetSpawns = NULL; + map* instSignSpawns = NULL; + + if ( this->IsInstanceZone() ) + { + LogWrite(SPAWN__DEBUG, 0, "Spawn", "Processing Instance Removed Spawns..."); + instNPCs = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_NPC ); + instGroundSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_GROUNDSPAWN ); + instObjSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_OBJECT ); + instWidgetSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_WIDGET ); + instSignSpawns = database.GetInstanceRemovedSpawns(this->GetInstanceID() , SPAWN_ENTRY_TYPE_SIGN ); + } + + map processed_spawn_locations; + map::iterator itr; + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_location_list.begin(); itr != spawn_location_list.end(); itr++) { + LogWrite(SPAWN__TRACE, 0, "Spawn", "while spawn_location_list itr (#%u)", spawn_location_list.size()); + + if(itr->second && processed_spawn_locations.count(itr->second->placement_id) > 0) //if we processed one spawn in a spawn group, we processed them all for that group + continue; + + if(itr->second && spawn_location_groups.count(itr->second->placement_id) > 0) + { + int32 group_id = CalculateSpawnGroup(itr->second); + + if(group_id) + { + LogWrite(SPAWN__TRACE, 0, "Spawn", "is group_id"); + set* associated_groups = GetAssociatedGroups(group_id); + + if(associated_groups) + { + LogWrite(SPAWN__TRACE, 0, "Spawn", "is associated_groups"); + vector* associated_locations = GetAssociatedLocations(associated_groups); + + if(associated_locations) + { + LogWrite(SPAWN__TRACE, 0, "Spawn", "is associated_locations"); + for(int32 i=0;isize();i++) + { + LogWrite(SPAWN__DEBUG, 5, "Spawn", "Loading processed_spawn_locations..."); + processed_spawn_locations[associated_locations->at(i)] = true; + } + + safe_delete(associated_locations); + } + } + } + } + else + { + if ( this->IsInstanceZone() ) + { + //LogWrite(SPAWN__DEBUG, 5, "Spawn", "ProcessInstanceSpawnLocation (%u)...", itr->second->placement_id); + ProcessInstanceSpawnLocation(itr->second,instNPCs,instGroundSpawns,instObjSpawns,instWidgetSpawns,instSignSpawns); + } + else + { + //LogWrite(SPAWN__DEBUG, 5, "Spawn", "ProcessSpawnLocation (%u)...", itr->second->placement_id); + ProcessSpawnLocation(itr->second); + } + } + } + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); + + safe_delete(instNPCs); + safe_delete(instGroundSpawns); + safe_delete(instObjSpawns); + safe_delete(instWidgetSpawns); + safe_delete(instSignSpawns); + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); +} + +void ZoneServer::AddLoot(NPC* npc, Spawn* killer, GroupLootMethod loot_method, int8 item_rarity, int32 group_id){ + // this function is ran twice, first on spawn of mob, then at death of mob (gray mob check and no_drop_quest_completed_id check) + + // first we see if the skipping of gray mobs loot is enabled, then we move all non body drops + if(killer) + { + npc->SetLootMethod(loot_method, item_rarity, group_id); + int8 skip_loot_gray_mob_flag = rule_manager.GetGlobalRule(R_Loot, SkipLootGrayMob)->GetInt8(); + if(skip_loot_gray_mob_flag) { + Player* player = 0; + if(killer->IsPlayer()) + player = (Player*)killer; + else if(killer->IsPet()) { + Spawn* owner = ((Entity*)killer)->GetOwner(); + if(owner->IsPlayer()) + player = (Player*)owner; + } + if(player) { + int8 difficulty = player->GetArrowColor(npc->GetLevel()); + if(difficulty == ARROW_COLOR_GRAY) { + npc->ClearNonBodyLoot(); + } + } + } + } + + // check for starting loot of Spawn and death of Spawn loot (no_drop_quest_completed_id) + vector loot_tables = GetSpawnLootList(npc->GetDatabaseID(), GetZoneID(), npc->GetLevel(), race_types_list.GetRaceType(npc->GetModelType()), npc); + if(loot_tables.size() > 0){ + vector* loot_drops = 0; + vector::iterator loot_drop_itr; + LootTable* table = 0; + vector::iterator loot_list_itr; + float chancecoin = 0; + float chancetable = 0; + float chancedrop = 0; + float chancetally = 0; + float droptally = 0; + // the following loop,loops through each table + for(loot_list_itr = loot_tables.begin(); loot_list_itr != loot_tables.end(); loot_list_itr++){ + table = GetLootTable(*loot_list_itr); + // if killer is assigned this is on-death, we already assigned coin + if(!killer && table && table->maxcoin > 0){ + chancecoin = rand()%100; + if(table->coin_probability >= chancecoin){ + if(table->maxcoin > table->mincoin) + npc->AddLootCoins(table->mincoin + rand()%(table->maxcoin - table->mincoin)); + } + } + int numberchances = 1; + + //if (table->lootdrop_probability == 100){ } + //else + //chancetally += table->lootdrop_probability; + int maxchance = 0; + if (table) { + maxchance = table->maxlootitems; + for (numberchances; numberchances <= maxchance; numberchances++) { + chancetable = static_cast (rand()) / (static_cast (RAND_MAX / 100)); + //LogWrite(PLAYER__DEBUG, 0, "Player", "Table Chance: '%f'", chancetable); + float droppercenttotal = 0; + //-------------------------------------------------------------------------------------------------------------------------------------------------------------- + if (table->lootdrop_probability == 100 || table->lootdrop_probability >= chancetable) { + + //LogWrite(PLAYER__DEBUG, 0, "Player", "Probability:%f Table Chance: '%f'", table->lootdrop_probability, chancetable); + loot_drops = GetLootDrops(*loot_list_itr); + if (loot_drops && loot_drops->size() > 0) { + LootDrop* drop = 0; + int16 count = 0; + + std::shuffle(loot_drops->begin(), loot_drops->end(), std::default_random_engine(Timer::GetCurrentTime2())); + + int16 IC = 0; + for (loot_drop_itr = loot_drops->begin(); loot_drop_itr != loot_drops->end(); loot_drop_itr++) { + drop = *loot_drop_itr; + droppercenttotal += drop->probability; + } + + + int droplistsize = loot_drops->size(); + float chancedroptally = 0; + bool breakIterMaxLoot = false; + chancedrop = static_cast (rand()) / (static_cast (RAND_MAX / 100)); + for (loot_drop_itr = loot_drops->begin(); loot_drop_itr != loot_drops->end(); loot_drop_itr++) { + drop = *loot_drop_itr; + + // if no killer is provided, then we are instantiating the spawn loot, quest related loot should be added on death of the spawn to check against spawn/group members + if(drop->no_drop_quest_completed_id && killer == nullptr) + continue; + else if(!drop->no_drop_quest_completed_id && killer) // skip since this doesn't have a quest id attached and we are doing after-math of death loot additions + continue; + else if(killer && drop->no_drop_quest_completed_id) // check if the player already completed quest related to item + { + Player* player = nullptr; + if(killer->IsPlayer()) + { + player = (Player*)killer; + // player has already completed the quest + if(player->HasQuestBeenCompleted(drop->no_drop_quest_completed_id) && !player->GetGroupMemberInfo()) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Player has completed quest %u, skipping loot item %u", npc->GetName(), drop->no_drop_quest_completed_id, drop->item_id); + continue; + } + else if(player->GetGroupMemberInfo() && world.GetGroupManager()->HasGroupCompletedQuest(player->GetGroupMemberInfo()->group_id, drop->no_drop_quest_completed_id)) + { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Group %u has completed quest %u, skipping loot item %u", npc->GetName(), player->GetGroupMemberInfo()->group_id, drop->no_drop_quest_completed_id, drop->item_id); + continue; + } + } + else + { + LogWrite(PLAYER__DEBUG, 0, "Player", "%s: Killer is not a player, skipping loot item %u", npc->GetName(), drop->item_id); + continue; + } + } + + if (npc->HasLootItemID(drop->item_id)) + continue; + + if (droppercenttotal >= 100) + droppercenttotal = 100; + chancedroptally += 100 / droppercenttotal * drop->probability; + //chancedrop = static_cast (rand()) / (static_cast (RAND_MAX / 100)); + //LogWrite(PLAYER__DEBUG, 0, "Player", "Loot drop: '%i' Chance: %f Prob tally: %f min: %f", drop, chancedrop, chancedroptally, chancedroptally - drop->probability); + if ((chancedroptally == 100) || ((chancedroptally >= chancedrop) && (chancedroptally - (100 / droppercenttotal * drop->probability)) <= chancedrop)) { + + //LogWrite(PLAYER__DEBUG, 0, "Player", "Loot drop: '%i' Chance: %f Prob: %f We have a loot drop winner", drop, chancedrop, chancedroptally); + count++; + npc->AddLootItem(drop->item_id, drop->item_charges); + //LogWrite(PLAYER__DEBUG, 0, "Player", "loot Count: '%i'",count); + //LogWrite(MISC__TODO, 1, "TODO", "Auto-Equip new looted items\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + //if(drop->equip_item) + + } + // so many items on this table break out and cap it! + if (table->maxlootitems > 0 && count >= table->maxlootitems) { + breakIterMaxLoot = true; + break; + } + } + // hit our max item drop for this table already, break out of numberchances + if(breakIterMaxLoot) { + break; + } + } + } + } + } + } + } +} + +void ZoneServer::DeterminePosition(SpawnLocation* spawnlocation, Spawn* spawn){ + if(!spawn || !spawnlocation) + return; + + int offset = 0; + if(spawnlocation->x_offset > 0){ + //since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers + offset = (int)((spawnlocation->x_offset*1000)+1); + spawn->SetX(spawnlocation->x + ((float)(rand()%offset - rand()%offset))/1000); + } + else + spawn->SetX(spawnlocation->x); + if(spawnlocation->y_offset > 0){ + //since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers + offset = (int)((spawnlocation->y_offset*1000)+1); + spawn->SetY(spawnlocation->y + ((float)(rand()%offset - rand()%offset))/1000); + } + else + spawn->SetY(spawnlocation->y, true, true); + if(spawnlocation->z_offset > 0){ + //since rand() deals with integers only, we are going to divide by 1000 later so that we can use fractions of integers + offset = (int)((spawnlocation->z_offset*1000)+1); + spawn->SetZ(spawnlocation->z + ((float)(rand()%offset - rand()%offset))/1000); + } + else + spawn->SetZ(spawnlocation->z); + spawn->SetHeading(spawnlocation->heading); + spawn->SetPitch(spawnlocation->pitch); + spawn->SetRoll(spawnlocation->roll); + spawn->SetSpawnOrigX(spawn->GetX()); + spawn->SetSpawnOrigY(spawn->GetY()); + spawn->SetSpawnOrigZ(spawn->GetZ()); + spawn->SetSpawnOrigHeading(spawn->GetHeading()); + spawn->SetSpawnOrigPitch(spawnlocation->pitch); + spawn->SetSpawnOrigRoll(spawnlocation->roll); + spawn->SetLocation(spawnlocation->grid_id); + spawn->SetSpawnLocationPlacementID(spawnlocation->placement_id); +} + +NPC* ZoneServer::AddNPCSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + NPC* npc = GetNewNPC(spawnentry->spawn_id); + if(npc && !npc->IsOmittedByDBFlag()){ + InfoStruct* info = npc->GetInfoStruct(); + DeterminePosition(spawnlocation, npc); + npc->SetDatabaseID(spawnentry->spawn_id); + npc->SetSpawnLocationID(spawnentry->spawn_location_id); + npc->SetSpawnEntryID(spawnentry->spawn_entry_id); + npc->SetRespawnTime(spawnentry->respawn); + npc->SetExpireTime(spawnentry->expire_time); + + //devn00b add overrides for some spawns + if(spawnentry->hp_override > 0){ + npc->SetHP(spawnentry->hp_override); + } + if(spawnentry->lvl_override > 0){ + npc->SetLevel(spawnentry->lvl_override); + } + if(spawnentry->mp_override > 0){ + npc->SetPower(spawnentry->mp_override); + } + if(spawnentry->str_override > 0){ + info->set_str_base(spawnentry->str_override); + info->set_str(spawnentry->str_override); + } + if(spawnentry->sta_override > 0){ + info->set_sta_base(spawnentry->sta_override); + info->set_sta(spawnentry->sta_override); + } + if(spawnentry->wis_override > 0){ + info->set_wis_base(spawnentry->wis_override); + info->set_wis(spawnentry->wis_override); + } + if(spawnentry->int_override > 0){ + info->set_intel_base(spawnentry->int_override); + info->set_intel(spawnentry->int_override); + } + if(spawnentry->agi_override > 0){ + info->set_agi_base(spawnentry->agi_override); + info->set_agi(spawnentry->agi_override); + } + if(spawnentry->heat_override > 0){ + info->set_heat_base(spawnentry->heat_override); + info->set_heat(spawnentry->heat_override); + } + if(spawnentry->cold_override > 0){ + info->set_cold_base(spawnentry->cold_override); + info->set_cold(spawnentry->cold_override); + } + if(spawnentry->magic_override > 0){ + info->set_magic_base(spawnentry->magic_override); + info->set_magic(spawnentry->magic_override); + } + if(spawnentry->mental_override > 0){ + info->set_mental_base(spawnentry->mental_override); + info->set_mental(spawnentry->mental_override); + } + if(spawnentry->divine_override > 0){ + info->set_divine_base(spawnentry->divine_override); + info->set_divine(spawnentry->divine_override); + } + if(spawnentry->disease_override > 0){ + info->set_disease_base(spawnentry->disease_override); + info->set_disease(spawnentry->disease_override); + } + if(spawnentry->poison_override > 0){ + info->set_poison_base(spawnentry->poison_override); + info->set_poison(spawnentry->poison_override); + } + if(spawnentry->difficulty_override > 0){ + npc->SetDifficulty(spawnentry->difficulty_override, 1); + } + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(npc, spawnentry->expire_time, spawnentry->expire_offset); + AddLoot(npc); + + SetSpawnScript(spawnentry, npc); + + CallSpawnScript(npc, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(npc); + } + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return npc; +} + +vector* ZoneServer::GetAssociatedLocations(set* groups){ + vector* ret = 0; + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + if(groups){ + int32 group_id = 0; + set::iterator group_itr; + for (group_itr = groups->begin(); group_itr != groups->end(); group_itr++) { + if(!ret) + ret = new vector(); + group_id = *group_itr; + + MSpawnGroupLocations.readlock(__FUNCTION__, __LINE__); + if(spawn_group_locations.count(group_id) > 0){ + map::iterator itr; + for (itr = spawn_group_locations[group_id]->begin(); itr != spawn_group_locations[group_id]->end(); itr++) { + ret->push_back(itr->first); + } + } + MSpawnGroupLocations.releasereadlock(__FUNCTION__, __LINE__); + } + } + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + return ret; +} + +set* ZoneServer::GetAssociatedGroups(int32 group_id) { + set* ret = 0; + MSpawnGroupAssociation.readlock(__FUNCTION__, __LINE__); + LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__); + if(spawn_group_associations.count(group_id) > 0) + ret = spawn_group_associations[group_id]; + LogWrite(SPAWN__TRACE, 1, "Spawn", "Exit %s", __FUNCTION__); + MSpawnGroupAssociation.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +map* ZoneServer::GetSpawnLocationsByGroup(int32 group_id) { + map* ret = 0; + + MSpawnGroupLocations.readlock(__FUNCTION__, __LINE__); + if(spawn_group_locations.count(group_id) > 0) + ret = spawn_group_locations[group_id]; + MSpawnGroupLocations.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +list* ZoneServer::GetSpawnGroupsByLocation(int32 location_id){ + list* ret = 0; + + MSpawnLocationGroups.readlock(__FUNCTION__, __LINE__); + if(spawn_location_groups.count(location_id) > 0) + ret = spawn_location_groups[location_id]; + MSpawnLocationGroups.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +float ZoneServer::GetSpawnGroupChance(int32 group_id){ + float ret = -1; + + MSpawnGroupChances.readlock(__FUNCTION__, __LINE__); + if(spawn_group_chances.count(group_id) > 0) + ret = spawn_group_chances[group_id]; + MSpawnGroupChances.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void ZoneServer::AddSpawnGroupChance(int32 group_id, float percent){ + MSpawnGroupChances.writelock(__FUNCTION__, __LINE__); + spawn_group_chances[group_id] = percent; + MSpawnGroupChances.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddSpawnGroupAssociation(int32 group_id1, int32 group_id2) { + MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__); + //Check if we already have containers for these group ids, if not create them + if (spawn_group_associations.count(group_id1) == 0) + spawn_group_associations[group_id1] = new set; + if (spawn_group_associations.count(group_id2) == 0) + spawn_group_associations[group_id2] = new set; + + //Associate groups 1 and 2 now + set* group_1 = spawn_group_associations.find(group_id1)->second; + set* group_2 = spawn_group_associations.find(group_id2)->second; + group_1->insert(group_id2); + group_2->insert(group_id1); + + //Associate the remaining groups together + set::iterator itr; + for (itr = group_1->begin(); itr != group_1->end(); itr++){ + group_2->insert(*itr); + map*>::iterator assoc_itr = spawn_group_associations.find(*itr); + if (assoc_itr != spawn_group_associations.end()) + assoc_itr->second->insert(group_id2); + else { + set* new_set = new set; + spawn_group_associations[*itr] = new_set; + new_set->insert(group_id2); + } + } + for (itr = group_2->begin(); itr != group_2->end(); itr++){ + group_1->insert(*itr); + map*>::iterator assoc_itr = spawn_group_associations.find(*itr); + if (assoc_itr != spawn_group_associations.end()) + assoc_itr->second->insert(group_id1); + else { + set* new_set = new set; + spawn_group_associations[*itr] = new_set; + new_set->insert(group_id1); + } + } + MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddSpawnGroupLocation(int32 group_id, int32 location_id, int32 spawn_location_id) { + MSpawnGroupLocations.writelock(__FUNCTION__, __LINE__); + if(spawn_group_locations.count(group_id) == 0) + spawn_group_locations[group_id] = new map(); + (*spawn_group_locations[group_id])[location_id] = spawn_location_id; + MSpawnGroupLocations.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnLocationGroups.writelock(__FUNCTION__, __LINE__); + if(spawn_location_groups.count(location_id) == 0) + spawn_location_groups[location_id] = new list(); + spawn_location_groups[location_id]->push_back(group_id); + MSpawnLocationGroups.releasewritelock(__FUNCTION__, __LINE__); + + MSpawnGroupAssociation.writelock(__FUNCTION__, __LINE__); + if(spawn_group_associations.count(group_id) == 0) + spawn_group_associations[group_id] = new set(); + spawn_group_associations[group_id]->insert(group_id); + MSpawnGroupAssociation.releasewritelock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::CallSpawnScript(Spawn* npc, int8 type, Spawn* spawn, const char* message, bool is_door_open, sint32 input_value, sint32* return_value){ + + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + if(!npc) + return false; + + const char* script = npc->GetSpawnScript(); + if ( script == nullptr || strlen(script) < 1 ) + { + if (!npc->IsPet() && npc->GetZone() != nullptr) + { + string tmpScript; + tmpScript.append("SpawnScripts/"); + tmpScript.append(npc->GetZone()->GetZoneName()); + tmpScript.append("/"); + int count = 0; + for (int s = 0; s < strlen(npc->GetName()); s++) + { + if (isalnum((unsigned char)npc->GetName()[s])) + { + tmpScript += npc->GetName()[s]; + count++; + } + } + + tmpScript.append(".lua"); + + if (count < 1) + { + LogWrite(SPAWN__TRACE, 0, "Spawn", "Could not form script name %s..", __FUNCTION__); + } + else + { + struct stat buffer; + bool fileExists = (stat(tmpScript.c_str(), &buffer) == 0); + if (fileExists) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "No script file described in the database, overriding with SpawnScript at %s", (char*)tmpScript.c_str()); + npc->SetSpawnScript(tmpScript); + script = npc->GetSpawnScript(); + } + } + } + } + + bool result = false; + if(lua_interface && script){ + result = true; // default to true, if we don't match a switch case, return false in default case + switch(type){ + case SPAWN_SCRIPT_SPAWN:{ + lua_interface->RunSpawnScript(script, "spawn", npc); + break; + } + case SPAWN_SCRIPT_RESPAWN:{ + lua_interface->RunSpawnScript(script, "respawn", npc); + break; + } + case SPAWN_SCRIPT_ATTACKED:{ + lua_interface->RunSpawnScript(script, "attacked", npc, spawn); + break; + } + case SPAWN_SCRIPT_TARGETED:{ + lua_interface->RunSpawnScript(script, "targeted", npc, spawn); + break; + } + case SPAWN_SCRIPT_HAILED:{ + result = lua_interface->RunSpawnScript(script, "hailed", npc, spawn); + break; + } + case SPAWN_SCRIPT_HAILED_BUSY:{ + lua_interface->RunSpawnScript(script, "hailed_busy", npc, spawn); + break; + } + case SPAWN_SCRIPT_DEATH:{ + lua_interface->RunSpawnScript(script, "death", npc, spawn); + break; + } + case SPAWN_SCRIPT_KILLED:{ + lua_interface->RunSpawnScript(script, "killed", npc, spawn); + break; + } + case SPAWN_SCRIPT_AGGRO:{ + lua_interface->RunSpawnScript(script, "aggro", npc, spawn); + break; + } + case SPAWN_SCRIPT_HEALTHCHANGED:{ + result = lua_interface->RunSpawnScript(script, "healthchanged", npc, spawn, 0, false, input_value, return_value); + break; + } + case SPAWN_SCRIPT_RANDOMCHAT:{ + lua_interface->RunSpawnScript(script, "randomchat", npc, 0, message); + break; + } + case SPAWN_SCRIPT_CUSTOM: + case SPAWN_SCRIPT_TIMER: + case SPAWN_SCRIPT_CONVERSATION:{ + lua_interface->RunSpawnScript(script, message, npc, spawn); + break; + } + case SPAWN_SCRIPT_CASTED_ON: { + lua_interface->RunSpawnScript(script, "casted_on", npc, spawn, message); + break; + } + case SPAWN_SCRIPT_AUTO_ATTACK_TICK: { + lua_interface->RunSpawnScript(script, "auto_attack_tick", npc, spawn); + break; + } + case SPAWN_SCRIPT_COMBAT_RESET: { + lua_interface->RunSpawnScript(script, "CombatReset", npc); + break; + } + case SPAWN_SCRIPT_GROUP_DEAD: { + lua_interface->RunSpawnScript(script, "group_dead", npc, spawn); + break; + } + case SPAWN_SCRIPT_HEAR_SAY: { + lua_interface->RunSpawnScript(script, "hear_say", npc, spawn, message); + break; + } + case SPAWN_SCRIPT_PRESPAWN: { + lua_interface->RunSpawnScript(script, "prespawn", npc); + break; + } + case SPAWN_SCRIPT_USEDOOR: { + result = lua_interface->RunSpawnScript(script, "usedoor", npc, spawn, "", is_door_open); + break; + } + case SPAWN_SCRIPT_BOARD: { + result = lua_interface->RunSpawnScript(script, "board", npc, spawn); + break; + } + case SPAWN_SCRIPT_DEBOARD: { + result = lua_interface->RunSpawnScript(script, "deboard", npc, spawn); + break; + } + default: + { + result = false; + break; + } + } + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + + return result; +} + +void ZoneServer::DeleteTransporters() { + MTransportLocations.writelock(__FUNCTION__, __LINE__); + transporter_locations.clear(); //world takes care of actually deleting the data + MTransportLocations.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ReloadTransporters(){ + MutexList* locations = GetLocationTransporters(GetZoneID()); + if(locations){ + MutexList::iterator itr = locations->begin(); + while(itr.Next()) + AddTransporter(itr->value); + } +} + +void ZoneServer::CheckTransporters(Client* client) { + MTransportLocations.readlock(__FUNCTION__, __LINE__); + if(transporter_locations.size() > 0){ + LocationTransportDestination* loc = 0; + list::iterator itr; + for (itr = transporter_locations.begin(); itr != transporter_locations.end(); itr++) { + loc = *itr; + if(client->GetPlayer()->GetDistance(loc->trigger_x, loc->trigger_y, loc->trigger_z) <= loc->trigger_radius){ + if(loc->destination_zone_id == 0 || loc->destination_zone_id == GetZoneID()){ + EQ2Packet* packet = client->GetPlayer()->Move(loc->destination_x, loc->destination_y, loc->destination_z, client->GetVersion()); + if(packet) + client->QueuePacket(packet); + } + else{ + ZoneServer* new_zone = zone_list.Get(loc->destination_zone_id); + if(new_zone){ + client->GetPlayer()->SetX(loc->destination_x); + client->GetPlayer()->SetY(loc->destination_y); + client->GetPlayer()->SetZ(loc->destination_z); + client->GetPlayer()->SetHeading(loc->destination_heading); + client->Zone(new_zone, false); + } + } + break; + } + } + } + MTransportLocations.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddTransporter(LocationTransportDestination* loc) { + MTransportLocations.writelock(__FUNCTION__, __LINE__); + transporter_locations.push_back(loc); + MTransportLocations.releasewritelock(__FUNCTION__, __LINE__); +} + +Sign* ZoneServer::AddSignSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + Sign* sign = GetNewSign(spawnentry->spawn_id); + if(sign && !sign->IsOmittedByDBFlag()){ + DeterminePosition(spawnlocation, sign); + sign->SetDatabaseID(spawnentry->spawn_id); + sign->SetSpawnLocationID(spawnentry->spawn_location_id); + sign->SetSpawnEntryID(spawnentry->spawn_entry_id); + sign->SetRespawnTime(spawnentry->respawn); + sign->SetExpireTime(spawnentry->expire_time); + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(sign, spawnentry->expire_time, spawnentry->expire_offset); + + SetSpawnScript(spawnentry, sign); + + CallSpawnScript(sign, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(sign); + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + return sign; +} + +Widget* ZoneServer::AddWidgetSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + Widget* widget = GetNewWidget(spawnentry->spawn_id); + if(widget && !widget->IsOmittedByDBFlag()){ + DeterminePosition(spawnlocation, widget); + widget->SetDatabaseID(spawnentry->spawn_id); + widget->SetSpawnLocationID(spawnentry->spawn_location_id); + widget->SetSpawnEntryID(spawnentry->spawn_entry_id); + if(!widget->GetIncludeLocation()){ + widget->SetX(widget->GetWidgetX()); + if(widget->GetCloseY() != 0) + widget->SetY(widget->GetCloseY()); + widget->SetZ(widget->GetWidgetZ()); + } + widget->SetRespawnTime(spawnentry->respawn); + widget->SetExpireTime(spawnentry->expire_time); + widget->SetSpawnOrigHeading(widget->GetHeading()); + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(widget, spawnentry->expire_time, spawnentry->expire_offset); + + SetSpawnScript(spawnentry, widget); + + CallSpawnScript(widget, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(widget); + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + return widget; +} + +Object* ZoneServer::AddObjectSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + Object* object = GetNewObject(spawnentry->spawn_id); + if(object && !object->IsOmittedByDBFlag()){ + DeterminePosition(spawnlocation, object); + object->SetDatabaseID(spawnentry->spawn_id); + object->SetSpawnLocationID(spawnentry->spawn_location_id); + object->SetSpawnEntryID(spawnentry->spawn_entry_id); + object->SetRespawnTime(spawnentry->respawn); + object->SetExpireTime(spawnentry->expire_time); + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(object, spawnentry->expire_time, spawnentry->expire_offset); + + SetSpawnScript(spawnentry, object); + + CallSpawnScript(object, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(object); + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + return object; +} + +GroundSpawn* ZoneServer::AddGroundSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){ + LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__); + GroundSpawn* spawn = GetNewGroundSpawn(spawnentry->spawn_id); + if(spawn && !spawn->IsOmittedByDBFlag()){ + DeterminePosition(spawnlocation, spawn); + spawn->SetDatabaseID(spawnentry->spawn_id); + spawn->SetSpawnLocationID(spawnentry->spawn_location_id); + spawn->SetSpawnEntryID(spawnentry->spawn_entry_id); + spawn->SetRespawnTime(spawnentry->respawn); + spawn->SetExpireTime(spawnentry->expire_time); + + if(spawn->GetRandomizeHeading()) { + float rand_heading = MakeRandomFloat(0.0f, 360.0f); + spawn->SetHeading(rand_heading); + } + else { + spawn->SetHeading(spawnlocation->heading); + } + + if (spawnentry->expire_time > 0) + AddSpawnExpireTimer(spawn, spawnentry->expire_time, spawnentry->expire_offset); + + SetSpawnScript(spawnentry, spawn); + + CallSpawnScript(spawn, SPAWN_SCRIPT_PRESPAWN); + + AddSpawn(spawn); + } + LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit %s", __FUNCTION__); + return spawn; +} + +void ZoneServer::AddSpawn(Spawn* spawn) { + if(!spawn->IsPlayer()) // we already set it on loadCharacter + spawn->SetZone(this); + + MIgnoredWidgets.lock_shared(); + std::map::iterator itr; + for(itr = ignored_widgets.begin(); itr != ignored_widgets.end(); itr++) { + spawn->AddIgnoredWidget(itr->first); + } + MIgnoredWidgets.unlock_shared(); + spawn->position_changed = false; + spawn->info_changed = false; + spawn->vis_changed = false; + spawn->changed = false; + + // Write locking the spawn list here will cause deadlocks, so instead add it to a temp list that the + // main spawn thread will put into the spawn_list when ever it has a chance. + MPendingSpawnListAdd.writelock(__FUNCTION__, __LINE__); + pending_spawn_list_add.push_back(spawn); + MPendingSpawnListAdd.releasewritelock(__FUNCTION__, __LINE__); + + spawn_range.Trigger(); + + if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE && spawn->IsObject()) + { + spawn->AddSecondaryEntityCommand("Examine", 20, "house_spawn_examine", "", 0, 0); + spawn->AddSecondaryEntityCommand("Move", 20, "move_item", "", 0, 0); + spawn->AddSecondaryEntityCommand("Pack in Moving Crate", 20, "house_spawn_pack_in_moving_crate", "", 0, 0); + spawn->AddSecondaryEntityCommand("Pick Up", 20, "pickup", "", 0, 0); + spawn->SetShowCommandIcon(1); + } + + if(spawn->IsNPC()) + AddEnemyList((NPC*)spawn); + if(spawn->IsPlayer() && ((Player*)spawn)->GetGroupMemberInfo()) + spawn->SendGroupUpdate(); + if (spawn->IsPlayer()) { + ((Player*)spawn)->GetInfoStruct()->set_rain(rain); + ((Player*)spawn)->SetCharSheetChanged(true); + } + + if (movementMgr != nullptr && spawn->IsEntity()) { + movementMgr->AddMob((Entity*)spawn); + } + + AddSpawnProximities(spawn); + + AddSpawnToGrid(spawn, spawn->GetLocation()); + spawn->SetAddedToWorldTimestamp(Timer::GetCurrentTime2()); +} + +void ZoneServer::AddClient(Client* client){ + MClientList.writelock(__FUNCTION__, __LINE__); + lifetime_client_count++; + DecrementIncomingClients(); + clients.push_back(client); + MClientList.releasewritelock(__FUNCTION__, __LINE__); + + connected_clients.Add(client); +} + +void ZoneServer::RemoveClient(Client* client) +{ + Guild *guild; + + bool dismissPets = false; + if(client) + { + if (client->GetPlayer()) + client_list.RemovePlayerFromInvisHistory(client->GetPlayer()->GetID()); + + LogWrite(ZONE__DEBUG, 0, "Zone", "Sending login equipment appearance updates..."); + loginserver.SendImmediateEquipmentUpdatesForChar(client->GetPlayer()->GetCharacterID()); + + if (!client->IsZoning()) + { + client->SaveSpells(); + + client->GetPlayer()->DeleteSpellEffects(true); + + if ((guild = client->GetPlayer()->GetGuild()) != NULL) + guild->GuildMemberLogoff(client->GetPlayer()); + + chat.LeaveAllChannels(client); + } + + if(!zoneShuttingDown && !client->IsZoning()) + { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo(); + int32 group_id = 0; + if (gmi) { + group_id = gmi->group_id; + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + if (group_id) { + int32 size = world.GetGroupManager()->GetGroupSize(group_id); + if (size > 1) { + bool send_left_message = size > 2; + // removegroupmember can delete the gmi, so make sure we still have a group_id after + world.GetGroupManager()->RemoveGroupMember(group_id, client->GetPlayer()); + if (send_left_message) + world.GetGroupManager()->GroupMessage(group_id, "%s has left the group.", client->GetPlayer()->GetName()); + } + } + if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to LD/Exit...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID()); + } + else + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to Camp/Quit...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID()); + } + + dismissPets = true; + //} + } + else + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Removing client '%s' (%u) due to some client zoning...", client->GetPlayer()->GetName(), client->GetPlayer()->GetCharacterID()); + } + + map::iterator itr; + for (itr = client->GetPlayer()->SpawnedBots.begin(); itr != client->GetPlayer()->SpawnedBots.end(); itr++) { + Spawn* spawn = GetSpawnByID(itr->second); + if (spawn) + ((Bot*)spawn)->Camp(); + } + + if(dismissPets) { + ((Entity*)client->GetPlayer())->DismissAllPets(); + } + + MClientList.writelock(__FUNCTION__, __LINE__); + LogWrite(ZONE__DEBUG, 0, "Zone", "Calling clients.Remove(client)..."); + + std::vector::iterator itr2 = find(clients.begin(), clients.end(), client); + if (itr2 != clients.end()) + clients.erase(itr2); + MClientList.releasewritelock(__FUNCTION__, __LINE__); + + LogWrite(ZONE__INFO, 0, "Zone", "Scheduling client '%s' for removal.", client->GetPlayer()->GetName()); + database.ToggleCharacterOnline(client, 0); + + RemoveSpawn(client->GetPlayer(), false, true, true, true, true); + + int32 DisconnectClientTimer = rule_manager.GetGlobalRule(R_World, RemoveDisconnectedClientsTimer)->GetInt32(); + connected_clients.Remove(client, true, DisconnectClientTimer); // changed from a hardcoded 30000 (30 sec) to the DisconnectClientTimer rule + } +} + +void ZoneServer::RemoveClientImmediately(Client* client) { + Guild *guild; + + if(client) + { + if(client->GetPlayer()) { + if((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) { + client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD); + } + if ((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0) { + client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_CAMPING); + } + client->Disconnect(); + } + MClientList.writelock(__FUNCTION__, __LINE__); + std::vector::iterator itr = find(clients.begin(), clients.end(), client); + if (itr != clients.end()) + clients.erase(itr); + MClientList.releasewritelock(__FUNCTION__, __LINE__); + //clients.Remove(client, true); + } +} + +void ZoneServer::ClientProcess(bool ignore_shutdown_timer) +{ + if(!ignore_shutdown_timer && connected_clients.size(true) == 0) + { + MIncomingClients.readlock(__FUNCTION__, __LINE__); + bool shutdownDelayCheck = shutdownDelayTimer.Check(); + if((!AlwaysLoaded() && !shutdownTimer.Enabled()) || shutdownDelayCheck) + { + if(incoming_clients && !shutdownDelayTimer.Enabled()) { + LogWrite(ZONE__INFO, 0, "Zone", "Incoming clients (%u) expected for %s, delaying shutdown timer...", incoming_clients, GetZoneName()); + int32 timerDelay = rule_manager.GetGlobalRule(R_Zone, ShutdownDelayTimer)->GetInt32(); + + if(timerDelay < 10) { + LogWrite(ZONE__INFO, 0, "Zone", "Overriding %s shutdown delay timer as other clients are incoming, value %u too short, setting to 10...", GetZoneName(), timerDelay); + timerDelay = 10; + } + shutdownDelayTimer.Start(timerDelay, true); + } + else if(!incoming_clients || shutdownDelayCheck) { + if(!shutdownTimer.Enabled()) { + LogWrite(ZONE__INFO, 0, "Zone", "Starting zone shutdown timer for %s...", GetZoneName()); + shutdownTimer.Start(); + } + else { + LogWrite(ZONE__INFO, 0, "Zone", "zone shutdown timer for %s has %u remaining...", GetZoneName(), shutdownTimer.GetRemainingTime()); + } + } + } + MIncomingClients.releasereadlock(__FUNCTION__, __LINE__); + return; + } + + shutdownTimer.Disable(); + shutdownDelayTimer.Disable(); + Client* client = 0; + MutexList::iterator iterator = connected_clients.begin(); + + while(iterator.Next()) + { + client = iterator->value; +#ifndef NO_CATCH + try + { +#endif + if(zoneShuttingDown || !client->Process(true)) + { + if(!zoneShuttingDown && !client->IsZoning()) + { + // avoid spam of messages while we await linkdead to complete + if(!client->IsLinkdeadTimerEnabled()) { + bool camping = (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING); + LogWrite(ZONE__DEBUG, 0, "Zone", "Client is disconnecting in %s (camping = %s)", __FUNCTION__, camping ? "true" : "false"); + if(!camping) { + client->setConnection(nullptr); + } + } + + if((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) { + client->StartLinkdeadTimer(); + } + else if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0 ) + { + //only set LD flag if we're disconnecting but not camping/quitting + client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_LINKDEAD); + client->StartLinkdeadTimer(); + } + else { + // camp timer completed, remove client + RemoveClient(client); + client->Disconnect(); + } + } + else { + // force boot all players or clients zoning + RemoveClient(client); + client->Disconnect(); + } + } +#ifndef NO_CATCH + } + catch(...) + { + LogWrite(ZONE__ERROR, 0, "Zone", "Exception caught when in ZoneServer::ClientProcess() for zone '%s'!\n%s, %i", GetZoneName(), __FUNCTION__, __LINE__); + try{ + bool isLinkdead = false; + if(!client->IsZoning()) + { + if( (client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_CAMPING) == 0 ) + { + client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() + ACTIVITY_STATUS_LINKDEAD); + client->StartLinkdeadTimer(); + isLinkdead = true; + if(client->GetPlayer()->GetGroupMemberInfo()) + world.GetGroupManager()->GroupMessage(client->GetPlayer()->GetGroupMemberInfo()->group_id, "%s has gone Linkdead.", client->GetPlayer()->GetName()); + } + } + + if(!isLinkdead) { + RemoveClient(client); + client->Disconnect(); + } + } + catch(...){ + LogWrite(ZONE__ERROR, 0, "Zone", "Exception caught when in ZoneServer::ClientProcess(), second try\n%s, %i", __FUNCTION__, __LINE__); + } + } +#endif + } +} + +void ZoneServer::SimpleMessage(int8 type, const char* message, Spawn* from, float distance, bool send_to_sender){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(from && client && client->IsConnected() && (send_to_sender || from != client->GetPlayer()) && from->GetDistance(client->GetPlayer()) <= distance){ + client->SimpleMessage(type, message); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::HandleChatMessage(Client* client, Spawn* from, const char* to, int16 channel, const char* message, float distance, const char* channel_name, bool show_bubble, int32 language) { + if ((!distance || from->GetDistance(client->GetPlayer()) <= distance) && (!from || !client->GetPlayer()->IsIgnored(from->GetName()))) { + PacketStruct* packet = configReader.getStruct("WS_HearChat", client->GetVersion()); + if (packet) { + if (from) + packet->setMediumStringByName("from", from->GetName()); + if (client->GetPlayer() != from) + packet->setMediumStringByName("to", client->GetPlayer()->GetName()); + int8 clientchannel = client->GetMessageChannelColor(channel); + packet->setDataByName("channel", client->GetMessageChannelColor(channel)); + if (from && ((from == client->GetPlayer()) || (client->GetPlayer()->WasSentSpawn(from->GetID())))) + packet->setDataByName("from_spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(from)); + else + packet->setDataByName("from_spawn_id", 0xFFFFFFFF); + packet->setDataByName("to_spawn_id", 0xFFFFFFFF); + packet->setMediumStringByName("message", message); + packet->setDataByName("language", language); + + bool hasLanguage = client->GetPlayer()->HasLanguage(language); + if (language > 0 && !hasLanguage) + packet->setDataByName("understood", 0); + else + packet->setDataByName("understood", 1); + + show_bubble == true ? packet->setDataByName("show_bubble", 1) : packet->setDataByName("show_bubble", 0); + if (channel_name) + packet->setMediumStringByName("channel_name", channel_name); + EQ2Packet* outapp = packet->serialize(); + //DumpPacket(outapp); + client->QueuePacket(outapp); + safe_delete(packet); + } + } +} + +void ZoneServer::HandleChatMessage(Spawn* from, const char* to, int16 channel, const char* message, float distance, const char* channel_name, bool show_bubble, int32 language){ + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) + HandleChatMessage(client, from, to, channel, message, distance, channel_name, show_bubble, language); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::HandleBroadcast(const char* message) { + vector::iterator client_itr; + Client* client = 0; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) + client->SimpleMessage(CHANNEL_BROADCAST, message); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::HandleAnnouncement(const char* message) { + vector::iterator client_itr; + Client* client = 0; + int32 words = ::CountWordsInString(message); + if (words < 5) + words = 5; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) { + client->SimpleMessage(CHANNEL_BROADCAST, message); + client->SendPopupMessage(10, message, "ui_harvest_normal", words, 0xFF, 0xFF, 0x00); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendTimeUpdate(Client* client){ + if(client){ + PacketStruct* packet = world.GetWorldTime(client->GetVersion()); + if(packet){ + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +void ZoneServer::SendTimeUpdateToAllClients(){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->IsConnected()) + SendTimeUpdate(client); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::UpdateVitality(float amount){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && client->GetPlayer()->GetInfoStruct()->get_xp_vitality() < 100){ + if((client->GetPlayer()->GetInfoStruct()->get_xp_vitality() + amount) > 100) + client->GetPlayer()->GetInfoStruct()->set_xp_vitality(100); + else + client->GetPlayer()->GetInfoStruct()->add_xp_vitality(amount); + client->GetPlayer()->SetCharSheetChanged(true); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSpawn(Spawn* spawn, Client* client){ + EQ2Packet* outapp = spawn->serialize(client->GetPlayer(), client->GetVersion()); + + if(!client->GetPlayer()->IsSendingSpawn(spawn->GetID())) { + safe_delete(outapp); + } + else { + LogWrite(ZONE__DEBUG, 7, "Zone", "%s: Processing SendSpawn for spawn index %u (%s)...", client->GetPlayer()->GetName(), client->GetPlayer()->GetIndexForSpawn(spawn), spawn->GetName()); + if(outapp) + client->QueuePacket(outapp, true); + + client->GetPlayer()->SetSpawnSentState(spawn, SpawnState::SPAWN_STATE_SENT_WAIT); + } + + /* + vis flags: + 2 = show icon + 4 = targetable + 16 = show name + 32 = show level/border + activity_status: + 4 - linkdead + 8 - camping + 16 - LFG + 32 - LFW + 2048 - mentoring + 4096 - displays shield + 8192 - immunity gained + 16384 - immunity remaining + attackable_status + 1 - no_hp_bar + 4 - not attackable + npc_con + -4 = scowls + -3 = threatening + -2 = dubiously + -1 = apprehensively + 0 = indifferent + 1 = amiably + 2 = kindly + 3 = warmly + 4 = ally + quest_flag + 1 = new quest + 2 = update and new quest + 3 = update + */ + if(spawn->IsEntity() && spawn->HasTrapTriggered()) + client->QueueStateCommand(client->GetPlayer()->GetIDWithPlayerSpawn(spawn), spawn->GetTrapState()); +} + +Client* ZoneServer::GetClientByName(char* name) { + Client* ret = 0; + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((*itr)->GetPlayer()) { + if (strncmp((*itr)->GetPlayer()->GetName(), name, strlen(name)) == 0) { + ret = *itr; + break; + } + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +Client* ZoneServer::GetClientByCharID(int32 charid) { + Client* ret = 0; + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + if ((*itr)->GetCharacterID() == charid) { + ret = *itr; + break; + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void ZoneServer::AddMovementNPC(Spawn* spawn){ + if (spawn) + movement_spawns.Put(spawn->GetID(), 1); +} + +void ZoneServer::RemoveMovementNPC(Spawn* spawn){ + if (spawn) + remove_movement_spawns.Add(spawn->GetID()); +} + +void ZoneServer::PlayFlavor(Client* client, Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language){ + if(!client || !spawn) + return; + + PacketStruct* packet = configReader.getStruct("WS_PlayFlavor", client->GetVersion()); + if(packet){ + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("unknown1", 0xFFFFFFFF); + packet->setDataByName("unknown5", 1, 1); + packet->setDataByName("unknown5", 1, 6); + if(mp3){ + packet->setMediumStringByName("mp3", mp3); + packet->setDataByName("key", key1); + packet->setDataByName("key", key2, 1); + } + packet->setMediumStringByName("name", spawn->GetName()); + if(text) + packet->setMediumStringByName("text", text); + if(emote) { + if(client->GetVersion() > 561) { + packet->setMediumStringByName("emote", emote); + } + else { + HandleEmote(spawn, std::string(emote)); + } + } + if (language != 0) + packet->setDataByName("language", language); + + //We should probably add Common = language id 0 or 0xFF so admins can customize more.. + if (language == 0 || client->GetPlayer()->HasLanguage(language)) + packet->setDataByName("understood", 1); + + EQ2Packet* app = packet->serialize(); + //DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet); + } +} + +void ZoneServer::PlayVoice(Client* client, Spawn* spawn, const char* mp3, int32 key1, int32 key2){ + if(!client || !spawn) + return; + + PacketStruct* packet = configReader.getStruct("WS_PlayVoice", client->GetVersion()); + if(packet){ + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setMediumStringByName("mp3", mp3); + packet->setDataByName("key", key1); + packet->setDataByName("key", key2, 1); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void ZoneServer::PlayFlavor(Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language){ + if(!spawn) + return; + + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->GetDistance(spawn) > 30) + continue; + PlayFlavor(client, spawn, mp3, text, emote, key1, key2, language); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::PlayFlavorID(Spawn* spawn, int8 type, int32 id, int16 index, int8 language){ + if(!spawn) + return; + + Client* client = 0; + vector::iterator client_itr; + + VoiceOverStruct non_garble, garble; + bool garble_success = false; + bool success = world.FindVoiceOver(type, id, index, &non_garble, &garble_success, &garble); + + VoiceOverStruct* resStruct = nullptr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->GetDistance(spawn) > 30) + continue; + + client->SendPlayFlavor(spawn, language, &non_garble, &garble, success, garble_success); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::PlayVoice(Spawn* spawn, const char* mp3, int32 key1, int32 key2){ + if(!spawn || !mp3) + return; + + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID())) + continue; + PlayVoice(client, spawn, mp3, key1, key2); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::PlaySoundFile(Client* client, const char* name, float origin_x, float origin_y, float origin_z){ + if(!name) + return; + + PacketStruct* packet = 0; + if(client){ + packet = configReader.getStruct("WS_Play3DSound", client->GetVersion()); + if(packet){ + packet->setMediumStringByName("name", name); + packet->setDataByName("x", origin_x); + packet->setDataByName("y", origin_y); + packet->setDataByName("z", origin_z); + packet->setDataByName("unknown1", 1); + packet->setDataByName("unknown2", 2.5); + packet->setDataByName("unknown3", 15); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + else{ + EQ2Packet* outapp = 0; + int16 packet_version = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client && (!packet || packet_version != client->GetVersion())){ + safe_delete(packet); + safe_delete(outapp); + packet_version = client->GetVersion(); + packet = configReader.getStruct("WS_Play3DSound", packet_version); + if(packet){ + packet->setMediumStringByName("name", name); + packet->setDataByName("x", origin_x); + packet->setDataByName("y", origin_y); + packet->setDataByName("z", origin_z); + packet->setDataByName("unknown1", 1); + packet->setDataByName("unknown2", 2.5); + packet->setDataByName("unknown3", 15); + outapp = packet->serialize(); + } + } + if(outapp && client && client->IsReadyForUpdates()) + client->QueuePacket(outapp->Copy()); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); + safe_delete(outapp); + } +} + +bool ZoneServer::HasWidgetTimer(Spawn* widget){ + bool ret = false; + if (widget) { + int32 id = widget->GetID(); + map::iterator itr; + MWidgetTimers.readlock(__FUNCTION__, __LINE__); + for (itr = widget_timers.begin(); itr != widget_timers.end(); itr++) { + if(itr->first == id){ + ret = true; + break; + } + } + MWidgetTimers.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +void ZoneServer::CheckWidgetTimers(){ + vector remove_list; + map::iterator itr; + + MWidgetTimers.readlock(__FUNCTION__, __LINE__); + for (itr = widget_timers.begin(); itr != widget_timers.end(); itr++) { + if(Timer::GetCurrentTime2() >= itr->second){ + /*Spawn* widget = GetSpawnByID(itr->first); + if (widget && widget->IsWidget()) + ((Widget*)widget)->HandleTimerUpdate();*/ + + remove_list.push_back(itr->first); + } + } + MWidgetTimers.releasereadlock(__FUNCTION__, __LINE__); + + for (int32 i = 0; i < remove_list.size(); i++) { + Spawn* widget = GetSpawnByID(remove_list[i]); + if (widget && widget->IsWidget()) + ((Widget*)widget)->HandleTimerUpdate(); + } + + MWidgetTimers.writelock(__FUNCTION__, __LINE__); + for(int32 i=0;iIsWidget()) { + MWidgetTimers.writelock(__FUNCTION__, __LINE__); + widget_timers[widget->GetID()] = ((int32)(time * 1000)) + Timer::GetCurrentTime2(); + MWidgetTimers.releasewritelock(__FUNCTION__, __LINE__); + } +} + +Spawn* ZoneServer::GetSpawnGroup(int32 id){ + Spawn* ret = 0; + Spawn* spawn = 0; + + if(id < 1) + return 0; + + bool lookup = false; + if(quick_group_id_lookup.count(id) > 0) { + ret = GetSpawnByID(quick_group_id_lookup.Get(id)); + lookup = true; + } + if(ret == NULL) { + if(lookup) + quick_group_id_lookup.erase(id); + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn){ + if(spawn->GetSpawnGroupID() == id){ + ret = spawn; + quick_group_id_lookup.Put(id, spawn->GetID()); + break; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +Spawn* ZoneServer::GetSpawnByLocationID(int32 location_id) { + Spawn* ret = 0; + Spawn* current_spawn = 0; + + if(location_id < 1) + return 0; + + bool lookup = false; + if(quick_location_id_lookup.count(location_id) > 0) { + ret = GetSpawnByID(quick_location_id_lookup.Get(location_id)); + lookup = true; + } + if(ret == NULL) { + if(lookup) + quick_location_id_lookup.erase(location_id); + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + current_spawn = itr->second; + if (current_spawn && current_spawn->GetSpawnLocationID() == location_id) { + ret = current_spawn; + quick_location_id_lookup.Put(location_id, ret->GetID()); + break; + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +Spawn* ZoneServer::GetSpawnByDatabaseID(int32 id){ + Spawn* ret = 0; + + if(id < 1) + return 0; + + bool lookup = false; + + if(quick_database_id_lookup.count(id) > 0) { + ret = GetSpawnByID(quick_database_id_lookup.Get(id)); + lookup = true; + } + if(ret == NULL){ + if(lookup) + quick_database_id_lookup.erase(id); + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++){ + spawn = itr->second; + if(spawn){ + if(spawn->GetDatabaseID() == id){ + quick_database_id_lookup.Put(id, spawn->GetID()); + ret = spawn; + break; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } + return ret; +} + +Spawn* ZoneServer::GetSpawnByID(int32 id, bool spawnListLocked) { + Spawn* ret = 0; + if (!spawnListLocked ) + MSpawnList.readlock(__FUNCTION__, __LINE__); + + if (spawn_list.count(id) > 0) + ret = spawn_list[id]; + + if (!spawnListLocked) + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +bool ZoneServer::SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* packet, bool delete_spawn) +{ + if(!client || !spawn || (client && client->GetPlayer() == spawn)) + return false; + + if(client->GetPlayerPOVGhostSpawnID() == spawn->GetID()) { + client->SetPlayerPOVGhost(nullptr); + } + + spawn->RemoveSpawnFromPlayer(client->GetPlayer()); + return true; +} + +void ZoneServer::SetSpawnCommand(Spawn* spawn, int8 type, char* value, Client* client){ + //commands + LogWrite(MISC__TODO, 1, "TODO", "%s does nothing!\n%s, %i", __FUNCTION__, __FILE__, __LINE__); +} + +void ZoneServer::SetSpawnCommand(int32 spawn_id, int8 type, char* value, Client* client){ + LogWrite(MISC__TODO, 1, "TODO", "%s does nothing!\n%s, %i", __FUNCTION__, __FILE__, __LINE__); +} + +void ZoneServer::ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value){ + // This will apply the /spawn set command to all the spawns in the zone with the same DB ID, we do not want to set + // location values (x, y, z, heading, grid) for all spawns in the zone with the same DB ID, only the targeted spawn + if(type == SPAWN_SET_VALUE_SPAWNENTRY_SCRIPT || type == SPAWN_SET_VALUE_SPAWNLOCATION_SCRIPT || (type >= SPAWN_SET_VALUE_X && type <= SPAWN_SET_VALUE_LOCATION) || + type == SPAWN_SET_VALUE_PITCH || type == SPAWN_SET_VALUE_ROLL) + return; + + Spawn* tmp = 0; + if(target->IsNPC()) + tmp = GetNPC(target->GetDatabaseID()); + else if(target->IsObject()) + tmp = GetObject(target->GetDatabaseID()); + else if(target->IsGroundSpawn()) + tmp = GetGroundSpawn(target->GetDatabaseID()); + else if(target->IsSign()) + tmp = GetSign(target->GetDatabaseID()); + else if(target->IsWidget()) + tmp = GetWidget(target->GetDatabaseID()); + if(tmp && type == SPAWN_SET_VALUE_SPAWN_SCRIPT) + tmp->SetSpawnScript(value); + else if(tmp) + commands.SetSpawnCommand(client, tmp, type, value); // set the master spawn + Spawn* spawn = 0; + + // this check needs to be here otherwise every spawn with 0 will be set + if ( target->GetDatabaseID ( ) > 0 ) + { + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn && spawn->GetDatabaseID() == target->GetDatabaseID()){ + if(type == SPAWN_SET_VALUE_SPAWN_SCRIPT) + spawn->SetSpawnScript(value); + else + commands.SetSpawnCommand(client, spawn, type, value); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + } +} + +void ZoneServer::StopSpawnScriptTimer(Spawn* spawn, std::string functionName){ + MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__); + MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__); + if(spawn_script_timers.size() > 0){ + set::iterator itr; + SpawnScriptTimer* timer = 0; + for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); ) { + timer = *itr; + if(timer->spawn == spawn->GetID() && (functionName == "" || timer->function == functionName) && remove_spawn_script_timers_list.count(timer) == 0) { + itr = spawn_script_timers.erase(itr); + safe_delete(timer); + } + else + itr++; + } + } + MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__); + MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DeleteSpawnScriptTimers(Spawn* spawn, bool all){ + MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__); + MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__); + if(spawn_script_timers.size() > 0){ + set::iterator itr; + SpawnScriptTimer* timer = 0; + for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); itr++) { + timer = *itr; + if((all || timer->spawn == spawn->GetID()) && remove_spawn_script_timers_list.count(timer) == 0) + remove_spawn_script_timers_list.insert(timer); + } + + if(all) + spawn_script_timers.clear(); + } + MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__); + MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DeleteSpawnScriptTimers() { + MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__); + MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__); + if(remove_spawn_script_timers_list.size() > 0){ + set::iterator itr; + SpawnScriptTimer* timer = 0; + + for (itr = remove_spawn_script_timers_list.begin(); itr != remove_spawn_script_timers_list.end(); itr++) { + timer = *itr; + set::iterator itr2; + + itr2 = spawn_script_timers.find(timer); + + if(itr2 != spawn_script_timers.end()) + spawn_script_timers.erase(itr2); + + safe_delete(timer); + } + remove_spawn_script_timers_list.clear(); + } + MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__); + MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::CheckSpawnScriptTimers(){ + DeleteSpawnScriptTimers(); + SpawnScriptTimer* timer = 0; + vector call_timers; + MSpawnScriptTimers.readlock(__FUNCTION__, __LINE__); + MRemoveSpawnScriptTimersList.writelock(__FUNCTION__, __LINE__); + if(spawn_script_timers.size() > 0){ + int32 current_time = Timer::GetCurrentTime2(); + set::iterator itr; + for (itr = spawn_script_timers.begin(); itr != spawn_script_timers.end(); itr++) { + timer = *itr; + if(remove_spawn_script_timers_list.count(timer) == 0 && + timer->current_count < timer->max_count && current_time >= timer->timer){ + timer->current_count++; + SpawnScriptTimer tmpTimer; + tmpTimer.current_count = timer->current_count; + tmpTimer.function = timer->function; + tmpTimer.player = timer->player; + tmpTimer.spawn = timer->spawn; + tmpTimer.max_count = timer->max_count; + call_timers.push_back(tmpTimer); + } + if(timer->current_count >= timer->max_count && remove_spawn_script_timers_list.count(timer) == 0) + remove_spawn_script_timers_list.insert(timer); + } + } + MRemoveSpawnScriptTimersList.releasewritelock(__FUNCTION__, __LINE__); + MSpawnScriptTimers.releasereadlock(__FUNCTION__, __LINE__); + if(call_timers.size() > 0){ + vector::iterator itr; + for(itr = call_timers.begin(); itr != call_timers.end(); itr++){ + SpawnScriptTimer tmpTimer = (SpawnScriptTimer)*itr; + CallSpawnScript(GetSpawnByID(tmpTimer.spawn), SPAWN_SCRIPT_TIMER, tmpTimer.player > 0 ? GetSpawnByID(tmpTimer.player) : 0, tmpTimer.function.c_str()); + } + } +} + +void ZoneServer::KillSpawnByDistance(Spawn* spawn, float max_distance, bool include_players, bool send_packet){ + if(!spawn) + return; + + auto loc = glm::vec3(spawn->GetX(), spawn->GetZ(), spawn->GetY()); + std::vector grids_by_radius; + if(spawn->GetMap()) { + grids_by_radius = GetGridsByLocation(spawn, loc, max_distance); + } + else { + grids_by_radius.push_back(spawn->GetLocation()); + } + + Spawn* test_spawn = 0; + MGridMaps.lock_shared(); + std::vector::iterator grid_radius_itr; + for(grid_radius_itr = grids_by_radius.begin(); grid_radius_itr != grids_by_radius.end(); grid_radius_itr++) { + std::map::iterator grids = grid_maps.find((*grid_radius_itr)); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + test_spawn = it->second; + if(test_spawn && test_spawn->Alive() && test_spawn->GetID() > 0 && test_spawn->GetID() != spawn->GetID() && test_spawn->IsEntity() && + (!test_spawn->IsPlayer() || include_players)){ + if(test_spawn->GetDistance(spawn) < max_distance) + KillSpawn(true, test_spawn, spawn, send_packet); + } + } + grids->second->MSpawns.unlock_shared(); + } + } + MGridMaps.unlock_shared(); +} + +void ZoneServer::SpawnSetByDistance(Spawn* spawn, float max_distance, string field, string value){ + if(!spawn) + return; + + Spawn* test_spawn = 0; + int32 type = commands.GetSpawnSetType(field); + if(type == 0xFFFFFFFF) + return; + + auto loc = glm::vec3(spawn->GetX(), spawn->GetZ(), spawn->GetY()); + std::vector grids_by_radius; + if(spawn->GetMap()) { + grids_by_radius = GetGridsByLocation(spawn, loc, max_distance); + } + else { + grids_by_radius.push_back(spawn->GetLocation()); + } + + MGridMaps.lock_shared(); + std::vector::iterator grid_radius_itr; + for(grid_radius_itr = grids_by_radius.begin(); grid_radius_itr != grids_by_radius.end(); grid_radius_itr++) { + std::map::iterator grids = grid_maps.find((*grid_radius_itr)); + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + test_spawn = it->second; + if(test_spawn && test_spawn->GetID() > 0 && test_spawn->GetID() != spawn->GetID() && !test_spawn->IsPlayer()){ + if(test_spawn->GetDistance(spawn) < max_distance){ + commands.SetSpawnCommand(0, test_spawn, type, value.c_str()); + } + } + } + grids->second->MSpawns.unlock_shared(); + } + MGridMaps.unlock_shared(); +} + +void ZoneServer::AddSpawnScriptTimer(SpawnScriptTimer* timer){ + MSpawnScriptTimers.writelock(__FUNCTION__, __LINE__); + spawn_script_timers.insert(timer); + MSpawnScriptTimers.releasewritelock(__FUNCTION__, __LINE__); +} + +/* +void ZoneServer::RemoveFromRangeMap(Client* client){ + spawn_range_map.erase(client); +} +*/ + +void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool lock, bool erase_from_spawn_list, bool lock_spell_process) +{ + LogWrite(ZONE__DEBUG, 3, "Zone", "Processing RemoveSpawn function for %s (%i)...", spawn->GetName(),spawn->GetID()); + + PacketStruct* packet = 0; + int16 packet_version = 0; + Client* client = 0; + + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + + if (client && (client->GetVersion() > 373 || !client->IsZoning() || client->GetPlayer() != spawn)) { //don't send destroy ghost of 283 client when zoning + if (client->GetPlayer()->HasTarget() && client->GetPlayer()->GetTarget() == spawn) + client->GetPlayer()->SetTarget(0); + if(client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->IsSendingSpawn(spawn->GetID())) + SendRemoveSpawn(client, spawn, packet, delete_spawn); + if (spawn_range_map.count(client) > 0) + spawn_range_map.Get(client)->erase(spawn->GetID()); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + safe_delete(packet); + + spawn->RemoveSpawnProximities(); + RemoveSpawnProximities(spawn); + + if (movementMgr != nullptr && spawn->IsEntity()) { + movementMgr->RemoveMob((Entity*)spawn); + } + + RemoveSpawnSupportFunctions(spawn, lock_spell_process); + if (reloading) + RemoveDeadEnemyList(spawn); + + if (lock) + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + + if (dead_spawns.count(spawn->GetID()) > 0) + dead_spawns.erase(spawn->GetID()); + if (lock) + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); + + if (spawn_expire_timers.count(spawn->GetID()) > 0) + spawn_expire_timers.erase(spawn->GetID()); + + spawn->SetDeletedSpawn(true); + + // we will remove the spawn ptr and entry in the spawn_list later.. it is not safe right now (lua? client process? spawn process? etc? too many factors) + if(erase_from_spawn_list) + AddPendingSpawnRemove(spawn->GetID()); + + if(respawn && !spawn->IsPlayer() && spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0) + { + LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn for '%s'.", spawn->GetName()); + + // handle instance spawn db info + // we don't care if a NPC or a client kills the spawn, we could have events that cause NPCs to kill NPCs. + if(spawn->GetZone()->GetInstanceID() > 0 && spawn->GetSpawnLocationID() > 0) + { + LogWrite(ZONE__DEBUG, 3, "Zone", "Handle NPC Respawn in an Instance."); + // use respawn time to either insert/update entry (likely insert in this situation) + if ( spawn->IsNPC() ) + { + database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_NPC, + spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID()); + } + else if ( spawn->IsObject ( ) ) + { + database.CreateInstanceSpawnRemoved(spawn->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT, + spawn->GetRespawnTime(),spawn->GetZone()->GetInstanceID()); + } + } + else + { + int32 spawnLocationID = spawn->GetSpawnLocationID(); + int32 spawnRespawnTime = spawn->GetRespawnTime(); + respawn_timers.Put(spawnLocationID, Timer::GetCurrentTime2() + spawnRespawnTime * 1000); + } + } + + RemoveSpawnFromGrid(spawn, spawn->GetLocation()); + + // Do we really need the mutex locks and check to dead_spawns as we remove it from dead spawns at the start of this function + if (lock && !respawn) + MDeadSpawns.readlock(__FUNCTION__, __LINE__); + if(delete_spawn && dead_spawns.count(spawn->GetID()) == 0) + AddPendingDelete(spawn); + if (lock && !respawn) + MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__); + + LogWrite(ZONE__DEBUG, 3, "Zone", "Done processing RemoveSpawn function..."); +} + +Spawn* ZoneServer::GetClosestSpawn(Spawn* spawn, int32 spawn_id){ + Spawn* closest_spawn = 0; + Spawn* test_spawn = 0; + float closest_distance = 1000000; + float test_distance = 0; + + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + test_spawn = itr->second; + if(test_spawn && test_spawn != spawn && test_spawn->GetDatabaseID() == spawn_id){ + test_distance = test_spawn->GetDistance(spawn); + if(test_distance < closest_distance){ + closest_distance = test_distance; + closest_spawn = test_spawn; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + return closest_spawn; +} + +int32 ZoneServer::GetClosestLocation(Spawn* spawn){ + Spawn* closest_spawn = 0; + Spawn* test_spawn = 0; + float closest_distance = 1000000; + float test_distance = 0; + + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + test_spawn = itr->second; + if(test_spawn){ + test_distance = test_spawn->GetDistance(spawn); + if(test_distance < closest_distance){ + closest_distance = test_distance; + closest_spawn = test_spawn; + if(closest_distance < 10) + break; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + if(closest_spawn) + return closest_spawn->GetLocation(); + return 0; +} + +void ZoneServer::SendQuestUpdates(Client* client, Spawn* spawn){ + if(!client) + return; + + if(spawn){ + if(client->GetPlayer()->WasSentSpawn(spawn->GetID())) + SendSpawnChanges(spawn, client, false, true); + } + else{ + client->GetCurrentZone()->SendAllSpawnsForVisChange(client); + } +} + +void ZoneServer::SendAllSpawnsForLevelChange(Client* client) { + Spawn* spawn = 0; + if (spawn_range_map.count(client) > 0) { + MutexMap::iterator itr = spawn_range_map.Get(client)->begin(); + while (itr.Next()) { + spawn = GetSpawnByID(itr->first); + if (spawn && client->GetPlayer()->WasSentSpawn(spawn->GetID())) { + SendSpawnChanges(spawn, client, false, true); + // Attempt to slow down the packet spam sent to the client + // who the bloody fuck put a Sleep here + //Sleep(5); + } + } + } +} + + +void ZoneServer::SendAllSpawnsForSeeInvisChange(Client* client) { + Spawn* spawn = 0; + if (spawn_range_map.count(client) > 0) { + MutexMap::iterator itr = spawn_range_map.Get(client)->begin(); + while (itr.Next()) { + spawn = GetSpawnByID(itr->first); + if (spawn && spawn->IsEntity() && (((Entity*)spawn)->IsInvis() || ((Entity*)spawn)->IsStealthed()) && client->GetPlayer()->WasSentSpawn(spawn->GetID())) { + SendSpawnChanges(spawn, client, true, true); + } + } + } +} + + +void ZoneServer::SendAllSpawnsForVisChange(Client* client, bool limitToEntities) { + Spawn* spawn = 0; + if (spawn_range_map.count(client) > 0) { + MutexMap::iterator itr = spawn_range_map.Get(client)->begin(); + while (itr.Next()) { + spawn = GetSpawnByID(itr->first); + if (spawn && (!limitToEntities || (limitToEntities && spawn->IsEntity())) && client->GetPlayer()->WasSentSpawn(spawn->GetID())) { + SendSpawnChanges(spawn, client, false, true); + } + } + } +} + +void ZoneServer::StartZoneSpawnsForLevelThread(Client* client){ + if(zoneShuttingDown) + return; + +#ifdef WIN32 + _beginthread(SendLevelChangedSpawns, 0, client); +#else + pthread_t thread; + pthread_create(&thread, NULL, SendLevelChangedSpawns, client); + pthread_detach(thread); +#endif +} + +void ZoneServer::ReloadClientQuests(){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(client) + client->ReloadQuests(); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendCalculatedXP(Player* player, Spawn* victim){ + if (player && victim) { + if (player->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + PlayerGroup* group = world.GetGroupManager()->GetGroup(player->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + deque::iterator itr; + bool skipGrayMob = false; + + for (itr = members->begin(); itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client) { + Player* group_member = gmi->client->GetPlayer(); + if(group_member && group_member->GetArrowColor(victim->GetLevel()) == ARROW_COLOR_GRAY) { + skipGrayMob = true; + break; + } + } + } + + for (itr = members->begin(); !skipGrayMob && itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client) { + Player* group_member = gmi->client->GetPlayer(); + if(group_member) { + float xp = group_member->CalculateXP(victim) / members->size(); + if (xp > 0) { + group_member->AddXP((int32)xp); + } + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else { + float xp = player->CalculateXP(victim); + if (xp > 0) { + Client* client = ((Player*)player)->GetClient(); + if(!client) + return; + player->AddXP((int32)xp); + } + } + } +} + +void ZoneServer::ProcessFaction(Spawn* spawn, Client* client) +{ + if(client && !spawn->IsPlayer() && spawn->GetFactionID() > 10) + { + bool update_result = false; + Faction* faction = 0; + vector* factions = 0; + Player* player = client->GetPlayer(); + + bool hasfaction = database.VerifyFactionID(player->GetCharacterID(), spawn->GetFactionID()); + //if 0 they dont have an entry in the db for this faction. if one they do and we can skip it. + if(hasfaction == 0) { + //Find out the default for this faction + sint32 defaultfaction = master_faction_list.GetDefaultFactionValue(spawn->GetFactionID()); + //add the default faction for the player. + player->SetFactionValue(spawn->GetFactionID(), defaultfaction); + //save the character so the new default gets written to the db. + client->Save(); + } + + if(player->GetFactions()->ShouldDecrease(spawn->GetFactionID())) + { + + update_result = player->GetFactions()->DecreaseFaction(spawn->GetFactionID()); + faction = master_faction_list.GetFaction(spawn->GetFactionID()); + + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got worse.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any worse.", faction->name.c_str()); + + factions = master_faction_list.GetHostileFactions(spawn->GetFactionID()); + + if(factions) + { + vector::iterator itr; + + for(itr = factions->begin(); itr != factions->end(); itr++) + { + if(player->GetFactions()->ShouldIncrease(*itr)) + { + + update_result = player->GetFactions()->IncreaseFaction(*itr); + faction = master_faction_list.GetFaction(*itr); + + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got better.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any better.", faction->name.c_str()); + } + } + } + } + + factions = master_faction_list.GetFriendlyFactions(spawn->GetFactionID()); + + if(factions) + { + vector::iterator itr; + + for(itr = factions->begin(); itr != factions->end(); itr++) + { + if(player->GetFactions()->ShouldDecrease(*itr)) + { + bool hasfaction = database.VerifyFactionID(player->GetCharacterID(),spawn->GetFactionID()); + if(hasfaction == 0) { + //they do not have the faction. Lets get the default value and feed it in. + sint32 defaultfaction = master_faction_list.GetDefaultFactionValue(spawn->GetFactionID()); + //add the default faction for the player. + player->SetFactionValue(spawn->GetFactionID(), defaultfaction); + } + + update_result = player->GetFactions()->DecreaseFaction(*itr); + faction = master_faction_list.GetFaction(*itr); + + if(faction && update_result) + client->Message(CHANNEL_FACTION, "Your faction standing with %s got worse.", faction->name.c_str()); + else if(faction) + client->Message(CHANNEL_FACTION, "Your faction standing with %s could not possibly get any worse.", faction->name.c_str()); + } + } + } + + EQ2Packet* outapp = client->GetPlayer()->GetFactions()->FactionUpdate(client->GetVersion()); + + if(outapp) + client->QueuePacket(outapp); + } +} + +void ZoneServer::Despawn(Spawn* spawn, int32 timer){ + if (spawn && movementMgr != nullptr) { + movementMgr->RemoveMob((Entity*)spawn); + } + if(!spawn || spawn->IsPlayer()) + return; + if(spawn->IsEntity()) + ((Entity*)spawn)->InCombat(false); + if(timer == 0) + timer = 1; + AddDeadSpawn(spawn, timer); +} + +void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet, int8 type, int8 damage_type, int16 kill_blow_type) +{ + bool isSpell = (type == DAMAGE_PACKET_TYPE_SIPHON_SPELL || type == DAMAGE_PACKET_TYPE_SIPHON_SPELL2 || + type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE || type == DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG || + type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE2 || type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE3); + + MDeadSpawns.readlock(__FUNCTION__, __LINE__); + if(!dead || this->dead_spawns.count(dead->GetID()) > 0) { + MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__); + return; + } + MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__); + + PacketStruct* packet = 0; + Client* client = 0; + vector* encounter = 0; + int32 encounter_player_bot_count = 1; + bool killer_in_encounter = false; + int8 loot_state = dead->GetLockedNoLoot(); + + if(dead->IsEntity()) + { + // add any special quest related loot (no_drop_quest_completed) + if(dead->IsNPC() && ((NPC*)dead)->Brain()) { + if(!((NPC*)dead)->Brain()->PlayerInEncounter() || (loot_state != ENCOUNTER_STATE_LOCKED && loot_state != ENCOUNTER_STATE_OVERMATCHED)) { + LogWrite(LOOT__DEBUG, 0, "Loot", "NPC %s bypassed loot drop due to no player in encounter, or encounter state not locked.", ((NPC*)dead)->GetName()); + } + else { + Entity* hated = ((NPC*)dead)->Brain()->GetMostHated(); + if(hated) { + GroupLootMethod loot_method = GroupLootMethod::METHOD_FFA; + int8 item_rarity = 0; + if(hated->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + PlayerGroup* group = world.GetGroupManager()->GetGroup(hated->GetGroupMemberInfo()->group_id); + if (group) { + loot_method = (GroupLootMethod)group->GetGroupOptions()->loot_method; + item_rarity = group->GetGroupOptions()->loot_items_rarity; + LogWrite(LOOT__DEBUG, 0, "Loot", "%s: Loot method set to %u.", dead->GetName(), loot_method); + } + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + AddLoot((NPC*)dead, hated, loot_method, item_rarity, hated->GetGroupMemberInfo() ? hated->GetGroupMemberInfo()->group_id : 0); + } + } + } + ((Entity*)dead)->InCombat(false); + dead->SetInitialState(16512, false); // This will make aerial npc's fall after death + dead->SetHP(0); + dead->SetSpawnType(3); + dead->appearance.attackable = 0; + + + // Remove hate towards dead from all npc's in the zone + ClearHate((Entity*)dead); + + // Check kill and death procs + if (killer && dead != killer){ + if (dead->IsEntity()) + ((Entity*)dead)->CheckProcs(PROC_TYPE_DEATH, killer); + if (killer->IsEntity()) + ((Entity*)killer)->CheckProcs(PROC_TYPE_KILL, dead); + } + + //Check if caster is alive after death proc called, incase of deathsave + if (dead->Alive()) + return; + + RemoveSpellTimersFromSpawn(dead, true, !dead->IsPlayer(), true, !isSpell); + ((Entity*)dead)->IsCasting(false); + + if(dead->IsPlayer()) + { + ((Player*)dead)->UpdatePlayerStatistic(STAT_PLAYER_TOTAL_DEATHS, 1); + client = ((Player*)dead)->GetClient(); + + ((Entity*)dead)->HandleDeathExperienceDebt(killer); + + if(client) { + + if(client->GetPlayer()->DamageEquippedItems(10, client)) + client->QueuePacket(client->GetPlayer()->GetEquipmentList()->serialize(client->GetVersion(), client->GetPlayer())); + + client->DisplayDeadWindow(); + } + } + else if (dead->IsNPC()) { + encounter = ((NPC*)dead)->Brain()->GetEncounter(); + encounter_player_bot_count = ((NPC*)dead)->Brain()->CountPlayerBotInEncounter(); + if(encounter_player_bot_count < 1) + encounter_player_bot_count = 1; + } + + } + + dead->SetActionState(0); + dead->SetTempActionState(0); + + // Needs npc to have access to the encounter list for who is allowed to loot + NPC* chest = 0; + + if (dead->IsNPC() && !((NPC*)dead)->Brain()->PlayerInEncounter()) { + dead->SetLootCoins(0); + dead->ClearLoot(); + } + + Spawn* groupMemberAlive = nullptr; + // If dead has loot attempt to drop a chest + if (dead->HasLoot()) { + if(!(groupMemberAlive = dead->IsSpawnGroupMembersAlive(dead))) { + chest = ((Entity*)dead)->DropChest(); + } + else { + switch(dead->GetLootDropType()) { + case 0: + // default drop all chest type as a group + dead->TransferLoot(groupMemberAlive); + break; + case 1: + // this is a primary mob it drops its own loot + chest = ((Entity*)dead)->DropChest(); + break; + } + } + } + // If dead is an npc get the encounter and loop through it giving out the rewards, no rewards for pets + if (dead->IsNPC() && !dead->IsPet() && !dead->IsBot()) { + Spawn* spawn = 0; + int8 size = encounter->size(); + + for (int8 i = 0; i < encounter->size(); i++) { + spawn = GetSpawnByID(encounter->at(i), spawnListLocked); + // set a flag to let us know if the killer is in the encounter + if (!killer_in_encounter && spawn == killer) + killer_in_encounter = true; + + if (spawn && spawn->IsPlayer()) { + // Update players total kill count + ((Player*)spawn)->UpdatePlayerStatistic(STAT_PLAYER_TOTAL_NPC_KILLS, 1); + + // If this was an epic mob kill send the announcement for this player + if (dead->GetDifficulty() >= 10) + SendEpicMobDeathToGuild((Player*)spawn, dead); + + // Clear hostile spells from the players spell queue + spellProcess->RemoveSpellFromQueue((Player*)spawn, true); + + // Get the client of the player + client = ((Player*)spawn)->GetClient(); + // valid client? + if (client) { + // Check for quest kill updates + if(!dead->IsNPC() || loot_state != ENCOUNTER_STATE_BROKEN) { + client->CheckPlayerQuestsKillUpdate(dead); + } + // If the dead mob is not a player and if it had a faction with an ID greater or equal to 10 the send faction changes + if (!dead->IsPlayer() && dead->GetFactionID() > 10) + ProcessFaction(dead, client); + + // Send xp...this is currently wrong fix it + if (spawn != dead && ((Player*)spawn)->GetArrowColor(dead->GetLevel()) >= ARROW_COLOR_GREEN) { + //SendCalculatedXP((Player*)spawn, dead); + + float xp = ((Player*)spawn)->CalculateXP(dead) / encounter_player_bot_count; + if (xp > 0) { + ((Player*)spawn)->AddXP((int32)xp); + } + } + } + } + + // If a chest is being dropped add this spawn to the chest's encounter so they can loot it + if (chest && spawn && spawn->IsEntity()) + chest->Brain()->AddToEncounter((Entity*)spawn); + } + } + + // If a chest is being dropped add it to the world and set the timer to remove it. + if (chest) { + AddSpawn(chest); + AddDeadSpawn(chest, 0xFFFFFFFF); + LogWrite(LOOT__DEBUG, 0, "Loot", "Adding a chest to the world..."); + } + + // Reset client pointer + client = 0; + + // Killer was not in the encounter, give them the faction hit but no xp + if (!killer_in_encounter) { + // make sure the killer is a player and the dead spawn had a faction and wasn't a player + if (killer && killer->IsPlayer()) { + if (!dead->IsPlayer() && dead->GetFactionID() > 10) { + client = ((Player*)killer)->GetClient(); + if (client) + ProcessFaction(dead, client); + } + + // Clear hostile spells from the killers spell queue + spellProcess->RemoveSpellFromQueue((Player*)killer, true); + } + } + + // Reset client pointer + client = 0; + + + vector* group = dead->GetSpawnGroup(); + if (group && group->size() == 1) + CallSpawnScript(dead, SPAWN_SCRIPT_GROUP_DEAD, killer); + safe_delete(group); + + + // Remove the support functions for the dead spawn + RemoveSpawnSupportFunctions(dead, !isSpell); + + // Erase the expire timer if it has one + if (spawn_expire_timers.count(dead->GetID()) > 0) + spawn_expire_timers.erase(dead->GetID()); + + // If dead is an npc or object call the spawn scrip and handle instance stuff + if(dead->IsNPC() || dead->IsObject()) + { + // handle instance spawn db info + // we don't care if a NPC or a client kills the spawn, we could have events that cause NPCs to kill NPCs. + if(dead->GetZone()->GetInstanceID() > 0 && dead->GetSpawnLocationID() > 0) + { + // use respawn time to either insert/update entry (likely insert in this situation) + if(dead->IsNPC()) + database.CreateInstanceSpawnRemoved(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_NPC, dead->GetRespawnTime(),dead->GetZone()->GetInstanceID()); + else if ( dead->IsObject ( ) ) + database.CreateInstanceSpawnRemoved(dead->GetSpawnLocationID(),SPAWN_ENTRY_TYPE_OBJECT, dead->GetRespawnTime(),dead->GetZone()->GetInstanceID()); + } + + // Call the spawn scripts death() function + CallSpawnScript(dead, SPAWN_SCRIPT_DEATH, killer); + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + if (zone_script && lua_interface) + lua_interface->RunZoneScript(zone_script, "spawn_killed", this, dead, 0, 0, killer); + } + + int32 victim_id = dead->GetID(); + int32 attacker_id = 0xFFFFFFFF; + + if(killer) + attacker_id = killer->GetID(); + + if(send_packet) + { + vector::iterator client_itr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client->GetPlayer()->WasSentSpawn(victim_id) || (attacker_id != 0xFFFFFFFF && !client->GetPlayer()->WasSentSpawn(attacker_id)) ) + continue; + else if(killer && killer->GetDistance(client->GetPlayer()) > HEAR_SPAWN_DISTANCE) + continue; + + packet = configReader.getStruct("WS_HearDeath", client->GetVersion()); + if(packet) + { + if(killer) + packet->setDataByName("attacker", client->GetPlayer()->GetIDWithPlayerSpawn(killer)); + else + packet->setDataByName("attacker", 0xFFFFFFFF); + + packet->setDataByName("defender", client->GetPlayer()->GetIDWithPlayerSpawn(dead)); + packet->setDataByName("damage_type", damage_type); + packet->setDataByName("blow_type", kill_blow_type); + + client->QueuePacket(packet->serialize()); + LogWrite(COMBAT__DEBUG, 0, "Combat", "Zone Killing of '%s' by '%s' damage type %u, blow type %u", dead->GetName(), killer ? killer->GetName() : "", damage_type, kill_blow_type); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } + + + int32 pop_timer = 0xFFFFFFFF; + if(killer && killer->IsNPC()) + { + // Call the spawn scripts killed() function + CallSpawnScript(killer, SPAWN_SCRIPT_KILLED, dead); + + if(!dead->IsPlayer()) + { + LogWrite(MISC__TODO, 1, "TODO", "Whenever pets are added, check for pet kills\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + // Set the time for the corpse to linger to 5 sec + //pop_timer = 5000; + // commented out the timer so in the event the killer is not a player (pet, guard, something else i haven't thought of) + // the corpse doesn't vanish sooner then it should if it had loot for the players. AddDeadSpawn() will set timers based on if + // the corpse has loot or not if the timer value (pop_timer) is 0xFFFFFFFF + } + } + + // If the dead spawns was not a player add it to the dead spawn list + if (!dead->IsPlayer() && !dead->IsBot()) + AddDeadSpawn(dead, pop_timer); + + // if dead was a player clear hostile spells from its spell queue + if (dead->IsPlayer()) + spellProcess->RemoveSpellFromQueue((Player*)dead, true); + + if (dead->IsNPC()) + ((NPC*)dead)->Brain()->ClearHate(); + + safe_delete(encounter); +} + +void ZoneServer::SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name) { + //Scat: was set but never being used anywhere. i see references to 0xFFFFFFFF below so could be old code not used anymore + //int32 attacker_id = 0xFFFFFFFF; + //if(attacker) + // attacker_id = attacker->GetID(); + PacketStruct* packet = 0; + Client* client = 0; + if (attacker && victim && victim->IsPlayer() && victim->GetTarget() == 0) { + client = ((Player*)victim)->GetClient(); + if (client) + client->TargetSpawn(attacker); + } + + if(damage_type == DAMAGE_PACKET_DAMAGE_TYPE_FOCUS) { + damage_type = 0; + type2 = DAMAGE_PACKET_RESULT_FOCUS; + } + + vector::iterator client_itr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if (!client || (client->GetPlayer() != attacker && client->GetPlayer() != victim && ((attacker && client->GetPlayer()->WasSentSpawn(attacker->GetID()) == false) || (victim && client->GetPlayer()->WasSentSpawn(victim->GetID()) == false)))) + continue; + if (attacker && attacker->GetDistance(client->GetPlayer()) > 50) + continue; + if (victim && victim->GetDistance(client->GetPlayer()) > 50) + continue; + + int8 mod_type1 = type1; + if(client->GetVersion() <= 561) { + mod_type1 = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE; + } + + switch (mod_type1) { + case DAMAGE_PACKET_TYPE_SIPHON_SPELL: + case DAMAGE_PACKET_TYPE_SIPHON_SPELL2: + packet = configReader.getStruct("WS_HearSiphonSpellDamage", client->GetVersion()); + break; + case DAMAGE_PACKET_TYPE_MULTIPLE_DAMAGE: + if (client->GetVersion() > 561) + packet = configReader.getStruct("WS_HearMultipleDamage", client->GetVersion()); + else + packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion()); + break; + case DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG: + case DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE: + packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion()); + break; + case DAMAGE_PACKET_TYPE_SPELL_DAMAGE2: + case DAMAGE_PACKET_TYPE_SPELL_DAMAGE3: + case DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG: + case DAMAGE_PACKET_TYPE_SPELL_DAMAGE: + if (client->GetVersion() > 561) + packet = configReader.getStruct("WS_HearSpellDamage", client->GetVersion()); + else + packet = configReader.getStruct("WS_HearSimpleDamage", client->GetVersion()); + if (packet) + packet->setSubstructDataByName("header", "unknown", 5); + break; + case DAMAGE_PACKET_TYPE_RANGE_DAMAGE: + packet = configReader.getStruct("WS_HearRangeDamage", client->GetVersion()); + break; + case DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG: + case DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG2: + packet = configReader.getStruct("WS_HearRangeDamage", client->GetVersion()); + break; + default: + LogWrite(ZONE__ERROR, 0, "Zone", "Unknown Damage Packet type: %i in ZoneServer::SendDamagePacket.", type1); + MClientList.releasereadlock(__FUNCTION__, __LINE__); + return; + } + + if (packet) { + if (client->GetVersion() > 561) { + packet->setSubstructDataByName("header", "packet_type", type1); + packet->setSubstructDataByName("header", "result_type", type2); + packet->setDataByName("damage_type", damage_type); + packet->setDataByName("damage", damage); + } + else { + switch (type2) { + case DAMAGE_PACKET_RESULT_MISS: + packet->setSubstructDataByName("header", "result_type", 1); + break; + case DAMAGE_PACKET_RESULT_DODGE: + packet->setSubstructDataByName("header", "result_type", 2); + break; + case DAMAGE_PACKET_RESULT_PARRY: + packet->setSubstructDataByName("header", "result_type", 3); + break; + case DAMAGE_PACKET_RESULT_RIPOSTE: + packet->setSubstructDataByName("header", "result_type", 4); + break; + case DAMAGE_PACKET_RESULT_BLOCK: + packet->setSubstructDataByName("header", "result_type", 5); + break; + case DAMAGE_PACKET_RESULT_INVULNERABLE: + packet->setSubstructDataByName("header", "result_type", 7); + break; + case DAMAGE_PACKET_RESULT_RESIST: + packet->setSubstructDataByName("header", "result_type", 9); + break; + case DAMAGE_PACKET_RESULT_REFLECT: + packet->setSubstructDataByName("header", "result_type", 10); + break; + case DAMAGE_PACKET_RESULT_IMMUNE: + packet->setSubstructDataByName("header", "result_type", 11); + break; + } + packet->setArrayLengthByName("num_dmg", 1); + packet->setSubstructDataByName("header", "defender_proxy", client->GetPlayer()->GetIDWithPlayerSpawn(victim)); + packet->setArrayDataByName("damage_type", damage_type); + packet->setArrayDataByName("damage", damage); + } + + if (!attacker) + packet->setSubstructDataByName("header", "attacker", 0xFFFFFFFF); + else + packet->setSubstructDataByName("header", "attacker", client->GetPlayer()->GetIDWithPlayerSpawn(attacker)); + packet->setSubstructDataByName("header", "defender", client->GetPlayer()->GetIDWithPlayerSpawn(victim)); + if (spell_name) { + packet->setDataByName("spell", 1); + packet->setDataByName("spell_name", spell_name); + } + EQ2Packet* app = packet->serialize(); + //DumpPacket(app); + client->QueuePacket(app); + safe_delete(packet); + packet = 0; + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendHealPacket(Spawn* caster, Spawn* target, int16 heal_type, int32 heal_amt, const char* spell_name){ + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || (client->GetPlayer() != caster && ((caster && client->GetPlayer()->WasSentSpawn(caster->GetID()) == false) || (target && client->GetPlayer()->WasSentSpawn(target->GetID()) == false)))) + continue; + if(caster && caster->GetDistance(client->GetPlayer()) > 50) + continue; + if(target && target->GetDistance(client->GetPlayer()) > 50) + continue; + + + PacketStruct* packet = configReader.getStruct("WS_HearHeal", client->GetVersion()); + if (packet) { + packet->setDataByName("caster", client->GetPlayer()->GetIDWithPlayerSpawn(caster)); + packet->setDataByName("target", client->GetPlayer()->GetIDWithPlayerSpawn(target)); + packet->setDataByName("heal_amt", heal_amt); + packet->setDataByName("spellname", spell_name); + packet->setDataByName("type", heal_type); + packet->setDataByName("unknown2", 1); + EQ2Packet* app = packet->serialize(); + client->QueuePacket(app); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendThreatPacket(Spawn* caster, Spawn* target, int32 threat_amt, const char* spell_name) { + Client* client = 0; + + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || (client->GetPlayer() != caster && ((caster && client->GetPlayer()->WasSentSpawn(caster->GetID()) == false) || (target && client->GetPlayer()->WasSentSpawn(target->GetID()) == false)))) + continue; + if(caster && caster->GetDistance(client->GetPlayer()) > 50) + continue; + if(target && target->GetDistance(client->GetPlayer()) > 50) + continue; + + if(client->GetVersion() <= 561) { + int8 channel = 46; + + if(client->GetPlayer() == caster || client->GetPlayer() == target) + channel = 42; + + client->Message(channel, "%s increases %s hate with %s for %u threat.", spell_name, (client->GetPlayer() == caster) ? "YOUR" : caster->GetName(), (client->GetPlayer() == target) ? "YOU" : target->GetName(), threat_amt); + } + else { + PacketStruct* packet = configReader.getStruct("WS_HearThreatCmd", client->GetVersion()); + if (packet) { + packet->setDataByName("spell_name", spell_name); + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(caster)); + packet->setDataByName("target", client->GetPlayer()->GetIDWithPlayerSpawn(target)); + packet->setDataByName("threat_amount", threat_amt); + + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + + +void ZoneServer::SendYellPacket(Spawn* yeller, float max_distance) { + Client* client = 0; + + + string yellMsg = std::string(yeller->GetName()) + " yelled for help!"; + vector::iterator client_itr; + PacketStruct* packet = nullptr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->GetPlayer() || client->GetPlayer()->WasSentSpawn(yeller->GetID()) == false) + continue; + if(client->GetPlayer()->GetDistance(yeller) > max_distance) + continue; + + if(packet && packet->GetVersion() == client->GetVersion()) { + client->QueuePacket(packet->serialize()); + } + else { + safe_delete(packet); + packet = configReader.getStruct("WS_EncounterBroken", client->GetVersion()); + if (packet) { + packet->setDataByName("message", yellMsg.c_str()); + /* none of the other data seems necessary, keeping for reference for future disassembly + packet2->setDataByName("unknown2", 0x40); + packet2->setDataByName("unknown3", 0x40); + packet2->setDataByName("unknown4", 0xFF); + packet2->setDataByName("unknown5", 0xFF); + packet2->setDataByName("unknown6", 0xFF);*/ + client->QueuePacket(packet->serialize()); + } + } + } + safe_delete(packet); + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSpellFailedPacket(Client* client, int16 error){ + if(!client) + return; + + PacketStruct* packet = configReader.getStruct("WS_DisplaySpellFailed", client->GetVersion()); + if(packet){ + /* Temp solution, need to modify the error code before this function and while we still have access to the spell/combat art */ + error = master_spell_list.GetSpellErrorValue(client->GetVersion(), error); + + if(client->GetVersion() > 373 && client->GetVersion() <= 561 && error) { + error += 1; + } + + packet->setDataByName("error_code", error); + //packet->PrintPacket(); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } +} + +void ZoneServer::SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool fizzle){ + if(!interrupted || !spell) + return; + + EQ2Packet* outapp = 0; + PacketStruct* packet = 0; + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || !client->GetPlayer()->WasSentSpawn(interrupted->GetID())) + continue; + packet = configReader.getStruct(fizzle ? "WS_SpellFizzle" : "WS_Interrupt", client->GetVersion()); + if(packet){ + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(interrupted)); + packet->setArrayLengthByName("num_targets", spell->targets.size()); + for (int32 i = 0; i < spell->targets.size(); i++) + packet->setArrayDataByName("target_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetZone()->GetSpawnByID(spell->targets[i])), i); + packet->setDataByName("spell_id", spell->spell->GetSpellID()); + outapp = packet->serialize(); + client->QueuePacket(outapp); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); +} + +void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override, int16 casttime_override){ + EQ2Packet* outapp = 0; + PacketStruct* packet = 0; + Client* client = 0; + if(!caster || !spell || !spell->spell || spell->interrupted) + return; + + if(spell->is_damage_spell && (!spell->has_damaged || spell->resisted)) { + // we did not successfully hit target, so we should not send the visual + return; + } + + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client) + continue; + + packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion()); + if(packet){ + int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(caster); + + if(!caster_id) { + safe_delete(packet); + continue; + } + + packet->setDataByName("spawn_id", caster_id); + packet->setArrayLengthByName("num_targets", spell->targets.size()); + for (int32 i = 0; i < spell->targets.size(); i++) { + int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(spell->caster->GetZone()->GetSpawnByID(spell->targets[i])); + if(target_id) { + packet->setArrayDataByName("target", target_id, i); + } + else { + packet->setArrayDataByName("target", 0xFFFFFFFF, i); + } + } + + int32 visual_to_use = spell_visual_override > 0 ? spell_visual_override : spell->spell->GetSpellData()->spell_visual; + int32 visual = client->GetSpellVisualOverride(visual_to_use); + + packet->setDataByName("spell_visual", visual); //result + if(casttime_override != 0xFFFF) { + packet->setDataByName("cast_time", casttime_override*.01f); //delay + } + else { + packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01f); //delay + } + packet->setDataByName("spell_id", spell->spell->GetSpellID()); + packet->setDataByName("spell_level", 1); + packet->setDataByName("spell_tier", spell->spell->GetSpellData()->tier); + outapp = packet->serialize(); + client->QueuePacket(outapp); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); +} + +void ZoneServer::SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* caster) { + if (target) { + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + Client* client = *client_itr; + if (!client) + continue; + PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion()); + if (packet) { + + int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(target); + if(!target_id) { // client is not aware of spawn + safe_delete(packet); + continue; + } + + if (!caster) { + packet->setDataByName("spawn_id", 0xFFFFFFFF); + } + else { + int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(caster); + + if(!caster_id) { // client is not aware of spawn + safe_delete(packet); + continue; + } + packet->setDataByName("spawn_id", caster_id); + } + packet->setArrayLengthByName("num_targets", 1); + packet->setArrayDataByName("target", target_id); + + int32 visual = client->GetSpellVisualOverride(spell_visual); + + packet->setDataByName("spell_visual", visual); + packet->setDataByName("cast_time", 0); + packet->setDataByName("spell_id", 0); + packet->setDataByName("spell_level", 0); + packet->setDataByName("spell_tier", 1); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } +} + +void ZoneServer::SendCastEntityCommandPacket(EntityCommand* entity_command, int32 spawn_id, int32 target_id) { + if (entity_command) { + Spawn* spawn = GetSpawnByID(spawn_id); + Spawn* target = GetSpawnByID(target_id); + if (!spawn || !target) + return; + + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if (!client || !client->GetPlayer()->WasSentSpawn(spawn_id) || !client->GetPlayer()->WasSentSpawn(target_id)) + continue; + PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion()); + if (packet) { + int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(spawn); + int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(target); + + if(!caster_id || !target_id) + continue; + + packet->setDataByName("spawn_id", caster_id); + packet->setArrayLengthByName("num_targets", 1); + packet->setArrayDataByName("target", target_id); + packet->setDataByName("num_targets", 1); + packet->setDataByName("spell_visual", entity_command->spell_visual); //result + packet->setDataByName("cast_time", entity_command->cast_time * 0.01); //delay + packet->setDataByName("spell_id", 1); + packet->setDataByName("spell_level", 1); + packet->setDataByName("spell_tier", 1); + EQ2Packet* outapp = packet->serialize(); + client->QueuePacket(outapp); + safe_delete(packet); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } +} + +void ZoneServer::StartZoneInitialSpawnThread(Client* client){ + if(zoneShuttingDown) + return; + +#ifdef WIN32 + _beginthread(SendInitialSpawns, 0, client); +#else + pthread_t thread; + pthread_create(&thread, NULL, SendInitialSpawns, client); + pthread_detach(thread); +#endif +} + +void ZoneServer::SendZoneSpawns(Client* client){ + int8 count = 0; + while (LoadingData && count <= 6000) { //sleep for max of 60 seconds (60000ms) while the maps are loading + count++; + Sleep(10); + } + count = 0; + int16 size = 0; + //give the spawn thread a tad bit of time to add the pending_spawns to spawn_list (up to 10 seconds) + while (count < 1000) { + MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__); + size = pending_spawn_list_add.size(); + MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__); + if (size == 0) + break; + Sleep(10); + count++; + } + initial_spawn_threads_active++; + + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + Spawn* spawn = itr->second; + if (spawn) { + if(spawn == client->GetPlayer() && (client->IsReloadingZone() || client->GetPlayer()->IsReturningFromLD())) + { + if(!client->GetPlayer()->SetSpawnMap(spawn)) + continue; + } + + CheckSpawnRange(client, spawn, true); + } + } + + CheckSendSpawnToClient(client, true); + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + client->SetConnected(true); + ClientPacketFunctions::SendFinishedEntitiesList(client); + initial_spawn_threads_active--; +} + +vector ZoneServer::GetPlayers(){ + vector ret; + Client* client = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + ret.push_back(client->GetPlayer()); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +int16 ZoneServer::SetSpawnTargetable(Spawn* spawn, float distance){ + Spawn* test_spawn = 0; + int16 ret_val = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + test_spawn = itr->second; + if(test_spawn){ + if(test_spawn->GetDistance(spawn) <= distance){ + test_spawn->SetTargetable(1); + ret_val++; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret_val; +} + +int16 ZoneServer::SetSpawnTargetable(int32 spawn_id){ + Spawn* spawn = 0; + int16 ret_val = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if(spawn){ + if(spawn->GetDatabaseID() == spawn_id){ + spawn->SetTargetable(1); + ret_val++; + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret_val; +} + +ZoneInfoSlideStruct* ZoneServer::GenerateSlideStruct(float unknown1a, float unknown1b, int32 unknown2a, int32 unknown2b, int32 unknown3, int32 unknown4, const char* slide, const char* voiceover, int32 key1, int32 key2) { + ZoneInfoSlideStructInfo* info = new ZoneInfoSlideStructInfo(); + memset(info, 0, sizeof(ZoneInfoSlideStructInfo)); + info->unknown1[0] = unknown1a; + info->unknown1[1] = unknown1b; + info->unknown2[0] = unknown2a; + info->unknown2[1] = unknown2b; + info->unknown3 = unknown3; + info->unknown4 = unknown4; + int8 length = strlen(slide); + if (length >= 128) + length = 127; + strncpy(info->slide, slide, length); + length = strlen(voiceover); + if (length >= 128) + length = 127; + strncpy(info->voiceover, voiceover, length); + info->key1 = key1; + info->key2 = key2; + ZoneInfoSlideStruct* ret = new ZoneInfoSlideStruct(); + ret->info = info; + return ret; +} + +void ZoneServer::AddZoneInfoSlideStructTransitionInfo(ZoneInfoSlideStruct* info, int32 x, int32 y, float zoom, float transition_time) { + ZoneInfoSlideStructTransitionInfo* transition_info = new ZoneInfoSlideStructTransitionInfo(); + transition_info->transition_x = x; + transition_info->transition_y = y; + transition_info->transition_zoom = zoom; + transition_info->transition_time = transition_time; + info->slide_transition_info.push_back(transition_info); +} + +vector* ZoneServer::GenerateTutorialSlides() { + vector* slides = new vector(); + ZoneInfoSlideStruct* slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt01_final001.dds", "voiceover/english/antonia_intro/antonia_intro_001_64.mp3", 2519553957, 1010319376); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1.5, 16); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt02_final001.dds", "voiceover/english/antonia_intro/antonia_intro_002_64.mp3", 567178266, 3055063399); + AddZoneInfoSlideStructTransitionInfo(slide, 600, 365, 1.60000002384186, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 800, 370, 1.39999997615814, 9); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 420, 1.20000004768372, 8); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt03_final001.dds", "voiceover/english/antonia_intro/antonia_intro_003_64.mp3", 3171561318, 593374281); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 420, 1.20000004768372, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 750, 320, 1.60000002384186, 10); + AddZoneInfoSlideStructTransitionInfo(slide, 575, 265, 2.29999995231628, 10); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt04_final001.dds", "voiceover/english/antonia_intro/antonia_intro_004_64.mp3", 1959944485, 4285605574); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 420, 2.5, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 420, 1.70000004768372, 8); + AddZoneInfoSlideStructTransitionInfo(slide, 675, 390, 2.20000004768372, 11); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt05_final001.dds", "voiceover/english/antonia_intro/antonia_intro_005_64.mp3", 609693392, 260295215); + AddZoneInfoSlideStructTransitionInfo(slide, 750, 500, 2.79999995231628, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 720, 300, 2.5, 9); + AddZoneInfoSlideStructTransitionInfo(slide, 975, 270, 2.20000004768372, 9); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt06_final001.dds", "voiceover/english/antonia_intro/antonia_intro_006_64.mp3", 3056613203, 775201556); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1.89999997615814, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 475, 1, 24); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt07_final001.dds", "voiceover/english/antonia_intro/antonia_intro_007_64.mp3", 3113327662, 1299367895); + AddZoneInfoSlideStructTransitionInfo(slide, 1400, 420, 2.40000009536743, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 1200, 375, 1.70000004768372, 7); + AddZoneInfoSlideStructTransitionInfo(slide, 800, 225, 2.29999995231628, 7); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt08_final001.dds", "voiceover/english/antonia_intro/antonia_intro_008_64.mp3", 2558791235, 2674773065); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1.5, 27); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt09_final001.dds", "voiceover/english/antonia_intro/antonia_intro_009_64.mp3", 4029296401, 1369011033); + AddZoneInfoSlideStructTransitionInfo(slide, 715, 305, 2.40000009536743, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 730, 325, 1.79999995231628, 6); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 395, 1.5, 5); + AddZoneInfoSlideStructTransitionInfo(slide, 1360, 330, 1.79999995231628, 9); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt10_final001.dds", "voiceover/english/antonia_intro/antonia_intro_010_64.mp3", 3055524517, 3787058332); + AddZoneInfoSlideStructTransitionInfo(slide, 670, 675, 2.20000004768372, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 710, 390, 1.79999995231628, 7); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 415, 1.60000002384186, 5.5); + AddZoneInfoSlideStructTransitionInfo(slide, 1250, 675, 1.79999995231628, 8); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt11_final001.dds", "voiceover/english/antonia_intro/antonia_intro_011_64.mp3", 3525586740, 812068950); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 2, 19); + slides->push_back(slide); + + slide = GenerateSlideStruct(0.5, 0.5, 15, 15, 1842, 1012, "images/slideshows/boat_06p_tutorial02/lore_chapt12_final001.dds", "voiceover/english/antonia_intro/antonia_intro_012_64.mp3", 3493874350, 2037661816); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 2, 0); + AddZoneInfoSlideStructTransitionInfo(slide, 920, 495, 1, 43); + slides->push_back(slide); + + return slides; +} + +EQ2Packet* ZoneServer::GetZoneInfoPacket(Client* client){ + PacketStruct* packet = configReader.getStruct("WS_ZoneInfo", client->GetVersion()); + packet->setSmallStringByName("server1",net.GetWorldName()); + packet->setSmallStringByName("server2",net.GetWorldName()); + packet->setDataByName("unknown1", 1, 1);//1, 1 + int32 expansions = EXPANSION_UNKNOWN + EXPANSION_DOF + EXPANSION_KOS + EXPANSION_EOF + EXPANSION_ROK + EXPANSION_TSO + EXPANSION_DOV; + //packet->setDataByName("expansions_enabled", 82313211);//expansions 63181 + //packet->setDataByName("expansions_enabled", 552075103);//expansions 63182 + packet->setDataByName("expansions_enabled", 4294967295);//expansions 1096 //612499455 works + if (client->GetVersion() >= 1193) { + packet->setDataByName("unknown3", 4294967295, 0); // DOV and down + packet->setDataByName("unknown3", 4294967295, 1); //COE and up + packet->setDataByName("unknown3", 4294967295, 2); + } + else + packet->setDataByName("unknown3", 4294967295, 0); // DOV and down + + packet->setSmallStringByName("auction_website", "eq2emulator.net"); + packet->setDataByName("auction_port", 80); + packet->setSmallStringByName("upload_page", "test_upload.m"); + packet->setSmallStringByName("upload_key", "dsya987yda9"); + packet->setSmallStringByName("zone", GetZoneFile()); + //packet->setSmallStringByName("zone2", GetZoneName()); + + //if ( strlen(GetZoneSkyFile()) > 0 ) + // packet->setSmallStringByName("zone_unknown2", GetZoneSkyFile()); // used for the sky map + + packet->setSmallStringByName("zone_desc", GetZoneDescription()); + packet->setSmallStringByName("char_name", client->GetPlayer()->GetName()); + + packet->setDataByName("x", client->GetPlayer()->GetX()); + packet->setDataByName("y", client->GetPlayer()->GetY()); + packet->setDataByName("z", client->GetPlayer()->GetZ()); + + if ((GetZoneFile() && strcmp("boat_06p_tutorial02", GetZoneFile()) == 0) && client->GetPlayer()->GetX() == this->GetSafeX() && client->GetPlayer()->GetY() == this->GetSafeY() && client->GetPlayer()->GetZ() == this->GetSafeZ()) { //basically the only time the player will see this is if their zone in coords are the exact same as the safe coords (they haven't moved) + vector* slides = GenerateTutorialSlides(); + if (slides) { + packet->setArrayLengthByName("num_slides", slides->size()); + ZoneInfoSlideStruct* slide = 0; + for (int8 i = 0; i < slides->size(); i++) { + slide = slides->at(i); + packet->setArrayDataByName("unknown1", slide->info->unknown1[0], i, 0); + packet->setArrayDataByName("unknown1", slide->info->unknown1[1], i, 1); + packet->setArrayDataByName("unknown2", slide->info->unknown2[0], i, 0); + packet->setArrayDataByName("unknown2", slide->info->unknown2[1], i, 1); + packet->setArrayDataByName("unknown3", slide->info->unknown3, i); + packet->setArrayDataByName("unknown4", slide->info->unknown4, i); + packet->setArrayDataByName("slide", slide->info->slide, i); + packet->setArrayDataByName("voiceover", slide->info->voiceover, i); + packet->setArrayDataByName("key1", slide->info->key1, i); + packet->setArrayDataByName("key2", slide->info->key2, i); + packet->setSubArrayLengthByName("num_transitions", slide->slide_transition_info.size(), i); + for (int8 x = 0; x < slide->slide_transition_info.size(); x++) { + packet->setSubArrayDataByName("transition_x", slide->slide_transition_info[x]->transition_x, i, x); + packet->setSubArrayDataByName("transition_y", slide->slide_transition_info[x]->transition_y, i, x); + packet->setSubArrayDataByName("transition_zoom", slide->slide_transition_info[x]->transition_zoom, i, x); + packet->setSubArrayDataByName("transition_time", slide->slide_transition_info[x]->transition_time, i, x); + safe_delete(slide->slide_transition_info[x]); + } + safe_delete(slide->info); + safe_delete(slide); + } + } + safe_delete(slides); + } + + if(rule_manager.GetGlobalRule(R_Zone, UseMapUnderworldCoords)->GetBool() && client->GetPlayer()->GetMap()) { + packet->setDataByName("underworld", client->GetPlayer()->GetMap()->GetMinY() + rule_manager.GetGlobalRule(R_Zone, MapUnderworldCoordOffset)->GetFloat()); + } + else { + packet->setDataByName("underworld", underworld); + } + + // unknown3 can prevent screen shots from being taken if + //packet->setDataByName("unknown3", 2094661567, 1); // Screenshots allowed with this value + //packet->setDataByName("unknown3", 3815767999, 1); // Screenshots disabled with this value + //packet->setDataByName("unknown3", 1, 2); + + /*if (client->GetVersion() >= 63587) { + packet->setArrayLengthByName("num_exp_feature_bytes", 9); + packet->setArrayDataByName("exp_feature_bytes", 95, 0);//kos and dof + packet->setArrayDataByName("exp_feature_bytes", 255, 1);//eof rok tso sf dov coe tov + packet->setArrayDataByName("exp_feature_bytes", 247, 2);//aom tot ka exp14 + packet->setArrayDataByName("exp_feature_bytes", 32, 3);//rum cellar + packet->setArrayDataByName("exp_feature_bytes", 140, 4); + packet->setArrayDataByName("exp_feature_bytes", 62, 5); + packet->setArrayDataByName("exp_feature_bytes", 0, 6); + packet->setArrayDataByName("exp_feature_bytes", 45, 7); + packet->setArrayDataByName("exp_feature_bytes", 128, 8); + + packet->setArrayLengthByName("num_unknown3b_bytes", 9); + packet->setArrayDataByName("unknown3b_bytes", 95, 0); + packet->setArrayDataByName("unknown3b_bytes", 255, 1); + packet->setArrayDataByName("unknown3b_bytes", 247, 2); + packet->setArrayDataByName("unknown3b_bytes", 237, 3); + packet->setArrayDataByName("unknown3b_bytes", 143, 4); + packet->setArrayDataByName("unknown3b_bytes", 255, 5); + packet->setArrayDataByName("unknown3b_bytes", 255, 6); + packet->setArrayDataByName("unknown3b_bytes", 255, 7); + packet->setArrayDataByName("unknown3b_bytes", 128, 8); + } + else if (client->GetVersion() >= 63214) { + packet->setArrayLengthByName("num_exp_feature_bytes", 9); + packet->setArrayDataByName("exp_feature_bytes", 95, 0);//kos and dof + packet->setArrayDataByName("exp_feature_bytes", 255, 1);//eof rok tso sf dov coe tov + packet->setArrayDataByName("exp_feature_bytes", 247, 2);//aom tot ka exp14 + packet->setArrayDataByName("exp_feature_bytes", 32, 3);//rum cellar + packet->setArrayDataByName("exp_feature_bytes", 140, 4); + packet->setArrayDataByName("exp_feature_bytes", 62, 5); + packet->setArrayDataByName("exp_feature_bytes", 0, 6); + packet->setArrayDataByName("exp_feature_bytes", 45, 7); + packet->setArrayDataByName("exp_feature_bytes", 128, 8); + + packet->setArrayLengthByName("num_unknown3b_bytes", 9); + packet->setArrayDataByName("unknown3b_bytes", 95, 0); + packet->setArrayDataByName("unknown3b_bytes", 255, 1); + packet->setArrayDataByName("unknown3b_bytes", 247, 2); + packet->setArrayDataByName("unknown3b_bytes", 237, 3); + packet->setArrayDataByName("unknown3b_bytes", 143, 4); + packet->setArrayDataByName("unknown3b_bytes", 255, 5); + packet->setArrayDataByName("unknown3b_bytes", 255, 6); + packet->setArrayDataByName("unknown3b_bytes", 255, 7); + packet->setArrayDataByName("unknown3b_bytes", 128, 8); + }*/ + if (client->GetVersion() >= 64644) { + packet->setDataByName("unknown3a", 12598924); + packet->setDataByName("unknown3b", 3992452959); + packet->setDataByName("unknown3c", 4294967183); + packet->setDataByName("unknown2a", 9); + packet->setDataByName("unknown2b", 9); + } + else if (client->GetVersion() >= 63181) { + packet->setDataByName("unknown3a", 750796556);//63182 73821356 + packet->setDataByName("unknown3b", 3991404383);// 63182 3991404383 + packet->setDataByName("unknown3c", 4278189967);// 63182 4278189967 + packet->setDataByName("unknown2a", 8);// 63182 + packet->setDataByName("unknown2b", 8);// 63182 + + } + else{ + //packet->setDataByName("unknown3", 872447025,0);//63181 + //packet->setDataByName("unknown3", 3085434875,1);// 63181 + //packet->setDataByName("unknown3", 2147483633,2);// 63181 + } + + packet->setDataByName("year", world.GetWorldTimeStruct()->year); + packet->setDataByName("month", world.GetWorldTimeStruct()->month); + packet->setDataByName("day", world.GetWorldTimeStruct()->day); + packet->setDataByName("hour", world.GetWorldTimeStruct()->hour); + packet->setDataByName("minute", world.GetWorldTimeStruct()->minute); + packet->setDataByName("unknown", 0); + packet->setDataByName("unknown7", 1); + packet->setDataByName("unknown7", 1, 1); + + packet->setDataByName("unknown9", 13); + //packet->setDataByName("unknown10", 25188959);4294967295 + //packet->setDataByName("unknown10", 25190239); + packet->setDataByName("unknown10", 25191524);//25191524 + packet->setDataByName("unknown10b", 1); + packet->setDataByName("permission_level",3);// added on 63182 for now till we figur it out 0=none,1=visitor,2=friend,3=trustee,4=owner + packet->setDataByName("num_adv", 9); + + packet->setArrayDataByName("adv_name", "adv02_dun_drowned_caverns", 0); + packet->setArrayDataByName("adv_id", 6, 0); + packet->setArrayDataByName("adv_name", "adv02_dun_sundered_splitpaw_hub", 1); + packet->setArrayDataByName("adv_id", 5, 1); + packet->setArrayDataByName("adv_name", "exp03_rgn_butcherblock", 2); + packet->setArrayDataByName("adv_id", 8, 2); + packet->setArrayDataByName("adv_name", "exp03_rgn_greater_faydark", 3); + packet->setArrayDataByName("adv_id", 7, 3); + packet->setArrayDataByName("adv_name", "mod01_dun_crypt_of_thaen", 4); + packet->setArrayDataByName("adv_id", 3, 4); + packet->setArrayDataByName("adv_name", "mod01_dun_tombs_of_night", 5); + packet->setArrayDataByName("adv_id", 4, 5); + packet->setArrayDataByName("adv_name", "nektulos_mini01", 6); + packet->setArrayDataByName("adv_id", 0, 6); + packet->setArrayDataByName("adv_name", "nektulos_mini02", 7); + packet->setArrayDataByName("adv_id", 1, 7); + packet->setArrayDataByName("adv_name", "nektulos_mini03", 8); + packet->setArrayDataByName("adv_id", 2, 8); + + + + + LogWrite(MISC__TODO, 0, "TODO", "Put cl_ client commands back in variables (not Rules) so they can be dynamically maintained"); + vector* variables = world.GetClientVariables(); + packet->setArrayLengthByName("num_client_setup", variables->size()); + for(int i=variables->size()-1;i>=0;i--) + packet->setArrayDataByName("client_cmds", variables->at(i)->GetNameValuePair().c_str(), i); + + // For AoM clients so item link work + if (client->GetVersion() >= 60114) + packet->setArrayDataByName("client_cmds", "chat_linkbrackets_item 1", variables->size()); + + safe_delete(variables); + //packet->setDataByName("unknown8", ); story? + // AA Tabs for 1193+ clients + if (client->GetVersion() >= 1193) { + packet->setArrayLengthByName("tab_count", 48); + int8 i = 0; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":es24a58bd8fcaac8c2:All", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c727bd47a6:Racial Innate", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c75a96e23c:Tradeskill Advancement", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c744f1fd99:Focus Effects", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c71edd2a66:Heroic", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c76ee6239f:Shadows", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7e678b977:Prestige", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c77ee422d7:Animalist", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7f165af77:Bard", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7421b9375:Brawler", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7a03ae7d1:Cleric", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7c9605e9f:Crusader", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7f9424168:Druid", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c79cb9556c:Enchanter", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c70c8b6aa4:Predator", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c73a43b6dd:Rogue", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c759fe7d15:Sorcerer", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7ad610aca:Summoner", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c71e056728:Warrior", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7ba864c0b:Assassin", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7b8116aad:Beastlord", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7f53feb7b:Berserker", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c73d8a70e2:Brigand", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c770c766d6:Bruiser", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c79226984b:Coercer", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c70c58bb30:Conjurer", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c73dfe68d0:Defiler", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c792919a6b:Dirge", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7062e5f55:Fury", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c762c1fdfc:Guardian", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c78addfbf4:Illusionist", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7ece054a7:Inquisitor", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7d550d2e7:Monk", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c743cfeaa2:Mystic", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7f63c9c8c:Necromancer", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c70c5de0ae:Paladin", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c79bc97b3a:Ranger", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c78fbd2256:Shadowknight", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7781cc625:Shaman", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c77eecdcdb:Swashbuckler", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7648d181e:Templar", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c78df47d77:Troubador", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7c78ce0b8:Warden", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c76290dcfa:Warlock", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7d1d52cf5:Wizard", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c71c8f6f4d:Shaper", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c72f6e354d:Channeler", i); + + i++; + packet->setArrayDataByName("tab_index", i, i); + packet->setArrayDataByName("tab_name", ":410385c7df8bd37d:Dragon", i); + } + packet->setDataByName("unknown_mj", 1);//int8 + packet->setDataByName("unknown_mj1", 335544320);//int32 + packet->setDataByName("unknown_mj2", 4);//int32 + packet->setDataByName("unknown_mj3", 3962504088);//int32 + packet->setDataByName("unknown_mj4", 3985947216);//int32 + packet->setDataByName("unknown_mj5", 1);//int32 + packet->setDataByName("unknown_mj6", 386);//int32 + packet->setDataByName("unknown_mj7", 4294967295);//int32 + packet->setDataByName("unknown_mj8", 2716312211);//int32 + packet->setDataByName("unknown_mj9", 1774338333);//int32 + packet->setDataByName("unknown_mj10", 1);//int32 + packet->setDataByName("unknown_mj11", 391);//int32 + packet->setDataByName("unknown_mj12", 4294967295);//int32 + packet->setDataByName("unknown_mj13", 3168965163);//int32 + packet->setDataByName("unknown_mj14", 4117025286);//int32 + packet->setDataByName("unknown_mj15", 1);//int32 + packet->setDataByName("unknown_mj16", 394);//int32 + packet->setDataByName("unknown_mj17", 4294967295);//int32 + packet->setDataByName("unknown_mj18", 1790669110);//int32 + packet->setDataByName("unknown_mj19", 107158108);//int32 + packet->setDataByName("unknown_mj20", 1);//int32 + packet->setDataByName("unknown_mj21", 393);//int32 + packet->setDataByName("unknown_mj22", 4294967295);//int32 + + EQ2Packet* outapp = packet->serialize(); + //packet->PrintPacket(); + //DumpPacket(outapp); + safe_delete(packet); + return outapp; +} + +void ZoneServer::SendUpdateDefaultCommand(Spawn* spawn, const char* command, float distance, Spawn* toPlayer){ + if (spawn == nullptr || command == nullptr) + return; + + if (toPlayer) + { + if (!toPlayer->IsPlayer()) + return; + + Client* client = ((Player*)toPlayer)->GetClient(); + if (client) + { + client->SendDefaultCommand(spawn, command, distance); + } + // we don't override the primary command cause that would change ALL clients + return; + } + + QueueDefaultCommand(spawn->GetID(), std::string(command), distance); + + if (strlen(command)>0) + spawn->SetPrimaryCommand(command, command, distance); +} + +void ZoneServer::CheckPlayerProximity(Spawn* spawn, Client* client){ + if (player_proximities.size() < 1) + return; + + if(player_proximities.count(spawn->GetID()) > 0){ + PlayerProximity* prox = player_proximities.Get(spawn->GetID()); + if(prox->clients_in_proximity.count(client) == 0 && spawn_range_map.count(client) > 0 && spawn_range_map.Get(client)->count(spawn->GetID()) > 0 && spawn_range_map.Get(client)->Get(spawn->GetID()) < prox->distance){ + prox->clients_in_proximity[client] = true; + CallSpawnScript(spawn, SPAWN_SCRIPT_CUSTOM, client->GetPlayer(), prox->in_range_lua_function.c_str()); + } + else if(prox->clients_in_proximity.count(client) > 0 && spawn_range_map.count(client) > 0 && spawn_range_map.Get(client)->count(spawn->GetID()) > 0 && spawn_range_map.Get(client)->Get(spawn->GetID()) > prox->distance){ + if(prox->leaving_range_lua_function.length() > 0) + CallSpawnScript(spawn, SPAWN_SCRIPT_CUSTOM, client->GetPlayer(), prox->leaving_range_lua_function.c_str()); + prox->clients_in_proximity.erase(client); + } + } +} + +void ZoneServer::AddPlayerProximity(Spawn* spawn, float distance, string in_range_function, string leaving_range_function){ + RemovePlayerProximity(spawn); + PlayerProximity* prox = new PlayerProximity; + prox->distance = distance; + prox->in_range_lua_function = in_range_function; + prox->leaving_range_lua_function = leaving_range_function; + player_proximities.Put(spawn->GetID(), prox); +} + +void ZoneServer::RemovePlayerProximity(Client* client){ + PlayerProximity* prox = 0; + MutexMap::iterator itr = player_proximities.begin(); + while(itr.Next()){ + prox = itr->second; + if(prox->clients_in_proximity.count(client) > 0) + prox->clients_in_proximity.erase(client); + } +} + +void ZoneServer::RemovePlayerProximity(Spawn* spawn, bool all){ + if(all){ + MutexMap::iterator itr = player_proximities.begin(); + while(itr.Next()){ + player_proximities.erase(itr->first, false, true, 10000); + } + } + else if(player_proximities.count(spawn->GetID()) > 0){ + player_proximities.erase(spawn->GetID(), false, true, 10000); + } +} + +void ZoneServer::AddLocationProximity(float x, float y, float z, float max_variation, string in_range_function, string leaving_range_function) { + LocationProximity* prox = new LocationProximity; + prox->x = x; + prox->y = y; + prox->z = z; + prox->max_variation = max_variation; + prox->in_range_lua_function = in_range_function; + prox->leaving_range_lua_function = leaving_range_function; + location_proximities.Add(prox); +} + +void ZoneServer::CheckLocationProximity() { + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + if (!zone_script) + return; + + if (location_proximities.size() > 0 && connected_clients.size() > 0) { + Client* client = 0; + MutexList::iterator iterator = connected_clients.begin(); + while(iterator.Next()){ + client = iterator->value; + if (client->IsConnected() && client->IsReadyForUpdates() && !client->IsZoning()) { + try { + MutexList::iterator itr = location_proximities.begin(); + LocationProximity* prox = 0; + while(itr.Next()){ + prox = itr->value; + bool in_range = false; + float char_x = client->GetPlayer()->GetX(); + float char_y = client->GetPlayer()->GetY(); + float char_z = client->GetPlayer()->GetZ(); + float x = prox->x; + float y = prox->y; + float z = prox->z; + float max_variation = prox->max_variation; + float total_diff = 0; + float diff = x - char_x; //Check X + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + diff = z - char_z; //Check Z (we check Z first because it is far more likely to be a much greater variation than y) + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + if(total_diff <= max_variation) { //Check Total + diff = y - char_y; //Check Y + if(diff < 0) + diff *= -1; + if(diff <= max_variation) { + total_diff += diff; + if(total_diff <= max_variation) { + in_range = true; + if(lua_interface && prox->in_range_lua_function.length() > 0 && prox->clients_in_proximity.count(client) == 0) { //Check Total + prox->clients_in_proximity[client] = true; + lua_interface->RunZoneScript(zone_script, prox->in_range_lua_function.c_str(), this, client->GetPlayer()); + } + } + } + } + } + } + if (!in_range) { + if(lua_interface && prox->leaving_range_lua_function.length() > 0 && prox->clients_in_proximity.count(client) > 0) { + lua_interface->RunZoneScript(zone_script, prox->leaving_range_lua_function.c_str(), this, client->GetPlayer()); + prox->clients_in_proximity.erase(client); + } + } + } + } + catch (...) { + LogWrite(ZONE__ERROR, 0, "Zone", "Except caught in ZoneServer::CheckLocationProximity"); + return; + } + } + } + } +} + +void ZoneServer::CheckLocationGrids() { + if (connected_clients.size() > 0 && location_grids.size() > 0) { + MutexList::iterator client_itr = connected_clients.begin(); + while (client_itr.Next()) { + Client* client = client_itr.value; + if (!client) + continue; + Player* player = client->GetPlayer(); + float x = player->GetX(); + float y = player->GetY(); + float z = player->GetZ(); + int32 grid_id = player->GetLocation(); + MutexList::iterator location_grid_itr = location_grids.begin(); + while (location_grid_itr.Next()) { + LocationGrid* grid = location_grid_itr.value; + bool playerInGrid = false; + if (grid->locations.size() > 0 || (playerInGrid = (grid->grid_id > 0 && grid->grid_id == grid_id)) || grid->players.count(player) > 0) { + float x_small = 0; + float x_large = 0; + float y_small = 0; + float y_large = 0; + float z_small = 0; + float z_large = 0; + bool first = true; + bool in_grid = false; + if(grid->locations.size() == 0 && playerInGrid) { // no locations, we presume player is in grid + in_grid = true; + } + else { + MutexList::iterator location_itr = grid->locations.begin(); + while (location_itr.Next()) { + Location* location = location_itr.value; + if (first) { + x_small = location->x; + x_large = location->x; + if (grid->include_y) { + y_small = location->y; + y_large = location->y; + } + z_small = location->z; + z_large = location->z; + first = false; + } + else { + if (location->x < x_small) + x_small = location->x; + else if (location->x > x_large) + x_large = location->x; + if (grid->include_y) { + if (location->y < y_small) + y_small = location->y; + else if (location->y > y_large) + y_large = location->y; + } + if (location->z < z_small) + z_small = location->z; + else if (location->z > z_large) + z_large = location->z; + } + } + if (grid->include_y && (x >= x_small && x <= x_large && y >= y_small && y <= y_large && z >= z_small && z <= z_large)) + in_grid = true; + else if (x >= x_small && x <= x_large && z >= z_small && z <= z_large) + in_grid = true; + } + if (in_grid && grid->players.count(player) == 0) { + grid->players.Put(player, true); + + bool show_enter_location_popup = true; + bool discovery_enabled = rule_manager.GetGlobalRule(R_World, EnablePOIDiscovery)->GetBool(); + + if( grid->discovery && discovery_enabled && !player->DiscoveredLocation(grid->id) ) + { + // check if player has already discovered this location + + // if not, process new discovery + char tmp[200] = {0}; + sprintf(tmp, "\\#FFE400You have discovered\12\\#FFF283%s", grid->name.c_str()); + client->SendPopupMessage(11, tmp, "ui_discovery", 2.25, 0xFF, 0xFF, 0xFF); + LogWrite(ZONE__DEBUG, 0, "Zone", "Player '%s' discovered location '%s' (%u)", player->GetName(), grid->name.c_str(), grid->id); + + player->UpdatePlayerHistory(HISTORY_TYPE_DISCOVERY, HISTORY_SUBTYPE_LOCATION, grid->id); + show_enter_location_popup = false; + + // else, print standard location entry + } + + if( show_enter_location_popup ) + { + LogWrite(ZONE__DEBUG, 0, "Zone", "Player '%s' entering location '%s' (%u)", player->GetName(), grid->name.c_str(), grid->id); + client->SendPopupMessage(10, grid->name.c_str(), 0, 2.5, 255, 255, 0); + } + } + else if (!in_grid && grid->players.count(player) > 0) { + LogWrite(ZONE__DEBUG, 0, "Zone", "Player '%s' leaving location '%s' (%u)", player->GetName(), grid->name.c_str(), grid->id); + grid->players.erase(player); + } + } + } + } + } +} + +// Called from a command (client, main zone thread) and the main zone thread +// so no need for a mutex container +void ZoneServer::AddLocationGrid(LocationGrid* grid) { + if (grid) + location_grids.Add(grid); +} + +void ZoneServer::RemoveLocationGrids() { + MutexList::iterator itr = location_grids.begin(); + while (itr.Next()) + itr.value->locations.clear(true); + location_grids.clear(true); +} + +void ZoneServer::RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, bool delete_recast, bool call_expire_function, bool lock_spell_process){ + if(spellProcess) + spellProcess->RemoveSpellTimersFromSpawn(spawn, remove_all, delete_recast, call_expire_function, lock_spell_process); +} + +void ZoneServer::Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel, bool from_movement){ + if(spellProcess) + spellProcess->Interrupted(caster, interruptor, error_code, cancel, from_movement); +} + +Spell* ZoneServer::GetSpell(Entity* caster){ + Spell* spell = 0; + if(spellProcess) + spell = spellProcess->GetSpell(caster); + return spell; +} + +void ZoneServer::ProcessSpell(Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell, LuaSpell* customSpell, int16 custom_cast_time, bool in_heroic_opp){ + if(spellProcess) + spellProcess->ProcessSpell(this, spell, caster, target, lock, harvest_spell, customSpell, custom_cast_time, in_heroic_opp); +} + +void ZoneServer::ProcessEntityCommand(EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock) { + if (target && target->GetSpawnScript()) { + Player* player = 0; + if (caster && caster->IsPlayer()) + player = (Player*)caster; + CallSpawnScript(target, SPAWN_SCRIPT_CUSTOM, player, entity_command->command.c_str()); + } + if (spellProcess) + spellProcess->ProcessEntityCommand(this, entity_command, caster, target, lock); +} + +void ZoneServer::RemoveSpawnSupportFunctions(Spawn* spawn, bool lock_spell_process, bool shutdown) { + if(!spawn) + return; + + // remove entity* in trade class + if(spawn->IsEntity()) { + ((Entity*)spawn)->TerminateTrade(); + } + + if(spawn->IsPlayer() && spawn->GetZone()) { + spawn->GetZone()->RemovePlayerPassenger(((Player*)spawn)->GetCharacterID()); + if(((Player*)spawn)->GetClient()) { + GetTradeskillMgr()->StopCrafting(((Player*)spawn)->GetClient()); + } + } + if(spawn->IsEntity()) + RemoveSpellTimersFromSpawn((Entity*)spawn, true, true, true, lock_spell_process); + + if(!shutdown) { // in case of shutdown, DeleteData(true) handles the cleanup later via DeleteSpawnScriptTimers + StopSpawnScriptTimer(spawn, ""); + } + + if(spawn->IsEntity()) { + ClearHate((Entity*)spawn); + } + + RemoveDamagedSpawn(spawn); + spawn->SendSpawnChanges(false); + RemoveChangedSpawn(spawn); + + // Everything inside this if will be nuked during a reload in other spots, no need to do it twice + if (!reloading) { + RemoveDeadEnemyList(spawn); + + spawn->changed = true; + spawn->info_changed = true; + spawn->vis_changed = true; + spawn->position_changed = true; + SendSpawnChanges(spawn); + + if (spawn->GetSpawnGroupID() > 0) { + int32 group_id = spawn->GetSpawnGroupID(); + spawn->RemoveSpawnFromGroup(); + if (spawn_group_map.count(group_id) > 0) + spawn_group_map.Get(group_id).Remove(spawn->GetID()); + } + + if (!spawn->IsPlayer()) { + if(quick_database_id_lookup.count(spawn->GetDatabaseID()) > 0) + quick_database_id_lookup.erase(spawn->GetDatabaseID()); + + if(spawn->GetSpawnLocationID() > 0 && quick_location_id_lookup.count(spawn->GetSpawnLocationID()) > 0 && quick_location_id_lookup.Get(spawn->GetSpawnLocationID()) == spawn->GetID()) + quick_location_id_lookup.erase(spawn->GetSpawnLocationID()); + + if(spawn->GetSpawnGroupID() > 0 && quick_group_id_lookup.count(spawn->GetSpawnGroupID()) > 0 && quick_group_id_lookup.Get(spawn->GetSpawnGroupID()) == spawn->GetID()) + quick_group_id_lookup.erase(spawn->GetSpawnGroupID()); + } + + DeleteSpawnScriptTimers(spawn); + RemovePlayerProximity(spawn); + } + + // We don't use RemoveMovementNPC() here as it caused a hell of a delay during reloads + // instead we remove it from the list directly + if (spawn->IsNPC()) + movement_spawns.erase(spawn->GetID()); +} + +void ZoneServer::HandleEmote(Spawn* originator, string name) { + if (!originator) { + LogWrite(ZONE__ERROR, 0, "Zone", "HandleEmote called with an invalid client"); + return; + } + + Client* orig_client = (originator->IsPlayer() && ((Player*)originator)->GetClient()) ? ((Player*)originator)->GetClient() : nullptr; + Client* client = 0; + int32 cur_client_version = orig_client ? orig_client->GetVersion() : 546; + Emote* origEmote = visual_states.FindEmote(name, cur_client_version); + if(!origEmote){ + if(orig_client) { + orig_client->Message(CHANNEL_COLOR_YELLOW, "Unable to find emote '%s'. If this should be a valid emote be sure to submit a /bug report.", name.c_str()); + } + return; + } + Emote* emote = origEmote; + + PacketStruct* packet = 0; + char* emoteResponse = 0; + vector::iterator client_itr; + + map emote_version_range; + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(!client || (client && originator->IsPlayer() && client->GetPlayer()->IsIgnored(originator->GetName()))) + continue; + + // establish appropriate emote for the version used by the client + if (client->GetVersion() != cur_client_version) + { + map::iterator rangeitr = emote_version_range.find(client->GetVersion()); + if (rangeitr == emote_version_range.end()) + { + Emote* tmp_new_emote = visual_states.FindEmote(name, client->GetVersion()); + if (tmp_new_emote) + { + emote_version_range.insert(make_pair(client->GetVersion(), tmp_new_emote)); + emote = tmp_new_emote; + } // else its missing just use the current clients default + } + else // we have an existing emote already cached + emote = rangeitr->second; + } + else // since the client and originator client match use the original emote + emote = origEmote; + + packet = configReader.getStruct("WS_CannedEmote", client->GetVersion()); + if(packet){ + packet->setDataByName("spawn_id" , client->GetPlayer()->GetIDWithPlayerSpawn(originator)); + if(!emoteResponse){ + string message; + if(originator->GetTarget() && originator->GetTarget()->GetID() != originator->GetID()){ + message = emote->GetTargetedMessageString(); + if(message.find("%t") < 0xFFFFFFFF) + message.replace(message.find("%t"), 2, originator->GetTarget()->GetName()); + } + if(message.length() == 0) + message = emote->GetMessageString(); + if(message.find("%g1") < 0xFFFFFFFF){ + if(originator->GetGender() == 1) + message.replace(message.find("%g1"), 3, "his"); + else + message.replace(message.find("%g1"), 3, "her"); + } + if(message.find("%g2") < 0xFFFFFFFF){ + if(originator->GetGender() == 1) + message.replace(message.find("%g2"), 3, "him"); + else + message.replace(message.find("%g2"), 3, "her"); + } + if(message.find("%g3") < 0xFFFFFFFF){ + if(originator->GetGender() == 1) + message.replace(message.find("%g3"), 3, "he"); + else + message.replace(message.find("%g3"), 3, "she"); + } + if(message.length() > 0){ + emoteResponse = new char[message.length() + strlen(originator->GetName()) + 10]; + sprintf(emoteResponse,"%s %s", originator->GetName(), message.c_str()); + } + } + if(originator->IsPlayer()) { + packet->setMediumStringByName("emote_msg", emoteResponse ? emoteResponse : ""); + } + packet->setDataByName("anim_type", emote->GetVisualState()); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + safe_delete_array(emoteResponse); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + + +void ZoneServer::SetupInstance(int32 createdInstanceID) { + if ( createdInstanceID == 0 ) // if this happens that isn't good! + instanceID = ++MinInstanceID; + else // db should pass the good ID + instanceID = createdInstanceID; +} + +void ZoneServer::RemoveDeadSpawn(Spawn* spawn){ + AddDeadSpawn(spawn, 0); +} + +void ZoneServer::AddDeadSpawn(Spawn* spawn, int32 timer){ + MDeadSpawns.writelock(__FUNCTION__, __LINE__); + if (dead_spawns.count(spawn->GetID()) > 0) + dead_spawns[spawn->GetID()] = Timer::GetCurrentTime2() + timer; + else if(timer != 0xFFFFFFFF) + dead_spawns.insert(make_pair(spawn->GetID(), Timer::GetCurrentTime2() + timer)); + else{ + if(spawn->IsEntity() && spawn->HasLoot()){ + dead_spawns.insert(make_pair(spawn->GetID(), Timer::GetCurrentTime2() + (15000 * spawn->GetLevel() + 240000))); + SendUpdateDefaultCommand(spawn, "loot", 10); + } + else + dead_spawns.insert(make_pair(spawn->GetID(), Timer::GetCurrentTime2() + 10000)); + } + MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::WritePlayerStatistics() { + MutexList::iterator client_itr = connected_clients.begin(); + while(client_itr.Next()) + client_itr->value->GetPlayer()->WritePlayerStatistics(); +} + +bool ZoneServer::SendRadiusSpawnInfo(Client* client, float radius) { + if (!client) + return false; + + Spawn* spawn = 0; + bool ret = false; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn != client->GetPlayer() && !spawn->IsPlayer() && spawn->GetDistance(client->GetPlayer()) <= radius) { + const char* type = "NPC"; + const char* specialTypeID = "N/A"; + int32 specialID = 0, spawnEntryID = spawn->GetSpawnEntryID(); + if (spawn->IsObject()) + { + Object* obj = (Object*)spawn; + specialID = obj->GetID(); + specialTypeID = "GetID"; + type = "Object"; + } + else if (spawn->IsSign()) + { + Sign* sign = (Sign*)spawn; + specialID = sign->GetWidgetID(); + specialTypeID = "WidgetID"; + type = "Sign"; + } + else if (spawn->IsWidget()) + { + Widget* widget = (Widget*)spawn; + specialID = widget->GetWidgetID(); + specialTypeID = "WidgetID"; + if ( specialID == 0xFFFFFFFF ) + specialTypeID = "WidgetID(spawn_widgets entry missing)"; + + type = "Widget"; + } + else if (spawn->IsGroundSpawn()) + { + GroundSpawn* gs = (GroundSpawn*)spawn; + specialID = gs->GetGroundSpawnEntryID(); + specialTypeID = "GroundSpawnEntryID"; + type = "GroundSpawn"; + } + client->Message(CHANNEL_COLOR_RED, "Name: %s (%s), Spawn Table ID: %u, %s: %u", spawn->GetName(), type, spawn->GetDatabaseID(), specialTypeID, specialID); + client->Message(CHANNEL_COLOR_RED, "Spawn Location ID: %u, Spawn Group ID: %u, SpawnEntryID: %u, Grid ID: %u", spawn->GetSpawnLocationID(), spawn->GetSpawnGroupID(), spawnEntryID, spawn->GetLocation()); + client->Message(CHANNEL_COLOR_RED, "Respawn Time: %u (sec), X: %f, Y: %f, Z: %f Heading: %f", spawn->GetRespawnTime(), spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetHeading()); + client->Message(CHANNEL_COLOR_YELLOW, "============================="); + ret = true; + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void ZoneServer::FindSpawn(Client* client, char* regSearchStr) +{ + if (!regSearchStr || strlen(regSearchStr) < 1) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Bad ZoneServer::FindSpawn(Client*, char* regSearchStr) attempt, regSearchStr is NULL or empty."); + return; + } + + string resString = string(regSearchStr); + try + { + std::regex pre_re_check("^[a-zA-Z0-9_ ]+$"); + bool output = std::regex_match(resString, pre_re_check); + if (output) + { + string newStr(".*"); + newStr.append(regSearchStr); + newStr.append(".*"); + resString = newStr; + } + } + catch (...) + { + client->SimpleMessage(CHANNEL_COLOR_RED, "Try/Catch ZoneServer::FindSpawn(Client*, char* regSearchStr) failure."); + return; + } + std::regex re; + try { + re = std::regex(resString, std::regex_constants::icase); + } + catch(...) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Invalid regex for FindSpawn."); + return; + } + + client->Message(CHANNEL_NARRATIVE, "RegEx Search Spawn List: %s", regSearchStr); + client->Message(CHANNEL_NARRATIVE, "Database ID | Spawn Name | X , Y , Z"); + client->Message(CHANNEL_NARRATIVE, "========================"); + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + int32 spawnsFound = 0; + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + Spawn* spawn = itr->second; + if (!spawn || !spawn->GetName()) + continue; + bool output = false; + try { + output = std::regex_match(string(spawn->GetName()), re); + } + catch (...) + { + continue; + } + + if (output) + { + client->Message(CHANNEL_NARRATIVE, "%i | %s | %f , %f , %f", spawn->GetDatabaseID(), spawn->GetName(), spawn->GetX(), spawn->GetY(), spawn->GetZ()); + spawnsFound++; + } + } + client->Message(CHANNEL_NARRATIVE, "========================", spawnsFound); + client->Message(CHANNEL_NARRATIVE, "%u Results Found.", spawnsFound); + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + + +void ZoneServer::AddPlayerTracking(Player* player) { + if (player && !player->GetIsTracking() && players_tracking.count(player->GetDatabaseID()) == 0) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + PacketStruct* packet = configReader.getStruct("WS_TrackingUpdate", client->GetVersion()); + if (packet) { + player->SetIsTracking(true); + players_tracking.Put(client->GetCharacterID(), player); + packet->setDataByName("mode", TRACKING_START); + packet->setDataByName("type", TRACKING_TYPE_ENTITIES); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void ZoneServer::RemovePlayerTracking(Player* player, int8 mode) { + if (player && player->GetIsTracking()) { + Client* client = ((Player*)player)->GetClient(); + if (client) { + PacketStruct* packet = configReader.getStruct("WS_TrackingUpdate", client->GetVersion()); + if (packet) { + player->SetIsTracking(false); + players_tracking.erase(client->GetCharacterID()); + packet->setDataByName("mode", mode); + packet->setDataByName("type", TRACKING_TYPE_ENTITIES); + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } + } +} + +void ZoneServer::ProcessTracking() { + MutexMap::iterator itr = players_tracking.begin(); + while (itr.Next()) { + Player* player = itr->second; + if(player->GetClient()) { + ProcessTracking(player->GetClient()); + } + } +} + +void ZoneServer::ProcessTracking(Client* client) { + if (!client) + return; + + Player* player = client->GetPlayer(); + if (player && player->GetIsTracking()) { + MutexMap::iterator spawn_itr; + PacketStruct* packet = configReader.getStruct("WS_TrackingUpdate", client->GetVersion()); + if (packet) { + packet->setDataByName("mode", TRACKING_UPDATE); + packet->setDataByName("type", TRACKING_TYPE_ENTITIES); + vector spawns_tracked; + while (spawn_itr.Next()) { + Spawn* spawn = spawn_itr->second; + float distance = player->GetDistance(spawn); + if (spawn->IsEntity() && distance <= 80 && spawn != player) { + TrackedSpawn* ts = new TrackedSpawn; + ts->spawn = spawn; + ts->distance = distance; + + /* Add spawns in ascending order from closest to furthest */ + if (spawns_tracked.empty()) + spawns_tracked.push_back(ts); + else { + vector::iterator tracked_itr; + bool added = false; + for (tracked_itr = spawns_tracked.begin(); tracked_itr != spawns_tracked.end(); tracked_itr++) { + TrackedSpawn* cur_ts = *tracked_itr; + if (ts->distance <= cur_ts->distance) { + spawns_tracked.insert(tracked_itr, ts); + added = true; + break; + } + } + if (!added) + spawns_tracked.push_back(ts); + } + } + } + packet->setArrayLengthByName("num_spawns", spawns_tracked.size()); + for (int32 i = 0; i < spawns_tracked.size(); i++) { + TrackedSpawn* ts = spawns_tracked[i]; + + LogWrite(ZONE__DEBUG, 0, "Zone", "%s (%f)", ts->spawn->GetName(), ts->distance); + + packet->setArrayDataByName("spawn_id", player->GetIDWithPlayerSpawn(ts->spawn), i); + packet->setArrayDataByName("spawn_name", ts->spawn->GetName(), i); + if (ts->spawn->IsPlayer()) + packet->setArrayDataByName("spawn_type", TRACKING_SPAWN_TYPE_PC, i); + else + packet->setArrayDataByName("spawn_type", TRACKING_SPAWN_TYPE_NPC, i); + packet->setArrayDataByName("spawn_con_color", player->GetArrowColor(ts->spawn->GetLevel()), i); + } + packet->setArrayLengthByName("num_array1", 0); + //for (int32 i = 0; i < spawns_tracked.size(); i++) { + //} + packet->setArrayLengthByName("num_spawns2", spawns_tracked.size()); + for (int32 i = 0; i < spawns_tracked.size(); i++) { + TrackedSpawn* ts = spawns_tracked[i]; + packet->setArrayDataByName("list_spawn_id", player->GetIDWithPlayerSpawn(ts->spawn), i); + packet->setArrayDataByName("list_number", i, i); + } + client->QueuePacket(packet->serialize()); + safe_delete(packet); + for (int32 i = 0; i < spawns_tracked.size(); i++) + safe_delete(spawns_tracked[i]); + } + } +} + +void ZoneServer::SendEpicMobDeathToGuild(Player* killer, Spawn* victim) { + if (killer && victim) { + + LogWrite(MISC__TODO, 1, "TODO" , "Check if player is in raid\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + + if (killer->GetGroupMemberInfo()) { + world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__); + + deque::iterator itr; + + + PlayerGroup* group = world.GetGroupManager()->GetGroup(killer->GetGroupMemberInfo()->group_id); + if (group) + { + group->MGroupMembers.readlock(__FUNCTION__, __LINE__); + deque* members = group->GetMembers(); + for (itr = members->begin(); itr != members->end(); itr++) { + GroupMemberInfo* gmi = *itr; + if (gmi->client) { + Player* group_member = gmi->client->GetPlayer(); + if (group_member && group_member->GetGuild()) { + Guild* guild = group_member->GetGuild(); + string message = Guild::GetEpicMobDeathMessage(group_member->GetName(), victim->GetName()); + guild->AddNewGuildEvent(GUILD_EVENT_KILLS_EPIC_MONSTER, message.c_str(), Timer::GetUnixTimeStamp()); + guild->SendMessageToGuild(GUILD_EVENT_KILLS_EPIC_MONSTER, message.c_str()); + } + } + } + group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__); + } + + world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__); + } + else if (killer->GetGuild()) { + Guild* guild = killer->GetGuild(); + string message = Guild::GetEpicMobDeathMessage(killer->GetName(), victim->GetName()); + guild->AddNewGuildEvent(GUILD_EVENT_KILLS_EPIC_MONSTER, message.c_str(), Timer::GetUnixTimeStamp()); + guild->SendMessageToGuild(GUILD_EVENT_KILLS_EPIC_MONSTER, message.c_str()); + } + } +} + +void ZoneServer::ProcessAggroChecks(Spawn* spawn) { + if (spawn->GetFactionID() < 1 || spawn->EngagedInCombat()) + return; + // If faction based combat is not allowed then no need to run the loops so just return out + if(!rule_manager.GetGlobalRule(R_Faction, AllowFactionBasedCombat)->GetBool()) + return; + + if (spawn && spawn->IsNPC() && spawn->Alive()) + CheckEnemyList((NPC*)spawn); +} + +void ZoneServer::SendUpdateTitles(Client* client, Title* suffix, Title* prefix) { + assert(client); + if (client->GetVersion() > 561) + SendUpdateTitles(client->GetPlayer(), suffix, prefix); +} + +void ZoneServer::SendUpdateTitles(Spawn *spawn, Title *suffix, Title *prefix) { + if (!spawn) + return; + + vector::iterator itr; + PacketStruct *packet; + Client* current_client; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + current_client = *itr; + + if (current_client->GetVersion() <= 561) + continue; + + if (!(packet = configReader.getStruct("WS_UpdateTitle", current_client->GetVersion()))) + continue; + + packet->setDataByName("player_id", current_client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("player_name", spawn->GetName()); + packet->setDataByName("unknown1", 1, 1); + if(suffix) + packet->setDataByName("suffix_title", suffix->GetName()); + else + packet->setDataByName("suffix_title", spawn->GetSuffixTitle()); + if(prefix) + packet->setDataByName("prefix_title", prefix->GetName()); + else + packet->setDataByName("prefix_title", spawn->GetPrefixTitle()); + packet->setDataByName("last_name", spawn->GetLastName()); + packet->setDataByName("sub_title", spawn->GetSubTitle()); + current_client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddTransportSpawn(Spawn* spawn){ + if(!spawn) + return; + MTransportSpawns.writelock(__FUNCTION__, __LINE__); + transport_spawns.push_back(spawn->GetID()); + spawn->SetTransportSpawn(true); + MTransportSpawns.releasewritelock(__FUNCTION__, __LINE__); +} + +Spawn* ZoneServer::GetClosestTransportSpawn(float x, float y, float z){ + Spawn* spawn = 0; + Spawn* closest_spawn = 0; + float closest_distance = 0.0; + MTransportSpawns.writelock(__FUNCTION__, __LINE__); + vector::iterator itr = transport_spawns.begin(); + while(itr != transport_spawns.end()){ + spawn = GetSpawnByID(*itr); + if(spawn){ + if(closest_distance == 0.0){ + closest_spawn = spawn; + closest_distance = spawn->GetDistance(x, y, z); + } + else if(spawn->GetDistance(x, y, z) < closest_distance){ + closest_spawn = spawn; + closest_distance = spawn->GetDistance(x, y, z); + } + itr++; + } + else + itr = transport_spawns.erase(itr); + } + MTransportSpawns.releasewritelock(__FUNCTION__, __LINE__); + + return closest_spawn; +} + +Spawn* ZoneServer::GetTransportByRailID(sint64 rail_id){ + Spawn* spawn = 0; + Spawn* closest_spawn = 0; + MTransportSpawns.readlock(__FUNCTION__, __LINE__); + vector::iterator itr = transport_spawns.begin(); + while(itr != transport_spawns.end()){ + spawn = GetSpawnByID(*itr); + //printf("Rail id: %i vs %i\n", spawn ? spawn->GetRailID() : 0, rail_id); + if(spawn && spawn->GetRailID() == rail_id){ + closest_spawn = spawn; + break; + } + itr++; + } + MTransportSpawns.releasereadlock(__FUNCTION__, __LINE__); + + return closest_spawn; +} + +void ZoneServer::SetRain(float val) { + rain = val; + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + client->GetPlayer()->GetInfoStruct()->set_rain(val); + client->GetPlayer()->SetCharSheetChanged(true); + if( val >= 0.75 && !weather_signaled ) + { + client->SimpleMessage(CHANNEL_NARRATIVE, "It starts to rain."); + } + else if( val < 0.75 && weather_signaled ) + { + client->SimpleMessage(CHANNEL_NARRATIVE, "It stops raining."); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + + if (val >= 0.75 && !weather_signaled) { + weather_signaled = true; + ProcessSpawnConditional(SPAWN_CONDITIONAL_RAINING); + } + else if (val < 0.75 && weather_signaled) { + weather_signaled = false; + ProcessSpawnConditional(SPAWN_CONDITIONAL_NOT_RAINING); + } +} + +void ZoneServer::SetWind(float val) { + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + client->GetPlayer()->GetInfoStruct()->set_wind(val); + client->GetPlayer()->SetCharSheetChanged(true); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ProcessWeather() +{ + // if the global rule to disable weather is set, or if the `weather_allowed` field in the zone record == 0, do not process weather + if( !weather_enabled || !isWeatherAllowed() ) + return; + + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Processing weather changes", zone_name); + float new_weather = 0; + float weather_offset = 0; + bool change_weather = false; + + // check to see if it is time to change the weather according to weather_frequency (time between changes) + if( weather_last_changed_time <= (Timer::GetUnixTimeStamp() - weather_frequency) ) + { + LogWrite(ZONE__DEBUG, 2, "Zone", "%s: Checking for weather changes", zone_name); + // reset last changed time (frequency check) + weather_last_changed_time = Timer::GetUnixTimeStamp(); + + // this is the chance a weather change occurs at all at the expired interval + int8 weather_random = MakeRandomInt(1, 100); + LogWrite(ZONE__DEBUG, 2, "Zone", "%s: Chance to change weather: %i%%, rolled: %i%% - Change weather: %s", zone_name, weather_change_chance, weather_random, weather_random <= weather_change_chance ? "True" : "False"); + + if( weather_random <= weather_change_chance ) + { + change_weather = true; + weather_offset = weather_change_amount; + + if( weather_type == 3 ) // chaotic weather patterns, random weather between min/max + { + new_weather = MakeRandomFloat(weather_min_severity, weather_max_severity); + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Chaotic weather severity changed to %.2f", zone_name, new_weather); + weather_pattern = 2; + } + else if( weather_type == 2 ) // random weather patterns, combination of normal + dynamic + max_offset + { + weather_offset = MakeRandomFloat(weather_change_amount, weather_dynamic_offset); + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Random weather severity changed by %.2f", zone_name, weather_offset); + + int8 weather_alter = weather_change_chance / 10; // the divide is to prevent too many direction changes in a cycle + weather_random = MakeRandomInt(1, 100); // chance that the weather changes direction (weather_pattern) + + if( weather_random <= weather_alter ) + weather_pattern = ( weather_pattern == 0 ) ? 1 : 0; + } + else if( weather_type == 1 ) // dynamic weather patterns, weather may not reach min/max + { + int8 weather_alter = weather_change_chance / 10; // the divide is to prevent too many direction changes in a cycle + weather_random = MakeRandomInt(1, 100); // chance that the weather changes direction (weather_pattern) + + if( weather_random <= weather_alter ) + { + weather_pattern = ( weather_pattern == 0 ) ? 1 : 0; + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Dynamic weather pattern changed to %i", zone_name, weather_pattern); + } + } + else // normal weather patterns, weather starts at min, goes to max, then back down again + { + // do nothing (processed below) + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Normal weather severity changed by %.2f", zone_name, weather_offset); + } + + // when all done, change the weather + if( change_weather ) + { + if( weather_pattern == 1 ) + { + // weather is getting worse, til it reaches weather_max_severity + new_weather = ( weather_current_severity <= weather_max_severity ) ? weather_current_severity + weather_offset : weather_max_severity; + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Increased weather severity by %.2f", zone_name, weather_offset); + + if(new_weather > weather_max_severity) + { + new_weather = weather_max_severity - weather_offset; + weather_pattern = 0; + } + } + else if( weather_pattern == 0 ) + { + // weather is clearing up, til it reaches weather_min_severity + new_weather = ( weather_current_severity >= weather_min_severity ) ? weather_current_severity - weather_offset : weather_min_severity; + LogWrite(ZONE__DEBUG, 3, "Zone", "%s: Decreased weather severity by %.2f", zone_name, weather_offset); + + if(new_weather < weather_min_severity) + { + new_weather = weather_min_severity + weather_offset; + weather_pattern = 1; + } + } + + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Weather change triggered from %.2f to %.2f", zone_name, weather_current_severity, new_weather); + this->SetRain(new_weather); + weather_current_severity = new_weather; + } + } + } + else + LogWrite(ZONE__DEBUG, 1, "Zone", "%s: Not time to change weather yet", zone_name); +} + +void ZoneServer::HidePrivateSpawn(Spawn* spawn) { + if (!spawn->IsPrivateSpawn()) + return; + + Client* client = 0; + Player* player = 0; + PacketStruct* packet = 0; + int32 packet_version = 0; + MutexList::iterator itr = connected_clients.begin(); + while (itr->Next()) { + client = itr->value; + player = client->GetPlayer(); + if (player->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->IsSendingSpawn(spawn->GetID())) { + if (!packet || packet_version != client->GetVersion()) { + safe_delete(packet); + packet_version = client->GetVersion(); + packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version); + } + + SendRemoveSpawn(client, spawn, packet); + if(spawn_range_map.count(client) > 0) + spawn_range_map.Get(client)->erase(spawn->GetID()); + + if(player->GetTarget() == spawn) + player->SetTarget(0); + } + } + + safe_delete(packet); +} + +SpawnLocation* ZoneServer::GetSpawnLocation(int32 id) { + SpawnLocation* ret = 0; + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + if (spawn_location_list.count(id) > 0) + ret = spawn_location_list[id]; + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); + return ret; +} + +void ZoneServer::PlayAnimation(Spawn* spawn, int32 visual_state, Spawn* spawn2, int8 hide_type){ + Client* client = 0; + PacketStruct* packet = 0; + Spawn* exclude_spawn = 0; + if (!spawn) + return; + if (spawn2){ + if(hide_type == 1){ + if(spawn2->IsPlayer()) { + client = ((Player*)spawn2)->GetClient(); + if(client){ + packet = configReader.getStruct("WS_CannedEmote", client->GetVersion()); + packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn)); + packet->setDataByName("anim_type", visual_state); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + return; + } + } + if(hide_type == 2) + exclude_spawn = spawn2; + } + + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { + client = *client_itr; + if(spawn->GetDistance(client->GetPlayer()) > 50) + continue; + if(exclude_spawn == client->GetPlayer()) + continue; + if(!client->IsReadyForUpdates()) // client is not in world yet so we shouldn't be sending animations of spawns yet + continue; + + if(!packet || packet->GetVersion() != client->GetVersion()) { + safe_delete(packet); + packet = configReader.getStruct("WS_CannedEmote", client->GetVersion()); + } + if (packet) { + int32 spawn_id = client->GetPlayer()->GetIDWithPlayerSpawn(spawn); + if(spawn_id) { + packet->setDataByName("spawn_id", spawn_id); + packet->setDataByName("anim_type", visual_state); + client->QueuePacket(packet->serialize()); + } + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + safe_delete(packet); +} + +vector ZoneServer::GetSpawnsByID(int32 id) { + vector tmp_list; + Spawn* spawn; + + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && (spawn->GetDatabaseID() == id)) + tmp_list.push_back(spawn); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + return tmp_list; +} + + +vector ZoneServer::GetSpawnsByRailID(sint64 rail_id) { + vector tmp_list; + Spawn* spawn; + MTransportSpawns.readlock(__FUNCTION__, __LINE__); + vector::iterator itr = transport_spawns.begin(); + while(itr != transport_spawns.end()){ + spawn = GetSpawnByID(*itr); + if(spawn && spawn->GetRailID() == rail_id){ + tmp_list.push_back(spawn); + } + itr++; + } + MTransportSpawns.releasereadlock(__FUNCTION__, __LINE__); + return tmp_list; +} + +void ZoneServer::RemovePlayerPassenger(int32 char_id) { + vector tmp_list; + Spawn* spawn; + MTransportSpawns.readlock(__FUNCTION__, __LINE__); + vector::iterator itr = transport_spawns.begin(); + while(itr != transport_spawns.end()){ + spawn = GetSpawnByID(*itr); + if(spawn) { + spawn->RemoveRailPassenger(char_id); + } + itr++; + } + MTransportSpawns.releasereadlock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::SetPlayerTargetByName(Client* originator, char* targetName, float distance) { + if(!targetName || !originator->GetPlayer()) { + return false; + } + int32 name_size = strlen(targetName); + if(name_size < 1 || name_size > 127) { + return false; + } + + auto loc = glm::vec3(originator->GetPlayer()->GetX(), originator->GetPlayer()->GetZ(), originator->GetPlayer()->GetY()); + std::vector grids_by_radius; + if(originator->GetPlayer()->GetMap()) { + grids_by_radius = GetGridsByLocation(originator->GetPlayer(), loc, distance); + } + else { + grids_by_radius.push_back(originator->GetPlayer()->GetLocation()); + } + + bool found_target = false; + float spawn_dist = 999999.0f; + Spawn* target_spawn = nullptr; + float tmp_dist = 0.0f; + MGridMaps.lock_shared(); + std::vector::iterator grid_radius_itr; + for(grid_radius_itr = grids_by_radius.begin(); grid_radius_itr != grids_by_radius.end(); grid_radius_itr++) { + std::map::iterator grids = grid_maps.find((*grid_radius_itr)); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + Spawn* spawn = it->second; + + bool inSameGroup = false; + if(spawn->IsEntity()) { + GroupMemberInfo* gmi = originator->GetPlayer()->GetGroupMemberInfo(); + inSameGroup = (((Entity*)spawn)->GetGroupMemberInfo() && originator->GetPlayer()->GetGroupMemberInfo() && ((Entity*)spawn)->GetGroupMemberInfo()->group_id == originator->GetPlayer()->GetGroupMemberInfo()->group_id); + } + if (spawn && spawn->appearance.targetable > 0 && !strncasecmp(spawn->GetName(), targetName, name_size) && (inSameGroup || (((tmp_dist = spawn->GetDistance(originator->GetPlayer(), true)) <= distance) && tmp_dist < spawn_dist && originator->GetPlayer()->CheckLoS(spawn)))) { + spawn_dist = tmp_dist; + target_spawn = spawn; + } + } + grids->second->MSpawns.unlock_shared(); + } + } + MGridMaps.unlock_shared(); + + if(target_spawn != nullptr) { + originator->TargetSpawn(target_spawn); + } + + return (target_spawn != nullptr); +} + +std::vector ZoneServer::GetGridsByLocation(Spawn* originator, glm::vec3 loc, float distance) { + std::vector grids_by_radius; + if(originator == nullptr) + return grids_by_radius; + + if(originator->GetMap()) { + grids_by_radius = originator->GetMap()->GetGridsByPoint(loc, distance); + if(default_zone_map && default_zone_map != originator->GetMap()) { + std::vector default_grids = default_zone_map->GetGridsByPoint(loc, distance); + + std::unordered_set elements(grids_by_radius.begin(), grids_by_radius.end()); + + for (const auto& elem : default_grids) { + if (elements.find(elem) == elements.end()) { + grids_by_radius.push_back(elem); + elements.insert(elem); + } + } + } + } + + return grids_by_radius; +} + +std::vector> ZoneServer::GetAttackableSpawnsByDistance(Spawn* caster, float distance) { + std::vector> spawns_by_distance; + Spawn* spawn = 0; + auto loc = glm::vec3(caster->GetX(), caster->GetZ(), caster->GetY()); + std::vector grids_by_radius; + if(caster->GetMap()) { + grids_by_radius = GetGridsByLocation(caster, loc, distance); + } + else { + grids_by_radius.push_back(caster->GetLocation()); + } + + float tmp_dist = 0.0f; + MGridMaps.lock_shared(); + std::vector::iterator grid_radius_itr; + for(grid_radius_itr = grids_by_radius.begin(); grid_radius_itr != grids_by_radius.end(); grid_radius_itr++) { + std::map::iterator grids = grid_maps.find((*grid_radius_itr)); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + Spawn* spawn = it->second; + if (spawn && spawn->IsNPC() && spawn->appearance.attackable > 0 && spawn->GetID() > 0 && spawn->GetID() != caster->GetID() && + spawn->Alive() && ((tmp_dist = spawn->GetDistance(caster, true)) <= distance)) { + spawns_by_distance.push_back({spawn->GetID(), tmp_dist}); + } + } + grids->second->MSpawns.unlock_shared(); + } + } + MGridMaps.unlock_shared(); + std::sort(spawns_by_distance.begin(), spawns_by_distance.end(), compareByValue); + + return spawns_by_distance; +} + +void ZoneServer::ResurrectSpawn(Spawn* spawn, Client* client) { + if(!client || !spawn) + return; + PendingResurrection* rez = client->GetCurrentRez(); + if(!rez || !rez->caster) + return; + + PacketStruct* packet = 0; + float power_perc = rez->mp_perc; + float health_perc = rez->hp_perc; + Spawn* caster_spawn = rez->caster; + sint32 heal_amt = 0; + sint32 power_amt = 0; + bool no_calcs = rez->no_calcs; + int8 crit_mod = rez->crit_mod; + Entity* caster = 0; + InfoStruct* info = 0; + bool crit = false; + string heal_spell = rez->heal_name; + int16 heal_packet_type = 0; + int16 power_packet_type = 0; + + //Calculations for how much to heal the spawn + if(health_perc > 0) + heal_amt = (spawn->GetTotalHP() * (health_perc / 100)); + if(power_perc > 0) + power_amt = (spawn->GetTotalPower() * (power_perc / 100)); + + if(caster_spawn->IsEntity()){ + caster = ((Entity*)caster_spawn); + info = caster->GetInfoStruct(); + } + + if(!no_calcs && caster){ + heal_amt = caster->CalculateHealAmount(spawn, heal_amt, crit_mod, &crit); + power_amt = caster->CalculateHealAmount(spawn, power_amt, crit_mod, &crit); + } + + //Set this rez as a crit to be passed to subspell (not yet used) + rez->crit = true; + + //Set Heal amt to 1 if 0 now so the player has health + if(heal_amt == 0) + heal_amt = 1; + + if(heal_amt > spawn->GetTotalHP()) + heal_amt = spawn->GetTotalHP(); + if(power_amt > spawn->GetTotalPower()) + power_amt = spawn->GetTotalPower(); + + spawn->SetAlive(true); + spawn->SetHP(heal_amt); + if(power_amt > 0) + spawn->SetPower(power_amt); + + if(client && caster){ + EQ2Packet* move = ((Player*)spawn)->Move(caster->GetX(), caster->GetY(), caster->GetZ(), client->GetVersion()); + if(move) + client->QueuePacket(move); + } + + if(crit){ + power_packet_type = HEAL_PACKET_TYPE_CRIT_MANA; + heal_packet_type = HEAL_PACKET_TYPE_CRIT_HEAL; + } + else { + power_packet_type = HEAL_PACKET_TYPE_SIMPLE_MANA; + heal_packet_type = HEAL_PACKET_TYPE_SIMPLE_HEAL; + } + + SendHealPacket(caster, spawn, heal_packet_type, heal_amt, heal_spell.c_str()); + if(power_amt > 0) + SendHealPacket(caster, spawn, power_packet_type, power_amt, heal_spell.c_str()); + + //The following code sets the spawn as alive + if(dead_spawns.count(spawn->GetID()) > 0) + dead_spawns.erase(spawn->GetID()); + + if(spawn->IsPlayer()){ + spawn->SetSpawnType(4); + client = ((Player*)spawn)->GetClient(); + if(client){ + packet = configReader.getStruct("WS_Resurrected", client->GetVersion()); + if(packet){ + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + + if(client->GetVersion() <= 561) { + ClientPacketFunctions::SendServerControlFlagsClassic(client, 8, 0); + ClientPacketFunctions::SendServerControlFlagsClassic(client, 16, 0); + } + else { + ClientPacketFunctions::SendServerControlFlags(client, 1, 8, 0); + ClientPacketFunctions::SendServerControlFlags(client, 1, 16, 0); + } + client->SimpleMessage(CHANNEL_NARRATIVE, "You regain consciousness!"); + } + } + spawn->SendSpawnChanges(true); + spawn->SetTempActionState(-1); + spawn->appearance.attackable = 1; +} + +void ZoneServer::SendDispellPacket(Entity* caster, Spawn* target, string dispell_name, string spell_name, int8 dispell_type){ + if(!caster || !target) + return; + + Client* client = 0; + Player* player = 0; + PacketStruct* packet = 0; + vector::iterator client_itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++){ + client = *client_itr; + if(!client || !(player = client->GetPlayer()) || (player != caster && ((caster && player->WasSentSpawn(caster->GetID()) == false) || (target && player->WasSentSpawn(target->GetID()) == false)))) + continue; + if(caster && caster->GetDistance(player) > 50) + continue; + if(target && target->GetDistance(player) > 50) + continue; + + packet = configReader.getStruct("WS_HearDispell", client->GetVersion()); + if(packet){ + packet->setDataByName("spell_name", spell_name.c_str()); + packet->setDataByName("dispell_name", dispell_name.c_str()); + packet->setDataByName("caster", player->GetIDWithPlayerSpawn(caster)); + packet->setDataByName("target", player->GetIDWithPlayerSpawn(target)); + packet->setDataByName("type", dispell_type); + client->QueuePacket(packet->serialize()); + } + safe_delete(packet); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::DismissAllPets() { + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn->IsEntity()) + ((Entity*)spawn)->DismissAllPets(); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::RemoveTargetFromSpell(LuaSpell* spell, Spawn* target, bool remove_caster){ + if (spellProcess) + spellProcess->RemoveTargetFromSpell(spell, target, remove_caster); +} + +void ZoneServer::ClearHate(Entity* entity) { + Spawn* spawn = 0; + map::iterator itr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn->IsNPC() && ((NPC*)spawn)->Brain()) + ((NPC*)spawn)->Brain()->ClearHate(entity); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +ThreadReturnType ZoneLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ZoneLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + ZoneServer* zs = (ZoneServer*) tmp; + while (zs->Process()) { + if(zs->GetClientCount() == 0) + Sleep(1000); + else + Sleep(10); + } + // we failed, time to disappear, no more processing period + safe_delete(zs); + THREAD_RETURN(NULL); +} + +ThreadReturnType SpawnLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("SpawnLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + ZoneServer* zs = (ZoneServer*) tmp; +#ifndef NO_CATCH + try { +#endif + zs->spawnthread_active = true; + while (zs->SpawnProcess()) { + if(zs->GetClientCount() == 0) + Sleep(1000); + else + Sleep(20); + } + zs->spawnthread_active = false; +#ifndef NO_CATCH + } + catch(...) { + zs->spawnthread_active = false; + zs->initial_spawn_threads_active = 0; + LogWrite(ZONE__ERROR, 0, "Zone", "Error Processing SpawnLoop, shutting down zone '%s'...", zs->GetZoneName()); + try{ + zs->Shutdown(); + } + catch(...){ + LogWrite(ZONE__ERROR, 0, "Zone", "Error Processing SpawnLoop while shutting down zone '%s'...", zs->GetZoneName()); + throw; + } + throw; + } +#endif + THREAD_RETURN(NULL); +} + +ThreadReturnType SendInitialSpawns(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("SendInitialSpawns(): tmp = 0!"); + THREAD_RETURN(NULL); + } + Client* client = (Client*) tmp; + client->GetCurrentZone()->SendZoneSpawns(client); + THREAD_RETURN(NULL); +} + +ThreadReturnType SendLevelChangedSpawns(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); +#endif + if (tmp == 0) { + ThrowError("SendLevelChangedSpawns(): tmp = 0!"); + THREAD_RETURN(NULL); + } + Client* client = (Client*)tmp; + client->GetCurrentZone()->SendAllSpawnsForLevelChange(client); + THREAD_RETURN(NULL); +} + +void ZoneServer::SetSpawnStructs(Client* client) { + int16 client_ver = client->GetVersion(); + Player* player = client->GetPlayer(); + + //Save a copy of the correct spawn substructs for the client's player, save here a copy if we don't have one + PacketStruct* pos = configReader.getStruct("Substruct_SpawnPositionStruct", client_ver); + player->SetSpawnPosStruct(pos); + if (versioned_pos_structs.count(pos->GetVersion()) == 0) + versioned_pos_structs[pos->GetVersion()] = new PacketStruct(pos, true); + + PacketStruct* vis = configReader.getStruct("Substruct_SpawnVisualizationInfoStruct", client_ver); + player->SetSpawnVisStruct(vis); + if (versioned_vis_structs.count(vis->GetVersion()) == 0) + versioned_vis_structs[vis->GetVersion()] = new PacketStruct(vis, true); + + PacketStruct* info = configReader.getStruct("Substruct_SpawnInfoStruct", client_ver); + player->SetSpawnInfoStruct(info); + if (versioned_info_structs.count(info->GetVersion()) == 0) + versioned_info_structs[info->GetVersion()] = new PacketStruct(info, true); + + PacketStruct* header = configReader.getStruct("WS_SpawnStruct_Header", client_ver); + player->SetSpawnHeaderStruct(header); + + PacketStruct* footer = configReader.getStruct("WS_SpawnStruct_Footer", client_ver); + player->SetSpawnFooterStruct(footer); + + PacketStruct* sfooter = configReader.getStruct("WS_SignWidgetSpawnStruct_Footer", client_ver); + player->SetSignFooterStruct(sfooter); + + PacketStruct* wfooter = configReader.getStruct("WS_WidgetSpawnStruct_Footer", client_ver); + player->SetWidgetFooterStruct(wfooter); +} + +Spawn* ZoneServer::GetSpawn(int32 id){ + Spawn* ret = 0; + + if(GetNPC(id)) + ret = GetNewNPC(id); + else if(this->GetObject(id)) + ret = GetNewObject(id); + else if(GetWidget(id)) + ret = GetNewWidget(id); + else if(GetSign(id)) + ret = GetNewSign(id); + else if(GetGroundSpawn(id)) + ret = GetNewGroundSpawn(id); + // Unable to find the spawn in the list lets attempt to add it if we are not currently reloading + else if (!reloading && database.LoadNPC(this, id)) { + if (GetNPC(id)) + ret = GetNewNPC(id); + else + LogWrite(NPC__ERROR, 0, "NPC", "Database inserted npc (%u) but was still unable to retrieve it!", id); + } + else if (!reloading && database.LoadObject(this, id)) { + if (this->GetObject(id)) + ret = GetNewObject(id); + else + LogWrite(OBJECT__ERROR, 0, "Object", "Database inserted object (%u) but was still unable to retrieve it!", id); + } + else if (!reloading && database.LoadWidget(this, id)) { + if (GetWidget(id)) + ret = GetNewWidget(id); + else + LogWrite(WIDGET__ERROR, 0, "Widget", "Database inserted widget (%u) but was still unable to retrieve it!", id); + } + else if (!reloading && database.LoadSign(this, id)) { + if (GetSign(id)) + ret = GetNewSign(id); + else + LogWrite(SIGN__ERROR, 0, "Sign", "Database inserted sign (%u) but was still unable to retrieve it!", id); + } + else if (!reloading && database.LoadGroundSpawn(this, id)) { + if (GetGroundSpawn(id)) + ret = GetNewGroundSpawn(id); + else + LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Database inserted ground spawn (%u) but was still unable to retrieve it!", id); + } + + if(ret && ret->IsOmittedByDBFlag()) + { + LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met.", id); + safe_delete(ret); + ret = 0; + } + + if(ret) + ret->SetID(Spawn::NextID()); + return ret; +} + + + + +vector* ZoneServer::GetEntityCommandList(int32 id){ + if(entity_command_list.count(id) > 0) + return entity_command_list[id]; + else + return 0; +} + +void ZoneServer::SetEntityCommandList(int32 id, EntityCommand* command) { + if (entity_command_list.count(id) == 0) + entity_command_list[id] = new vector; + + entity_command_list[id]->push_back(command); +} + +EntityCommand* ZoneServer::GetEntityCommand(int32 id, string name) { + EntityCommand* ret = 0; + if (entity_command_list.count(id) == 0) + return ret; + + vector::iterator itr; + for (itr = entity_command_list[id]->begin(); itr != entity_command_list[id]->end(); itr++) { + if ((*itr)->name == name) { + ret = (*itr); + break; + } + } + + return ret; +} + +void ZoneServer::ClearEntityCommands() { + if (entity_command_list.size() > 0) { + map* >::iterator itr; + for (itr = entity_command_list.begin(); itr != entity_command_list.end(); itr++) { + vector* entity_commands = itr->second; + if (entity_commands && entity_commands->size() > 0) { + vector::iterator v_itr; + for (v_itr = entity_commands->begin(); v_itr != entity_commands->end(); v_itr++) + safe_delete(*v_itr); + entity_commands->clear(); + } + safe_delete(entity_commands); + } + entity_command_list.clear(); + } +} + +void ZoneServer::AddNPCSkill(int32 list_id, int32 skill_id, int16 value){ + npc_skill_list[list_id][skill_id] = value; +} + +map* ZoneServer::GetNPCSkills(int32 primary_list, int32 secondary_list){ + map* ret = 0; + if(npc_skill_list.count(primary_list) > 0){ + ret = new map(); + map::iterator itr; + Skill* tmpSkill = 0; + for(itr = npc_skill_list[primary_list].begin(); itr != npc_skill_list[primary_list].end(); itr++){ + tmpSkill = master_skill_list.GetSkill(itr->first); + if(tmpSkill){ + tmpSkill = new Skill(tmpSkill); + tmpSkill->current_val = itr->second; + tmpSkill->max_val = tmpSkill->current_val+5; + (*ret)[tmpSkill->name.data] = tmpSkill; + } + } + } + if(npc_skill_list.count(secondary_list) > 0){ + if(!ret) + ret = new map(); + map::iterator itr; + Skill* tmpSkill = 0; + for(itr = npc_skill_list[secondary_list].begin(); itr != npc_skill_list[secondary_list].end(); itr++){ + tmpSkill = master_skill_list.GetSkill(itr->first); + if(tmpSkill){ + tmpSkill = new Skill(tmpSkill); + tmpSkill->current_val = itr->second; + tmpSkill->max_val = tmpSkill->current_val+5; + (*ret)[tmpSkill->name.data] = tmpSkill; + } + } + } + if(ret && ret->size() == 0){ + safe_delete(ret); + ret = 0; + } + return ret; +} + +void ZoneServer::AddNPCEquipment(int32 list_id, int32 item_id){ + npc_equipment_list[list_id].push_back(item_id); +} + +void ZoneServer::SetNPCEquipment(NPC* npc) { + if(npc_equipment_list.count(npc->GetEquipmentListID()) > 0){ + Item* tmpItem = 0; + int8 slot = 0; + vector::iterator itr; + for(itr = npc_equipment_list[npc->GetEquipmentListID()].begin(); itr != npc_equipment_list[npc->GetEquipmentListID()].end(); itr++){ + tmpItem = master_item_list.GetItem(*itr); + if(tmpItem){ + slot = npc->GetEquipmentList()->GetFreeSlot(tmpItem); + if(slot < 255){ + tmpItem = new Item(tmpItem); + npc->GetEquipmentList()->SetItem(slot, tmpItem); + } + } + } + } +} + +void ZoneServer::AddNPC(int32 id, NPC* npc) { + npc_list[id] = npc; +} + +void ZoneServer::AddWidget(int32 id, Widget* widget) { + widget_list[id] = widget; +} + +Widget* ZoneServer::GetWidget(int32 id, bool override_loading) { + if((!reloading || override_loading) && widget_list.count(id) > 0) + return widget_list[id]; + else + return 0; +} + +Widget* ZoneServer::GetNewWidget(int32 id) { + if(!reloading && widget_list.count(id) > 0) + return widget_list[id]->Copy(); + else + return 0; +} + + +void ZoneServer::LoadGroundSpawnEntries(){ + MGroundSpawnItems.lock(); + database.LoadGroundSpawnEntries(this); + MGroundSpawnItems.unlock(); +} + +void ZoneServer::LoadGroundSpawnItems() { +} + +void ZoneServer::AddGroundSpawnEntry(int32 groundspawn_id, int16 min_skill_level, int16 min_adventure_level, int8 bonus_table, float harvest1, float harvest3, float harvest5, float harvest_imbue, float harvest_rare, float harvest10, int32 harvest_coin) { + GroundSpawnEntry* entry = new GroundSpawnEntry; + entry->min_skill_level = min_skill_level; + entry->min_adventure_level = min_adventure_level; + entry->bonus_table = bonus_table; + entry->harvest1 = harvest1; + entry->harvest3 = harvest3; + entry->harvest5 = harvest5; + entry->harvest_imbue = harvest_imbue; + entry->harvest_rare = harvest_rare; + entry->harvest10 = harvest10; + entry->harvest_coin = harvest_coin; + groundspawn_entries[groundspawn_id].push_back(entry); +} + +void ZoneServer::AddGroundSpawnItem(int32 groundspawn_id, int32 item_id, int8 is_rare, int32 grid_id) { + GroundSpawnEntryItem* entry = new GroundSpawnEntryItem; + entry->item_id = item_id; + entry->is_rare = is_rare; + entry->grid_id = grid_id; + groundspawn_items[groundspawn_id].push_back(entry); + +} + +vector* ZoneServer::GetGroundSpawnEntries(int32 id){ + vector* ret = 0; + MGroundSpawnItems.lock(); + if(groundspawn_entries.count(id) > 0) + ret = &groundspawn_entries[id]; + MGroundSpawnItems.unlock(); + return ret; +} + +vector* ZoneServer::GetGroundSpawnEntryItems(int32 id){ + vector* ret = 0; + if(groundspawn_items.count(id) > 0) + ret = &groundspawn_items[id]; + return ret; +} + +// TODO - mis-named, should be DeleteGroundSpawnEntries() but this is ok for now :) +void ZoneServer::DeleteGroundSpawnItems() +{ + MGroundSpawnItems.lock(); + + map >::iterator groundspawnentry_map_itr; + vector::iterator groundspawnentry_itr; + for(groundspawnentry_map_itr = groundspawn_entries.begin(); groundspawnentry_map_itr != groundspawn_entries.end(); groundspawnentry_map_itr++) + { + for(groundspawnentry_itr = groundspawnentry_map_itr->second.begin(); groundspawnentry_itr != groundspawnentry_map_itr->second.end(); groundspawnentry_itr++) + { + safe_delete(*groundspawnentry_itr); + } + } + groundspawn_entries.clear(); + + map >::iterator groundspawnitem_map_itr; + vector::iterator groundspawnitem_itr; + for(groundspawnitem_map_itr = groundspawn_items.begin(); groundspawnitem_map_itr != groundspawn_items.end(); groundspawnitem_map_itr++) + { + for(groundspawnitem_itr = groundspawnitem_map_itr->second.begin(); groundspawnitem_itr != groundspawnitem_map_itr->second.end(); groundspawnitem_itr++) + { + safe_delete(*groundspawnitem_itr); + } + } + groundspawn_items.clear(); + + MGroundSpawnItems.unlock(); +} + +void ZoneServer::AddGroundSpawn(int32 id, GroundSpawn* spawn) { + groundspawn_list[id] = spawn; +} + +GroundSpawn* ZoneServer::GetGroundSpawn(int32 id, bool override_loading) { + if((!reloading || override_loading) && groundspawn_list.count(id) > 0) + return groundspawn_list[id]; + else + return 0; +} + +GroundSpawn* ZoneServer::GetNewGroundSpawn(int32 id) { + if(!reloading && groundspawn_list.count(id) > 0) + return groundspawn_list[id]->Copy(); + else + return 0; +} + +void ZoneServer::AddLootTable(int32 id, LootTable* table){ + loot_tables[id] = table; +} + +void ZoneServer::AddLootDrop(int32 id, LootDrop* drop){ + loot_drops[id].push_back(drop); +} + +void ZoneServer::AddSpawnLootList(int32 spawn_id, int32 id) { + spawn_loot_list[spawn_id].push_back(id); +} + +void ZoneServer::ClearSpawnLootList(int32 spawn_id) { + spawn_loot_list[spawn_id].clear(); +} + +void ZoneServer::AddLevelLootList(GlobalLoot* loot) { + level_loot_list.push_back(loot); +} + +void ZoneServer::AddRacialLootList(int16 racial_id, GlobalLoot* loot) { + racial_loot_list[racial_id].push_back(loot); +} + +void ZoneServer::AddZoneLootList(int32 zone, GlobalLoot* loot) { + zone_loot_list[zone].push_back(loot); +} + +void ZoneServer::ClearLootTables(){ + map::iterator table_itr; + for(table_itr = loot_tables.begin(); table_itr != loot_tables.end(); table_itr++){ + safe_delete(table_itr->second); + } + + map >::iterator drop_itr; + vector::iterator drop_itr2; + for(drop_itr = loot_drops.begin(); drop_itr != loot_drops.end(); drop_itr++){ + for(drop_itr2 = drop_itr->second.begin(); drop_itr2 != drop_itr->second.end(); drop_itr2++){ + safe_delete(*drop_itr2); + } + } + + vector::iterator level_itr; + for (level_itr = level_loot_list.begin(); level_itr != level_loot_list.end(); level_itr++) { + safe_delete(*level_itr); + } + + + map >::iterator race_itr; + vector::iterator race_itr2; + for (race_itr = racial_loot_list.begin(); race_itr != racial_loot_list.end(); race_itr++) { + for (race_itr2 = race_itr->second.begin(); race_itr2 != race_itr->second.end(); race_itr2++) { + safe_delete(*race_itr2); + } + } + + map >::iterator zone_itr; + vector::iterator zone_itr2; + for(zone_itr = zone_loot_list.begin(); zone_itr != zone_loot_list.end(); zone_itr++) { + for (zone_itr2 = zone_itr->second.begin(); zone_itr2 != zone_itr->second.end(); zone_itr2++) { + safe_delete(*zone_itr2); + } + } + + loot_tables.clear(); + loot_drops.clear(); + spawn_loot_list.clear(); + level_loot_list.clear(); + racial_loot_list.clear(); + zone_loot_list.clear(); +} + +vector ZoneServer::GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 spawn_level, int16 racial_id, Spawn* spawn) { + vector ret; + int32 returnValue = 0; + + if(reloading) + return ret; + + if (spawn_loot_list.count(spawn_id) > 0) + ret.insert(ret.end(), spawn_loot_list[spawn_id].begin(), spawn_loot_list[spawn_id].end()); + + if (level_loot_list.size() > 0) { + vector::iterator itr; + for (itr = level_loot_list.begin(); itr != level_loot_list.end(); itr++) { + GlobalLoot* loot = *itr; + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + returnValue = 0; // reset since this can override the database setting + if(zone_script) + { + if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_level", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0) + continue; + } + bool entryAdded = false; + if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + else { + if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + } + + if(!entryAdded && returnValue) // DB override via LUA scripting + ret.push_back(loot->table_id); + } + } + + if (racial_loot_list.count(racial_id) > 0) { + vector::iterator itr; + for (itr = racial_loot_list[racial_id].begin(); itr != racial_loot_list[racial_id].end(); itr++) { + GlobalLoot* loot = *itr; + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + returnValue = 0; // reset since this can override the database setting + if(zone_script) + { + if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_racial", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0) + continue; + } + bool entryAdded = false; + if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + else { + if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + } + + if(!entryAdded && returnValue) // DB override via LUA scripting + ret.push_back(loot->table_id); + } + } + + if (zone_loot_list.count(zone_id) > 0) { + vector::iterator itr; + for (itr = zone_loot_list[zone_id].begin(); itr != zone_loot_list[zone_id].end(); itr++) { + GlobalLoot* loot = *itr; + const char* zone_script = world.GetZoneScript(this->GetZoneID()); + returnValue = 0; // reset since this can override the database setting + if(zone_script) + { + if(lua_interface->RunZoneScriptWithReturn(zone_script, "loot_criteria_zone", spawn->GetZone(), spawn, loot->table_id, loot->minLevel, loot->maxLevel, &returnValue) && returnValue == 0) + continue; + } + bool entryAdded = false; + if (loot->minLevel == 0 && loot->maxLevel == 0 && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + else { + if (spawn_level >= loot->minLevel && spawn_level <= loot->maxLevel && (!loot->loot_tier || spawn->GetLootTier() >= loot->loot_tier) && (entryAdded = true)) // successful plan to add set entryAdded to true + ret.push_back(loot->table_id); + } + + if(!entryAdded && returnValue) // DB override via LUA scripting + ret.push_back(loot->table_id); + } + } + + return ret; +} + +vector* ZoneServer::GetLootDrops(int32 table_id){ + if(!reloading && loot_drops.count(table_id) > 0) + return &(loot_drops[table_id]); + else + return 0; +} + +LootTable* ZoneServer::GetLootTable(int32 table_id){ + return loot_tables[table_id]; +} + +void ZoneServer::AddLocationTransporter(int32 zone_id, string message, float trigger_x, float trigger_y, float trigger_z, float trigger_radius, int32 destination_zone_id, float destination_x, float destination_y, float destination_z, float destination_heading, int32 cost, int32 unique_id){ + LocationTransportDestination* loc = new LocationTransportDestination; + loc->message = message; + loc->trigger_x = trigger_x; + loc->trigger_y = trigger_y; + loc->trigger_z = trigger_z; + loc->trigger_radius = trigger_radius; + loc->destination_zone_id = destination_zone_id; + loc->destination_x = destination_x; + loc->destination_y = destination_y; + loc->destination_z = destination_z; + loc->destination_heading = destination_heading; + loc->cost = cost; + loc->unique_id = unique_id; + MTransporters.lock(); + if(location_transporters.count(zone_id) == 0) + location_transporters[zone_id] = new MutexList(); + location_transporters[zone_id]->Add(loc); + MTransporters.unlock(); +} + +void ZoneServer::AddTransporter(int32 transport_id, int8 type, string name, string message, int32 destination_zone_id, float destination_x, float destination_y, float destination_z, float destination_heading, + int32 cost, int32 unique_id, int8 min_level, int8 max_level, int32 quest_req, int16 quest_step_req, int32 quest_complete, int32 map_x, int32 map_y, int32 expansion_flag, int32 holiday_flag, int32 min_client_version, + int32 max_client_version, int32 flight_path_id, int16 mount_id, int8 mount_red_color, int8 mount_green_color, int8 mount_blue_color){ + TransportDestination* transport = new TransportDestination; + transport->type = type; + transport->display_name = name; + transport->message = message; + transport->destination_zone_id = destination_zone_id; + transport->destination_x = destination_x; + transport->destination_y = destination_y; + transport->destination_z = destination_z; + transport->destination_heading = destination_heading; + transport->cost = cost; + transport->unique_id = unique_id; + + transport->min_level = min_level; + transport->max_level = max_level; + transport->req_quest = quest_req; + transport->req_quest_step = quest_step_req; + transport->req_quest_complete = quest_complete; + + transport->map_x = map_x; + transport->map_y = map_y; + + transport->expansion_flag = expansion_flag; + transport->holiday_flag = holiday_flag; + + transport->min_client_version = min_client_version; + transport->max_client_version = max_client_version; + + transport->flight_path_id = flight_path_id; + + transport->mount_id = mount_id; + transport->mount_red_color = mount_red_color; + transport->mount_green_color = mount_green_color; + transport->mount_blue_color = mount_blue_color; + + MTransporters.lock(); + transporters[transport_id].push_back(transport); + MTransporters.unlock(); +} + +void ZoneServer::GetTransporters(vector* returnList, Client* client, int32 transport_id){ + if (!returnList) + return; + + MTransporters.lock(); + if (transporters.count(transport_id) > 0) + { + vector list; + for (int i = 0; i < transporters[transport_id].size(); i++) + { + if (transporters[transport_id][i]->min_client_version && client->GetVersion() < transporters[transport_id][i]->min_client_version) + continue; + else if (transporters[transport_id][i]->max_client_version && client->GetVersion() > transporters[transport_id][i]->max_client_version) + continue; + + if (database.CheckExpansionFlags(this, transporters[transport_id][i]->expansion_flag) && database.CheckHolidayFlags(this, transporters[transport_id][i]->holiday_flag)) + { + returnList->push_back(transporters[transport_id][i]); + } + } + } + MTransporters.unlock(); +} + +MutexList* ZoneServer::GetLocationTransporters(int32 zone_id){ + MutexList* ret = 0; + MTransporters.lock(); + if(location_transporters.count(zone_id) > 0) + ret = location_transporters[zone_id]; + MTransporters.unlock(); + return ret; +} + +void ZoneServer::DeleteGlobalTransporters(){ + MTransporters.lock(); + map >::iterator itr; + vector::iterator transport_vector_itr; + for(itr = transporters.begin(); itr != transporters.end(); itr++){ + for(transport_vector_itr = itr->second.begin(); transport_vector_itr != itr->second.end(); transport_vector_itr++){ + safe_delete(*transport_vector_itr); + } + } + map* >::iterator itr2; + for(itr2 = location_transporters.begin(); itr2 != location_transporters.end(); itr2++){ + itr2->second->clear(true); + delete itr2->second; + } + transporters.clear(); + location_transporters.clear(); + MTransporters.unlock(); +} + +void ZoneServer::DeleteGlobalSpawns() { + ClearLootTables(); + + map::iterator npc_list_iter; + for(npc_list_iter=npc_list.begin();npc_list_iter!=npc_list.end();npc_list_iter++) { + safe_delete(npc_list_iter->second); + } + npc_list.clear(); + map::iterator object_list_iter; + for(object_list_iter=object_list.begin();object_list_iter!=object_list.end();object_list_iter++) { + safe_delete(object_list_iter->second); + } + object_list.clear(); + map::iterator groundspawn_list_iter; + for(groundspawn_list_iter=groundspawn_list.begin();groundspawn_list_iter!=groundspawn_list.end();groundspawn_list_iter++) { + safe_delete(groundspawn_list_iter->second); + } + groundspawn_list.clear(); + map::iterator widget_list_iter; + for(widget_list_iter=widget_list.begin();widget_list_iter!=widget_list.end();widget_list_iter++) { + safe_delete(widget_list_iter->second); + } + widget_list.clear(); + map::iterator sign_list_iter; + for(sign_list_iter=sign_list.begin();sign_list_iter!=sign_list.end();sign_list_iter++) { + safe_delete(sign_list_iter->second); + } + sign_list.clear(); + + /*map::iterator appearance_list_iter; + for(appearance_list_iter=npc_appearance_list.begin();appearance_list_iter!=npc_appearance_list.end();appearance_list_iter++) { + safe_delete(appearance_list_iter->second); + } + npc_appearance_list.clear();*/ + + + ClearEntityCommands(); + + DeleteGroundSpawnItems(); + DeleteGlobalTransporters(); + DeleteTransporterMaps(); +} + +void ZoneServer::AddTransportMap(int32 id, string name) { + MTransportMaps.writelock(__FUNCTION__, __LINE__); + m_transportMaps[id] = name; + MTransportMaps.releasewritelock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::TransportHasMap(int32 id) { + bool ret = false; + + MTransportMaps.readlock(__FUNCTION__, __LINE__); + ret = m_transportMaps.count(id) > 0; + MTransportMaps.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +string ZoneServer::GetTransportMap(int32 id) { + string ret; + + MTransportMaps.readlock(__FUNCTION__, __LINE__); + if (m_transportMaps.count(id) > 0) + ret = m_transportMaps[id]; + MTransportMaps.releasereadlock(__FUNCTION__, __LINE__); + + return ret; +} + +void ZoneServer::DeleteTransporterMaps() { + MTransportMaps.writelock(__FUNCTION__, __LINE__); + m_transportMaps.clear(); + MTransportMaps.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ReloadSpawns() { + if (reloading) + return; + + reloading = true; + world.SetReloadingSubsystem("Spawns"); + // Let every one in the zone know what is happening + HandleBroadcast("Reloading all spawns for this zone."); + DeleteGlobalSpawns(); + Depop(false, true); +} + +void ZoneServer::SendStateCommand(Spawn* spawn, int32 state) { + vector::iterator itr; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if (client && client->GetPlayer()->WasSentSpawn(spawn->GetID())) + ClientPacketFunctions::SendStateCommand(client, client->GetPlayer()->GetIDWithPlayerSpawn(spawn), state); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddFlightPath(int32 id, FlightPathInfo* info) { + if (m_flightPaths.count(id) > 0) { + LogWrite(ZONE__ERROR, 0, "Zone", "Duplicate flight path (%u)", id); + safe_delete(info); + return; + } + + m_flightPaths[id] = info; +} + +void ZoneServer::AddFlightPathLocation(int32 id, FlightPathLocation* location) { + if (m_flightPaths.count(id) == 0) { + LogWrite(ZONE__ERROR, 0, "Zone", "There is no flight info for this route (%u)", id); + safe_delete(location); + return; + } + + m_flightPathRoutes[id].push_back(location); +} + +void ZoneServer::DeleteFlightPaths() { + map >::iterator itr; + vector::iterator itr2; + map::iterator itr3; + + for (itr = m_flightPathRoutes.begin(); itr != m_flightPathRoutes.end(); itr++) { + for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) { + safe_delete(*itr2); + } + + itr->second.clear(); + } + m_flightPathRoutes.clear(); + + for (itr3 = m_flightPaths.begin(); itr3 != m_flightPaths.end(); itr3++) { + safe_delete(itr3->second); + } + m_flightPaths.clear(); +} + +void ZoneServer::SendFlightPathsPackets(Client* client) { + // Only send a packet if there are flight paths + if (m_flightPathRoutes.size() > 0) { + PacketStruct* packet = configReader.getStruct("WS_FlightPathsMsg", client->GetVersion()); + if (packet) { + + int32 num_routes = m_flightPaths.size(); + packet->setArrayLengthByName("number_of_routes", num_routes); + packet->setArrayLengthByName("number_of_routes2", num_routes); + packet->setArrayLengthByName("number_of_routes3", num_routes); + packet->setArrayLengthByName("number_of_routes4", num_routes); + + map::iterator itr; + int32 i = 0; + for (itr = m_flightPaths.begin(); itr != m_flightPaths.end(); itr++, i++) { + packet->setArrayDataByName("route_length", m_flightPathRoutes[itr->first].size(), i); + packet->setArrayDataByName("ground_mount", itr->second->flying ? 0 : 1, i); + packet->setArrayDataByName("allow_dismount", itr->second->dismount ? 1 : 0, i); + + + packet->setSubArrayLengthByName("route_length2", m_flightPathRoutes[itr->first].size(), i); + vector::iterator itr2; + int32 j = 0; + for (itr2 = m_flightPathRoutes[itr->first].begin(); itr2 != m_flightPathRoutes[itr->first].end(); itr2++, j++) { + packet->setSubArrayDataByName("x", (*itr2)->X, i, j); + packet->setSubArrayDataByName("y", (*itr2)->Y, i, j); + packet->setSubArrayDataByName("z", (*itr2)->Z, i, j); + } + } + + client->QueuePacket(packet->serialize()); + safe_delete(packet); + } + } +} + +int32 ZoneServer::GetFlightPathIndex(int32 id) { + int32 index = 0; + map::iterator itr; + for (itr = m_flightPaths.begin(); itr != m_flightPaths.end(); itr++, index++) { + if (itr->first == id) + return index; + } + + return -1; +} + +float ZoneServer::GetFlightPathSpeed(int32 id) { + float speed = 1; + + if (m_flightPaths.count(id) > 0) + speed = m_flightPaths[id]->speed; + + return speed; +} + +void ZoneServer::ProcessSpawnConditional(int8 condition) { + MSpawnLocationList.readlock(__FUNCTION__, __LINE__); + MSpawnList.readlock(__FUNCTION__, __LINE__); + map::iterator itr; + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + if (itr->second != NULL) // null itr->second still coming into ProcessSpawnConditional + { + SpawnLocation* loc = spawn_location_list[itr->second->GetSpawnLocationID()]; + if (loc && loc->conditional > 0) { + if ((loc->conditional & condition) != condition) { + Despawn(itr->second, 0); + } + } + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + map::iterator itr2; + for (itr2 = spawn_location_list.begin(); itr2 != spawn_location_list.end(); itr2++) { + SpawnLocation* loc = itr2->second; + if (loc && loc->conditional > 0 && ((loc->conditional & condition) == condition)) + if (GetSpawnByLocationID(loc->placement_id) == NULL) + ProcessSpawnLocation(loc); + } + + MSpawnLocationList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddSpawnProximities(Spawn* newSpawn) { + Spawn* spawn = 0; + map::iterator itr; + MPendingSpawnListAdd.readlock(__FUNCTION__, __LINE__); + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn != newSpawn) { + if (newSpawn->GetDatabaseID()) + spawn->AddSpawnToProximity(newSpawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (newSpawn->GetSpawnLocationID()) + spawn->AddSpawnToProximity(newSpawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + + if (spawn->GetDatabaseID()) + newSpawn->AddSpawnToProximity(spawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (spawn->GetSpawnLocationID()) + newSpawn->AddSpawnToProximity(spawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + } + } + + list::iterator itr2; + for (itr2 = pending_spawn_list_add.begin(); itr2 != pending_spawn_list_add.end(); itr2++) { + spawn = *itr2; + if (spawn && spawn != newSpawn) { + if (newSpawn->GetDatabaseID()) + spawn->AddSpawnToProximity(newSpawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (newSpawn->GetSpawnLocationID()) + spawn->AddSpawnToProximity(newSpawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + + if (spawn->GetDatabaseID()) + newSpawn->AddSpawnToProximity(spawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (spawn->GetSpawnLocationID()) + newSpawn->AddSpawnToProximity(spawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + MPendingSpawnListAdd.releasereadlock(__FUNCTION__, __LINE__); +} + +// we only call this inside a write lock +void ZoneServer::RemoveSpawnProximities(Spawn* oldSpawn) { + Spawn* spawn = 0; + map::iterator itr; + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn != oldSpawn) { + if (oldSpawn->GetDatabaseID()) + spawn->RemoveSpawnFromProximity(oldSpawn->GetDatabaseID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_DATABASE_ID); + if (oldSpawn->GetSpawnLocationID()) + spawn->RemoveSpawnFromProximity(oldSpawn->GetSpawnLocationID(), Spawn::SpawnProximityType::SPAWNPROXIMITY_LOCATION_ID); + + // don't need to remove oldSpawn proximities, we clear them all out + } + } +} + +void ZoneServer::SetSpawnScript(SpawnEntry* entry, Spawn* spawn) +{ + if (!entry || !spawn) + return; + + const char* script = 0; + + for (int x = 0; x < 3; x++) + { + switch (x) + { + case 0: + script = world.GetSpawnEntryScript(entry->spawn_entry_id); + break; + case 1: + script = world.GetSpawnLocationScript(entry->spawn_location_id); + break; + case 2: + script = world.GetSpawnScript(entry->spawn_id); + break; + } + + if (script && lua_interface && lua_interface->GetSpawnScript(script) != 0) + { + spawn->SetSpawnScript(string(script)); + break; + } + } +} + +vector ZoneServer::GetHouseItems(Client* client) +{ + if (!client->GetCurrentZone()->GetInstanceID() || !client->HasOwnerOrEditAccess()) + return std::vector(); + + PacketStruct* packet = configReader.getStruct("WS_HouseItemsList", client->GetVersion()); + + std::vector items; + map::iterator itr; + Spawn* spawn = 0; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn->IsObject() && spawn->GetPickupItemID()) + { + HouseItem tmpItem; + tmpItem.item_id = spawn->GetPickupItemID(); + tmpItem.unique_id = spawn->GetPickupUniqueItemID(); + tmpItem.spawn_id = spawn->GetID(); + tmpItem.item = master_item_list.GetItem(spawn->GetPickupItemID()); + + if (!tmpItem.item) + continue; + + items.push_back(tmpItem); + } + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + + return items; +} + +void ZoneServer::SendHouseItems(Client* client) +{ + if (!client->GetCurrentZone()->GetInstanceID() || !client->HasOwnerOrEditAccess()) + return; + + PacketStruct* packet = configReader.getStruct("WS_HouseItemsList", client->GetVersion()); + + if(!packet) { + return; + } + + std::vector items = GetHouseItems(client); + + // setting this to 1 puts it on the door widget + packet->setDataByName("is_widget_door", 1); + packet->setArrayLengthByName("num_items", items.size()); + for (int i = 0; i < items.size(); i++) + { + HouseItem tmpItem = items[i]; + packet->setArrayDataByName("unique_id", tmpItem.unique_id, i); // unique_id is in fact the item_id... + packet->setArrayDataByName("item_name", tmpItem.item->name.c_str(), i); + packet->setArrayDataByName("status_reduction", tmpItem.item->houseitem_info->status_rent_reduction, i); + + // location, 0 = floor, 1 = ceiling + //packet->setArrayDataByName("location", 1, i, 0); + + // item_state int8 + // 0 = normal (cannot pick up item / move item / toggle visibility) + // 1 = virtual (toggle visibility available, no move item) + // 2 = hidden (cannot pick up item / move item / toggle visibility) + // 3 = virtual/hidden/toggle visibility + // 4 = none (cannot pick up item / move item / toggle visibility) + // 5 = none, toggle visibility (cannot pick up item / move item) + // 8 = none (cannot pick up item / move item / toggle visibility) + //packet->setArrayDataByName("item_state", tmpvalue, i, 0); + + // makes it so we don't have access to move item/retrieve item + // cannot use in conjunction with ui_tab_flag1/ui_tab_flag2 + //packet->setArrayDataByName("tradeable", 1, i); + //packet->setArrayDataByName("item_description", "failboat", i); + + // access to move item/retrieve item, do not use in conjunction with tradeable + packet->setArrayDataByName("ui_tab_flag1", 1, i, 0); + packet->setArrayDataByName("ui_tab_flag2", 1, i, 0); + + // both of these can serve as description fields (only one should be used they populate the same area below the item name) + //packet->setArrayDataByName("first_item_description", "test", i); + //packet->setArrayDataByName("second_item_description", "Description here!", i); + + packet->setArrayDataByName("icon", tmpItem.item->GetIcon(client->GetVersion()), i); + } + + EQ2Packet* pack = packet->serialize(); + client->QueuePacket(pack); + safe_delete(packet); +} + +Spawn* ZoneServer::GetSpawnFromUniqueItemID(int32 unique_id) +{ + if (!GetInstanceID() || GetInstanceType() != Instance_Type::PERSONAL_HOUSE_INSTANCE) + return nullptr; + + map::iterator itr; + Spawn* spawn = 0; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { + spawn = itr->second; + if (spawn && spawn->IsObject() && spawn->GetPickupUniqueItemID() == unique_id) + { + Spawn* tmpSpawn = spawn; + MSpawnList.releasereadlock(); + return tmpSpawn; + } + } + MSpawnList.releasereadlock(); + + return nullptr; +} + +void ZoneServer::AddPendingSpawnRemove(int32 id) +{ + MPendingSpawnRemoval.writelock(__FUNCTION__, __LINE__); + m_pendingSpawnRemove.insert(make_pair(id,true)); + MPendingSpawnRemoval.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::ProcessSpawnRemovals() +{ + MSpawnList.writelock(__FUNCTION__, __LINE__); + MPendingSpawnRemoval.writelock(__FUNCTION__, __LINE__); + if (m_pendingSpawnRemove.size() > 0) { + map::iterator itr2; + for (itr2 = m_pendingSpawnRemove.begin(); itr2 != m_pendingSpawnRemove.end(); itr2++) { + spawn_list.erase(itr2->first); + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(itr2->first); + + std::map::iterator hsmitr = housing_spawn_map.find(itr2->first); + if(hsmitr != housing_spawn_map.end()) { + subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].erase(hsmitr->second); + housing_spawn_map.erase(hsmitr); + } + } + + m_pendingSpawnRemove.clear(); + } + MPendingSpawnRemoval.releasewritelock(__FUNCTION__, __LINE__); + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); +} + +void ZoneServer::AddSpawnToGroup(Spawn* spawn, int32 group_id) +{ + if( spawn->GetSpawnGroupID() > 0 ) + spawn->RemoveSpawnFromGroup(); + MutexList* groupList = &spawn_group_map.Get(group_id); + MutexList::iterator itr2 = groupList->begin(); + + while(itr2.Next()) + { + Spawn* groupSpawn = GetSpawnByID(itr2.value); + if(groupSpawn) + { + // found existing group member to add it in + spawn->AddSpawnToGroup(groupSpawn); + break; + } + } + groupList->Add(spawn->GetID()); + spawn->SetSpawnGroupID(group_id); +} + +void ZoneServer::QueueStateCommandToClients(int32 spawn_id, int32 state) +{ + if(spawn_id < 1) + return; + + MLuaQueueStateCmd.lock(); + lua_queued_state_commands.insert(make_pair(spawn_id, state)); + MLuaQueueStateCmd.unlock(); +} + +void ZoneServer::QueueDefaultCommand(int32 spawn_id, std::string command, float distance) +{ + if(spawn_id < 1) + return; + + MLuaQueueStateCmd.lock(); + lua_spawn_update_command[spawn_id].insert(make_pair(command,distance)); + MLuaQueueStateCmd.unlock(); +} + +void ZoneServer::ProcessQueuedStateCommands() // in a client list lock only +{ + vector::iterator itr; + + MLuaQueueStateCmd.lock(); + + if(lua_queued_state_commands.size() > 0) + { + std::map::iterator statecmds; + for(statecmds = lua_queued_state_commands.begin(); statecmds != lua_queued_state_commands.end(); statecmds++) + { + Spawn* spawn = GetSpawnByID(statecmds->first, false); + if(!spawn) + continue; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if (client && client->GetPlayer()->WasSentSpawn(spawn->GetID())) + ClientPacketFunctions::SendStateCommand(client, client->GetPlayer()->GetIDWithPlayerSpawn(spawn), statecmds->second); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } + lua_queued_state_commands.clear(); + } + + if(lua_spawn_update_command.size() > 0) + { + std::map>::iterator updatecmds; + for(updatecmds = lua_spawn_update_command.begin(); updatecmds != lua_spawn_update_command.end(); updatecmds++) + { + Spawn* spawn = GetSpawnByID(updatecmds->first, false); + if(!spawn) + continue; + + std::map::iterator innermap; + for(innermap = lua_spawn_update_command[updatecmds->first].begin(); innermap != lua_spawn_update_command[updatecmds->first].end(); innermap++) + { + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if (client && client->GetPlayer()->WasSentSpawn(spawn->GetID())) + client->SendDefaultCommand(spawn, innermap->first.c_str(), innermap->second); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } + lua_spawn_update_command[updatecmds->first].clear(); + } + lua_spawn_update_command.clear(); + } + MLuaQueueStateCmd.unlock(); +} + +void ZoneServer::RemoveClientsFromZone(ZoneServer* zone) { + vector::iterator itr; + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if(client->GetCurrentZone() == zone) { + client->SetCurrentZone(nullptr); + } + if(client->GetZoningDestination() == zone) { + client->SetZoningDestination(nullptr); + } + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); +} + +void ZoneServer::SendSubSpawnUpdates(SUBSPAWN_TYPES subtype) { + std::map::iterator subitr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for(subitr = subspawn_list[subtype].begin(); subitr != subspawn_list[subtype].end(); subitr++) { + subitr->second->changed = true; + subitr->second->info_changed = true; + AddChangedSpawn(subitr->second); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} + +bool ZoneServer::HouseItemSpawnExists(int32 item_id) { + bool exists = false; + std::map::iterator subitr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + subitr = subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].find(item_id); + if(subitr != subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].end()) { + exists = true; + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + return exists; +} + +void ZoneServer::ProcessPendingSpawns() { + MPendingSpawnListAdd.writelock(__FUNCTION__, __LINE__); + list::iterator itr2; + for (itr2 = pending_spawn_list_add.begin(); itr2 != pending_spawn_list_add.end(); itr2++) { + Spawn* spawn = *itr2; + + MSpawnList.writelock(__FUNCTION__, __LINE__); + if (spawn) + spawn_list[spawn->GetID()] = spawn; + + if(spawn->IsCollector()) { + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].insert(make_pair(spawn->GetID(),spawn)); + } + if(spawn->GetPickupItemID()) { + subspawn_list[SUBSPAWN_TYPES::HOUSE_ITEM_SPAWN].insert(make_pair(spawn->GetPickupItemID(),spawn)); + housing_spawn_map.insert(make_pair(spawn->GetID(), spawn->GetPickupItemID())); + } + MSpawnList.releasewritelock(__FUNCTION__, __LINE__); + + CheckSpawnRange(spawn); + } + + pending_spawn_list_add.clear(); + MPendingSpawnListAdd.releasewritelock(__FUNCTION__, __LINE__); + spawn_check_add.Trigger(); +} + +void ZoneServer::AddSpawnToGrid(Spawn* spawn, int32 grid_id) { + if(spawn->GetID() == 0 || spawn->IsDeletedSpawn()) + return; + + MGridMaps.lock_shared(); + std::map::iterator grids = grid_maps.find(grid_id); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock(); + grids->second->spawns.insert(make_pair(spawn->GetID(), spawn)); + grids->second->MSpawns.unlock(); + } + else { + MGridMaps.unlock_shared(); + MGridMaps.lock(); + GridMap* gm = new GridMap; + gm->grid_id = grid_id; + gm->spawns.insert(make_pair(spawn->GetID(), spawn)); + grid_maps.insert(make_pair(grid_id, gm)); + MGridMaps.unlock(); + return; + } + + MGridMaps.unlock_shared(); +} + +void ZoneServer::RemoveSpawnFromGrid(Spawn* spawn, int32 grid_id) { + std::shared_lock lock(MGridMaps); + std::map::iterator grids = grid_maps.find(grid_id); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock(); + if(grids->second->spawns.count(spawn->GetID()) > 0) { + grids->second->spawns.erase(spawn->GetID()); + } + grids->second->MSpawns.unlock(); + } +} + +int32 ZoneServer::GetSpawnCountInGrid(int32 grid_id) { + int32 count = 0; + std::shared_lock lock(MGridMaps); + std::map::iterator grids = grid_maps.find(grid_id); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + count = grids->second->spawns.size(); + grids->second->MSpawns.unlock_shared(); + } + + return count; +} + +void ZoneServer::SendClientSpawnListInGrid(Client* client, int32 grid_id){ + std::shared_lock lock(MGridMaps); + + Spawn* spawn = nullptr; + client->Message(CHANNEL_COLOR_RED, "Grid ID %u has %u spawns.", grid_id, GetSpawnCountInGrid(grid_id)); + std::map::iterator grids = grid_maps.find(grid_id); + if(grids != grid_maps.end()) { + grids->second->MSpawns.lock_shared(); + typedef map SpawnMapType; + for( SpawnMapType::iterator it = grids->second->spawns.begin(); it != grids->second->spawns.end(); ++it ) { + spawn = it->second; + client->Message(CHANNEL_COLOR_YELLOW, "Spawn %s (%u), Loc X/Y/Z: %f/%f/%f.", spawn->GetName(), spawn->GetID(), spawn->GetX(), spawn->GetY(), spawn->GetZ()); + } + grids->second->MSpawns.unlock_shared(); + } +} + +void ZoneServer::AddIgnoredWidget(int32 id) { + std::unique_lock lock(MIgnoredWidgets); + if(ignored_widgets.find(id) == ignored_widgets.end()) { + ignored_widgets.insert(make_pair(id,true)); + } +} diff --git a/source/WorldServer/zoneserver.h b/source/WorldServer/zoneserver.h new file mode 100644 index 0000000..047b8e0 --- /dev/null +++ b/source/WorldServer/zoneserver.h @@ -0,0 +1,1168 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef ZONESERVER_H +#define ZONESERVER_H + +#include +#include + +#include "../common/linked_list.h" +#include "../common/timer.h" +#include "../common/queue.h" +#include "../common/servertalk.h" +#include "../common/TCPConnection.h" +#include "WorldTCPConnection.h" +#include "../common/Mutex.h" +#include "../common/DataBuffer.h" +#include "net.h" +#include "Player.h" +#include "Combat.h" +#include +#include +#include +#include "MutexList.h" +#include "MutexMap.h" +#include "MutexVector.h" +#include "NPC.h" +#include "Widget.h" +#include "Object.h" +#include "GroundSpawn.h" +#include "Sign.h" +#include "Zone/map.h" +#include "Zone/pathfinder_interface.h" +#include "Zone/mob_movement_manager.h" +#include "Zone/region_map.h" + +extern NetConnection net; // needs to be here or compile errors in commands.cpp +class SpellProcess; +class TradeskillMgr; +class Bot; + +#define EXPANSION_UNKNOWN 1 +#define EXPANSION_UNKNOWN2 64 +#define EXPANSION_UNKNOWN3 128 +#define EXPANSION_UNKNOWN4 256 +#define EXPANSION_UNKNOWN5 512 +#define EXPANSION_DOF 1024 +#define EXPANSION_KOS 2048 +#define EXPANSION_EOF 4096 +#define EXPANSION_ROK 8192 +#define EXPANSION_TSO 16384 +#define EXPANSION_DOV 65536 // This enables DoV and CoE AA tree's lower values disable both trees +// Can't verify these 3 values +// 32768 - SF +// 131072 - AoD + +#define SPAWN_SCRIPT_SPAWN 0 +#define SPAWN_SCRIPT_RESPAWN 1 +#define SPAWN_SCRIPT_ATTACKED 2 +#define SPAWN_SCRIPT_TARGETED 3 +#define SPAWN_SCRIPT_HAILED 4 +#define SPAWN_SCRIPT_DEATH 5 +#define SPAWN_SCRIPT_KILLED 6 +#define SPAWN_SCRIPT_AGGRO 7 +#define SPAWN_SCRIPT_HEALTHCHANGED 8 +#define SPAWN_SCRIPT_RANDOMCHAT 9 +#define SPAWN_SCRIPT_CONVERSATION 10 +#define SPAWN_SCRIPT_TIMER 11 +#define SPAWN_SCRIPT_CUSTOM 12 +#define SPAWN_SCRIPT_HAILED_BUSY 13 +#define SPAWN_SCRIPT_CASTED_ON 14 +#define SPAWN_SCRIPT_AUTO_ATTACK_TICK 15 +#define SPAWN_SCRIPT_COMBAT_RESET 16 +#define SPAWN_SCRIPT_GROUP_DEAD 17 +#define SPAWN_SCRIPT_HEAR_SAY 18 +#define SPAWN_SCRIPT_PRESPAWN 19 +#define SPAWN_SCRIPT_USEDOOR 20 +#define SPAWN_SCRIPT_BOARD 21 +#define SPAWN_SCRIPT_DEBOARD 22 + +#define SPAWN_CONDITIONAL_NONE 0 +#define SPAWN_CONDITIONAL_DAY 1 +#define SPAWN_CONDITIONAL_NIGHT 2 +#define SPAWN_CONDITIONAL_NOT_RAINING 4 +#define SPAWN_CONDITIONAL_RAINING 8 + +#define MAX_REVIVEPOINT_DISTANCE 1000 + +/* JA: TODO Turn into R_World Rules */ +#define SEND_SPAWN_DISTANCE 250 /* when spawns appear visually to the client */ +#define HEAR_SPAWN_DISTANCE 30 /* max distance a client can be from a spawn to 'hear' it */ +#define MAX_CHASE_DISTANCE 80 +#define REMOVE_SPAWN_DISTANCE 300 // increased distance between send/remove is ideal, this makes sure there is no overlap if a 'fast' client (AKA GM warp speed) + +#define TRACKING_STOP 0 +#define TRACKING_START 1 +#define TRACKING_UPDATE 2 +#define TRACKING_CLOSE_WINDOW 3 + +#define TRACKING_TYPE_ENTITIES 1 +#define TRACKING_TYPE_HARVESTABLES 2 + +#define TRACKING_SPAWN_TYPE_PC 0 +#define TRACKING_SPAWN_TYPE_NPC 1 + +#define WAYPOINT_CATEGORY_GROUP 0 +#define WAYPOINT_CATEGORY_QUESTS 1 +#define WAYPOINT_CATEGORY_PEOPLE 2 +#define WAYPOINT_CATEGORY_PLACES 3 +#define WAYPOINT_CATEGORY_USER 4 +#define WAYPOINT_CATEGORY_DIRECTIONS 5 +#define WAYPOINT_CATEGORY_TRACKING 6 +#define WAYPOINT_CATEGORY_HOUSES 7 +#define WAYPOINT_CATEGORY_MAP 8 + +struct PlayerProximity{ + float distance; + string in_range_lua_function; + string leaving_range_lua_function; + map clients_in_proximity; +}; + +struct LocationProximity { + float x; + float y; + float z; + float max_variation; + string in_range_lua_function; + string leaving_range_lua_function; + map clients_in_proximity; +}; + +struct LocationGrid { + int32 id; + int32 grid_id; + string name; + bool include_y; + bool discovery; + MutexList locations; + MutexMap players; +}; + +struct GridMap { + int32 grid_id; + std::map spawns; + mutable std::shared_mutex MSpawns; +}; + +struct TrackedSpawn { + Spawn* spawn; + float distance; +}; + +struct HouseItem { + int32 spawn_id; + int32 item_id; + int32 unique_id; + Item* item; +}; + +class Widget; +class Client; +class Sign; +class Object; +class GroundSpawn; +struct GroundSpawnEntry; +struct GroundSpawnEntryItem; +struct LootTable; +struct LootDrop; +struct GlobalLoot; +struct TransportDestination; +struct LocationTransportDestination; + +#ifdef WIN32 + void ZoneLoop(void *tmp); + void SpawnLoop(void *tmp); + void SendInitialSpawns(void *tmp); + void SendLevelChangedSpawns(void*tmp); +#else + void *ZoneLoop(void *tmp); + void *SpawnLoop(void *tmp); + void *SendInitialSpawns(void *tmp); + void *SendLevelChangedSpawns(void *tmp); +#endif +using namespace std; +struct RevivePoint{ + int32 id; + int32 zone_id; //usually this zone, but not always + string location_name; + float x; + float y; + float z; + float heading; + bool always_included; +}; + +struct SpawnScriptTimer { + int32 timer; + int32 spawn; + int32 player; + string function; + int32 current_count; + int32 max_count; +}; + +enum Instance_Type { + NONE, + GROUP_LOCKOUT_INSTANCE, + GROUP_PERSIST_INSTANCE, + RAID_LOCKOUT_INSTANCE, + RAID_PERSIST_INSTANCE, + SOLO_LOCKOUT_INSTANCE, + SOLO_PERSIST_INSTANCE, + TRADESKILL_INSTANCE, // allows anyone to enter, server searches for the first instance that is available + PUBLIC_INSTANCE, // same as tradeskill, except dead spawns are tracked + PERSONAL_HOUSE_INSTANCE, + GUILD_HOUSE_INSTANCE, + QUEST_INSTANCE +}; + +struct FlightPathInfo { + float speed; + bool flying; + bool dismount; +}; + +struct FlightPathLocation { + float X; + float Y; + float Z; +}; + +struct ZoneInfoSlideStructInfo { + float unknown1[2]; + int32 unknown2[2]; + int32 unknown3; + int32 unknown4; + char slide[128]; + char voiceover[128]; + int32 key1; + int32 key2; +}; +struct ZoneInfoSlideStructTransitionInfo { + int32 transition_x; + int32 transition_y; + float transition_zoom; + float transition_time; +}; +struct ZoneInfoSlideStruct { + ZoneInfoSlideStructInfo* info; + vector slide_transition_info; +}; + +enum SUBSPAWN_TYPES { + COLLECTOR = 0, + HOUSE_ITEM_SPAWN = 1, + MAX_SUBSPAWN_TYPE = 20 +}; + +// need to attempt to clean this up and add xml comments, remove unused code, find a logical way to sort the functions maybe by get/set/process/add etc... +class ZoneServer { +public: + ZoneServer(const char* file); + ~ZoneServer(); + + void IncrementIncomingClients(); + void DecrementIncomingClients(); + void Init(); + bool Process(); + bool SpawnProcess(); + + ZoneInfoSlideStruct* GenerateSlideStruct(float unknown1a, float unknown1b, int32 unknown2a, int32 unknown2b, int32 unknown3, int32 unknown4, const char* slide, const char* voiceover, int32 key1, int32 key2); + void AddZoneInfoSlideStructTransitionInfo(ZoneInfoSlideStruct* info, int32 x, int32 y, float zoom, float transition_time); + vector* GenerateTutorialSlides(); + + void LoadRevivePoints(vector* revive_points); + vector* GetRevivePoints(Client* client); + RevivePoint* GetRevivePoint(int32 id); + + void AddClient(Client* client); + + void SimpleMessage(int8 type, const char* message, Spawn* from, float distance, bool send_to_sender = true); + void HandleChatMessage(Spawn* from, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, bool show_bubble = true, int32 language = 0); + void HandleChatMessage(Client* client, Spawn* from, const char* to, int16 channel, const char* message, float distance = 0, const char* channel_name = 0, bool show_bubble = true, int32 language = 0); + void HandleBroadcast(const char* message); + void HandleAnnouncement(const char* message); + + int16 SetSpawnTargetable(Spawn* spawn, float distance); + int16 SetSpawnTargetable(int32 spawn_id); + void ApplySetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value); + void SetSpawnCommand(Spawn* spawn, int8 type, char* value, Client* client = 0); + void SetSpawnCommand(int32 spawn_id, int8 type, char* value, Client* client = 0); + void AddLoot(NPC* npc, Spawn* killer = nullptr, GroupLootMethod loot_method = GroupLootMethod::METHOD_FFA, int8 item_rarity = 0, int32 group_id = 0); + + NPC* AddNPCSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + Object* AddObjectSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + GroundSpawn* AddGroundSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + Widget* AddWidgetSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + Sign* AddSignSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry); + void AddSpawn(Spawn* spawn); + void RemoveDeadEnemyList(Spawn* spawn); + void RemoveDeadSpawn(Spawn* spawn); + + void AddSpawnGroupLocation(int32 group_id, int32 location_id, int32 spawn_location_id); + void AddSpawnGroupAssociation(int32 group_id1, int32 group_id2); + + void AddSpawnGroupChance(int32 group_id, float percent); + + void RemoveSpawn(Spawn* spawn, bool delete_spawn = true, bool respawn = true, bool lock = true, bool erase_from_spawn_list = true, bool lock_spell_process = false); + void ProcessSpawnLocations(); + void SendQuestUpdates(Client* client, Spawn* spawn = 0); + + EQ2Packet* GetZoneInfoPacket(Client* client); + Spawn* FindSpawn(Player* searcher, const char* name); + bool CallSpawnScript(Spawn* npc, int8 type, Spawn* spawn = 0, const char* message = 0, bool is_door_open = false, sint32 input_value = 0, sint32* return_value = 0); + void SendSpawnVisualState(Spawn* spawn, int16 type); + void SendSpellFailedPacket(Client* client, int16 error); + void SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool fizzle=false); + void HandleEmote(Spawn* originator, string name); + Spawn* GetSpawnByDatabaseID(int32 id); + Spawn* GetSpawnByID(int32 id, bool spawnListLocked=false); + + void PlaySoundFile(Client* client, const char* name, float origin_x, float origin_y, float origin_z); + void SendZoneSpawns(Client* client); + void StartZoneInitialSpawnThread(Client* client); + void SendSpawnChanges(); + void SendSpawnChanges(Spawn* spawn); + void SendSpawnChanges(Spawn* spawn, Client* client, bool override_changes = false, bool override_vis_changes = false); + void SendSpawnChangesByDBID(int32 spawn_id, Client* client, bool override_changes = false, bool override_vis_changes = false); + void SendPlayerPositionChanges(Player* player); + + void UpdateVitality(float amount); + + vector GetPlayers(); + + void KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet = true, int8 type = 0, int8 damage_type = 0, int16 kill_blow_type = 0); + + void SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name); + void SendHealPacket(Spawn* caster, Spawn* target, int16 type, int32 heal_amt, const char* spell_name); + + void SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override = 0, int16 casttime_override = 0xFFFF); + void SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* caster = 0); + void SendCastEntityCommandPacket(EntityCommand* entity_command, int32 spawn_id, int32 target_id); + void TriggerCharSheetTimer(); + + /// Sends the game time packet to all connected clients + void SendTimeUpdateToAllClients(); + void AddWidgetTimer(Spawn* widget, float time); + bool HasWidgetTimer(Spawn* widget); + + void Despawn(Spawn* spawn, int32 timer); + + void RepopSpawns(Client* client, Spawn* spawn); + bool AddCloseSpawnsToSpawnGroup(Spawn* spawn, float radius); + void Depop(bool respawns = false, bool repop = false); + + Spawn* GetSpawnGroup(int32 id); + + void AddEnemyList(NPC* npc); + + void ReloadClientQuests(); + void SendAllSpawnsForLevelChange(Client* client); + void SendAllSpawnsForSeeInvisChange(Client* client); + void SendAllSpawnsForVisChange(Client* client, bool limitToEntities=true); + + void AddLocationGrid(LocationGrid* grid); + void RemoveLocationGrids(); + + void DeleteTransporters(); + + void CheckTransporters(Client* client); + + void WritePlayerStatistics(); + + bool SendRadiusSpawnInfo(Client* client, float radius); + void FindSpawn(Client* client, char* regSearchStr); + + volatile bool spawnthread_active; + volatile bool combatthread_active; + volatile int8 initial_spawn_threads_active; + volatile bool client_thread_active; + void AddChangedSpawn(Spawn* spawn); + + void AddDamagedSpawn(Spawn* spawn); + + void AddDrowningVictim(Player* player); + void RemoveDrowningVictim(Player* player); + Client* GetDrowningVictim(Player* player); + + void DeleteSpellProcess(); + void LoadSpellProcess(); + void LockAllSpells(Player* player); + void UnlockAllSpells(Player* player); + void RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, bool delete_recast = true, bool call_expire_function = true, bool lock_spell_process = false); + void Interrupted(Entity* caster, Spawn* interruptor, int16 error_code, bool cancel = false, bool from_movement = false); + Spell* GetSpell(Entity* caster); + void ProcessSpell(Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false, LuaSpell* customSpell = 0, int16 custom_cast_time = 0, bool in_heroic_opp = false); + void ProcessEntityCommand(EntityCommand* entity_command, Entity* caster, Spawn* target, bool lock = true); + void AddPlayerTracking(Player* player); + void RemovePlayerTracking(Player* player, int8 mode); + + void SendUpdateTitles(Client *client, Title *suffix = 0, Title *prefix = 0); + void SendUpdateTitles(Spawn *spawn, Title *suffix = 0, Title *prefix = 0); + + void RemoveTargetFromSpell(LuaSpell* spell, Spawn* target, bool remove_caster = false); + + /// Set the rain levl in the zone + /// Level of rain in the zone 0.0 - 1.1 (rain starts at 0.76) + void SetRain(float val); + + /// Sets the wind direction + /// Direction in degrees to set the wind + void SetWind(float val); + + /// Handles zone-wide weather changes + void ProcessWeather(); + + Spawn* GetClosestTransportSpawn(float x, float y, float z); + Spawn* GetTransportByRailID(sint64 rail_id); + + void ResurrectSpawn(Spawn* spawn, Client* client); + + void HidePrivateSpawn(Spawn* spawn); + Client* GetClientByName(char* name); + Client* GetClientByCharID(int32 charid); + + bool SetPlayerTargetByName(Client* originator, char* targetName, float distance); + std::vector GetGridsByLocation(Spawn* originator, glm::vec3 loc, float distance); + /// Gets spawns for a true AoE spell + std::vector> GetAttackableSpawnsByDistance(Spawn* spawn, float distance); + + // Comparator function to sort by the value (second element of the pair) + static bool compareByValue(const std::pair& a, const std::pair& b) { + return a.second < b.second; + } + + void StartZoneSpawnsForLevelThread(Client* client); + + void SendDispellPacket(Entity* caster, Spawn* target, string dispell_name, string spell_name, int8 dispell_type); + + void SetupInstance(int32 createdInstanceID=0); + void SendUpdateDefaultCommand(Spawn* spawn, const char* command, float distance, Spawn* toplayer = NULL); + + map* GetSpawnLocationsByGroup(int32 group_id); + + IPathfinder* pathing; + MobMovementManager* movementMgr; + + /**************************************************** + Following functions are only used for LUA commands + ****************************************************/ + + int32 GetClosestLocation(Spawn* spawn); + Spawn* GetClosestSpawn(Spawn* spawn, int32 spawn_id); + SpawnLocation* GetSpawnLocation(int32 id); + void PlayFlavor(Client* client, Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language); + void PlayVoice(Client* client, Spawn* spawn, const char* mp3, int32 key1, int32 key2); + void PlayFlavor(Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language); + void PlayFlavorID(Spawn* spawn, int8 type, int32 id, int16 index, int8 language); + void PlayVoice(Spawn* spawn, const char* mp3, int32 key1, int32 key2); + void SendThreatPacket(Spawn* caster, Spawn* target, int32 threat_amt, const char* spell_name); + void SendYellPacket(Spawn* yeller, float max_distance=50.0f); + void KillSpawnByDistance(Spawn* spawn, float max_distance, bool include_players = false, bool send_packet = false); + void SpawnSetByDistance(Spawn* spawn, float max_distance, string field, string value); + void AddSpawnScriptTimer(SpawnScriptTimer* timer); + Spawn* GetSpawnByLocationID(int32 location_id); + void AddMovementNPC(Spawn* spawn); + void AddPlayerProximity(Spawn* spawn, float distance, string in_range_function, string leaving_range_function); + void AddLocationProximity(float x, float y, float z, float max_variation, string in_range_function, string leaving_range_function); + void PlayAnimation(Spawn* spawn, int32 visual_state, Spawn* spawn2 = 0, int8 type = 1); + void AddTransportSpawn(Spawn* spawn); + vector GetSpawnsByID(int32 id); + vector GetSpawnsByRailID(sint64 rail_id); + void RemovePlayerPassenger(int32 char_id); + bool IsDusk() { return isDusk; } // never used, probably meant for lua though + + + /**************************************************** + Following functions are all contained in the header + ****************************************************/ + + inline const char* GetZoneName() { return zone_name; } + void SetZoneName(char* new_zone) { + if( strlen(new_zone) >= sizeof zone_name ) + return; + strcpy(zone_name, new_zone); + } + inline const char* GetZoneFile() { return zone_file; } + void SetZoneFile(char* zone) { + if (strlen(zone) >= sizeof zone_file) + return; + strcpy(zone_file, zone); + } + inline const char* GetZoneSkyFile() { return zonesky_file; } + void SetZoneSkyFile(char* zone) { + if (strlen(zone) >= sizeof zonesky_file) + return; + strcpy(zonesky_file, zone); + } + inline const char* GetZoneDescription() { return zone_description; } + void SetZoneDescription(char* desc) { + if( strlen(desc) >= sizeof zone_description ) + return; + strcpy(zone_description, desc); + } + + void SetUnderWorld(float under){ underworld = under; } + float GetUnderWorld(){ return underworld; } + + inline int32 GetZoneID() { return zoneID; } + void SetZoneID(int32 new_id){ zoneID = new_id; } + + inline bool IsCityZone() { return cityzone; } + inline bool AlwaysLoaded() { return always_loaded; } + void SetCityZone(bool val) { cityzone = val; } + void SetAlwaysLoaded(bool val) { always_loaded = val; } + inline int32& NumPlayers() { return pNumPlayers; } + void SetMinimumStatus(sint16 minStatus) { minimumStatus = minStatus; } + sint16 GetMinimumStatus() { return minimumStatus; } + void SetMinimumLevel(int16 minLevel) { minimumLevel = minLevel; } + void SetMaximumLevel(int16 maxLevel) { maximumLevel = maxLevel; } + void SetMinimumVersion(int16 minVersion) { minimumVersion = minVersion; } + int16 GetMinimumLevel() { return minimumLevel; } + int16 GetMaximumLevel() { return maximumLevel; } + int16 GetMinimumVersion() { return minimumVersion; } + inline bool GetZoneLockState() { return locked; } // JA: /zone lock|unlock + void SetZoneLockState(bool lock_state) { locked = lock_state; } // JA: /zone lock|unlock + int32 GetInstanceID() { return instanceID; } + bool IsInstanceZone() { return isInstance; } + + void SetShutdownTimer(int val){ + shutdownTimer.SetTimer(val*1000); + } + + void AddSpawnLocation(int32 id, SpawnLocation* spawnlocation) { + MSpawnLocationList.writelock(__FUNCTION__, __LINE__); + if (spawn_location_list.count(id) > 0) + safe_delete(spawn_location_list[id]); + spawn_location_list[id] = spawnlocation; + MSpawnLocationList.releasewritelock(__FUNCTION__, __LINE__); + } + + void SetInstanceType(int16 type) { InstanceType = (Instance_Type)type; if(type>0)isInstance=true; else isInstance=false; } + Instance_Type GetInstanceType() { return InstanceType; } + float GetSafeX(){ return safe_x; } + float GetSafeY(){ return safe_y; } + float GetSafeZ(){ return safe_z; } + float GetSafeHeading() { return safe_heading; } + void SetSafeX(float val){ safe_x = val; } + void SetSafeY(float val){ safe_y = val; } + void SetSafeZ(float val){ safe_z = val; } + void SetSafeHeading(float val) { safe_heading = val; } + float GetXPModifier() { return xp_mod; } + void SetXPModifier(float val) { xp_mod = val; } + void SetZoneMOTD(string z_motd) { zone_motd = z_motd; } + string GetZoneMOTD() { return zone_motd; } + bool isZoneShuttingDown ( ) { return zoneShuttingDown; } + void Shutdown(){ zoneShuttingDown = true; } + int32 GetClientCount(){ return clients.size(); } + int32 GetDefaultLockoutTime() { return def_lockout_time; } + int32 GetDefaultReenterTime() { return def_reenter_time; } + int32 GetDefaultResetTime() { return def_reset_time; } + int8 GetForceGroupZoneOption() { return group_zone_option; } + void SetDefaultLockoutTime(int32 val) { def_lockout_time = val; } + void SetDefaultReenterTime(int32 val) { def_reenter_time = val; } + void SetDefaultResetTime(int32 val) { def_reset_time = val; } + void SetForceGroupZoneOption(int8 val) { group_zone_option = val; } + SpellProcess* GetSpellProcess() {return spellProcess;} + bool FinishedDepop(){ return finished_depop; } + + /// Returns the Tradeskill Manager for this zone + TradeskillMgr* GetTradeskillMgr() { return tradeskillMgr; } + + + // had to add these to access weather from Commands + bool isWeatherEnabled() { return weather_enabled; } + void SetWeatherEnabled(bool val) { weather_enabled = val; } + bool isWeatherAllowed() { return weather_allowed; } + void SetWeatherAllowed(bool val) { weather_allowed = val; } + int8 GetWeatherType() { return weather_type; } + void SetWeatherType(int8 val) { weather_type = val; } + int32 GetWeatherFrequency() { return weather_frequency; } + void SetWeatherFrequency(int32 val) { weather_frequency = val; } + float GetWeatherMinSeverity() { return weather_min_severity; } + void SetWeatherMinSeverity(float val) { weather_min_severity = val; } + float GetWeatherMaxSeverity() { return weather_max_severity; } + void SetWeatherMaxSeverity(float val) { weather_max_severity = val; } + float GetWeatherChangeAmount() { return weather_change_amount; } + void SetWeatherChangeAmount(float val) { weather_change_amount = val; } + float GetWeatherDynamicOffset() { return weather_dynamic_offset; } + void SetWeatherDynamicOffset(float val) { weather_dynamic_offset = val; } + int8 GetWeatherChance() { return weather_change_chance; } + void SetWeatherChance(int8 val) { weather_change_chance = val; } + float GetCurrentWeather() { return weather_current_severity; } + void SetCurrentWeather(float val) { weather_current_severity = val; } + int8 GetWeatherPattern() { return weather_pattern; } + void SetWeatherPattern(int8 val) { weather_pattern = val; } + void SetWeatherLastChangedTime(int32 val) { weather_last_changed_time = val; } + + int32 GetExpansionFlag() { return expansion_flag; } + void SetExpansionFlag(int32 val) { expansion_flag = val; } + + int32 GetHolidayFlag() { return holiday_flag; } + void SetHolidayFlag(int32 val) { holiday_flag = val; } + + int32 GetCanBind() { return can_bind; } + void SetCanBind(int32 val) { can_bind = val; } + + bool GetCanGate() { return can_gate; } + void SetCanGate(int32 val) { can_gate = val; } + + bool GetCanEvac() { return can_evac; } + void SetCanEvac(int32 val) { can_evac = val; } + + void RemoveClientImmediately(Client* client); + + void ClearHate(Entity* entity); + + + + /**************************************************** + Following functions are pending deletion, left in for + now just to make sure one won't be of future use. + ****************************************************/ + //void RemoveFromRangeMap(Client* client); // never used? + //void AddSpawnAssociatedGroup(vector* ret, int32 group_id); // never used, not even any code for it + //inline const char* GetCAddress() { return clientaddress; } // never used? + //inline int16 GetCPort() { return clientport; } // never used? + //inline bool IsBootingUp() { return BootingUp; } // never used? + //int32 GetShutdownTimer() {return shutdownTimer.GetTimerTime();} // never used + + // Following were private + + //char clientaddress[250]; // never used + //int16 clientport; // never used + //bool BootingUp; // never used + //bool authenticated; // never used? + //int16 next_index; // never used + + + + + + + + + + + + + + + + + + void AddFlightPath(int32 id, FlightPathInfo* info); + void AddFlightPathLocation(int32 id, FlightPathLocation* location); + void DeleteFlightPaths(); + void SendFlightPathsPackets(Client* client); + int32 GetFlightPathIndex(int32 id); + float GetFlightPathSpeed(int32 id); + + + void SendSpawn(Spawn* spawn, Client* client); // moved from private to public for bots + + void ProcessSpawnConditional(int8 condition); + + void SetSpawnStructs(Client* client); + + void AddSpawnProximities(Spawn* spawn); + void RemoveSpawnProximities(Spawn* spawn); + void SetSpawnScript(SpawnEntry* entry, Spawn* spawn); + bool IsLoading() { + return LoadingData; + } + + vector GetHouseItems(Client* client); + Spawn* GetSpawnFromUniqueItemID(int32 unique_id); + void SendHouseItems(Client* client); + + MutexMap house_object_database_lookup; // 1st int32 = model type, 2nd int32 = spawn id + + int32 GetWatchdogTime() { return watchdogTimestamp; } + void SetWatchdogTime(int32 time) { watchdogTimestamp = time; } + void CancelThreads(); + + void AddPendingSpawnRemove(int32 id); + void ProcessSpawnRemovals(); + + bool SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* packet = 0, bool delete_spawn = false); + + void AddSpawnToGroup(Spawn* spawn, int32 group_id); + + void QueueStateCommandToClients(int32 spawn_id, int32 state); + void QueueDefaultCommand(int32 spawn_id, std::string command, float distance); + void ProcessQueuedStateCommands(); + void RemoveClientsFromZone(ZoneServer* zone); + + void WorldTimeUpdateTrigger() { sync_game_time_timer.Trigger(); } + void StopSpawnScriptTimer(Spawn* spawn, std::string functionName); + + Client* RemoveZoneServerFromClient(ZoneServer* zone); + + void SendSubSpawnUpdates(SUBSPAWN_TYPES subtype); + bool HouseItemSpawnExists(int32 item_id); + void ProcessPendingSpawns(); + void AddSpawnToGrid(Spawn* spawn, int32 grid_id); + void RemoveSpawnFromGrid(Spawn* spawn, int32 grid_id); + int32 GetSpawnCountInGrid(int32 grid_id); + void SendClientSpawnListInGrid(Client* client, int32 grid_id); + + void AddIgnoredWidget(int32 id); + +private: +#ifndef WIN32 + pthread_t ZoneThread; + pthread_t SpawnThread; +#endif + + /* Private Functions */ + void AddTransporter(LocationTransportDestination* loc); + void CheckDeadSpawnRemoval(); + void DeleteData(bool boot_clients = true); + void DeleteFactionLists(); + void ProcessDepop(bool respawns_allowed = false, bool repop = false); + + /* + Following functions were public but never used outside the zone server so moved them to private + */ + void ClientProcess(bool ignore_shutdown_timer = false); // never used outside zone server + void RemoveClient(Client* client); // never used outside zone server + void DeterminePosition(SpawnLocation* spawnlocation, Spawn* spawn); // never used outside zone server + void AddDeadSpawn(Spawn* spawn, int32 timer = 0xFFFFFFFF); // never used outside zone server + int32 CalculateSpawnGroup(SpawnLocation* spawnlocation, bool respawn = false); // never used outside zone server + float GetSpawnGroupChance(int32 group_id); // never used outside zone server + vector* GetAssociatedLocations(set* groups); // never used outside zone server + set* GetAssociatedGroups(int32 group_id); // never used outside zone server + list* GetSpawnGroupsByLocation(int32 location_id); // never used outside zone server + void ProcessSpawnLocation(int32 location_id, bool respawn = false); // never used outside zone server + Spawn* ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respawn = false); // never used outside zone server + Spawn* ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, map* instNPCs, map* instGroundSpawns, map* instObjSpawns, map* instWidgetSpawns, map* instSignSpawns, bool respawn = false); // never used outside zone server + void SendCharSheetChanges(); // never used outside zone server + void SendCharSheetChanges(Client* client); // never used outside zone server + void SaveClients(); // never used outside zone server + void CheckSendSpawnToClient(); // never used outside zone server + void CheckSendSpawnToClient(Client* client, bool initial_login = false); // never used outside zone server + void CheckRemoveSpawnFromClient(Spawn* spawn); // never used outside zone server + void SaveClient(Client* client); // never used outside zone server + void ProcessFaction(Spawn* spawn, Client* client); // never used outside zone server + void RegenUpdate(); // never used outside zone server + void SendCalculatedXP(Player* player, Spawn* victim); // never used outside zone server, might not be used at all any more + void SendTimeUpdate(Client* client); // never used outside zone server + void CheckWidgetTimers(); // never used outside zone server + void CheckRespawns(); // never used outside zone server + void CheckSpawnExpireTimers(); // never used outside zone server + void AddSpawnExpireTimer(Spawn* spawn, int32 expire_time, int32 expire_offset = 0); // never used outside zone server + void CheckSpawnRange(Client* client, Spawn* spawn, bool initial_login = false); // never used outside zone server + void CheckSpawnRange(Spawn* spawn); // never used outside zone server + void DeleteSpawnScriptTimers(Spawn* spawn, bool all = false); // never used outside zone server + void DeleteSpawnScriptTimers(); // never used outside zone server + void CheckSpawnScriptTimers(); // never used outside zone server + bool PrepareSpawnID(Player* player, Spawn* spawn); // never used outside zone server + void RemoveMovementNPC(Spawn* spawn); // never used outside zone server + bool CheckNPCAttacks(NPC* npc, Spawn* victim, Client* client = 0); // never used outside zone server + bool AggroVictim(NPC* npc, Spawn* victim, Client* client = 0); // never used outside zone server + bool CheckEnemyList(NPC* npc); // never used outside zone server + void RemovePlayerProximity(Spawn* spawn, bool all = false); // never used outside zone server + void RemovePlayerProximity(Client* client); // never used outside zone server + void CheckPlayerProximity(Spawn* spawn, Client* client); // never used outside zone server + void RemoveLocationProximities(); // never used outside zone server + void CheckLocationProximity(); // never used outside zone server + void CheckLocationGrids(); // never used outside zone server + void RemoveSpawnSupportFunctions(Spawn* spawn, bool lock_spell_process = false, bool shutdown = false); // never used outside zone server + void ReloadTransporters(); // never used outside zone server + void DeleteSpawns(bool delete_all); // never used outside zone server + void AddPendingDelete(Spawn* spawn); // never used outside zone server + void ClearDeadSpawns(); // never used outside zone server + void RemoveChangedSpawn(Spawn* spawn); // never used outside zone server + void ProcessDrowning(); // never used outside zone server + void RemoveDamagedSpawn(Spawn* spawn); // never used outside zone server + void ProcessTracking(); // never used outside zone server + void ProcessTracking(Client* client); // never used outside zone server + void SendEpicMobDeathToGuild(Player* killer, Spawn* victim); // never used outside zone server + void ProcessAggroChecks(Spawn* spawn); // never used outside zone server + /// Checks to see if it is time to remove a spawn and removes it + /// Forces all spawns scheduled to be removed regardless of time + bool CombatProcess(Spawn* spawn); // never used outside zone server + void LootProcess(Spawn* spawn); + void CloseSpawnLootWindow(Spawn* spawn); + void InitWeather(); // never used outside zone server + ///Dismiss all pets in the zone, useful when the spell process needs to be reloaded + void DismissAllPets(); // never used outside zone server + + /* Mutex Lists */ + std::map changed_spawns; // int32 = spawn id + vector clients; + MutexList connected_clients; // probably remove this list so we are not maintaining 2 client lists + MutexList damaged_spawns; // int32 = spawn id + MutexList location_proximities; + MutexList location_grids; + MutexList remove_movement_spawns; // int32 = spawn id + set spawn_script_timers; + Mutex MSpawnScriptTimers; + set remove_spawn_script_timers_list; + Mutex MRemoveSpawnScriptTimersList; + list transporter_locations; + + /* Mutex Maps */ + MutexMap drowning_victims; + MutexMap movement_spawns; // 1st int32 = spawn id + MutexMap player_proximities; // 1st int32 = spawn id + MutexMap players_tracking; + MutexMap quick_database_id_lookup; // 1st int32 = database id, 2nd int32 = spawn id + MutexMap quick_location_id_lookup; // 1st int32 = location id, 2nd int32 = spawn id + MutexMap quick_group_id_lookup; // 1st int32 = group id, 2nd int32 = spawn id + MutexMap respawn_timers; + map spawn_delete_list; + MutexMap spawn_expire_timers; // 1st int32 = spawn id + map* > spawn_group_associations; + map spawn_group_chances; + map* > spawn_group_locations; + MutexMap > spawn_group_map; // MutexList is a list of spawn id's + map* > spawn_location_groups; + map spawn_location_list; + MutexMap* > spawn_range_map; // int32 in the MutexMap* = spawn id, float = distance + Mutex MWidgetTimers; + map widget_timers; // 1st int32 = spawn id + + std::map grid_maps; + + /* Mutexs */ + mutable std::shared_mutex MGridMaps; + mutable std::shared_mutex MChangedSpawns; + + Mutex m_enemy_faction_list; + Mutex m_npc_faction_list; + Mutex m_reverse_enemy_faction_list; + Mutex MDeadSpawns; + CriticalSection* MMasterZoneLock; //This needs to be a recursive lock to fix a possible /reload spells crash with multiple zones loaded - Foof + Mutex MMasterSpawnLock; + Mutex MPendingSpawnListAdd; + Mutex MSpawnList; + Mutex MTransportSpawns; + Mutex MSpawnGroupAssociation; + Mutex MSpawnGroupLocations; + Mutex MSpawnLocationGroups; + Mutex MSpawnGroupChances; + Mutex MTransportLocations; + Mutex MSpawnLocationList; + Mutex MSpawnDeleteList; + Mutex MClientList; + Mutex MIncomingClients; + + /* Maps */ + map dead_spawns; + map* > enemy_faction_list; + map* > npc_faction_list; + map* > reverse_enemy_faction_list; + map spawn_list; + map m_flightPaths; + map > m_flightPathRoutes; + + /* Lists */ + list pending_spawn_list_add; + + /* Specialized Lists to update specific scenarios */ + std::map subspawn_list[SUBSPAWN_TYPES::MAX_SUBSPAWN_TYPE]; + std::map housing_spawn_map; + + /* Vectors */ + vector* revive_points; + vector transport_spawns; + + /* Classes */ + SpellProcess* spellProcess; + TradeskillMgr* tradeskillMgr; + + /* Timers */ + Timer aggro_timer; + Timer charsheet_changes; + Timer client_save; + Timer location_prox_timer; + Timer location_grid_timer; + Timer movement_timer; + Timer regenTimer; + Timer respawn_timer; + Timer shutdownTimer; + Timer startupDelayTimer; + Timer spawn_check_add; + Timer spawn_check_remove; + Timer spawn_expire_timer; + Timer spawn_range; + Timer spawn_update; + Timer sync_game_time_timer; + Timer tracking_timer; + Timer weatherTimer; + Timer widget_timer; + Timer queue_updates; + Timer shutdownDelayTimer; + + /* Enums */ + Instance_Type InstanceType; + + /* Variables */ + volatile bool finished_depop; + volatile bool depop_zone; + volatile bool repop_zone; + volatile bool respawns_allowed; + volatile bool LoadingData; + std::atomic reloading_spellprocess; + std::atomic zoneShuttingDown; + bool cityzone; + bool always_loaded; + bool isInstance; + + int32 pNumPlayers; + sint16 minimumStatus; + int16 minimumLevel; + int16 maximumLevel; + int16 minimumVersion; + char zone_name[64]; + char zonesky_file[64]; + char zone_file[64]; + char zone_description[255]; + float underworld; + float safe_x; + float safe_y; + float safe_z; + float safe_heading; + float xp_mod; + volatile int32 zoneID; + bool locked; // JA: implementing /zone lock|unlock commands + int32 instanceID; + string zone_motd; + int32 def_reenter_time; + int32 def_reset_time; + int32 def_lockout_time; + int8 group_zone_option; + float rain; + bool isDusk; + int dusk_hour; + int dawn_hour; + int dusk_minute; + int dawn_minute; + int32 spawn_delete_timer; + int32 expansion_flag; + int32 holiday_flag; + //devn00b:test + int can_bind; + bool can_gate; + bool can_evac; + + map versioned_pos_structs; + map versioned_info_structs; + map versioned_vis_structs; + + /* Weather Stuff */ + bool weather_enabled; // false = disabled, true = enabled + int8 weather_type; // 0 = normal, 1 = dynamic, 2 = random, 3 = chaotic + int32 weather_frequency; // how often weather changes + float weather_min_severity; // minimum weather severity in a zone + float weather_max_severity; // maximum weather severity in a zone + float weather_change_amount; // how much does the weather change each interval (normal weather conditions) + float weather_dynamic_offset; // max amount the weather change each interval (dynamic weather conditions) + int8 weather_change_chance; // percentage chance the weather will change + int8 weather_pattern; // 0 = decreasing severity, 1 = increasing severity, 2 = random severity + int32 weather_last_changed_time; // last time weather changed (used with weather_frequency) + float weather_current_severity; // current weather conditions in a zone + bool weather_allowed; // from zones.weather_allowed field in database + bool weather_signaled; // whether or not we told the client "it begins to rain" + + + + + + + + + bool reloading; + map* > entity_command_list; + map > npc_skill_list; + map > npc_equipment_list; + map npc_list; + map object_list; + map sign_list; + map widget_list; + map > groundspawn_entries; + map > groundspawn_items; + Mutex MGroundSpawnItems; + map groundspawn_list; + map loot_tables; + map > loot_drops; + map > spawn_loot_list; + vector level_loot_list; + map > racial_loot_list; + map > zone_loot_list; + map > transporters; + map* > location_transporters; + Mutex MTransporters; + Mutex MTransportMaps; + // Map + map m_transportMaps; + + int32 watchdogTimestamp; + + std::map m_pendingSpawnRemove; + Mutex MPendingSpawnRemoval; + + std::map lua_queued_state_commands; + std::map> lua_spawn_update_command; + std::mutex MLuaQueueStateCmd; + + mutable std::shared_mutex MIgnoredWidgets; + std::map ignored_widgets; + Map* default_zone_map; // this is the map that npcs, ground spawns, so on use. May not be the same as the clients! +public: + Spawn* GetSpawn(int32 id); + + /* Entity Commands */ + map*>* GetEntityCommandListAll() {return &entity_command_list;} + vector* GetEntityCommandList(int32 id); + void SetEntityCommandList(int32 id, EntityCommand* command); + void ClearEntityCommands(); + EntityCommand* GetEntityCommand(int32 id, string name); + + /* NPC's */ + void AddNPC(int32 id, NPC* npc); + NPC* GetNPC(int32 id, bool override_loading = false) { + if((!reloading || override_loading) && npc_list.count(id) > 0) + return npc_list[id]; + else + return 0; + } + NPC* GetNewNPC(int32 id) { + if(!reloading && npc_list.count(id) > 0) + return new NPC(npc_list[id]); + else + return 0; + } + + /* NPC Skills */ + void AddNPCSkill(int32 list_id, int32 skill_id, int16 value); + map* GetNPCSkills(int32 primary_list, int32 secondary_list); + + /* NPC Equipment */ + void AddNPCEquipment(int32 list_id, int32 item_id); + void SetNPCEquipment(NPC* npc); + + /* Objects */ + void AddObject(int32 id, Object* object){ object_list[id] = object; } + Object* GetObject(int32 id, bool override_loading = false) { + if((!reloading || override_loading) && object_list.count(id) > 0) + return object_list[id]; + else + return 0; + } + Object* GetNewObject(int32 id) { + if(!reloading && object_list.count(id) > 0) + return object_list[id]->Copy(); + else + return 0; + } + + /* Signs */ + void AddSign(int32 id, Sign* sign){ sign_list[id] = sign; } + Sign* GetSign(int32 id, bool override_loading = false) { + if((!reloading || override_loading) && sign_list.count(id) > 0) + return sign_list[id]; + else + return 0; + } + Sign* GetNewSign(int32 id) { + if(!reloading && sign_list.count(id) > 0) + return sign_list[id]->Copy(); + else + return 0; + } + + /* Widgets */ + void AddWidget(int32 id, Widget* widget); + Widget* GetWidget(int32 id, bool override_loading = false); + Widget* GetNewWidget(int32 id); + + /* Groundspawns */ + // JA: groundspawn revamp + void AddGroundSpawnEntry(int32 groundspawn_id, int16 min_skill_level, int16 min_adventure_level, int8 bonus_table, float harvest1, float harvest3, float harvest5, float harvest_imbue, float harvest_rare, float harvest10, int32 harvest_coin); + void AddGroundSpawnItem(int32 groundspawn_id, int32 item_id, int8 is_rare, int32 grid_id); + vector* GetGroundSpawnEntries(int32 id); + vector* GetGroundSpawnEntryItems(int32 id); + void LoadGroundSpawnEntries(); + void LoadGroundSpawnItems(); + // + void DeleteGroundSpawnItems(); + + void AddGroundSpawn(int32 id, GroundSpawn* spawn); + GroundSpawn* GetGroundSpawn(int32 id, bool override_loading = false); + GroundSpawn* GetNewGroundSpawn(int32 id); + + /* Pet names */ + vector pet_names; + + /* Loot */ + void AddLootTable(int32 id, LootTable* table); + void AddLootDrop(int32 id, LootDrop* drop); + void AddSpawnLootList(int32 spawn_id, int32 id); + void ClearSpawnLootList(int32 spawn_id); + void AddLevelLootList(GlobalLoot* loot); + void AddRacialLootList(int16 racial_id, GlobalLoot* loot); + void AddZoneLootList(int32 zone, GlobalLoot* loot); + void ClearLootTables(); + vector GetSpawnLootList(int32 spawn_id, int32 zone_id, int8 spawn_level, int16 racial_id, Spawn* spawn = 0); + vector* GetLootDrops(int32 table_id); + LootTable* GetLootTable(int32 table_id); + + /* Transporters */ + void AddLocationTransporter(int32 zone_id, string message, float trigger_x, float trigger_y, float trigger_z, float trigger_radius, int32 destination_zone_id, float destination_x, float destination_y, float destination_z, float destination_heading, int32 cost, int32 unique_id); + void AddTransporter(int32 transport_id, int8 type, string name, string message, int32 destination_zone_id, float destination_x, float destination_y, float destination_z, float destination_heading, + int32 cost, int32 unique_id, int8 min_level, int8 max_level, int32 quest_req, int16 quest_step_req, int32 quest_complete, int32 map_x, int32 map_y, int32 expansion_flag, int32 holiday_flag, int32 min_client_version, + int32 max_client_version, int32 flight_path_id, int16 mount_id, int8 mount_red_color, int8 mount_green_color, int8 mount_blue_color); + void GetTransporters(vector* returnList, Client* client, int32 transport_id); + MutexList* GetLocationTransporters(int32 zone_id); + void DeleteGlobalTransporters(); + /// + ///The transport id + ///Name of the map + void AddTransportMap(int32 id, string name); + + ///Checks to see if the transport has a map + ///The transport id we want to check + ///True if the transport id has a map + bool TransportHasMap(int32 id); + + ///Gets the map name for the given transport id + ///The transport id that we want a map for + ///Map name + string GetTransportMap(int32 id); + + ///Clears the list of transporter maps + void DeleteTransporterMaps(); + + + void DeleteGlobalSpawns(); + + void ReloadSpawns(); + + void SendStateCommand(Spawn* spawn, int32 state); + + int32 lifetime_client_count; + int32 incoming_clients; +}; + +#endif diff --git a/source/common/CRC16.cpp b/source/common/CRC16.cpp new file mode 100644 index 0000000..f7b43ad --- /dev/null +++ b/source/common/CRC16.cpp @@ -0,0 +1,328 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include + +unsigned long IntArray[]={ +0x00000000, +0x77073096, +0xEE0E612C, +0x990951BA, +0x076DC419, +0x706AF48F, +0xE963A535, +0x9E6495A3, +0x0EDB8832, +0x79DCB8A4, +0xE0D5E91E, +0x97D2D988, +0x09B64C2B, +0x7EB17CBD, +0xE7B82D07, +0x90BF1D91, +0x1DB71064, +0x6AB020F2, +0xF3B97148, +0x84BE41DE, +0x1ADAD47D, +0x6DDDE4EB, +0xF4D4B551, +0x83D385C7, +0x136C9856, +0x646BA8C0, +0xFD62F97A, +0x8A65C9EC, +0x14015C4F, +0x63066CD9, +0xFA0F3D63, +0x8D080DF5, +0x3B6E20C8, +0x4C69105E, +0xD56041E4, +0xA2677172, +0x3C03E4D1, +0x4B04D447, +0xD20D85FD, +0xA50AB56B, +0x35B5A8FA, +0x42B2986C, +0xDBBBC9D6, +0xACBCF940, +0x32D86CE3, +0x45DF5C75, +0xDCD60DCF, +0xABD13D59, +0x26D930AC, +0x51DE003A, +0xC8D75180, +0xBFD06116, +0x21B4F4B5, +0x56B3C423, +0xCFBA9599, +0xB8BDA50F, +0x2802B89E, +0x5F058808, +0xC60CD9B2, +0xB10BE924, +0x2F6F7C87, +0x58684C11, +0xC1611DAB, +0xB6662D3D, +0x76DC4190, +0x01DB7106, +0x98D220BC, +0xEFD5102A, +0x71B18589, +0x06B6B51F, +0x9FBFE4A5, +0xE8B8D433, +0x7807C9A2, +0x0F00F934, +0x9609A88E, +0xE10E9818, +0x7F6A0DBB, +0x086D3D2D, +0x91646C97, +0xE6635C01, +0x6B6B51F4, +0x1C6C6162, +0x856530D8, +0xF262004E, +0x6C0695ED, +0x1B01A57B, +0x8208F4C1, +0xF50FC457, +0x65B0D9C6, +0x12B7E950, +0x8BBEB8EA, +0xFCB9887C, +0x62DD1DDF, +0x15DA2D49, +0x8CD37CF3, +0xFBD44C65, +0x4DB26158, +0x3AB551CE, +0xA3BC0074, +0xD4BB30E2, +0x4ADFA541, +0x3DD895D7, +0xA4D1C46D, +0xD3D6F4FB, +0x4369E96A, +0x346ED9FC, +0xAD678846, +0xDA60B8D0, +0x44042D73, +0x33031DE5, +0xAA0A4C5F, +0xDD0D7CC9, +0x5005713C, +0x270241AA, +0xBE0B1010, +0xC90C2086, +0x5768B525, +0x206F85B3, +0xB966D409, +0xCE61E49F, +0x5EDEF90E, +0x29D9C998, +0xB0D09822, +0xC7D7A8B4, +0x59B33D17, +0x2EB40D81, +0xB7BD5C3B, +0xC0BA6CAD, +0xEDB88320, +0x9ABFB3B6, +0x03B6E20C, +0x74B1D29A, +0xEAD54739, +0x9DD277AF, +0x04DB2615, +0x73DC1683, +0xE3630B12, +0x94643B84, +0x0D6D6A3E, +0x7A6A5AA8, +0xE40ECF0B, +0x9309FF9D, +0x0A00AE27, +0x7D079EB1, +0xF00F9344, +0x8708A3D2, +0x1E01F268, +0x6906C2FE, +0xF762575D, +0x806567CB, +0x196C3671, +0x6E6B06E7, +0xFED41B76, +0x89D32BE0, +0x10DA7A5A, +0x67DD4ACC, +0xF9B9DF6F, +0x8EBEEFF9, +0x17B7BE43, +0x60B08ED5, +0xD6D6A3E8, +0xA1D1937E, +0x38D8C2C4, +0x4FDFF252, +0xD1BB67F1, +0xA6BC5767, +0x3FB506DD, +0x48B2364B, +0xD80D2BDA, +0xAF0A1B4C, +0x36034AF6, +0x41047A60, +0xDF60EFC3, +0xA867DF55, +0x316E8EEF, +0x4669BE79, +0xCB61B38C, +0xBC66831A, +0x256FD2A0, +0x5268E236, +0xCC0C7795, +0xBB0B4703, +0x220216B9, +0x5505262F, +0xC5BA3BBE, +0xB2BD0B28, +0x2BB45A92, +0x5CB36A04, +0xC2D7FFA7, +0xB5D0CF31, +0x2CD99E8B, +0x5BDEAE1D, +0x9B64C2B0, +0xEC63F226, +0x756AA39C, +0x026D930A, +0x9C0906A9, +0xEB0E363F, +0x72076785, +0x05005713, +0x95BF4A82, +0xE2B87A14, +0x7BB12BAE, +0x0CB61B38, +0x92D28E9B, +0xE5D5BE0D, +0x7CDCEFB7, +0x0BDBDF21, +0x86D3D2D4, +0xF1D4E242, +0x68DDB3F8, +0x1FDA836E, +0x81BE16CD, +0xF6B9265B, +0x6FB077E1, +0x18B74777, +0x88085AE6, +0xFF0F6A70, +0x66063BCA, +0x11010B5C, +0x8F659EFF, +0xF862AE69, +0x616BFFD3, +0x166CCF45, +0xA00AE278, +0xD70DD2EE, +0x4E048354, +0x3903B3C2, +0xA7672661, +0xD06016F7, +0x4969474D, +0x3E6E77DB, +0xAED16A4A, +0xD9D65ADC, +0x40DF0B66, +0x37D83BF0, +0xA9BCAE53, +0xDEBB9EC5, +0x47B2CF7F, +0x30B5FFE9, +0xBDBDF21C, +0xCABAC28A, +0x53B39330, +0x24B4A3A6, +0xBAD03605, +0xCDD70693, +0x54DE5729, +0x23D967BF, +0xB3667A2E, +0xC4614AB8, +0x5D681B02, +0x2A6F2B94, +0xB40BBE37, +0xC30C8EA1, +0x5A05DF1B, +0x2D02EF8D, +}; + +unsigned long CRC16(const unsigned char *buf, int size, int key) +{ + unsigned long ecx = key; //mov ecx, [esp+arg_8] + unsigned long eax = ecx; //mov eax, ecx + unsigned long edi; + + eax = ~ eax; //not eax + eax&=0xFF; //and eax, 0FFh + eax=IntArray[eax]; //mov eax, dword_0_10115D38[eax*4] IntArray + eax ^= 0x00FFFFFF; //xor eax, 0FFFFFFh + int edx = ecx; //mov edx, ecx + edx = edx >> 8; //sar edx, 8 + edx = edx ^ eax; //xor edx, eax + eax = eax >> 8; //sar eax, 8 + edx &= 0xFF; //and edx, 0FFh + eax &= 0x00FFFFFF; //and eax, 0FFFFFFh + eax ^= IntArray[edx]; //xor eax, dword_0_10115D38[edx*4] + edx = ecx; //mov edx, ecx + edx = edx >> 0x10; //sar edx, 10h + edx ^= eax; //xor edx, eax + eax = eax >> 8; //sar eax, 8 + edx &= 0xFF; //and edx, 0FFh + int esi = IntArray[edx]; //mov esi, dword_0_10115D38[edx*4] + edx = size; //mov edx, [esp+4+arg_4] + eax &= 0x00FFFFFF; //and eax, 0FFFFFFh + eax ^= esi; //xor eax, esi + ecx = ecx >> 0x18; //sar ecx, 18h + ecx ^= eax; //xor ecx, eax + ecx &= 0xFF; //and ecx, 0FFh + esi = IntArray[ecx]; //mov esi, dword_0_10115D38[ecx*4] + ecx = (int)*buf; //mov ecx, [esp+4+arg_0] + eax = eax >> 8; //sar eax, 8 + eax &= 0x00FFFFFF; //and eax, 0FFFFFFh + eax ^= esi; //xor eax, esi + for(int x = 0; x < size; x++) + { //eax is the crc, ecx is the current part of the buffer + int edx = 0; //xor edx, edx + edx = buf[x] & 0x00FF; //mov dl, [ecx] + + edx ^= eax; //xor edx, eax + eax = eax >> 8; //sar eax, 8 + edx &= 0xFF; //and edx, 0FFh + edi = IntArray[edx]; //mov edi, dword_0_10115D38[edx*4] + eax &= 0x00FFFFFF; //and eax, 0FFFFFFh + eax ^= edi; //xor eax, edi + } + return ~eax; +} diff --git a/source/common/CRC16.h b/source/common/CRC16.h new file mode 100644 index 0000000..7aacd36 --- /dev/null +++ b/source/common/CRC16.h @@ -0,0 +1,25 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _CRC16_H +#define _CRC16_H + +unsigned long CRC16(const unsigned char *buf, int size, int key); + +#endif diff --git a/source/common/Common_Defines.h b/source/common/Common_Defines.h new file mode 100644 index 0000000..4600d97 --- /dev/null +++ b/source/common/Common_Defines.h @@ -0,0 +1,32 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#define BASEDIR "./" + +#ifndef DB_INI_FILE + #ifdef LOGIN + #define DB_INI_FILE BASEDIR "login_db.ini" + #else + #define DB_INI_FILE BASEDIR "world_db.ini" + #endif +#endif + +#ifndef MAIN_CONFIG_FILE + #define MAIN_CONFIG_FILE BASEDIR "server_config.json" +#endif \ No newline at end of file diff --git a/source/common/Condition.cpp b/source/common/Condition.cpp new file mode 100644 index 0000000..348c90a --- /dev/null +++ b/source/common/Condition.cpp @@ -0,0 +1,133 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "debug.h" +#include "Condition.h" + +#ifdef WIN32 +#else +#include +#include +#include +#endif + +#ifdef WIN32 +/* + + Windows does not support condition variables by default. + So we use a simple hack of sleeping in wait() and doing + nothing anywhere else. + + some possible places to look for ways to do this: + http://www.cs.wustl.edu/~schmidt/win32-cv-1.html + + http://sources.redhat.com/pthreads-win32/ + http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/pthread_cond_signal.c?rev=1.7&content-type=text/x-cvsweb-markup&cvsroot=pthreads-win32 + +*/ + +#define CONDITION_HACK_GRANULARITY 4 + + +Condition::Condition() +{ +} + +void Condition::Signal() +{ +} + +void Condition::SignalAll() +{ +} + +void Condition::Wait() +{ + Sleep(CONDITION_HACK_GRANULARITY); +} + +Condition::~Condition() +{ +} + + +#else //!WIN32 + +Condition::Condition() +{ + pthread_cond_init(&cond,NULL); + pthread_mutex_init(&mutex,NULL); +} + +void Condition::Signal() +{ + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); +} + +void Condition::SignalAll() +{ + pthread_mutex_lock(&mutex); + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); +} + +void Condition::Wait() +{ + pthread_mutex_lock(&mutex); + pthread_cond_wait(&cond,&mutex); + pthread_mutex_unlock(&mutex); +} + +/* +I commented this specifically because I think it might be very +difficult to write a windows counterpart to it, so I would like +to discourage its use until we can confirm that it can be reasonably +implemented on windows. + +bool Condition::TimedWait(unsigned long usec) +{ +struct timeval now; +struct timespec timeout; +int retcode=0; + pthread_mutex_lock(&mutex); + gettimeofday(&now,NULL); + now.tv_usec+=usec; + timeout.tv_sec = now.tv_sec + (now.tv_usec/1000000); + timeout.tv_nsec = (now.tv_usec%1000000) *1000; + //cout << "now=" << now.tv_sec << "."<. +*/ +#ifndef __CONDITION_H +#define __CONDITION_H + +#ifndef WIN32 +#include +#endif + +//Sombody, someday needs to figure out how to implement a condition +//system on windows... + + +class Condition { + private: +#ifndef WIN32 + pthread_cond_t cond; + pthread_mutex_t mutex; +#endif + public: + Condition(); + void Signal(); + void SignalAll(); + void Wait(); +// bool TimedWait(unsigned long usec); + ~Condition(); +}; + +#endif + + diff --git a/source/common/ConfigReader.cpp b/source/common/ConfigReader.cpp new file mode 100644 index 0000000..33869f4 --- /dev/null +++ b/source/common/ConfigReader.cpp @@ -0,0 +1,302 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "ConfigReader.h" +#include "Log.h" + +ConfigReader::~ConfigReader(){ + MStructs.lock(); + DestroyStructs(); + MStructs.unlock(); +} +PacketStruct* ConfigReader::getStructByVersion(const char* name, int16 version){ + PacketStruct* packet = 0; + PacketStruct* newpacket = 0; + MStructs.lock(); + vector* struct_versions = structs[string(name)]; + if(struct_versions){ + vector::iterator iter; + for(iter = struct_versions->begin(); iter != struct_versions->end(); iter++){ + packet = *iter; + if(packet && packet->GetVersion() == version){ + newpacket = new PacketStruct(packet, version); + break; + } + } + } + MStructs.unlock(); + if(!newpacket) + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find struct named '%s' with version: %i", name, version); + return newpacket; +} +void ConfigReader::ReloadStructs(){ + MStructs.lock(); + DestroyStructs(); + for(int32 i=0;i*>::iterator struct_iterator; + for(struct_iterator=structs.begin();struct_iterator!=structs.end();struct_iterator++) { + vector* versions = struct_iterator->second; + vector::iterator version_iter; + if(versions){ + for(version_iter = versions->begin(); version_iter != versions->end(); version_iter++){ + safe_delete(*version_iter); + } + } + safe_delete(versions); + } + structs.clear(); +} +PacketStruct* ConfigReader::getStruct(const char* name, int16 version){ + PacketStruct* latest_version = 0; + PacketStruct* new_latest_version = 0; + MStructs.lock(); + vector* struct_versions = structs[string(name)]; + if(struct_versions){ + vector::iterator iter; + for(iter = struct_versions->begin(); iter != struct_versions->end(); iter++){ + if((*iter)->GetVersion() <= version && (!latest_version || (*iter)->GetVersion() > latest_version->GetVersion())) + latest_version = *iter; + } + if (latest_version) { + if (latest_version->GetOpcode() != OP_Unknown && (latest_version->GetOpcodeValue(version) == 0xFFFF || latest_version->GetOpcodeValue(version)==0xCDCD)) { + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find valid opcode for Packet Struct '%s' and client version %d", latest_version->GetName(), version); + } + else if(strlen(latest_version->GetOpcodeType()) == 0 || latest_version->GetOpcode() != OP_Unknown) + new_latest_version = new PacketStruct(latest_version, version); + } + + } + MStructs.unlock(); + if(!new_latest_version && !latest_version) + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find struct named '%s'", name); + return new_latest_version; +} +int16 ConfigReader::GetStructVersion(const char* name, int16 version){ + MStructs.lock(); + vector* struct_versions = structs[string(name)]; + int16 ret = 0; + if(struct_versions){ + vector::iterator iter; + PacketStruct* latest_version = 0; + for(iter = struct_versions->begin(); iter != struct_versions->end(); iter++){ + if(!latest_version || ( (*iter)->GetVersion() > latest_version->GetVersion() && (*iter)->GetVersion() <= version) ) + latest_version = *iter; + } + if(latest_version) + ret = latest_version->GetVersion(); + } + MStructs.unlock(); + return ret; +} +void ConfigReader::addStruct(const char* name, int16 version, PacketStruct* new_struct){ + string strname(name); + vector* struct_versions = structs[strname]; + if(struct_versions) + struct_versions->push_back(new_struct); + else{ + struct_versions = new vector; + struct_versions->push_back(new_struct); + structs[strname] = struct_versions; + } +} +bool ConfigReader::LoadFile(const char* name){ + load_files.push_back(name); + return processXML_Elements(name); +} +bool ConfigReader::processXML_Elements(const char* fileName){ + XMLNode xMainNode=XMLNode::openFileHelper(fileName,"EQ2Emulator"); + if(xMainNode.isEmpty()) + return false; + for(int i=0;iSetName(struct_name); + if(opcode_type) + new_struct->SetOpcodeType(opcode_type); + if(opcode_name){ + if(!new_struct->SetOpcode(opcode_name)){ + safe_delete(new_struct); + continue; + } + } + new_struct->SetVersion(version); + loadDataStruct(new_struct, xMainNode.getChildNode("Struct", i)); + addStruct(struct_name, version, new_struct); + } + return true; +} +void ConfigReader::loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool array_packet){ + for(int x=0;xGetVersion()); + if(substruct_packet){ + vector::iterator itr; + vector* structs = substruct_packet->getStructs(); + DataStruct* ds = 0; + int i = 0; + char tmp[12] = {0}; + for(i=0;ibegin();itr!=structs->end();itr++) { + ds = *itr; + string new_name; + if(array_packet) + new_name = string(name).append("_").append(ds->GetStringName()); + else + new_name = string(name).append("_").append(ds->GetStringName()).append("_").append(tmp); + + DataStruct* ds2 = new DataStruct(new_name.c_str(), ds->GetType(),ds->GetLength(), ds->GetType2()); + + if(!array_packet && strlen(ds->GetArraySizeVariable()) > 1) + ds2->SetArraySizeVariable(string(name).append("_").append(ds->GetArraySizeVariable()).append("_").append(tmp).c_str()); + ds2->SetOversized(ds->GetOversized()); + ds2->SetOversizedByte(ds->GetOversizedByte()); + ds2->SetDefaultValue(ds->GetDefaultValue()); + ds2->SetMaxArraySize(ds->GetMaxArraySize()); + ds2->SetIfSetVariable(ds->GetIfSetVariable() ? ds->GetIfSetVariable() : if_variable); + ds2->SetIfNotSetVariable(ds->GetIfSetVariable() ? ds->GetIfNotSetVariable() : if_not_variable); + ds2->SetIfNotEqualsVariable(ds->GetIfNotEqualsVariable()); + ds2->SetIfFlagNotSetVariable(ds->GetIfFlagNotSetVariable()); + ds2->SetIfFlagSetVariable(ds->GetIfFlagSetVariable()); + ds2->SetIsOptional(ds->IsOptional()); + ds2->AddIfSetVariable(if_variable); //add this if the modifier is on the piece that is including the substruct + ds2->AddIfNotSetVariable(if_not_variable); //add this if the modifier is on the piece that is including the substruct + packet->add(ds2); + } + } + if(!array_packet){ + i--; + substruct_packet->renameSubstructArray(name, i); + //ds2->SetArraySizeVariable((char*)string(name).append("_").append(ds->GetArraySizeVariable()).append("_").append(tmp).c_str()); + packet->addPacketArrays(substruct_packet); + } + + safe_delete(substruct_packet); + } + continue; + } + else if(type && strncasecmp(type,"Array", 5)==0 && array_size){ + PacketStruct* new_packet = new PacketStruct; + new_packet->SetName(name); + new_packet->IsSubPacket(true); + new_packet->SetVersion(packet->GetVersion()); + loadDataStruct(new_packet, parentNode.getChildNode("Data", x), true); + packet->add(new_packet); + } + if(!name || !type) + { + LogWrite(MISC__WARNING, 0, "Misc", "Ignoring invalid Data Element, all elements must include at least an ElementName and Type!"); + LogWrite(MISC__WARNING, 0, "Misc", "\tStruct: '%s', version: %i", parentNode.getAttribute("Name"), parentNode.getAttribute("ClientVersion")); + continue; + } + DataStruct* ds = new DataStruct(name, type, num_size, type2); + int8 oversized_value = 0; + int8 oversized_byte_value = 255; + if(oversized){ + try{ + oversized_value = atoi(oversized); + } + catch(...){} + } + if(oversized_byte){ + try{ + oversized_byte_value = atoi(oversized_byte); + } + catch(...){} + } + ds->SetOversizedByte(oversized_byte_value); + ds->SetOversized(oversized_value); + ds->SetMaxArraySize(max_array_size); + if(array_size) + ds->SetArraySizeVariable(array_size); + ds->SetDefaultValue(byte_val); + ds->SetIfSetVariable(if_variable); + ds->SetIfNotSetVariable(if_not_variable); + ds->SetIfEqualsVariable(if_equals_variable); + ds->SetIfNotEqualsVariable(if_not_equals_variable); + ds->SetIfFlagNotSetVariable(if_flag_not_set_variable); + ds->SetIfFlagSetVariable(if_flag_set_variable); + if (optional && strlen(optional) > 0 && (strcmp("true", optional) == 0 || strcmp("TRUE", optional) == 0 || strcmp("True", optional) == 0)) + ds->SetIsOptional(true); + packet->add(ds); + } +} + diff --git a/source/common/ConfigReader.h b/source/common/ConfigReader.h new file mode 100644 index 0000000..cd34ae5 --- /dev/null +++ b/source/common/ConfigReader.h @@ -0,0 +1,52 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __CONFIG_READER__ +#define __CONFIG_READER__ +#include +#include "PacketStruct.h" +#include +#include +#include +#include "xmlParser.h" +#include "Mutex.h" + +using namespace std; + +class ConfigReader{ +public: + ~ConfigReader(); + + void addStruct(const char* name, int16 version, PacketStruct* new_struct); + PacketStruct* getStruct(const char* name, int16 version); + PacketStruct* getStructByVersion(const char* name, int16 version); + void loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool array_packet = false); + bool processXML_Elements(const char* fileName); + int16 GetStructVersion(const char* name, int16 version); + void DestroyStructs(); + void ReloadStructs(); + bool LoadFile(const char* name); +private: + Mutex MStructs; + vector load_files; + map*> structs; + //vector structs; +}; +#endif + diff --git a/source/common/Crypto.cpp b/source/common/Crypto.cpp new file mode 100644 index 0000000..369390e --- /dev/null +++ b/source/common/Crypto.cpp @@ -0,0 +1,47 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Crypto.h" +#include +#include "../common/packet_dump.h" + +using namespace std; +void test(); +int64 Crypto::RSADecrypt(uchar* text, int16 size){ + int64 ret = 0; + uchar* buffer = new uchar[8]; + for(int i=7;i>=0;i--) + buffer[7-i] = text[i]; + memcpy(&ret, buffer, 8); + safe_delete_array(buffer); + return ret; +} + +void Crypto::RC4Decrypt(uchar* text, int32 size){ + MCrypto.lock(); + client->Cypher(text, size); + MCrypto.unlock(); +} + +void Crypto::RC4Encrypt(uchar* text, int32 size){ + MCrypto.lock(); + server->Cypher(text, size); + MCrypto.unlock(); +} + diff --git a/source/common/Crypto.h b/source/common/Crypto.h new file mode 100644 index 0000000..d2c478b --- /dev/null +++ b/source/common/Crypto.h @@ -0,0 +1,66 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _CRYPTO_H +#define _CRYPTO_H +#include +#include +#include "RC4.h" +#include "../common/types.h" + +using namespace std; +class Crypto { +public: + ~Crypto(){ safe_delete(client); safe_delete(server); } + Crypto() { rc4_key = 0; encrypted = false; client = 0; server = 0; }; + + static int64 RSADecrypt(uchar* text, int16 size); + void RC4Encrypt(uchar* text, int32 size); + void RC4Decrypt(uchar* text, int32 size); + int64 getRC4Key() { return rc4_key; } + void setRC4Key(int64 key) { + rc4_key = key; + if(key > 0){ + encrypted = true; + client = new RC4(~key); + server = new RC4(key); + uchar temp[20]; + client->Cypher(temp, 20); + server->Cypher(temp, 20); + } + else{ + encrypted = false; + safe_delete(client); + safe_delete(server); + } + } + bool isEncrypted(){ return encrypted; } + void setEncrypted(bool in_val){ encrypted = in_val; } + + +private: + RC4* server; + RC4* client; + bool encrypted; + int64 rc4_key; + mutex MCrypto; +}; + +#endif + diff --git a/source/common/DataBuffer.h b/source/common/DataBuffer.h new file mode 100644 index 0000000..f40486d --- /dev/null +++ b/source/common/DataBuffer.h @@ -0,0 +1,207 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_DATABUFFER_ +#define __EQ2_DATABUFFER_ +#include +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/EQ2_Common_Structs.h" + +#ifdef WORLD + #include "../WorldServer/SpawnLists.h" +#endif + +using namespace std; + +class DataBuffer{ +public: + bool changed; + uchar* getData(){ return (uchar*)buffer.c_str(); } + int32 getDataSize(){ return buffer.length(); } + string* getDataString(){ return &buffer; } + void CreateEQ2Color(EQ2_Color& color){ + CreateEQ2Color(&color); + } + uchar* GetLoadBuffer(){ + return load_buffer; + } + int32 GetLoadPos(){ + return load_pos; + } + int32 GetLoadLen(){ + return load_len; + } + void SetLoadPos(int32 new_pos){ + load_pos = new_pos; + } + void CreateEQ2Color(EQ2_Color* color){ + int8 rgb[3]; + float* tmp = 0; + for(int i=0;i<3;i++){ + tmp = (float*)(load_buffer + load_pos); + rgb[i] = (int8)((*tmp)*255); + load_pos += sizeof(float); + } + color->red = rgb[0]; + color->green = rgb[1]; + color->blue = rgb[2]; + } + + template void MakeEQ2_Int8(Type& output){ + MakeEQ2_Int8(&output); + } + template void MakeEQ2_Int8(Type* output){ + float* tmp = (float*)(load_buffer + load_pos); + if(*tmp < 0) + *tmp *= -1; + sint8 result = (sint8)((*tmp)*100); + memcpy(output, &result, sizeof(sint8)); + load_pos += sizeof(float); + } + void InitializeGetData(){ + get_buffer = (uchar*)buffer.c_str(); + get_len = buffer.length(); + get_pos = 0; + } + void InitializeLoadData(uchar* input, int32 size){ + buffer = string((char*)input, size); + load_buffer = (uchar*)buffer.c_str(); + load_len = size; + load_pos = 0; + } + template void LoadDataString(String& output){ + LoadDataString(&output); + } + template void LoadDataString(String* output){ + if((sizeof(output->size) + load_pos) <= load_len){ + memcpy(&output->size, load_buffer + load_pos, sizeof(output->size)); + load_pos += sizeof(output->size); + } + if((output->size + load_pos) <= load_len){ + output->data = string((char*)(load_buffer + load_pos), output->size); + load_pos += output->size; + } + } + template void LoadData(Type& output){ + LoadData(&output); + } + template void LoadData(Type* output, int32 array_size){ + if(array_size<=1){ + LoadData(output); + } + else{ + for(int32 i=0;i void LoadData(Type* output){ + if((sizeof(Type) + load_pos) <= load_len){ + memcpy(output, load_buffer + load_pos, sizeof(Type)); + load_pos += sizeof(Type); + } + } + template void LoadData(Type& output, int32 array_size){ + LoadData(&output, array_size); + } + void LoadSkip(int8 bytes){ + load_pos += bytes; + } + template void LoadSkip(Type& skip){ + LoadSkip(&skip); + } + template void LoadSkip(Type* skip){ + load_pos += sizeof(Type); + } + template void GetData(Type* output){ + if((sizeof(Type) + get_pos) <= get_len){ + *output = (Type*)get_buffer; + get_pos += sizeof(output); + } + } + void AddZeros(int16 num){ + int8* data = new int8[num]; + memset(data, 0, num); + AddData(*data); + safe_delete_array(data); + } + template void StructAddData(Type input, int16 size, string* datastring){ + if(datastring) + datastring->append((char*)&input, size); + else + buffer.append((char*)&input, size); + } + template void StructAddData(Type input, int32 array_size, int16 size, string* datastring){ + if(array_size>0){ + for(int32 i=0;i void AddData(Type input, string* datastring = 0){ + if(!datastring) + datastring = &buffer; + datastring->append((char*)&input, sizeof(input)); + } + template void AddData(Type input, int32 array_size, string* datastring = 0){ + if(array_size>0){ + for(int32 i=0;i void AddDataString(String* input, string* datastring = 0){ + AddDataString(*input, datastring); + } + template void AddDataString(String input, string* datastring = 0){ + input.size = input.data.length(); + if(!datastring) + datastring = &buffer; + datastring->append((char*)&input.size, sizeof(input.size)); + datastring->append(input.data); + } + void AddCharArray(char* array, string* datastring = 0){ + if(!datastring) + datastring = &buffer; + datastring->append(array); + } + void AddCharArray(char* array, int16 size, string* datastring = 0){ + if(!datastring) + datastring = &buffer; + datastring->append(array, size); + } + void AddData(string data, string* datastring = 0){ + if(!datastring) + datastring = &buffer; + datastring->append(data); + } + void Clear() { buffer.clear(); } +private: + string buffer; + uchar* get_buffer; + uchar* load_buffer; + int32 get_len; + int32 get_pos; + int32 load_len; + int32 load_pos; +}; +#endif + diff --git a/source/common/DatabaseNew.cpp b/source/common/DatabaseNew.cpp new file mode 100644 index 0000000..e1e2450 --- /dev/null +++ b/source/common/DatabaseNew.cpp @@ -0,0 +1,422 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include +#include +#include +#include +#include "Log.h" +#include "DatabaseNew.h" +#include + +//increase this if large queries are being run frequently to make less calls to malloc() +#define QUERY_INITIAL_SIZE 512 + +#if defined WORLD +#define DB_INI "world_db.ini" +#elif defined LOGIN +#define DB_INI "login_db.ini" +#elif defined PARSER +#define DB_INI "parser_db.ini" +#endif + +DatabaseNew::DatabaseNew() { + mysql_init(&mysql); + int timeout = 10; + mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); + MMysql.SetName("DatabaseNew::mysql"); +} + +DatabaseNew::~DatabaseNew() { + mysql_close(&mysql); +#if MYSQL_VERSION_ID >= 50003 + mysql_library_end(); +#else + mysql_server_end(); +#endif +} + +bool DatabaseNew::Connect() { + char line[256], *key, *val; + char host[256], user[64], password[64], database[64], port[64]; + bool found_section = false; + FILE *f; + + if ((f = fopen(DB_INI, "r")) == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Unable to read %s\n", DB_INI); + return false; + } + + memset(host, 0, sizeof(host)); + memset(user, 0, sizeof(user)); + memset(password, 0, sizeof(password)); + memset(database, 0, sizeof(database)); + memset(port, 0, sizeof(port)); + + while (fgets(line, sizeof(line), f) != NULL) { + if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') + continue; + + if (!found_section) { + if (strncasecmp(line, "[Database]", 10) == 0) + found_section = true; + } + else { + if ((key = strtok(line, "=")) != NULL) { + if ((val = strtok(NULL, "\r\n")) != NULL) { + if (strncasecmp(line, "host", 4) == 0) + strncpy(host, val, sizeof(host) - 1); + else if (strncasecmp(line, "user", 4) == 0) + strncpy(user, val, sizeof(user) - 1); + else if (strncasecmp(line, "password", 8) == 0) + strncpy(password, val, sizeof(password) - 1); + else if (strncasecmp(line, "database", 8) == 0) + strncpy(database, val, sizeof(database) - 1); + else if (strncasecmp(line, "port", 4) == 0) + strncpy(port, val, sizeof(port) - 1); + } + } + } + } + + fclose(f); + + if (host[0] == '\0') { + LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'host' in '%s'\n", DB_INI); + return false; + } + if (user[0] == '\0') { + LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'user' in '%s'\n", DB_INI); + return false; + } + if (password[0] == '\0') { + LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'password' in '%s'\n", DB_INI); + return false; + } + if (database[0] == '\0') { + LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'database' in '%s'\n", DB_INI); + return false; + } + + unsigned int portnum = atoul(port); + return Connect(host, user, password, database, portnum); +} + +bool DatabaseNew::Connect(const char *host, const char *user, const char *password, const char *database, unsigned int port) { + if (mysql_real_connect(&mysql, host, user, password, database, port, NULL, 0) == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Unable to connect to MySQL server at %s:%u: %s\n", host, port, mysql_error(&mysql)); + return false; + } + + return true; +} + +bool DatabaseNew::Query(const char *query, ...) { + char *buf; + size_t size = QUERY_INITIAL_SIZE; + int num_chars; + va_list args; + bool ret = true; + + MMysql.writelock(__FUNCTION__, __LINE__); + while (true) { + if ((buf = (char *)malloc(size)) == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate database query of %u bytes\n", size); + MMysql.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + + va_start(args, query); + num_chars = vsnprintf(buf, size, query, args); + va_end(args); + + if (num_chars > -1 && (size_t)num_chars < size) + break; + + if (num_chars > -1) + size = num_chars + 1; + else + size *= 2; + + free(buf); + } + + if (mysql_real_query(&mysql, buf, num_chars) != 0) { + + if (mysql_errno(&mysql) == CR_SERVER_LOST || mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) { + LogWrite(DATABASE__ERROR, 0, "Database", "Lost connection, attempting to recover and retry query..."); + Connect(); + + // retry attempt of previous query (1 try and we give up) + if (mysql_real_query(&mysql, buf, num_chars) != 0) { + ret = false; + } + } + else if (!IsIgnoredErrno(mysql_errno(&mysql))) { + LogWrite(DATABASE__ERROR, 0, "Database", "Error %i running MySQL query: %s\n%s\n", mysql_errno(&mysql), mysql_error(&mysql), buf); + ret = false; + } + } + free(buf); + + MMysql.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +bool DatabaseNew::Select(DatabaseResult *result, const char *query, ...) { + char *buf; + size_t size = QUERY_INITIAL_SIZE; + int num_chars; + va_list args; + MYSQL_RES *res; + bool ret = true; + + MMysql.writelock(__FUNCTION__, __LINE__); + while (true) { + if ((buf = (char *)malloc(size)) == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate database query of %u bytes\n", size); + MMysql.releasewritelock(__FUNCTION__, __LINE__); + return false; + } + + va_start(args, query); + num_chars = vsnprintf(buf, size, query, args); + va_end(args); + + if (num_chars > -1 && (size_t)num_chars < size) + break; + + if (num_chars > -1) + size = num_chars + 1; + else + size *= 2; + + free(buf); + } + + if (mysql_real_query(&mysql, buf, (unsigned long)num_chars) != 0) { + + if (mysql_errno(&mysql) == CR_SERVER_LOST || mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) { + LogWrite(DATABASE__ERROR, 0, "Database", "Lost connection, attempting to recover and retry query..."); + + mysql_close(&mysql); + Connect(); + + // retry attempt of previous query (1 try and we give up) + if (mysql_real_query(&mysql, buf, (unsigned long)num_chars) != 0) { + ret = false; + } + } + else if (!IsIgnoredErrno(mysql_errno(&mysql))) { + LogWrite(DATABASE__ERROR, 0, "Database", "Error %i running MySQL query: %s\n%s\n", mysql_errno(&mysql), mysql_error(&mysql), buf); + ret = false; + } + } + + if (ret && !IsIgnoredErrno(mysql_errno(&mysql))) { + res = mysql_store_result(&mysql); + + if (res != NULL) + { + // Grab number of rows and number of fields from the query + uint8 num_rows = mysql_affected_rows(&mysql); + uint8 num_fields = mysql_field_count(&mysql); + + ret = result->StoreResult(res, num_fields, num_rows); + } + else { + LogWrite(DATABASE__ERROR, 0, "Database", "Error storing MySql query result (%d): %s\n%s", mysql_errno(&mysql), mysql_error(&mysql), buf); + ret = false; + } + } + + free(buf); + + MMysql.releasewritelock(__FUNCTION__, __LINE__); + return ret; +} + +int32 DatabaseNew::LastInsertID() +{ + return (int32)mysql_insert_id(&mysql); +} + +long DatabaseNew::AffectedRows() +{ + return mysql_affected_rows(&mysql); +} + +char * DatabaseNew::Escape(const char *str, size_t len) { + char *buf = (char *)malloc(len * 2 + 1); + + if (buf == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", len * 2 + 1, __FUNCTION__, __LINE__); + return NULL; + } + + mysql_real_escape_string(&mysql, buf, str, len); + return buf; +} + +char * DatabaseNew::Escape(const char *str) { + return Escape(str, strlen(str)); +} + +string DatabaseNew::EscapeStr(const char *str, size_t len) { + char *buf = (char *)malloc(len * 2 + 1); + string ret; + + if (buf == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", len * 2 + 1, __FUNCTION__, __LINE__); + return NULL; + } + + mysql_real_escape_string(&mysql, buf, str, len); + ret.append(buf); + free(buf); + + return ret; +} + +string DatabaseNew::EscapeStr(const char *str) { + return EscapeStr(str, strlen(str)); +} + +string DatabaseNew::EscapeStr(string str) { + return EscapeStr(str.c_str(), str.length()); +} + +bool DatabaseNew::QueriesFromFile(const char * file) { + bool success = true; + long size; + char *buf; + int ret; + MYSQL_RES *res; + FILE *f; + + f = fopen(file, "rb"); + if (f == NULL) { + LogWrite(DATABASE__ERROR, 0, "Database", "Unable to open '%s' for reading: %s", file, strerror(errno)); + return false; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + + buf = (char *)malloc(size + 1); + if (buf == NULL) { + fclose(f); + LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", size + 1, __FUNCTION__, __LINE__); + return false; + } + + if (fread(buf, sizeof(*buf), size, f) != (size_t)size) { + LogWrite(DATABASE__ERROR, 0, "Database", "Failed to read from '%s': %s", file, strerror(errno)); + fclose(f); + free(buf); + return false; + } + + buf[size] = '\0'; + fclose(f); + + mysql_set_server_option(&mysql, MYSQL_OPTION_MULTI_STATEMENTS_ON); + ret = mysql_real_query(&mysql, buf, size); + free(buf); + + if (ret != 0) { + LogWrite(DATABASE__ERROR, 0, "Database", "Error running MySQL queries from file '%s' (%d): %s", file, mysql_errno(&mysql), mysql_error(&mysql)); + success = false; + } + else { + //all results must be processed + do { + res = mysql_store_result(&mysql); + if (res != NULL) + mysql_free_result(res); + ret = mysql_next_result(&mysql); + + if (ret > 0) { + LogWrite(DATABASE__ERROR, 0, "Database", "Error running MySQL queries from file '%s' (%d): %s", file, mysql_errno(&mysql), mysql_error(&mysql)); + success = false; + } + + } while (ret == 0); + } + mysql_set_server_option(&mysql, MYSQL_OPTION_MULTI_STATEMENTS_OFF); + + return success; +} + +void DatabaseNew::SetIgnoredErrno(unsigned int db_errno) { + vector::iterator itr; + + for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) { + if ((*itr) == db_errno) + return; + } + + ignored_errnos.push_back(db_errno); +} + +void DatabaseNew::RemoveIgnoredErrno(unsigned int db_errno) { + vector::iterator itr; + + for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) { + if ((*itr) == db_errno) { + ignored_errnos.erase(itr); + break; + } + } +} + +bool DatabaseNew::IsIgnoredErrno(unsigned int db_errno) { + vector::iterator itr; + + for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) { + if ((*itr) == db_errno) + return true; + } + + return false; +} + +// Sends the MySQL server a keepalive +void DatabaseNew::PingNewDB() { + MMysql.writelock(__FUNCTION__, __LINE__); + mysql_ping(&mysql); + + int32* errnum = new int32; + *errnum = mysql_errno(&mysql); + + switch (*errnum) + { + case CR_COMMANDS_OUT_OF_SYNC: + case CR_SERVER_GONE_ERROR: + case CR_UNKNOWN_ERROR: + { + LogWrite(DATABASE__ERROR, 0, "Database", "[Database] We lost connection to the database., errno: %i", errno); + break; + } + } + + safe_delete(errnum); + MMysql.releasewritelock(__FUNCTION__, __LINE__); +} \ No newline at end of file diff --git a/source/common/DatabaseNew.h b/source/common/DatabaseNew.h new file mode 100644 index 0000000..2e2e002 --- /dev/null +++ b/source/common/DatabaseNew.h @@ -0,0 +1,48 @@ +#ifndef COMMON_DATABASE_H_ +#define COMMON_DATABASE_H_ + +#include +#include "DatabaseResult.h" + +using namespace std; + +class DatabaseNew { +public: + DatabaseNew(); + virtual ~DatabaseNew(); + + unsigned int GetError() {return mysql_errno(&mysql);} + const char * GetErrorMsg() {return mysql_error(&mysql);} + + bool Connect(); + bool Connect(const char *host, const char *user, const char *password, const char *database, unsigned int port = 3306); + + bool Query(const char *query, ...); + bool Select(DatabaseResult *result, const char *query, ...); + + int32 LastInsertID(); + long AffectedRows(); + + //these two must free() the return char* after it's used in a query + char * Escape(const char *str, size_t len); + char * Escape(const char *str); + + //does not need free() + string EscapeStr(const char *str, size_t len); + string EscapeStr(const char *str); + string EscapeStr(string str); + + bool QueriesFromFile(const char *file); + void SetIgnoredErrno(unsigned int db_errno); + void RemoveIgnoredErrno(unsigned int db_errno); + bool IsIgnoredErrno(unsigned int db_errno); + + void PingNewDB(); +private: + MYSQL mysql; + Mutex MMysql; + + vector ignored_errnos; +}; + +#endif diff --git a/source/common/DatabaseResult.cpp b/source/common/DatabaseResult.cpp new file mode 100644 index 0000000..05df1a8 --- /dev/null +++ b/source/common/DatabaseResult.cpp @@ -0,0 +1,234 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include +#include +#include "Log.h" +#include "DatabaseResult.h" + +//enforced by MySQL...couldn't find a #define in their headers though +#define FIELD_NAME_MAX 64 + +//return this instead of NULL for certain functions to prevent crashes from coding errors +static const char *empty_str = ""; + +DatabaseResult::DatabaseResult(): field_map(), result(0), num_fields(0), row(0) { +} + +DatabaseResult::~DatabaseResult() { + unsigned int i; + + if (result != NULL) + mysql_free_result(result); + + if (field_map.size()) { + field_map.clear(); + } +} + +bool DatabaseResult::StoreResult(MYSQL_RES* res, uint8 field_count, uint8 row_count) { + + //clear any previously stored result + if (result != NULL) + mysql_free_result(result); + + //clear any field names from a previous result + if (field_map.size()) { + field_map.clear(); + } + + result = res; + num_rows = row_count; + num_fields = field_count; + + // No rows or fields then we don't care + if (!num_rows || !num_fields) { + mysql_free_result(res); + result = NULL; + return false; + } + + + const MYSQL_FIELD* fields = mysql_fetch_fields(result); + + for (uint8 i = 0; i < num_fields; ++i) { + field_map.emplace(std::make_pair(std::string_view(fields[i].name), i)); + } + + return true; +} + +const char * DatabaseResult::GetFieldValue(unsigned int index) { + if (index >= num_fields) { + LogWrite(DATABASE__ERROR, 0, "Database Result", "Attempt to access field at index %u but there %s only %u field%s", index, num_fields == 1 ? "is" : "are", num_fields, num_fields == 1 ? "" : "s"); + return NULL; + } + + return row[index]; +} + +const char * DatabaseResult::GetFieldValueStr(const char *field_name) { + const auto& map_iterator = field_map.find(std::string_view(field_name)); + if (map_iterator != field_map.end()) { + return row[map_iterator->second]; + } + + LogWrite(DATABASE__ERROR, 0, "Database Result", "Unknown field name '%s'", field_name); + return NULL; +} + +bool DatabaseResult::Next() { + return (result != NULL && (row = mysql_fetch_row(result)) != NULL); +} + +bool DatabaseResult::IsNull(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL; +} + +bool DatabaseResult::IsNullStr(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL; +} + +int8 DatabaseResult::GetInt8(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +int8 DatabaseResult::GetInt8Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +sint8 DatabaseResult::GetSInt8(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +sint8 DatabaseResult::GetSInt8Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +int16 DatabaseResult::GetInt16(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +int16 DatabaseResult::GetInt16Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +sint16 DatabaseResult::GetSInt16(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +sint16 DatabaseResult::GetSInt16Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +int32 DatabaseResult::GetInt32(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0U : strtoul(value, NULL, 10); +} + +int32 DatabaseResult::GetInt32Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0U : strtoul(value, NULL, 10); +} + +sint32 DatabaseResult::GetSInt32(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0 : atoi(value); +} + +sint32 DatabaseResult::GetSInt32Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0 : atoi(value); +} + +uint64 DatabaseResult::GetInt64(unsigned int index) { + const char *value = GetFieldValue(index); +#ifdef _WIN32 + return value == NULL ? 0UL : _strtoui64(value, NULL, 10); +#else + return value == NULL ? 0UL : strtoull(value, NULL, 10); +#endif +} + +uint64 DatabaseResult::GetInt64Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); +#ifdef _WIN32 + return value == NULL ? 0UL : _strtoui64(value, NULL, 10); +#else + return value == NULL ? 0UL : strtoull(value, NULL, 10); +#endif +} + +sint64 DatabaseResult::GetSInt64(unsigned int index) { + const char *value = GetFieldValue(index); +#ifdef _WIN32 + return value == NULL ? 0L : _strtoi64(value, NULL, 10); +#else + return value == NULL ? 0L : strtoll(value, NULL, 10); +#endif +} + +sint64 DatabaseResult::GetSInt64Str(const char *field_name) { + const char *value = GetFieldValueStr(field_name); +#ifdef _WIN32 + return value == NULL ? 0L : _strtoi64(value, NULL, 10); +#else + return value == NULL ? 0L : strtoll(value, NULL, 10); +#endif +} + +float DatabaseResult::GetFloat(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? 0.0F : atof(value); +} + +float DatabaseResult::GetFloatStr(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? 0.0F : atof(value); +} + +char DatabaseResult::GetChar(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? '\0' : value[0]; +} + +char DatabaseResult::GetCharStr(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? '\0' : value[0]; +} + +const char * DatabaseResult::GetString(unsigned int index) { + const char *value = GetFieldValue(index); + return value == NULL ? empty_str : value; +} + +const char * DatabaseResult::GetStringStr(const char *field_name) { + const char *value = GetFieldValueStr(field_name); + return value == NULL ? empty_str : value; +} diff --git a/source/common/DatabaseResult.h b/source/common/DatabaseResult.h new file mode 100644 index 0000000..36b3c3e --- /dev/null +++ b/source/common/DatabaseResult.h @@ -0,0 +1,57 @@ +#ifndef COMMON_DATABASERESULT_H_ +#define COMMON_DATABASERESULT_H_ + +#include "types.h" +#ifdef _WIN32 +#include //#include when we/if we go to winsock2 :/ +#endif +#include +#include + +class DatabaseResult { +public: + DatabaseResult(); + virtual ~DatabaseResult(); + + bool StoreResult(MYSQL_RES* result, uint8 field_count, uint8 row_count); + bool Next(); + + bool IsNull(unsigned int index); + bool IsNullStr(const char *field_name); + int8 GetInt8(unsigned int index); + int8 GetInt8Str(const char *field_name); + sint8 GetSInt8(unsigned int index); + sint8 GetSInt8Str(const char *field_name); + int16 GetInt16(unsigned int index); + int16 GetInt16Str(const char *field_name); + sint16 GetSInt16(unsigned int index); + sint16 GetSInt16Str(const char *field_name); + int32 GetInt32(unsigned int index); + int32 GetInt32Str(const char *field_name); + sint32 GetSInt32(unsigned int index); + sint32 GetSInt32Str(const char *field_name); + int64 GetInt64(unsigned int index); + int64 GetInt64Str(const char *field_name); + sint64 GetSInt64(unsigned int index); + sint64 GetSInt64Str(const char *field_name); + float GetFloat(unsigned int index); + float GetFloatStr(const char *field_name); + char GetChar(unsigned int index); + char GetCharStr(const char *field_name); + const char * GetString(unsigned int index); + const char * GetStringStr(const char *field_name); + + const unsigned int GetNumRows() { return num_rows; } + + const char * GetFieldValue(unsigned int index); + const char * GetFieldValueStr(const char *field_name); +private: + MYSQL_RES *result; + MYSQL_ROW row; + unsigned int num_rows; + unsigned int num_fields; + + std::map field_map; +}; + +#endif diff --git a/source/common/EQ2_Common_Structs.h b/source/common/EQ2_Common_Structs.h new file mode 100644 index 0000000..e6db286 --- /dev/null +++ b/source/common/EQ2_Common_Structs.h @@ -0,0 +1,361 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _EQ2COMMON_STRUCTS_ +#define _EQ2COMMON_STRUCTS_ + +#define SPAWN_PACKET_SIZE 895 +#define EQUIPMENT_L_WEAPON_INDEX 0 //chars left hand weapon +#define EQUIPMENT_R_WEAPON_INDEX 1 //chars right hand weapon +#define EQUIPMENT_HELMET 2 + +#pragma pack(1) +struct KeyGen_Struct{ + int32 size; +}; +struct KeyGen_End_Struct{ + int32 exponent_len; + int8 exponent; +}; +struct LoginByNumRequest_Struct{ + int32 account_id; + int32 access_code; + int16 version; + int32 unknown2[5]; +}; +struct LS_LoginResponse{ + int8 reply_code; // 0 granted, 1 denied + int16 unknown01; + int8 unknown02; + sint32 unknown03; // -1 denied, 0 granted + sint32 unknown04; + sint32 unknown05; + sint32 unknown06; + int8 unknown07; + int8 unknown08; + int8 unknown09; + int8 unknown10; + sint32 unknown11; + int32 accountid; + int16 unknown12; +}; +#pragma pack() +enum EQ2_EquipmentSlot { + slot_primary=0, + slot_secondary=1, + slot_head=2, + slot_chest=3, + slot_shoulders=4, + slot_forearms=5, + slot_hands=6, + slot_legs=7, + slot_feet=8, + slot_left_ring=9, + slot_right_ring=10, + slot_ears=11, + slot_neck=12, + slot_left_wrist=13, + slot_right_wrist=14, + slot_ranged=15, + slot_ammo=16, + slot_waist=17, + slot_activate1=18, + slot_activate2=19, + slot_textures=20, + slot_hair=21, + slot_beard=22, + slot_naked_chest=23, + slot_naked_legs=24 +}; +struct EQ2_EquipmentItem{ + int16 type; + EQ2_Color color; + EQ2_Color highlight; +}; +struct EQ2_Equipment{ + int16 equip_id[25]; + EQ2_Color color[25]; + EQ2_Color highlight[25]; +}; +#pragma pack(1) +struct CharFeatures{ + int16 hair_type; + int16 hair_face_type; + int16 wing_type; + int16 chest_type; + int16 legs_type; + sint8 eye_type[3]; + sint8 ear_type[3]; + sint8 eye_brow_type[3]; + sint8 cheek_type[3]; + sint8 lip_type[3]; + sint8 chin_type[3]; + sint8 nose_type[3]; + sint8 body_size; + sint8 body_age; + sint8 soga_eye_type[3]; + sint8 soga_ear_type[3]; + sint8 soga_eye_brow_type[3]; + sint8 soga_cheek_type[3]; + int16 soga_chest_type; + int16 soga_legs_type; + sint8 soga_lip_type[3]; + sint8 soga_chin_type[3]; + sint8 soga_nose_type[3]; + sint8 soga_body_size; + sint8 soga_body_age; + int16 soga_hair_type; + int16 soga_hair_face_type; + int16 combat_voice; + int16 emote_voice; + int16 mount_model_type; + + EQ2_Color mount_saddle_color; + EQ2_Color mount_color; + EQ2_Color skin_color; + EQ2_Color eye_color; + EQ2_Color hair_type_color; + EQ2_Color hair_type_highlight_color; + EQ2_Color hair_face_color; + EQ2_Color hair_face_highlight_color; + EQ2_Color hair_highlight_color; + EQ2_Color wing_color1; + EQ2_Color wing_color2; + EQ2_Color shirt_color; + EQ2_Color pants_color; + EQ2_Color hair_color1; + EQ2_Color hair_color2; + EQ2_Color soga_skin_color; + EQ2_Color soga_eye_color; + EQ2_Color soga_hair_color1; + EQ2_Color soga_hair_color2; + EQ2_Color soga_hair_type_color; + EQ2_Color soga_hair_type_highlight_color; + EQ2_Color soga_hair_face_color; + EQ2_Color soga_hair_face_highlight_color; + EQ2_Color soga_hair_highlight_color; + + EQ2_Color model_color; + EQ2_Color soga_model_color; +}; +struct PositionData{ + int32 grid_id; + int32 bad_grid_id; + sint8 Speed1; + sint8 Speed2; + sint16 Dir1; + sint16 Dir2; + sint16 Pitch1; + sint16 Pitch2; + sint16 Roll; + float X; + float Y; + float Z; + float X2; + float Y2; + float Z2; + float X3; + float Y3; + float Z3; + float SpawnOrigX; + float SpawnOrigY; + float SpawnOrigZ; + float SpawnOrigHeading; + float SpawnOrigPitch; + float SpawnOrigRoll; + float SpeedX; + float SpeedY; + float SpeedZ; + float SideSpeed; + float VertSpeed; + float ClientHeading1; + float ClientHeading2; + float ClientPitch; + int16 collision_radius; + int16 state; +}; +struct AppearanceData { + PositionData pos; + int16 model_type; + int16 soga_model_type; + int16 activity_status; + int16 visual_state; + int16 action_state; + int16 mood_state; + int16 emote_state; + int8 attackable; + int8 icon; + int8 hide_hood; + int8 show_level; + + int8 locked_no_loot; + int8 quest_flag; + int8 heroic_flag; + int8 show_command_icon; + int8 display_hand_icon; + int8 player_flag; + int8 targetable; + int8 display_name; + char sub_title[255]; //Guild + int32 display_hp;//0 = 100 percent + int32 power_left; //bar not shown if >=100 + int8 adventure_class; + int8 tradeskill_class; + int8 level; + int8 tradeskill_level; + int8 min_level; + int8 max_level; + int8 difficulty; + int16 visible; // 02 = normal, 15 = shadow + char name[128]; //size around here somewhere + char last_name[64]; + char prefix_title[128]; + char suffix_title[128]; + int8 race; + int8 gender; + int32 randomize; + int8 lua_race_id; +}; +struct Player_Update{ +/*0000*/ int32 activity; +/*0004*/ float unknown2; // 1 +/*0008*/ float direction1; +/*0012*/ float unknown3[8]; +/*0044*/ float speed; +/*0048*/ float side_speed; +/*0052*/ float vert_speed; +/*0056*/ float orig_x; +/*0060*/ float orig_y; +/*0064*/ float orig_z; +/*0068*/ float orig_x2; +/*0072*/ float orig_y2; +/*0076*/ float orig_z2; +/*0080*/ float unknown5[3]; +/*0092*/ int32 unknown6; +/*0096*/ float unknown7[3]; +/*0108*/ int32 unknown8; +/*0112*/ int32 grid_location; +/*0116*/ float x; +/*0120*/ float y; +/*0124*/ float z; +/*0128*/ float direction2; +/*0132*/ float pitch; +/*0136*/ float unknown10; +/*0140*/ float speed_x; +/*0144*/ float speed_y; +/*0148*/ float speed_z; +}; +struct Player_Update283 { + /*0000*/ int32 activity; + /*0004*/ int32 movement_mode; // 1 + /*0008*/ float direction1; + /*0012*/ float desiredpitch; + /*0016*/ float desired_heading_speed; + /*0020*/ float desired_pitch_speed; + /*0024*/ float collision_radius; + /*0028*/ float collision_scale; + /*0032*/ float temp_scale; + /*0036*/ float speed_modifier; + /*0040*/ float swim_speed_modifier; + /*0044*/ float speed; + /*0048*/ float side_speed; + /*0052*/ float vert_speed; + /*0056*/ float orig_x; + /*0060*/ float orig_y; + /*0064*/ float orig_z; + /*0068*/ float orig_x2; + /*0072*/ float orig_y2; + /*0076*/ float orig_z2; + /*0080*/ int32 face_actor_id; + /*0084*/ int32 face_actor_range; + /*0088*/ int32 grid_location; + /*0092*/ float x; + /*0096*/ float y; + /*0100*/ float z; + /*0104*/ float direction2; + /*0108*/ float pitch; + /*0112*/ float roll; + /*0116*/ float speed_x; + /*0120*/ float speed_y; + /*0124*/ float speed_z; +};//0128 +struct Player_Update1096{ +/*0000*/ int32 activity; +/*0004*/ float unknown2; // 1 +/*0008*/ float direction1; +/*0012*/ float unknown3[8]; +/*0044*/ float unk_speed; +/*0048*/ float speed; +/*0052*/ float side_speed; +/*0056*/ float vert_speed; +/*0060*/ float orig_x; +/*0064*/ float orig_y; +/*0068*/ float orig_z; +/*0072*/ float orig_x2; +/*0076*/ float orig_y2; +/*0080*/ float orig_z2; +/*0092*/ float unknown5[3]; +/*0096*/ int32 unknown6; +/*0108*/ float unknown7[3]; +/*0112*/ int32 unknown8; +/*0116*/ int32 grid_location; +/*0120*/ float x; +/*0124*/ float y; +/*0128*/ float z; +/*0132*/ float direction2; +/*0136*/ float pitch; +/*0140*/ float unknown10; +/*0144*/ float speed_x; +/*0148*/ float speed_y; +/*0152*/ float speed_z; +}; + +struct Player_Update1144{ +/*0000*/ int32 activity; +/*0004*/ float unknown2; // 1 +/*0008*/ float direction1; +/*0012*/ float unknown3[12]; +/*0044*/ float unk_speed; +/*0048*/ float speed; +/*0052*/ float side_speed; +/*0056*/ float vert_speed; +/*0060*/ float orig_x; +/*0064*/ float orig_y; +/*0068*/ float orig_z; +/*0072*/ float orig_x2; +/*0076*/ float orig_y2; +/*0080*/ float orig_z2; +/*0092*/ float unknown5[3]; +/*0096*/ int32 unknown6; +/*0108*/ float unknown7[3]; +/*0112*/ int32 unknown8; +/*0116*/ int32 grid_location; +/*0120*/ float x; +/*0124*/ float y; +/*0128*/ float z; +/*0132*/ float direction2; +/*0136*/ float pitch; +/*0140*/ float unknown10; +/*0144*/ float speed_x; +/*0148*/ float speed_y; +/*0152*/ float speed_z; +}; +#pragma pack() +#endif + diff --git a/source/common/EQEMuError.cpp b/source/common/EQEMuError.cpp new file mode 100644 index 0000000..323463a --- /dev/null +++ b/source/common/EQEMuError.cpp @@ -0,0 +1,131 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 +#include +#include +#endif +#include "EQEMuError.h" +#include "linked_list.h" +#include "Mutex.h" +#include "MiscFunctions.h" +#include +#include +#ifdef WIN32 + #include +#endif + +void CatchSignal(int sig_num); + +const char* EQEMuErrorText[EQEMuError_MaxErrorID] = { "ErrorID# 0, No Error", + "MySQL Error #1405 or #2001 means your mysql server rejected the username and password you presented it.", + "MySQL Error #2003 means you were unable to connect to the mysql server.", + "MySQL Error #2005 means you there are too many connections on the mysql server. The server is overloaded.", + "MySQL Error #2007 means you the server is out of memory. The server is overloaded.", + }; + +LinkedList* EQEMuErrorList; +Mutex* MEQEMuErrorList; +AutoDelete< LinkedList > ADEQEMuErrorList(&EQEMuErrorList); +AutoDelete ADMEQEMuErrorList(&MEQEMuErrorList); + +const char* GetErrorText(int32 iError) { + if (iError >= EQEMuError_MaxErrorID) + return "ErrorID# out of range"; + else + return EQEMuErrorText[iError]; +} + +void AddEQEMuError(eEQEMuError iError, bool iExitNow) { + if (!iError) + return; + if (!EQEMuErrorList) { + EQEMuErrorList = new LinkedList; + MEQEMuErrorList = new Mutex; + } + LockMutex lock(MEQEMuErrorList); + + LinkedListIterator iterator(*EQEMuErrorList); + iterator.Reset(); + while (iterator.MoreElements()) { + if (iterator.GetData()[0] == 1) { + if (*((eEQEMuError*) &(iterator.GetData()[1])) == iError) + return; + } + iterator.Advance(); + } + + char* tmp = new char[6]; + tmp[0] = 1; + tmp[5] = 0; + *((int32*) &tmp[1]) = iError; + EQEMuErrorList->Append(tmp); + + if (iExitNow) + CatchSignal(2); +} + +void AddEQEMuError(char* iError, bool iExitNow) { + if (!iError) + return; + if (!EQEMuErrorList) { + EQEMuErrorList = new LinkedList; + MEQEMuErrorList = new Mutex; + } + LockMutex lock(MEQEMuErrorList); + char* tmp = strcpy(new char[strlen(iError) + 1], iError); + EQEMuErrorList->Append(tmp); + + if (iExitNow) + CatchSignal(2); +} + +int32 CheckEQEMuError() { + if (!EQEMuErrorList) + return 0; + int32 ret = 0; + char* tmp = 0; + bool HeaderPrinted = false; + LockMutex lock(MEQEMuErrorList); + + while ((tmp = EQEMuErrorList->Pop() )) { + if (!HeaderPrinted) { + fprintf(stdout, "===============================\nRuntime errors:\n\n"); + HeaderPrinted = true; + } + if (tmp[0] == 1) { + fprintf(stdout, "%s\n", GetErrorText(*((int32*) &tmp[1]))); + } + else { + fprintf(stdout, "%s\n\n", tmp); + } + safe_delete(tmp); + ret++; + } + return ret; +} + +void CheckEQEMuErrorAndPause() { + if (CheckEQEMuError()) { + fprintf(stdout, "Hit any key to exit\n"); + getchar(); + } +} + + diff --git a/source/common/EQEMuError.h b/source/common/EQEMuError.h new file mode 100644 index 0000000..03b364c --- /dev/null +++ b/source/common/EQEMuError.h @@ -0,0 +1,39 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQEMuError_H +#define EQEMuError_H + +#include "../common/types.h" + +enum eEQEMuError { EQEMuError_NoError, + EQEMuError_Mysql_1405, + EQEMuError_Mysql_2003, + EQEMuError_Mysql_2005, + EQEMuError_Mysql_2007, + EQEMuError_MaxErrorID }; + +void AddEQEMuError(eEQEMuError iError, bool iExitNow = false); +void AddEQEMuError(char* iError, bool iExitNow = false); +int32 CheckEQEMuError(); +void CheckEQEMuErrorAndPause(); + +#endif + + diff --git a/source/common/EQPacket.cpp b/source/common/EQPacket.cpp new file mode 100644 index 0000000..499ac90 --- /dev/null +++ b/source/common/EQPacket.cpp @@ -0,0 +1,652 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "debug.h" +#include +#include +#include +#include +#include "EQPacket.h" +#include "misc.h" +#include "op_codes.h" +#include "CRC16.h" +#include "opcodemgr.h" +#include "packet_dump.h" +#include +#include "Log.h" +#include + +using namespace std; +extern mapEQOpcodeManager; + +uint8 EQApplicationPacket::default_opcode_size=2; + +EQPacket::EQPacket(const uint16 op, const unsigned char *buf, uint32 len) +{ + this->opcode=op; + this->pBuffer=NULL; + this->size=0; + version = 0; + setTimeInfo(0,0); + if (len>0) { + this->size=len; + pBuffer= new unsigned char[this->size]; + if (buf) { + memcpy(this->pBuffer,buf,this->size); + } else { + memset(this->pBuffer,0,this->size); + } + } +} + +const char* EQ2Packet::GetOpcodeName() { + int16 OpcodeVersion = GetOpcodeVersion(version); + if (EQOpcodeManager.count(OpcodeVersion) > 0) + return EQOpcodeManager[OpcodeVersion]->EmuToName(login_op); + else + return NULL; +} + +int8 EQ2Packet::PreparePacket(int16 MaxLen) { + int16 OpcodeVersion = GetOpcodeVersion(version); + + // stops a crash for incorrect version + if (EQOpcodeManager.count(OpcodeVersion) == 0) + { + LogWrite(PACKET__ERROR, 0, "Packet", "Version %i is not listed in the opcodes table.", version); + return -1; + } + + packet_prepared = true; + + int16 login_opcode = EQOpcodeManager[OpcodeVersion]->EmuToEQ(login_op); + if (login_opcode == 0xcdcd) + { + LogWrite(PACKET__ERROR, 0, "Packet", "Version %i is not listed in the opcodes table for opcode %s", version, EQOpcodeManager[OpcodeVersion]->EmuToName(login_op)); + return -1; + } + + int16 orig_opcode = login_opcode; + int8 offset = 0; + //one of the int16s is for the seq, other is for the EQ2 opcode and compressed flag (OP_Packet is the header, not the opcode) + int32 new_size = size + sizeof(int16) + sizeof(int8); + bool oversized = false; + if (login_opcode != 2) { + new_size += sizeof(int8); //for opcode + if (login_opcode >= 255) { + new_size += sizeof(int16); + oversized = true; + } + else + login_opcode = ntohs(login_opcode); + } + uchar* new_buffer = new uchar[new_size]; + memset(new_buffer, 0, new_size); + uchar* ptr = new_buffer + sizeof(int16); // sequence is first + if (login_opcode != 2) { + if (oversized) { + ptr += sizeof(int8); //compressed flag + int8 addon = 0xff; + memcpy(ptr, &addon, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, &login_opcode, sizeof(int16)); + ptr += sizeof(int16); + } + else { + memcpy(ptr, &login_opcode, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, pBuffer, size); + + safe_delete_array(pBuffer); + pBuffer = new_buffer; + offset = new_size - size - 1; + size = new_size; + + return offset; +} + +uint32 EQProtocolPacket::serialize(unsigned char *dest, int8 offset) const +{ + if (opcode>0xff) { + *(uint16 *)dest=opcode; + } else { + *(dest)=0; + *(dest+1)=opcode; + } + memcpy(dest+2,pBuffer+offset,size-offset); + + return size+2; +} + +uint32 EQApplicationPacket::serialize(unsigned char *dest) const +{ + uint8 OpCodeBytes = app_opcode_size; + + if (app_opcode_size==1) + *(unsigned char *)dest=opcode; + else + { + // Application opcodes with a low order byte of 0x00 require an extra 0x00 byte inserting prior to the opcode. + if ((opcode & 0x00ff) == 0) + { + *(uint8*)dest = 0; + *(uint16*)(dest + 1) = opcode; + ++OpCodeBytes; + } + else + *(uint16*)dest = opcode; + } + + memcpy(dest+app_opcode_size,pBuffer,size); + + return size+ OpCodeBytes; +} + +EQPacket::~EQPacket() +{ + safe_delete_array(pBuffer); + pBuffer=NULL; +} + + +void EQPacket::DumpRawHeader(uint16 seq, FILE* to) const +{ + /*if (timestamp.tv_sec) { + char temp[20]; + tm t; + const time_t sec = timestamp.tv_sec; + localtime_s(&t, &sec); + strftime(temp, 20, "%F %T", &t); + fprintf(to, "%s.%06lu ", temp, timestamp.tv_usec); + }*/ + + DumpRawHeaderNoTime(seq, to); +} + +const char* EQPacket::GetOpcodeName(){ + int16 OpcodeVersion = GetOpcodeVersion(version); + if(EQOpcodeManager.count(OpcodeVersion) > 0) + return EQOpcodeManager[OpcodeVersion]->EQToName(opcode); + else + return NULL; +} +void EQPacket::DumpRawHeaderNoTime(uint16 seq, FILE *to) const +{ + if (src_ip) { + string sIP,dIP;; + sIP=long2ip(src_ip); + dIP=long2ip(dst_ip); + fprintf(to, "[%s:%d->%s:%d] ",sIP.c_str(),src_port,dIP.c_str(),dst_port); + } + if (seq != 0xffff) + fprintf(to, "[Seq=%u] ",seq); + + string name; + int16 OpcodeVersion = GetOpcodeVersion(version); + if(EQOpcodeManager.count(OpcodeVersion) > 0) + name = EQOpcodeManager[OpcodeVersion]->EQToName(opcode); + + fprintf(to, "[OpCode 0x%04x (%s) Size=%u]\n",opcode,name.c_str(),size); +} + +void EQPacket::DumpRaw(FILE *to) const +{ + DumpRawHeader(); + if (pBuffer && size) + dump_message_column(pBuffer, size, " ", to); + fprintf(to, "\n"); +} + +EQProtocolPacket::EQProtocolPacket(const unsigned char *buf, uint32 len, int in_opcode) +{ + uint32 offset = 0; + if(in_opcode>=0) + opcode = in_opcode; + else{ + offset=2; + opcode=ntohs(*(const uint16 *)buf); + } + + if (len-offset) { + pBuffer= new unsigned char[len-offset]; + size=len-offset; + if(buf) + memcpy(pBuffer,buf+offset,len-offset); + else + memset(pBuffer,0,size); + + } else { + pBuffer=NULL; + size=0; + } + version = 0; + eq2_compressed = false; + packet_prepared = false; + packet_encrypted = false; + sent_time = 0; + attempt_count = 0; + sequence = 0; +} + +bool EQ2Packet::AppCombine(EQ2Packet* rhs){ + bool result = false; + uchar* tmpbuffer = 0; + bool over_sized_packet = false; + int32 new_size = 0; + //bool whee = false; +// DumpPacket(this); +// DumpPacket(rhs); + /*if(rhs->size >= 255){ + DumpPacket(this); + DumpPacket(rhs); + whee = true; + }*/ + if (opcode==OP_AppCombined && ((size + rhs->size + 3) < 255)){ + int16 tmp_size = rhs->size - 2; + if(tmp_size >= 255){ + new_size = size+tmp_size+3; + over_sized_packet = true; + } + else + new_size = size+tmp_size+1; + tmpbuffer = new uchar[new_size]; + uchar* ptr = tmpbuffer; + memcpy(ptr, pBuffer, size); + ptr += size; + if(over_sized_packet){ + memset(ptr, 255, sizeof(int8)); + ptr += sizeof(int8); + tmp_size = htons(tmp_size); + memcpy(ptr, &tmp_size, sizeof(int16)); + ptr += sizeof(int16); + } + else{ + memcpy(ptr, &tmp_size, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, rhs->pBuffer+2, rhs->size-2); + delete[] pBuffer; + size = new_size; + pBuffer=tmpbuffer; + safe_delete(rhs); + result=true; + } + else if (rhs->size > 2 && size > 2 && (size + rhs->size + 6) < 255) { + int32 tmp_size = size - 2; + int32 tmp_size2 = rhs->size - 2; + opcode=OP_AppCombined; + bool over_sized_packet2 = false; + new_size = size; + if(tmp_size >= 255){ + new_size += 5; + over_sized_packet = true; + } + else + new_size += 3; + if(tmp_size2 >= 255){ + new_size += tmp_size2+3; + over_sized_packet2 = true; + } + else + new_size += tmp_size2+1; + tmpbuffer = new uchar[new_size]; + tmpbuffer[2]=0; + tmpbuffer[3]=0x19; + uchar* ptr = tmpbuffer+4; + if(over_sized_packet){ + memset(ptr, 255, sizeof(int8)); + ptr += sizeof(int8); + tmp_size = htons(tmp_size); + memcpy(ptr, &tmp_size, sizeof(int16)); + ptr += sizeof(int16); + } + else{ + memcpy(ptr, &tmp_size, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, pBuffer+2, size-2); + ptr += (size-2); + if(over_sized_packet2){ + memset(ptr, 255, sizeof(int8)); + ptr += sizeof(int8); + tmp_size2 = htons(tmp_size2); + memcpy(ptr, &tmp_size2, sizeof(int16)); + ptr += sizeof(int16); + } + else{ + memcpy(ptr, &tmp_size2, sizeof(int8)); + ptr += sizeof(int8); + } + memcpy(ptr, rhs->pBuffer+2, rhs->size-2); + size = new_size; + delete[] pBuffer; + pBuffer=tmpbuffer; + safe_delete(rhs); + result=true; + } + /*if(whee){ + DumpPacket(this); + cout << "fsdfsdf"; + }*/ + //DumpPacket(this); + return result; +} + +bool EQProtocolPacket::combine(const EQProtocolPacket *rhs) +{ + bool result=false; + //if(dont_combine) + // return false; + //if (opcode==OP_Combined && size+rhs->size+5<256) { + if (opcode == OP_Combined && size + rhs->size + 5 < 256) { + auto tmpbuffer = new unsigned char[size + rhs->size + 3]; + memcpy(tmpbuffer, pBuffer, size); + uint32 offset = size; + tmpbuffer[offset++] = rhs->Size(); + offset += rhs->serialize(tmpbuffer + offset); + size = offset; + delete[] pBuffer; + pBuffer = tmpbuffer; + result = true; + } + else if (size + rhs->size + 7 < 256) { + auto tmpbuffer = new unsigned char[size + rhs->size + 6]; + uint32 offset = 0; + tmpbuffer[offset++] = Size(); + offset += serialize(tmpbuffer + offset); + tmpbuffer[offset++] = rhs->Size(); + offset += rhs->serialize(tmpbuffer + offset); + size = offset; + delete[] pBuffer; + pBuffer = tmpbuffer; + opcode = OP_Combined; + result = true; + } + return result; +} + +EQApplicationPacket::EQApplicationPacket(const unsigned char *buf, uint32 len, uint8 opcode_size) +{ +uint32 offset=0; + app_opcode_size=(opcode_size==0) ? EQApplicationPacket::default_opcode_size : opcode_size; + + if (app_opcode_size==1) { + opcode=*(const unsigned char *)buf; + offset++; + } else { + opcode=*(const uint16 *)buf; + offset+=2; + } + + if ((len-offset)>0) { + pBuffer=new unsigned char[len-offset]; + memcpy(pBuffer,buf+offset,len-offset); + size=len-offset; + } else { + pBuffer=NULL; + size=0; + } + + emu_opcode = OP_Unknown; +} + +bool EQApplicationPacket::combine(const EQApplicationPacket *rhs) +{ +cout << "CALLED AP COMBINE!!!!\n"; + return false; +} + +void EQApplicationPacket::SetOpcode(EmuOpcode emu_op) { + if(emu_op == OP_Unknown) { + opcode = 0; + emu_opcode = OP_Unknown; + return; + } + + opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(emu_op); + + if(opcode == OP_Unknown) { + LogWrite(PACKET__DEBUG, 0, "Packet", "Unable to convert Emu opcode %s (%d) into an EQ opcode.", OpcodeNames[emu_op], emu_op); + } + + //save the emu opcode we just set. + emu_opcode = emu_op; +} + +const EmuOpcode EQApplicationPacket::GetOpcodeConst() const { + if(emu_opcode != OP_Unknown) { + return(emu_opcode); + } + if(opcode == 10000) { + return(OP_Unknown); + } + + EmuOpcode emu_op; + emu_op = EQOpcodeManager[GetOpcodeVersion(version)]->EQToEmu(opcode); + if(emu_op == OP_Unknown) { + LogWrite(PACKET__DEBUG, 1, "Packet", "Unable to convert EQ opcode 0x%.4X (%i) to an emu opcode (%s)", opcode, opcode, __FUNCTION__); + } + + return(emu_op); +} + +EQApplicationPacket *EQProtocolPacket::MakeApplicationPacket(uint8 opcode_size) const { + EQApplicationPacket *res = new EQApplicationPacket; + res->app_opcode_size=(opcode_size==0) ? EQApplicationPacket::default_opcode_size : opcode_size; + if (res->app_opcode_size==1) { + res->pBuffer= new unsigned char[size+1]; + memcpy(res->pBuffer+1,pBuffer,size); + *(res->pBuffer)=htons(opcode)&0xff; + res->opcode=opcode&0xff; + res->size=size+1; + } else { + res->pBuffer= new unsigned char[size]; + memcpy(res->pBuffer,pBuffer,size); + res->opcode=opcode; + res->size=size; + } + res->copyInfo(this); + return(res); +} +bool EQProtocolPacket::ValidateCRC(const unsigned char *buffer, int length, uint32 Key) +{ +bool valid=false; + // OP_SessionRequest, OP_SessionResponse, OP_OutOfSession are not CRC'd + if (buffer[0]==0x00 && (buffer[1]==OP_SessionRequest || buffer[1]==OP_SessionResponse || buffer[1]==OP_OutOfSession)) { + valid=true; + } else if(buffer[2] == 0x00 && buffer[3] == 0x19){ + valid = true; + } + else { + uint16 comp_crc=CRC16(buffer,length-2,Key); + uint16 packet_crc=ntohs(*(const uint16 *)(buffer+length-2)); +#ifdef EQN_DEBUG + if (packet_crc && comp_crc != packet_crc) { + cout << "CRC mismatch: comp=" << hex << comp_crc << ", packet=" << packet_crc << dec << endl; + } +#endif + valid = (!packet_crc || comp_crc == packet_crc); + } + return valid; +} + +uint32 EQProtocolPacket::Decompress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize) +{ +uint32 newlen=0; +uint32 flag_offset=0; + newbuf[0]=buffer[0]; + if (buffer[0]==0x00) { + flag_offset=2; + newbuf[1]=buffer[1]; + } else + flag_offset=1; + + if (length>2 && buffer[flag_offset]==0x5a) { + LogWrite(PACKET__DEBUG, 0, "Packet", "In Decompress 1"); + newlen=Inflate(const_cast(buffer+flag_offset+1),length-(flag_offset+1)-2,newbuf+flag_offset,newbufsize-flag_offset)+2; + + // something went bad with zlib + if (newlen == -1) + { + LogWrite(PACKET__ERROR, 0, "Packet", "Debug Bad Inflate!"); + DumpPacket(buffer, length); + memcpy(newbuf, buffer, length); + return length; + } + + newbuf[newlen++]=buffer[length-2]; + newbuf[newlen++]=buffer[length-1]; + } else if (length>2 && buffer[flag_offset]==0xa5) { + LogWrite(PACKET__DEBUG, 0, "Packet", "In Decompress 2"); + memcpy(newbuf+flag_offset,buffer+flag_offset+1,length-(flag_offset+1)); + newlen=length-1; + } else { + memcpy(newbuf,buffer,length); + newlen=length; + } + + return newlen; +} + +uint32 EQProtocolPacket::Compress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize) { +uint32 flag_offset=1,newlength; + //dump_message_column(buffer,length,"Before: "); + newbuf[0]=buffer[0]; + if (buffer[0]==0) { + flag_offset=2; + newbuf[1]=buffer[1]; + } + if (length>30) { + newlength=Deflate(const_cast(buffer+flag_offset),length-flag_offset,newbuf+flag_offset+1,newbufsize); + *(newbuf+flag_offset)=0x5a; + newlength+=flag_offset+1; + } else { + memmove(newbuf+flag_offset+1,buffer+flag_offset,length-flag_offset); + *(newbuf+flag_offset)=0xa5; + newlength=length+1; + } + //dump_message_column(newbuf,length,"After: "); + + return newlength; +} + +void EQProtocolPacket::ChatDecode(unsigned char *buffer, int size, int DecodeKey) +{ + if (buffer[1]!=0x01 && buffer[0]!=0x02 && buffer[0]!=0x1d) { + int Key=DecodeKey; + unsigned char *test=(unsigned char *)malloc(size); + buffer+=2; + size-=2; + + int i; + for (i = 0 ; i+4 <= size ; i+=4) + { + int pt = (*(int*)&buffer[i])^(Key); + Key = (*(int*)&buffer[i]); + *(int*)&test[i]=pt; + } + unsigned char KC=Key&0xFF; + for ( ; i < size ; i++) + { + test[i]=buffer[i]^KC; + } + memcpy(buffer,test,size); + free(test); + } +} + +void EQProtocolPacket::ChatEncode(unsigned char *buffer, int size, int EncodeKey) +{ + if (buffer[1]!=0x01 && buffer[0]!=0x02 && buffer[0]!=0x1d) { + int Key=EncodeKey; + char *test=(char*)malloc(size); + int i; + buffer+=2; + size-=2; + for ( i = 0 ; i+4 <= size ; i+=4) + { + int pt = (*(int*)&buffer[i])^(Key); + Key = pt; + *(int*)&test[i]=pt; + } + unsigned char KC=Key&0xFF; + for ( ; i < size ; i++) + { + test[i]=buffer[i]^KC; + } + memcpy(buffer,test,size); + free(test); + } +} + +bool EQProtocolPacket::IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC) { + bool ret = false; + uint16_t opcode = ntohs(*(uint16_t*)in_buff); + uint32_t offset = 2; + + switch (opcode) { + case OP_SessionRequest: + case OP_SessionDisconnect: + case OP_KeepAlive: + case OP_SessionStatResponse: + case OP_Packet: + case OP_Combined: + case OP_Fragment: + case OP_Ack: + case OP_OutOfOrderAck: + case OP_OutOfSession: + { + ret = true; + break; + } + } + + return ret; +} + + + +void DumpPacketHex(const EQApplicationPacket* app) +{ + DumpPacketHex(app->pBuffer, app->size); +} + +void DumpPacketAscii(const EQApplicationPacket* app) +{ + DumpPacketAscii(app->pBuffer, app->size); +} +void DumpPacket(const EQProtocolPacket* app) { + DumpPacketHex(app->pBuffer, app->size); +} +void DumpPacket(const EQApplicationPacket* app, bool iShowInfo) { + if (iShowInfo) { + cout << "Dumping Applayer: 0x" << hex << setfill('0') << setw(4) << app->GetOpcode() << dec; + cout << " size:" << app->size << endl; + } + DumpPacketHex(app->pBuffer, app->size); +// DumpPacketAscii(app->pBuffer, app->size); +} + +void DumpPacketBin(const EQApplicationPacket* app) { + DumpPacketBin(app->pBuffer, app->size); +} + + diff --git a/source/common/EQPacket.h b/source/common/EQPacket.h new file mode 100644 index 0000000..455a72c --- /dev/null +++ b/source/common/EQPacket.h @@ -0,0 +1,209 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _EQPACKET_H +#define _EQPACKET_H + +#include "types.h" +#include +#include + +#ifdef WIN32 + #include + #include +#else + #include + #include +#endif + +#include "emu_opcodes.h" +#include "op_codes.h" +#include "packet_dump.h" + +class OpcodeManager; + +class EQStream; + +class EQPacket { + friend class EQStream; +public: + unsigned char *pBuffer; + uint32 size; + uint32 src_ip,dst_ip; + uint16 src_port,dst_port; + uint32 priority; + timeval timestamp; + int16 version; + ~EQPacket(); + void DumpRawHeader(uint16 seq=0xffff, FILE *to = stdout) const; + void DumpRawHeaderNoTime(uint16 seq=0xffff, FILE *to = stdout) const; + void DumpRaw(FILE *to = stdout) const; + const char* GetOpcodeName(); + + void setVersion(int16 new_version){ version = new_version; } + void setSrcInfo(uint32 sip, uint16 sport) { src_ip=sip; src_port=sport; } + void setDstInfo(uint32 dip, uint16 dport) { dst_ip=dip; dst_port=dport; } + void setTimeInfo(uint32 ts_sec, uint32 ts_usec) { timestamp.tv_sec=ts_sec; timestamp.tv_usec=ts_usec; } + void copyInfo(const EQPacket *p) { src_ip=p->src_ip; src_port=p->src_port; dst_ip=p->dst_ip; dst_port=p->dst_port; timestamp.tv_sec=p->timestamp.tv_sec; timestamp.tv_usec=p->timestamp.tv_usec; } + uint32 Size() const { return size+2; } + +//no reason to have this method in zone or world + + uint16 GetRawOpcode() const { return(opcode); } + + + inline bool operator<(const EQPacket &rhs) { + return (timestamp.tv_sec < rhs.timestamp.tv_sec || (timestamp.tv_sec==rhs.timestamp.tv_sec && timestamp.tv_usec < rhs.timestamp.tv_usec)); + } + void SetProtocolOpcode(int16 new_opcode){ + opcode = new_opcode; + } + +protected: + uint16 opcode; + + EQPacket(const uint16 op, const unsigned char *buf, const uint32 len); + EQPacket(const EQPacket &p) { version = 0; } + EQPacket() { opcode=0; pBuffer=NULL; size=0; version = 0; setTimeInfo(0, 0); } + +}; + +class EQApplicationPacket; + +class EQProtocolPacket : public EQPacket { +public: + EQProtocolPacket(uint16 op, const unsigned char *buf, uint32 len) : EQPacket(op,buf,len) { + eq2_compressed = false; + packet_prepared = false; + packet_encrypted = false; + sequence = 0; + sent_time = 0; + attempt_count = 0; + acked = false; + } + EQProtocolPacket(const unsigned char *buf, uint32 len, int in_opcode = -1); + bool combine(const EQProtocolPacket *rhs); + uint32 serialize (unsigned char *dest, int8 offset = 0) const; + static bool ValidateCRC(const unsigned char *buffer, int length, uint32 Key); + static uint32 Decompress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize); + static uint32 Compress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize); + static void ChatDecode(unsigned char *buffer, int size, int DecodeKey); + static void ChatEncode(unsigned char *buffer, int size, int EncodeKey); + static bool IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC); + + EQProtocolPacket *Copy() { + EQProtocolPacket* new_packet = new EQProtocolPacket(opcode,pBuffer,size); + new_packet->eq2_compressed = this->eq2_compressed; + new_packet->packet_prepared = this->packet_prepared; + new_packet->packet_encrypted = this->packet_encrypted; + return new_packet; + } + EQApplicationPacket *MakeApplicationPacket(uint8 opcode_size=0) const; + bool eq2_compressed; + bool packet_prepared; + bool packet_encrypted; + bool acked; + int32 sent_time; + int8 attempt_count; + int32 sequence; + +private: + EQProtocolPacket(const EQProtocolPacket &p) { } + //bool dont_combine; +}; +class EQ2Packet : public EQProtocolPacket { +public: + EQ2Packet(const EmuOpcode in_login_op, const unsigned char *buf, uint32 len) : EQProtocolPacket(OP_Packet,buf,len){ + login_op = in_login_op; + eq2_compressed = false; + packet_prepared = false; + packet_encrypted = false; + } + bool AppCombine(EQ2Packet* rhs); + EQ2Packet* Copy() { + EQ2Packet* new_packet = new EQ2Packet(login_op,pBuffer,size); + new_packet->eq2_compressed = this->eq2_compressed; + new_packet->packet_prepared = this->packet_prepared; + new_packet->packet_encrypted = this->packet_encrypted; + return new_packet; + } + int8 PreparePacket(int16 MaxLen); + const char* GetOpcodeName(); + EmuOpcode login_op; +}; +class EQApplicationPacket : public EQPacket { + friend class EQProtocolPacket; + friend class EQStream; +public: + EQApplicationPacket() : EQPacket(0,NULL,0) { emu_opcode = OP_Unknown; app_opcode_size=default_opcode_size; } + EQApplicationPacket(const EmuOpcode op) : EQPacket(0,NULL,0) { SetOpcode(op); app_opcode_size=default_opcode_size; } + EQApplicationPacket(const EmuOpcode op, const uint32 len) : EQPacket(0,NULL,len) { SetOpcode(op); app_opcode_size=default_opcode_size; } + EQApplicationPacket(const EmuOpcode op, const unsigned char *buf, const uint32 len) : EQPacket(0,buf,len) { SetOpcode(op); app_opcode_size=default_opcode_size; } + bool combine(const EQApplicationPacket *rhs); + uint32 serialize (unsigned char *dest) const; + uint32 Size() const { return size+app_opcode_size; } + EQApplicationPacket *Copy() const { + EQApplicationPacket *it = new EQApplicationPacket; + try { + it->pBuffer= new unsigned char[size]; + memcpy(it->pBuffer,pBuffer,size); + it->size=size; + it->opcode = opcode; + it->emu_opcode = emu_opcode; + it->version = version; + return(it); + } + catch( bad_alloc &ba ) + { + cout << ba.what() << endl; + if( NULL != it ) + delete it; + } + return NULL; + } + + void SetOpcodeSize(uint8 s) { app_opcode_size=s; } + void SetOpcode(EmuOpcode op); + const EmuOpcode GetOpcodeConst() const; + inline const EmuOpcode GetOpcode() const { return(GetOpcodeConst()); } + //caching version of get + inline const EmuOpcode GetOpcode() { EmuOpcode r = GetOpcodeConst(); emu_opcode = r; return(r); } + + static uint8 default_opcode_size; + +protected: + //this is just a cache so we dont look it up several times on Get() + EmuOpcode emu_opcode; + +private: + //this constructor should only be used by EQProtocolPacket, as it + //assumes the first two bytes of buf are the opcode. + EQApplicationPacket(const unsigned char *buf, uint32 len, uint8 opcode_size=0); + EQApplicationPacket(const EQApplicationPacket &p) { emu_opcode = OP_Unknown; app_opcode_size=default_opcode_size; } + + uint8 app_opcode_size; +}; + +void DumpPacketHex(const EQApplicationPacket* app); +void DumpPacket(const EQProtocolPacket* app); +void DumpPacketAscii(const EQApplicationPacket* app); +void DumpPacket(const EQApplicationPacket* app, bool iShowInfo = false); +void DumpPacketBin(const EQApplicationPacket* app); + +#endif diff --git a/source/common/EQStream.cpp b/source/common/EQStream.cpp new file mode 100644 index 0000000..6544496 --- /dev/null +++ b/source/common/EQStream.cpp @@ -0,0 +1,1908 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 +#include + #include +#endif +#include "debug.h" +#include +#include +#include +#include +#include +#include +#ifdef WIN32 + #include +#else + #include + #include + #include + #include + #include + #include + #include +#endif +#include "EQPacket.h" +#include "EQStream.h" +#include "EQStreamFactory.h" +#include "misc.h" +#include "Mutex.h" +#include "op_codes.h" +#include "CRC16.h" +#include "packet_dump.h" +#ifdef LOGIN + #include "../LoginServer/login_structs.h" +#endif +#include "EQ2_Common_Structs.h" +#include "Log.h" + + +//#define DEBUG_EMBEDDED_PACKETS 1 +uint16 EQStream::MaxWindowSize=2048; + +void EQStream::init(bool resetSession) { + if (resetSession) + { + streamactive = false; + sessionAttempts = 0; + } + + timeout_delays = 0; + + MInUse.lock(); + active_users = 0; + MInUse.unlock(); + + Session=0; + Key=0; + MaxLen=0; + NextInSeq=0; + NextOutSeq=0; + CombinedAppPacket=NULL; + + MAcks.lock(); + MaxAckReceived = -1; + NextAckToSend = -1; + LastAckSent = -1; + MAcks.unlock(); + + LastSeqSent=-1; + MaxSends=5; + LastPacket=Timer::GetCurrentTime2(); + oversize_buffer=NULL; + oversize_length=0; + oversize_offset=0; + Factory = NULL; + + rogue_buffer=NULL; + roguebuf_offset=0; + roguebuf_size=0; + + MRate.lock(); + RateThreshold=RATEBASE/250; + DecayRate=DECAYBASE/250; + MRate.unlock(); + + BytesWritten=0; + SequencedBase = 0; + AverageDelta = 500; + + crypto->setRC4Key(0); + + retransmittimer = Timer::GetCurrentTime2(); + retransmittimeout = 500 * RETRANSMIT_TIMEOUT_MULT; + + reconnectAttempt = 0; + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "init Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } +} + +EQStream::EQStream(sockaddr_in addr){ + crypto = new Crypto(); + resend_que_timer = new Timer(1000); + combine_timer = new Timer(250); //250 milliseconds + combine_timer->Start(); + resend_que_timer->Start(); + init(); + remote_ip=addr.sin_addr.s_addr; + remote_port=addr.sin_port; + State=CLOSED; + StreamType=UnknownStream; + compressed=true; + encoded=false; + app_opcode_size=2; + #ifdef WIN32 + ZeroMemory(&stream, sizeof(z_stream)); + #else + bzero(&stream, sizeof(z_stream)); + #endif + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + deflateInit2(&stream, 9, Z_DEFLATED, 13, 9, Z_DEFAULT_STRATEGY); + //deflateInit(&stream, 5); + compressed_offset = 0; + client_version = 0; + received_packets = 0; + sent_packets = 0; + +#ifdef WRITE_PACKETS + write_packets = 0; + char write_packets_filename[64]; + snprintf(write_packets_filename, sizeof(write_packets_filename), "PacketLog%i.log", Timer::GetCurrentTime2()); + write_packets = fopen(write_packets_filename, "w+"); +#endif +} + +EQProtocolPacket* EQStream::ProcessEncryptedData(uchar* data, int32 size, int16 opcode){ + //cout << "B4:\n"; + //DumpPacket(data, size); + /*if(size >= 2 && data[0] == 0 && data[1] == 0){ + cout << "Attempting to fix packet!\n"; + //Have to fix bad packet from client or it will screw up encryption :P + size--; + data++; + }*/ + crypto->RC4Decrypt(data,size); + int8 offset = 0; + if(data[0] == 0xFF && size > 2){ + offset = 3; + memcpy(&opcode, data+sizeof(int8), sizeof(int16)); + } + else{ + offset = 1; + memcpy(&opcode, data, sizeof(int8)); + } + //cout << "After:\n"; + //DumpPacket(data, size); + return new EQProtocolPacket(opcode, data+offset, size - offset); +} + +EQProtocolPacket* EQStream::ProcessEncryptedPacket(EQProtocolPacket *p){ + EQProtocolPacket* ret = NULL; + if(p->opcode == OP_Packet && p->size > 2) + ret = ProcessEncryptedData(p->pBuffer+2, p->size-2, p->opcode); + else + ret = ProcessEncryptedData(p->pBuffer, p->size, p->opcode); + return ret; +} + +bool EQStream::ProcessEmbeddedPacket(uchar* pBuffer, int16 length,int8 opcode) { + if(!pBuffer || !crypto->isEncrypted()) + return false; + + MCombineQueueLock.lock(); + EQProtocolPacket* newpacket = ProcessEncryptedData(pBuffer, length, opcode); + MCombineQueueLock.unlock(); + + if (newpacket) { +#ifdef DEBUG_EMBEDDED_PACKETS + printf("Opcode: %u\n", newpacket->opcode); + DumpPacket(newpacket->pBuffer, newpacket->size); +#endif + + EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2); + if (ap->version == 0) + ap->version = client_version; + InboundQueuePush(ap); +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), pBuffer, length, false); +#endif + safe_delete(newpacket); + return true; + } + + return false; +} + +bool EQStream::HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset, int16 length){ + if(!p) + return false; + +#ifdef DEBUG_EMBEDDED_PACKETS + // printf works better with DumpPacket + printf( "Start Packet with offset %u, length %u, p->size %u\n", offset, length, p->size); +#endif + + if(p->size >= ((uint32)(offset+2))){ + if(p->pBuffer[offset] == 0 && p->pBuffer[offset+1] == 0x19){ + if(length == 0) + length = p->size-2-offset; + else + length-=2; +#ifdef DEBUG_EMBEDDED_PACKETS + printf( "Creating OP_AppCombined Packet with offset %u, length %u, p->size %u\n", offset, length, p->size); + DumpPacket(p->pBuffer, p->size); +#endif + + EQProtocolPacket *subp=new EQProtocolPacket(OP_AppCombined, p->pBuffer+2+offset, length); + subp->copyInfo(p); + ProcessPacket(subp, p); + safe_delete(subp); + return true; + } + else if (p->pBuffer[offset] == 0 && p->pBuffer[offset + 1] == 0) { + if (length == 0) + length = p->size - 1 - offset; + else + length--; + +#ifdef DEBUG_EMBEDDED_PACKETS + printf( "Creating Opcode 0 Packet!"); + DumpPacket(p->pBuffer + 1 + offset, length); +#endif + uchar* buffer = (p->pBuffer + 1 + offset); + bool valid = ProcessEmbeddedPacket(buffer, length); + + if(valid) + return true; + } + else if(offset+4 < p->size && ntohl(*(uint32 *)(p->pBuffer+offset)) != 0xffffffff) { +#ifdef DEBUG_EMBEDDED_PACKETS + uint16 seq = NextInSeq-1; + sint8 check = 0; + + if(offset == 2) { + seq=ntohs(*(uint16 *)(p->pBuffer)); + check=CompareSequence(NextInSeq,seq); + } + printf( "Unhandled Packet with offset %u, length %u, p->size %u, check: %i, nextinseq: %u, seq: %u\n", offset, length, p->size, check, NextInSeq, seq); + DumpPacket(p->pBuffer, p->size); +#endif + + if(length == 0) + length = p->size - offset; + + + uchar* buffer = (p->pBuffer + offset); + + bool valid = ProcessEmbeddedPacket(buffer, length); + + if(valid) + return true; + } + else if(p->pBuffer[offset] != 0xff && p->pBuffer[offset+1] == 0xff && p->size > (1+offset)) { + uint8 new_length = 0; + + memcpy(&new_length, p->pBuffer+offset, sizeof(int8)); + if((new_length+offset+2) == p->size) { + new_length -= 2; + EQProtocolPacket *subp=new EQProtocolPacket(p->pBuffer+offset+2, new_length, OP_Packet); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + return true; + } + } + } + return false; +} + +void EQStream::ProcessPacket(EQProtocolPacket *p, EQProtocolPacket* lastp) +{ + uint32 processed=0,subpacket_length=0; + + if (p) { + + if (p->opcode!=OP_SessionRequest && p->opcode!=OP_SessionResponse && !Session) { +#ifdef EQN_DEBUG + LogWrite(PACKET__ERROR, 0, "Packet", "*** Session not initialized, packet ignored "); + //p->DumpRaw(); +#endif + return; + } + + //cout << "Received " << (int)p->opcode << ":\n"; + //DumpPacket(p->pBuffer, p->size); + switch (p->opcode) { + case OP_Combined: { + processed=0; + int8 offset = 0; + int count = 0; +#ifdef LE_DEBUG + printf( "OP_Combined:\n"); + DumpPacket(p); +#endif + while(processedsize) { + if ((subpacket_length=(unsigned char)*(p->pBuffer+processed))==0xff) { + subpacket_length = ntohs(*(uint16*)(p->pBuffer + processed + 1)); + //printf("OP_Combined subpacket_length %u\n",subpacket_length); + offset = 3; + } + else { + offset = 1; + } + + //printf("OP_Combined processed %u p->size %u subpacket length %u count %i\n",processed, p->size, subpacket_length, count); + count++; +#ifdef LE_DEBUG + printf( "OP_Combined Packet %i (%u) (%u):\n", count, subpacket_length, processed); +#endif + bool isSubPacket = EQProtocolPacket::IsProtocolPacket(p->pBuffer + processed + offset, subpacket_length, false); + if (isSubPacket) { + EQProtocolPacket* subp = new EQProtocolPacket(p->pBuffer + processed + offset, subpacket_length); + subp->copyInfo(p); +#ifdef LE_DEBUG + printf( "Opcode %i:\n", subp->opcode); + DumpPacket(subp); +#endif + ProcessPacket(subp, p); +#ifdef LE_DEBUG + DumpPacket(subp); +#endif + delete subp; + } + else { + offset = 1; // 0xFF in this case means it is actually 255 bytes of encrypted data after a 00 09 packet + //Garbage packet? + if(ntohs(*reinterpret_cast(p->pBuffer + processed + offset)) <= 0x1e) { + subpacket_length=(unsigned char)*(p->pBuffer+processed); + LogWrite(PACKET__ERROR, 0, "Packet", "!!!!!!!!!Garbage Packet Unknown Process as OP_Packet!!!!!!!!!!!!!\n"); + DumpPacket(p->pBuffer + processed + offset, subpacket_length); + uchar* newbuf = p->pBuffer; + newbuf += processed + offset; + EQProtocolPacket *subp=new EQProtocolPacket(newbuf,subpacket_length); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } + else { + crypto->RC4Decrypt(p->pBuffer + processed + offset, subpacket_length); + LogWrite(PACKET__ERROR, 0, "Packet", "!!!!!!!!!Garbage Packet!!!!!!!!!!!!! processed: %u, offset: %u, count: %i, subpacket_length: %u, offset_pos_1: %u, oversized_buffer_present: %u, offset size: %u, offset length: %u\n", + processed, offset, count, subpacket_length, p->pBuffer[processed + offset], oversize_buffer ? 1 : 0, oversize_offset, oversize_length); + if(p->pBuffer[processed + offset] == 0xff) + { + uchar* newbuf = p->pBuffer; + newbuf += processed + offset + 1; + + DumpPacket(p->pBuffer + processed + offset, subpacket_length); + EQProtocolPacket *subp=new EQProtocolPacket(newbuf, subpacket_length, OP_Packet); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } + else + break; // bad packet + } + } + processed+=subpacket_length+offset; + } + break; + } + case OP_AppCombined: { + processed=0; + EQProtocolPacket* newpacket = 0; + int8 offset = 0; +#ifdef DEBUG_EMBEDDED_PACKETS + printf( "OP_AppCombined: \n"); + DumpPacket(p); +#endif + int count = 0; + while(processedsize) { + count++; + if ((subpacket_length=(unsigned char)*(p->pBuffer+processed))==0xff) { + subpacket_length=ntohs(*(uint16 *)(p->pBuffer+processed+1)); + offset = 3; + } else + offset = 1; + + if(crypto->getRC4Key()==0 && p && subpacket_length > 8+offset){ + #ifdef DEBUG_EMBEDDED_PACKETS + DumpPacket(p->pBuffer, p->size); + #endif + p->pBuffer += offset; + processRSAKey(p, subpacket_length); + p->pBuffer -= offset; + } + else if(crypto->isEncrypted()){ +#ifdef DEBUG_EMBEDDED_PACKETS + printf( "OP_AppCombined Packet %i (%u) (%u): \n", count, subpacket_length, processed); + DumpPacket(p->pBuffer+processed+offset, subpacket_length); +#endif + if(!HandleEmbeddedPacket(p, processed + offset, subpacket_length)){ + uchar* buffer = (p->pBuffer + processed + offset); + if(!ProcessEmbeddedPacket(buffer, subpacket_length, OP_AppCombined)) { + LogWrite(PACKET__ERROR, 0, "Packet", "*** This is bad, ProcessEmbeddedPacket failed, report to Image!"); + } + } + } + processed+=subpacket_length+offset; + } + } + break; + case OP_Packet: { + if (!p->pBuffer || (p->Size() < 4)) + { + break; + } + + uint16 seq=ntohs(*(uint16 *)(p->pBuffer)); + sint8 check=CompareSequence(NextInSeq,seq); + if (check == SeqFuture) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Future packet: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + OutOfOrderpackets[seq] = p->Copy(); + + // Image (2020): Removed as this is bad contributes to infinite loop + //SendOutOfOrderAck(seq); + } else if (check == SeqPast) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Duplicate packet: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + // Image (2020): Removed as this is bad contributes to infinite loop + //OutOfOrderpackets[seq] = p->Copy(); + SendOutOfOrderAck(seq); + } else { + EQProtocolPacket* qp = RemoveQueue(seq); + if (qp) { + LogWrite(PACKET__DEBUG, 1, "Packet", "OP_Fragment: Removing older queued packet with sequence %i", seq); + delete qp; + } + + SetNextAckToSend(seq); + NextInSeq++; + + if(HandleEmbeddedPacket(p)) + break; + if(crypto->getRC4Key()==0 && p && p->size >= 69){ + #ifdef DEBUG_EMBEDDED_PACKETS + DumpPacket(p->pBuffer, p->size); + #endif + processRSAKey(p); + } + else if(crypto->isEncrypted() && p){ + MCombineQueueLock.lock(); + EQProtocolPacket* newpacket = ProcessEncryptedPacket(p); + MCombineQueueLock.unlock(); + if(newpacket){ + EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2); + if (ap->version == 0) + ap->version = client_version; +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), p->pBuffer, p->size, false); +#endif + InboundQueuePush(ap); + safe_delete(newpacket); + } + } + } + } + break; + case OP_Fragment: { + if (!p->pBuffer || (p->Size() < 4)) + { + break; + } + + uint16 seq=ntohs(*(uint16 *)(p->pBuffer)); + sint8 check=CompareSequence(NextInSeq,seq); + if (check == SeqFuture) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Future packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + //p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + OutOfOrderpackets[seq] = p->Copy(); + //SendOutOfOrderAck(seq); + } else if (check == SeqPast) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Duplicate packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + //p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + //OutOfOrderpackets[seq] = p->Copy(); + SendOutOfOrderAck(seq); + } else { + // In case we did queue one before as well. + EQProtocolPacket* qp = RemoveQueue(seq); + if (qp) { + LogWrite(PACKET__DEBUG, 1, "Packet", "OP_Fragment: Removing older queued packet with sequence %i", seq); + delete qp; + } + + SetNextAckToSend(seq); + NextInSeq++; + if (oversize_buffer) { + memcpy(oversize_buffer+oversize_offset,p->pBuffer+2,p->size-2); + oversize_offset+=p->size-2; + //cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-2) << ") Seq=" << seq << endl; + if (oversize_offset==oversize_length) { + if (*(p->pBuffer+2)==0x00 && *(p->pBuffer+3)==0x19) { + EQProtocolPacket *subp=new EQProtocolPacket(oversize_buffer,oversize_offset); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } else { + + if(crypto->isEncrypted() && p && p->size > 2){ + MCombineQueueLock.lock(); + EQProtocolPacket* p2 = ProcessEncryptedData(oversize_buffer, oversize_offset, p->opcode); + MCombineQueueLock.unlock(); + EQApplicationPacket* ap = p2->MakeApplicationPacket(2); + ap->copyInfo(p); + if (ap->version == 0) + ap->version = client_version; +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), oversize_buffer, oversize_offset, false); +#endif + ap->copyInfo(p); + InboundQueuePush(ap); + safe_delete(p2); + } + } + delete[] oversize_buffer; + oversize_buffer=NULL; + oversize_offset=0; + } + } else if (!oversize_buffer) { + oversize_length=ntohl(*(uint32 *)(p->pBuffer+2)); + oversize_buffer=new unsigned char[oversize_length]; + memcpy(oversize_buffer,p->pBuffer+6,p->size-6); + oversize_offset=p->size-6; + //cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-6) << ") Seq=" << seq << endl; + } + } + } + break; + case OP_KeepAlive: { +#ifndef COLLECTOR + NonSequencedPush(new EQProtocolPacket(p->opcode,p->pBuffer,p->size)); +#endif + } + break; + case OP_Ack: { + if (!p->pBuffer || (p->Size() < 4)) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_Ack that was of malformed size"); + break; + } + uint16 seq = ntohs(*(uint16*)(p->pBuffer)); + AckPackets(seq); + retransmittimer = Timer::GetCurrentTime2(); + } + break; + case OP_SessionRequest: { + if (p->Size() < sizeof(SessionRequest)) + { + break; + } + + if (GetState() == ESTABLISHED) { + //_log(NET__ERROR, _L "Received OP_SessionRequest in ESTABLISHED state (%d) streamactive (%i) attempt (%i)" __L, GetState(), streamactive, sessionAttempts); + + // client seems to try a max of 4 times (initial +3 retries) then gives up, giving it a few more attempts just in case + // streamactive means we identified the opcode, we cannot re-establish this connection + if (streamactive || (sessionAttempts > 30)) + { + SendDisconnect(false); + SetState(CLOSED); + break; + } + } + + sessionAttempts++; + if(GetState() == WAIT_CLOSE) { + printf("WAIT_CLOSE Reconnect with streamactive %u, sessionAttempts %u\n", streamactive, sessionAttempts); + reconnectAttempt++; + } + init(GetState() != ESTABLISHED); + OutboundQueueClear(); + SessionRequest *Request=(SessionRequest *)p->pBuffer; + Session=ntohl(Request->Session); + SetMaxLen(ntohl(Request->MaxLength)); +#ifndef COLLECTOR + NextInSeq=0; + Key=0x33624702; + SendSessionResponse(); +#endif + SetState(ESTABLISHED); + } + break; + case OP_SessionResponse: { + if (p->Size() < sizeof(SessionResponse)) + { + break; + } + init(); + OutboundQueueClear(); + SetActive(true); + SessionResponse *Response=(SessionResponse *)p->pBuffer; + SetMaxLen(ntohl(Response->MaxLength)); + Key=ntohl(Response->Key); + NextInSeq=0; + SetState(ESTABLISHED); + if (!Session) + Session=ntohl(Response->Session); + compressed=(Response->Format&FLAG_COMPRESSED); + encoded=(Response->Format&FLAG_ENCODED); + + // Kinda kludgy, but trie for now + if (compressed) { + if (remote_port==9000 || (remote_port==0 && p->src_port==9000)) + SetStreamType(WorldStream); + else + SetStreamType(ZoneStream); + } else if (encoded) + SetStreamType(ChatOrMailStream); + else + SetStreamType(LoginStream); + } + break; + case OP_SessionDisconnect: { + //NextInSeq=0; + SendDisconnect(); + //SetState(CLOSED); + } + break; + case OP_OutOfOrderAck: { + if (!p->pBuffer || (p->Size() < 4)) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck that was of malformed size"); + break; + } + uint16 seq = ntohs(*(uint16*)(p->pBuffer)); + MOutboundQueue.lock(); + + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Pre-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } + + //if the packet they got out of order is between our last acked packet and the last sent packet, then its valid. + if (CompareSequence(SequencedBase, seq) != SeqPast && CompareSequence(NextOutSeq, seq) == SeqPast) { + uint16 sqsize = SequencedQueue.size(); + uint16 index = seq - SequencedBase; + LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck marking packet acked in queue (queue index = %u, queue size = %u)", index, sqsize); + if (index < sqsize) { + SequencedQueue[index]->acked = true; + // flag packets for a resend + uint16 count = 0; + uint32 timeout = AverageDelta * 2 + 100; + for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end() && count < index; ++sitr, ++count) { + if (!(*sitr)->acked && (*sitr)->sent_time > 0 && (((*sitr)->sent_time + timeout) < Timer::GetCurrentTime2())) { + (*sitr)->sent_time = 0; + LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck Flagging packet %u for retransmission", SequencedBase + count); + } + } + } + + if (RETRANSMIT_TIMEOUT_MULT) { + retransmittimer = Timer::GetCurrentTime2(); + } + } + else { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck for out-of-window %u. Window (%u->%u)", seq, SequencedBase, NextOutSeq); + } + + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } + + MOutboundQueue.unlock(); + } + break; + case OP_ServerKeyRequest:{ + if (p->Size() < sizeof(ClientSessionStats)) + { + //_log(NET__ERROR, _L "Received OP_SessionStatRequest that was of malformed size" __L); + break; + } + + ClientSessionStats* Stats = (ClientSessionStats*)p->pBuffer; + int16 request_id = Stats->RequestID; + AdjustRates(ntohl(Stats->average_delta)); + ServerSessionStats* stats=(ServerSessionStats*)p->pBuffer; + memset(stats, 0, sizeof(ServerSessionStats)); + stats->RequestID = request_id; + stats->current_time = ntohl(Timer::GetCurrentTime2()); + stats->sent_packets = ntohl(sent_packets); + stats->sent_packets2 = ntohl(sent_packets); + stats->received_packets = ntohl(received_packets); + stats->received_packets2 = ntohl(received_packets); + NonSequencedPush(new EQProtocolPacket(OP_SessionStatResponse,p->pBuffer,p->size)); + if(!crypto->isEncrypted()) + SendKeyRequest(); + else + SendSessionResponse(); + } + break; + case OP_SessionStatResponse: { + LogWrite(PACKET__INFO, 0, "Packet", "OP_SessionStatResponse"); + } + break; + case OP_OutOfSession: { + LogWrite(PACKET__INFO, 0, "Packet", "OP_OutOfSession"); + SendDisconnect(); + SetState(CLOSED); + } + break; + default: + //EQApplicationPacket *ap = p->MakeApplicationPacket(app_opcode_size); + //InboundQueuePush(ap); + + cout << "Orig Packet: " << p->opcode << endl; + DumpPacket(p->pBuffer, p->size); + if(p && p->size >= 69){ + processRSAKey(p); + } + MCombineQueueLock.lock(); + EQProtocolPacket* p2 = ProcessEncryptedData(p->pBuffer, p->size, OP_Packet); + MCombineQueueLock.unlock(); + cout << "Decrypted Packet: " << p2->opcode << endl; + DumpPacket(p2->pBuffer, p2->size); + + safe_delete(p2); + /* if(p2) + { + EQApplicationPacket* ap = p2->MakeApplicationPacket(2); + if (ap->version == 0) + ap->version = client_version; + InboundQueuePush(ap); + safe_delete(p2); + }*/ + + //EQProtocolPacket* puse = p2; + /* if (!rogue_buffer) { + roguebuf_size=puse->size; + rogue_buffer=new unsigned char[roguebuf_size]; + memcpy(rogue_buffer,puse->pBuffer,puse->size); + roguebuf_offset=puse->size; + cout << "RogueBuf is " << roguebuf_offset << "/" << roguebuf_size << " (" << (p->size-6) << ") NextInSeq=" << NextInSeq << endl; + } + else { + int32 new_size = roguebuf_size + puse->size; + uchar* tmp_buffer = new unsigned char[new_size]; + uchar* ptr = tmp_buffer; + + memcpy(ptr,rogue_buffer,roguebuf_size); + ptr += roguebuf_size; + memcpy(ptr,puse->pBuffer,puse->size); + roguebuf_offset=puse->size; + + safe_delete_array(rogue_buffer); + + rogue_buffer = tmp_buffer; + roguebuf_size = new_size; + roguebuf_offset = new_size; + cout << "RogueBuf is " << roguebuf_offset << "/" << roguebuf_size << " (" << (p->size-6) << ") NextInSeq=" << NextInSeq << endl; + }*/ +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), p->pBuffer, p->size, false); +#endif + //InboundQueuePush(ap); + LogWrite(PACKET__INFO, 0, "Packet", "Received unknown packet type, not adding to inbound queue"); + //safe_delete(p2); + //SendDisconnect(); + break; + } + } +} + +int8 EQStream::EQ2_Compress(EQ2Packet* app, int8 offset){ + +#ifdef LE_DEBUG + printf( "Before Compress in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + + + uchar* pDataPtr = app->pBuffer + offset; + int xpandSize = app->size * 2; + uchar* deflate_buff = new uchar[xpandSize]; + MCompressData.lock(); + stream.next_in = pDataPtr; + stream.avail_in = app->size - offset; + stream.next_out = deflate_buff; + stream.avail_out = xpandSize; + + int ret = deflate(&stream, Z_SYNC_FLUSH); + + if (ret != Z_OK) + { + printf("ZLIB COMPRESSION RETFAIL: %i, %i (Ret: %i)\n", app->size, stream.avail_out, ret); + MCompressData.unlock(); + safe_delete_array(deflate_buff); + return 0; + } + + int32 newsize = xpandSize - stream.avail_out; + safe_delete_array(app->pBuffer); + app->size = newsize + offset; + app->pBuffer = new uchar[app->size]; + app->pBuffer[(offset - 1)] = 1; + memcpy(app->pBuffer + offset, deflate_buff, newsize); + MCompressData.unlock(); + safe_delete_array(deflate_buff); + +#ifdef LE_DEBUG + printf( "After Compress in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + + return offset - 1; +} + +int16 EQStream::processRSAKey(EQProtocolPacket *p, uint16 subpacket_length){ + /*int16 limit = 0; + int8 offset = 13; + int8 offset2 = 0; + if(p->pBuffer[2] == 0) + limit = p->pBuffer[9]; + else{ + limit = p->pBuffer[5]; + offset2 = 5; + offset-=1; + } + crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + offset + (limit-8), 8)); + return (limit + offset +1) - offset2;*/ + if(subpacket_length) + crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + subpacket_length - 8, 8)); + else + crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + p->size - 8, 8)); + + return 0; +} + +void EQStream::SendKeyRequest(){ + int32 crypto_key_size = 60; + int16 size = sizeof(KeyGen_Struct) + sizeof(KeyGen_End_Struct) + crypto_key_size; + EQ2Packet *outapp=new EQ2Packet(OP_WSLoginRequestMsg,NULL,size); + memcpy(&outapp->pBuffer[0], &crypto_key_size, sizeof(int32)); + memset(&outapp->pBuffer[4], 0xFF, crypto_key_size); + memset(&outapp->pBuffer[size-5], 1, 1); + memset(&outapp->pBuffer[size-1], 1, 1); + EQ2QueuePacket(outapp, true); +} + +void EQStream::EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset){ + if(app->size>2 && crypto->isEncrypted()){ + app->packet_encrypted = true; + uchar* crypt_buff = app->pBuffer; + if(app->eq2_compressed) + crypto->RC4Encrypt(crypt_buff + compress_offset, app->size - compress_offset); + else + crypto->RC4Encrypt(crypt_buff + 2 + offset, app->size - 2 - offset); + } +} + +void EQStream::EQ2QueuePacket(EQ2Packet* app, bool attempted_combine){ + if(CheckActive()){ + if(!attempted_combine){ + MCombineQueueLock.lock(); + combine_queue.push_back(app); + MCombineQueueLock.unlock(); + } + else{ + MCombineQueueLock.lock(); + PreparePacket(app); + MCombineQueueLock.unlock(); +#ifdef LE_DEBUG + printf( "After B in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + SendPacket(app); + } + } +} + +void EQStream::UnPreparePacket(EQ2Packet* app){ + if(app->pBuffer[2] == 0 && app->pBuffer[3] == 19){ + uchar* new_buffer = new uchar[app->size-3]; + memcpy(new_buffer+2, app->pBuffer+5, app->size-3); + delete[] app->pBuffer; + app->size-=3; + app->pBuffer = new_buffer; + } +} + +#ifdef WRITE_PACKETS +char EQStream::GetChar(uchar in) +{ + if (in < ' ' || in > '~') + return '.'; + return (char)in; +} +void EQStream::WriteToFile(char* pFormat, ...) { + va_list args; + va_start(args, pFormat); + vfprintf(write_packets, pFormat, args); + va_end(args); +} + +void EQStream::WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing) { + MWritePackets.lock(); + struct in_addr ip_addr; + ip_addr.s_addr = remote_ip; + char timebuffer[80]; + time_t rawtime; + struct tm* timeinfo; + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(timebuffer, 80, "%m/%d/%Y %H:%M:%S", timeinfo); + if (outgoing) + WriteToFile("-- %s --\n%s\nSERVER -> %s\n", opcodeName, timebuffer, inet_ntoa(ip_addr)); + else + WriteToFile("-- %s --\n%s\n%s -> SERVER\n", opcodeName, timebuffer, inet_ntoa(ip_addr)); + int i; + int nLines = size / 16; + int nExtra = size % 16; + uchar* pPtr = data; + for (i = 0; i < nLines; i++) + { + WriteToFile("%4.4X:\t%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n", i * 16, pPtr[0], pPtr[1], pPtr[2], pPtr[3], pPtr[4], pPtr[5], pPtr[6], pPtr[7], pPtr[8], pPtr[9], pPtr[10], pPtr[11], pPtr[12], pPtr[13], pPtr[14], pPtr[15], GetChar(pPtr[0]), GetChar(pPtr[1]), GetChar(pPtr[2]), GetChar(pPtr[3]), GetChar(pPtr[4]), GetChar(pPtr[5]), GetChar(pPtr[6]), GetChar(pPtr[7]), GetChar(pPtr[8]), GetChar(pPtr[9]), GetChar(pPtr[10]), GetChar(pPtr[11]), GetChar(pPtr[12]), GetChar(pPtr[13]), GetChar(pPtr[14]), GetChar(pPtr[15])); + pPtr += 16; + } + if (nExtra) + { + WriteToFile("%4.4X\t", nLines * 16); + for (i = 0; i < nExtra; i++) + { + WriteToFile("%2.2X ", pPtr[i]); + } + for (i; i < 16; i++) + WriteToFile(" "); + for (i = 0; i < nExtra; i++) + { + WriteToFile("%c", GetChar(pPtr[i])); + } + WriteToFile("\n"); + } + WriteToFile("\n\n"); + fflush(write_packets); + MWritePackets.unlock(); +} + +void EQStream::WritePackets(EQ2Packet* app, bool outgoing) { + if (app->version == 0) + app->version = client_version; + WritePackets(app->GetOpcodeName(), app->pBuffer, app->size, outgoing); +} +#endif + +void EQStream::PreparePacket(EQ2Packet* app, int8 offset){ + app->setVersion(client_version); + compressed_offset = 0; + +#ifdef LE_DEBUG + printf( "Before A in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + if(!app->packet_prepared){ + if(app->PreparePacket(MaxLen) == 255) //invalid version + return; + } + +#ifdef LE_DEBUG + printf( "After Prepare in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif +#ifdef WRITE_PACKETS + if (!app->eq2_compressed && !app->packet_encrypted) + WritePackets(app, true); +#endif + + if(!app->eq2_compressed && app->size>128){ + compressed_offset = EQ2_Compress(app); + if (compressed_offset) + app->eq2_compressed = true; + } + if(!app->packet_encrypted){ + EncryptPacket(app, compressed_offset, offset); + if(app->size > 2 && app->pBuffer[2] == 0){ + uchar* new_buffer = new uchar[app->size+1]; + new_buffer[2] = 0; + memcpy(new_buffer+3, app->pBuffer+2, app->size-2); + delete[] app->pBuffer; + app->pBuffer = new_buffer; + app->size++; + } + } + +#ifdef LE_DEBUG + printf( "After A in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + +} + +void EQStream::SendPacket(EQProtocolPacket *p) +{ + uint32 chunksize,used; + uint32 length; + + // Convert the EQApplicationPacket to 1 or more EQProtocolPackets + if (p->size>( MaxLen-8)) { // proto-op(2), seq(2), app-op(2) ... data ... crc(2) + uchar* tmpbuff=p->pBuffer; + length=p->size - 2; + + EQProtocolPacket *out=new EQProtocolPacket(OP_Fragment,NULL,MaxLen-4); + *(uint32 *)(out->pBuffer+2)=htonl(length); + used=MaxLen-10; + memcpy(out->pBuffer+6,tmpbuff+2,used); + +#ifdef LE_DEBUG + printf("(%s, %i) New Fragment:\n ", __FUNCTION__, __LINE__); + DumpPacket(out); +#endif + + SequencedPush(out); + + while (usedpBuffer+2,tmpbuff,1); + memcpy(out->pBuffer+2,tmpbuff+used+2,chunksize); +#ifdef LE_DEBUG + printf("Chunk: \n"); + DumpPacket(out); +#endif + SequencedPush(out); + used+=chunksize; + + } + +#ifdef LE_DEBUG + printf( "ChunkDelete: \n"); + DumpPacket(out); + //cerr << "1: Deleting 0x" << hex << (uint32)(p) << dec << endl; +#endif + + delete p; + } else { + SequencedPush(p); + } +} +void EQStream::SendPacket(EQApplicationPacket *p) +{ +uint32 chunksize,used; +uint32 length; + + // Convert the EQApplicationPacket to 1 or more EQProtocolPackets + if (p->size>(MaxLen-8)) { // proto-op(2), seq(2), app-op(2) ... data ... crc(2) + //cout << "Making oversized packet for: " << endl; + //cout << p->size << endl; + //p->DumpRawHeader(); + //dump_message(p->pBuffer,p->size,timestamp()); + //cout << p->size << endl; + unsigned char *tmpbuff=new unsigned char[p->size+2]; + //cout << hex << (int)tmpbuff << dec << endl; + length=p->serialize(tmpbuff); + + EQProtocolPacket *out=new EQProtocolPacket(OP_Fragment,NULL,MaxLen-4); + *(uint32 *)(out->pBuffer+2)=htonl(p->Size()); + memcpy(out->pBuffer+6,tmpbuff,MaxLen-10); + used=MaxLen-10; + SequencedPush(out); + //cout << "Chunk #" << ++i << " size=" << used << ", length-used=" << (length-used) << endl; + while (usedpBuffer+2,tmpbuff+used,chunksize); + out->size=chunksize+2; + SequencedPush(out); + used+=chunksize; + //cout << "Chunk #"<< ++i << " size=" << chunksize << ", length-used=" << (length-used) << endl; + } + //cerr << "1: Deleting 0x" << hex << (uint32)(p) << dec << endl; + delete p; + delete[] tmpbuff; + } else { + EQProtocolPacket *out=new EQProtocolPacket(OP_Packet,NULL,p->Size()+2); + p->serialize(out->pBuffer+2); + SequencedPush(out); + //cerr << "2: Deleting 0x" << hex << (uint32)(p) << dec << endl; + delete p; + } +} + +void EQStream::SequencedPush(EQProtocolPacket *p) +{ + p->setVersion(client_version); + MOutboundQueue.lock(); + *(uint16 *)(p->pBuffer)=htons(NextOutSeq); + SequencedQueue.push_back(p); + p->sequence = NextOutSeq; + NextOutSeq++; + MOutboundQueue.unlock(); +} + +void EQStream::NonSequencedPush(EQProtocolPacket *p) +{ + p->setVersion(client_version); + MOutboundQueue.lock(); + NonSequencedQueue.push(p); + MOutboundQueue.unlock(); +} + +void EQStream::SendAck(uint16 seq) +{ + uint16 Seq=htons(seq); + SetLastAckSent(seq); + NonSequencedPush(new EQProtocolPacket(OP_Ack,(unsigned char *)&Seq,sizeof(uint16))); +} + +void EQStream::SendOutOfOrderAck(uint16 seq) +{ + uint16 Seq=htons(seq); + NonSequencedPush(new EQProtocolPacket(OP_OutOfOrderAck,(unsigned char *)&Seq,sizeof(uint16))); +} + +bool EQStream::CheckCombineQueue(){ + bool ret = true; //processed all packets + MCombineQueueLock.lock(); + if(combine_queue.size() > 0){ + EQ2Packet* first = combine_queue.front(); + combine_queue.pop_front(); + if(combine_queue.size() == 0){ //nothing to combine this with + EQ2QueuePacket(first, true); + } + else{ + PreparePacket(first); + EQ2Packet* second = 0; + bool combine_worked = false; + int16 count = 0; + while(combine_queue.size()){ + count++; + second = combine_queue.front(); + combine_queue.pop_front(); + PreparePacket(second); + /*if(first->GetRawOpcode() != OP_AppCombined && first->pBuffer[2] == 0){ + EQ2Packet* tmp = second; + second = first; + first = tmp; + }*/ + if(!first->AppCombine(second)){ + first->SetProtocolOpcode(OP_Packet); + if(combine_worked){ + SequencedPush(first); + } + else{ + EQ2QueuePacket(first, true); + } + first = second; + combine_worked = false; + } + else{ + combine_worked = true; + //DumpPacket(first); + } + if(count >= 60 || first->size > 4000){ //other clients need packets too + ret = false; + break; + } + } + if(first){ + first->SetProtocolOpcode(OP_Packet); + if(combine_worked){ + SequencedPush(first); + } + else{ + EQ2QueuePacket(first, true); + } + } + } + } + MCombineQueueLock.unlock(); + return ret; +} + +void EQStream::CheckResend(int eq_fd){ + int32 curr = Timer::GetCurrentTime2(); + EQProtocolPacket* packet = 0; + deque::iterator itr; + MResendQue.lock(); + for(itr=resend_que.begin();itr!=resend_que.end();itr++){ + packet = *itr; + if(packet->attempt_count >= 5){//tried to resend this packet 5 times, client must already have it but didnt ack it + safe_delete(packet); + itr = resend_que.erase(itr); + if(itr == resend_que.end()) + break; + } + else{ + if((curr - packet->sent_time) < 1000) + continue; + packet->sent_time -=1000; + packet->attempt_count++; + WritePacket(eq_fd, packet); + } + } + MResendQue.unlock(); +} + + + +//returns SeqFuture if `seq` is later than `expected_seq` +EQStream::SeqOrder EQStream::CompareSequence(uint16 expected_seq, uint16 seq) +{ + if (expected_seq == seq) { + // Curent + return SeqInOrder; + } + else if ((seq > expected_seq && (uint32)seq < ((uint32)expected_seq + EQStream::MaxWindowSize)) || seq < (expected_seq - EQStream::MaxWindowSize)) { + // Future + return SeqFuture; + } + else { + // Past + return SeqPast; + } +} + +void EQStream::AckPackets(uint16 seq) +{ + std::deque::iterator itr, tmp; + + MOutboundQueue.lock(); + + SeqOrder ord = CompareSequence(SequencedBase, seq); + if (ord == SeqInOrder) { + //they are not acking anything new... + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with no window advancement (seq %u)", seq); + } + else if (ord == SeqPast) { + //they are nacking blocks going back before our buffer, wtf? + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with backward window advancement (they gave %u, our window starts at %u). This is bad" , seq, SequencedBase); + } + else { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack up through sequence %u. Our base is %u", seq, SequencedBase); + + + //this is a good ack, we get to ack some blocks. + seq++; //we stop at the block right after their ack, counting on the wrap of both numbers. + while (SequencedBase != seq) { + if (SequencedQueue.empty()) { + LogWrite(PACKET__DEBUG, 9, "Packet", "OUT OF PACKETS acked packet with sequence %u. Next send is %u before this", (unsigned long)SequencedBase, SequencedQueue.size()); + SequencedBase = NextOutSeq; + break; + } + LogWrite(PACKET__DEBUG, 9, "Packet", "Removing acked packet with sequence %u", (unsigned long)SequencedBase); + //clean out the acked packet + delete SequencedQueue.front(); + SequencedQueue.pop_front(); + //advance the base sequence number to the seq of the block after the one we just got rid of. + SequencedBase++; + } + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post-Ack on %u Invalid Sequenced queue: BS %u + SQ %u != NOS %u", seq, SequencedBase, SequencedQueue.size(), NextOutSeq); + } + } + + MOutboundQueue.unlock(); +} + +void EQStream::Write(int eq_fd) +{ + queue ReadyToSend; + long maxack; + + // Check our rate to make sure we can send more + MRate.lock(); + sint32 threshold=RateThreshold; + MRate.unlock(); + if (BytesWritten > threshold) { + //cout << "Over threshold: " << BytesWritten << " > " << threshold << endl; + return; + } + + MCombinedAppPacket.lock(); + EQApplicationPacket *CombPack=CombinedAppPacket; + CombinedAppPacket=NULL; + MCombinedAppPacket.unlock(); + + if (CombPack) { + SendPacket(CombPack); + } + + // If we got more packets to we need to ack, send an ack on the highest one + MAcks.lock(); + maxack=MaxAckReceived; + // Added from peaks findings + if (NextAckToSend>LastAckSent || LastAckSent == 0x0000ffff) + SendAck(NextAckToSend); + MAcks.unlock(); + + // Lock the outbound queues while we process + MOutboundQueue.lock(); + + // Adjust where we start sending in case we get a late ack + //if (maxack>LastSeqSent) + // LastSeqSent=maxack; + + // Place to hold the base packet t combine into + EQProtocolPacket *p=NULL; + std::deque::iterator sitr; + + // Find the next sequenced packet to send from the "queue" + sitr = SequencedQueue.begin(); + + uint16 count = 0; + // get to start of packets + while (sitr != SequencedQueue.end() && (*sitr)->sent_time > 0) { + ++sitr; + ++count; + } + + bool SeqEmpty = false, NonSeqEmpty = false; + // Loop until both are empty or MaxSends is reached + while (!SeqEmpty || !NonSeqEmpty) { + + // See if there are more non-sequenced packets left + if (!NonSequencedQueue.empty()) { + if (!p) { + // If we don't have a packet to try to combine into, use this one as the base + // And remove it form the queue + p = NonSequencedQueue.front(); + LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with non-seq packet of len %u",p->size); + NonSequencedQueue.pop(); + } + else if (!p->combine(NonSequencedQueue.front())) { + // Trying to combine this packet with the base didn't work (too big maybe) + // So just send the base packet (we'll try this packet again later) + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next non-seq packet is len %u", p->size, (NonSequencedQueue.front())->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + + if (BytesWritten > threshold) { + // Sent enough this round, lets stop to be fair + LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in nonseq (%u > %u)", BytesWritten, threshold); + break; + } + } + else { + // Combine worked, so just remove this packet and it's spot in the queue + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined non-seq packet of len %u, yeilding %u combined", (NonSequencedQueue.front())->size, p->size); + delete NonSequencedQueue.front(); + NonSequencedQueue.pop(); + } + } + else { + // No more non-sequenced packets + NonSeqEmpty = true; + } + + if (sitr != SequencedQueue.end()) { + uint16 seq_send = SequencedBase + count; //just for logging... + + if (SequencedQueue.empty()) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Tried to write a packet with an empty queue (%u is past next out %u)", seq_send, NextOutSeq); + SeqEmpty = true; + continue; + } + + if ((*sitr)->acked || (*sitr)->sent_time != 0) { + ++sitr; + ++count; + if (p) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + } + LogWrite(PACKET__DEBUG, 9, "Packet", "Not retransmitting seq packet %u because already marked as acked", seq_send); + } + else if (!p) { + // If we don't have a packet to try to combine into, use this one as the base + // Copy it first as it will still live until it is acked + p = (*sitr)->Copy(); + LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with seq packet %u of len %u", seq_send, p->size); + (*sitr)->sent_time = Timer::GetCurrentTime2(); + ++sitr; + ++count; + } + else if (!p->combine(*sitr)) { + // Trying to combine this packet with the base didn't work (too big maybe) + // So just send the base packet (we'll try this packet again later) + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next seq packet %u is len %u", p->size, seq_send + 1, (*sitr)->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + if ((*sitr)->opcode != OP_Fragment && BytesWritten > threshold) { + // Sent enough this round, lets stop to be fair + LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in seq (%u > %u)", BytesWritten, threshold); + break; + } + } + else { + // Combine worked + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined seq packet %u of len %u, yeilding %u combined", seq_send, (*sitr)->size, p->size); + (*sitr)->sent_time = Timer::GetCurrentTime2(); + ++sitr; + ++count; + } + + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post send Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } + } + else { + // No more sequenced packets + SeqEmpty = true; + } + } + MOutboundQueue.unlock(); // Unlock the queue + + // We have a packet still, must have run out of both seq and non-seq, so send it + if (p) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size); + ReadyToSend.push(p); + BytesWritten += p->size; + } + + // Send all the packets we "made" + while (!ReadyToSend.empty()) { + p = ReadyToSend.front(); + WritePacket(eq_fd, p); + delete p; + ReadyToSend.pop(); + } + + //see if we need to send our disconnect and finish our close + if (SeqEmpty && NonSeqEmpty) { + //no more data to send + if (GetState() == CLOSING) { + MOutboundQueue.lock(); + if (SequencedQueue.size() > 0 ) { + // retransmission attempts + } + else + { + LogWrite(PACKET__DEBUG, 9, "Packet", "All outgoing data flushed, disconnecting client."); + //we are waiting for the queues to empty, now we can do our disconnect. + //this packet will not actually go out until the next call to Write(). + SendDisconnect(); + //SetState(CLOSED); + } + MOutboundQueue.unlock(); + } + } +} + +void EQStream::WritePacket(int eq_fd, EQProtocolPacket *p) +{ +uint32 length = 0; +sockaddr_in address; +unsigned char tmpbuffer[2048]; + address.sin_family = AF_INET; + address.sin_addr.s_addr=remote_ip; + address.sin_port=remote_port; +#ifdef NOWAY + uint32 ip=address.sin_addr.s_addr; + cout << "Sending to: " + << (int)*(unsigned char *)&ip + << "." << (int)*((unsigned char *)&ip+1) + << "." << (int)*((unsigned char *)&ip+2) + << "." << (int)*((unsigned char *)&ip+3) + << "," << (int)ntohs(address.sin_port) << "(" << p->size << ")" << endl; + + p->DumpRaw(); + cout << "-------------" << endl; +#endif + length=p->serialize(buffer); + if (p->opcode!=OP_SessionRequest && p->opcode!=OP_SessionResponse) { + if (compressed) { + BytesWritten -= p->size; + uint32 newlen=EQProtocolPacket::Compress(buffer,length,tmpbuffer,2048); + memcpy(buffer,tmpbuffer,newlen); + length=newlen; + BytesWritten += newlen; + } + if (encoded) { + EQProtocolPacket::ChatEncode(buffer,length,Key); + } + *(uint16 *)(buffer+length)=htons(CRC16(buffer,length,Key)); + length+=2; + } + sent_packets++; + //dump_message_column(buffer,length,"Writer: "); + //cout << "Raw Data:\n"; + //DumpPacket(buffer, length); + sendto(eq_fd,(char *)buffer,length,0,(sockaddr *)&address,sizeof(address)); +} + +EQProtocolPacket *EQStream::Read(int eq_fd, sockaddr_in *from) +{ +int socklen; +int length=0; +unsigned char buffer[2048]; +EQProtocolPacket *p=NULL; +char temp[15]; + + socklen=sizeof(sockaddr); +#ifdef WIN32 + length=recvfrom(eq_fd, (char *)buffer, 2048, 0, (struct sockaddr*)from, (int *)&socklen); +#else + length=recvfrom(eq_fd, buffer, 2048, 0, (struct sockaddr*)from, (socklen_t *)&socklen); +#endif + if (length>=2) { + DumpPacket(buffer, length); + p=new EQProtocolPacket(buffer[1],&buffer[2],length-2); + //printf("Read packet: opcode %i length %u, expected-length: %u\n",buffer[1], length, p->size); + uint32 ip=from->sin_addr.s_addr; + sprintf(temp,"%d.%d.%d.%d:%d", + *(unsigned char *)&ip, + *((unsigned char *)&ip+1), + *((unsigned char *)&ip+2), + *((unsigned char *)&ip+3), + ntohs(from->sin_port)); + //cout << timestamp() << "Data from: " << temp << " OpCode 0x" << hex << setw(2) << setfill('0') << (int)p->opcode << dec << endl; + //dump_message(p->pBuffer,p->size,timestamp()); + + } + return p; +} + +void EQStream::SendSessionResponse() +{ +EQProtocolPacket *out=new EQProtocolPacket(OP_SessionResponse,NULL,sizeof(SessionResponse)); + SessionResponse *Response=(SessionResponse *)out->pBuffer; + Response->Session=htonl(Session); + Response->MaxLength=htonl(MaxLen); + Response->UnknownA=2; + Response->Format=0; + if (compressed) + Response->Format|=FLAG_COMPRESSED; + if (encoded) + Response->Format|=FLAG_ENCODED; + Response->Key=htonl(Key); + + out->size=sizeof(SessionResponse); + + NonSequencedPush(out); +} + +void EQStream::SendSessionRequest() +{ + EQProtocolPacket *out=new EQProtocolPacket(OP_SessionRequest,NULL,sizeof(SessionRequest)); + SessionRequest *Request=(SessionRequest *)out->pBuffer; + memset(Request,0,sizeof(SessionRequest)); + Request->Session=htonl(time(NULL)); + Request->MaxLength=htonl(512); + + NonSequencedPush(out); +} + +void EQStream::SendDisconnect(bool setstate) +{ + try{ + if(GetState() != ESTABLISHED && GetState() != WAIT_CLOSE) + return; + + EQProtocolPacket *out=new EQProtocolPacket(OP_SessionDisconnect,NULL,sizeof(uint32)+sizeof(int16)); + *(uint32 *)out->pBuffer=htonl(Session); + out->pBuffer[4] = 0; + out->pBuffer[5] = 6; + NonSequencedPush(out); + if(setstate) + SetState(CLOSING); + } + catch(...){} +} + +void EQStream::InboundQueuePush(EQApplicationPacket *p) +{ + MInboundQueue.lock(); + InboundQueue.push_back(p); + MInboundQueue.unlock(); +} + +EQApplicationPacket *EQStream::PopPacket() +{ +EQApplicationPacket *p=NULL; + + MInboundQueue.lock(); + if (InboundQueue.size()) { + p=InboundQueue.front(); + InboundQueue.pop_front(); + } + MInboundQueue.unlock(); + if(p) + p->setVersion(client_version); + return p; +} + +void EQStream::InboundQueueClear() +{ + MInboundQueue.lock(); + while(InboundQueue.size()){ + delete InboundQueue.front(); + InboundQueue.pop_front(); + } + MInboundQueue.unlock(); +} +void EQStream::EncryptPacket(uchar* data, int16 size){ + if(size>6){ + + } +} +bool EQStream::HasOutgoingData() +{ +bool flag; + + //once closed, we have nothing more to say + if(CheckClosed()) + return(false); + + MOutboundQueue.lock(); + flag=(!NonSequencedQueue.empty()); + if (!flag) { + flag = (!SequencedQueue.empty()); + } + MOutboundQueue.unlock(); + + if (!flag) { + MAcks.lock(); + flag= (NextAckToSend>LastAckSent); + MAcks.unlock(); + } + + if (!flag) { + MCombinedAppPacket.lock(); + flag=(CombinedAppPacket!=NULL); + MCombinedAppPacket.unlock(); + } + + return flag; +} + +void EQStream::OutboundQueueClear() +{ + MOutboundQueue.lock(); + while(NonSequencedQueue.size()) { + delete NonSequencedQueue.front(); + NonSequencedQueue.pop(); + } + while(SequencedQueue.size()) { + delete SequencedQueue.front(); + SequencedQueue.pop_front(); + } + MOutboundQueue.unlock(); +} + +void EQStream::Process(const unsigned char *buffer, const uint32 length) +{ + received_packets++; +static unsigned char newbuffer[2048]; +uint32 newlength=0; + +#ifdef LE_DEBUG +printf("ProcessBuffer:\n"); +DumpPacket(buffer, length); +#endif + + if (EQProtocolPacket::ValidateCRC(buffer,length,Key)) { + if (compressed) { + newlength=EQProtocolPacket::Decompress(buffer,length,newbuffer,2048); +#ifdef LE_DEBUG + printf("ProcessBufferDecompress:\n"); + DumpPacket(buffer, newlength); +#endif + } else { + memcpy(newbuffer,buffer,length); + newlength=length; + if (encoded) + EQProtocolPacket::ChatDecode(newbuffer,newlength-2,Key); + } + +#ifdef LE_DEBUG + printf("ResultProcessBuffer:\n"); + DumpPacket(buffer, newlength); +#endif + uint16 opcode=ntohs(*(const uint16 *)newbuffer); + //printf("Read packet: opcode %i newlength %u, newbuffer2len: %u, newbuffer3len: %u\n",opcode, newlength, newbuffer[2], newbuffer[3]); + if(opcode > 0 && opcode <= OP_OutOfSession) + { + if (buffer[1]!=0x01 && buffer[1]!=0x02 && buffer[1]!=0x1d) + newlength-=2; + + EQProtocolPacket p(newbuffer,newlength); + ProcessPacket(&p); + } + else + { + cout << "2Orig Packet: " << opcode << endl; + DumpPacket(newbuffer, newlength); + ProcessEmbeddedPacket(newbuffer, newlength, OP_Fragment); + } + ProcessQueue(); + } else { + cout << "Incoming packet failed checksum:" <(buffer),length,"CRC failed: "); + } +} + +long EQStream::GetMaxAckReceived() +{ + MAcks.lock(); + long l=MaxAckReceived; + MAcks.unlock(); + + return l; +} + +long EQStream::GetNextAckToSend() +{ + MAcks.lock(); + long l=NextAckToSend; + MAcks.unlock(); + + return l; +} + +long EQStream::GetLastAckSent() +{ + MAcks.lock(); + long l=LastAckSent; + MAcks.unlock(); + + return l; +} + +void EQStream::SetMaxAckReceived(uint32 seq) +{ + deque::iterator itr; + + MAcks.lock(); + MaxAckReceived=seq; + MAcks.unlock(); + MOutboundQueue.lock(); + if (long(seq) > LastSeqSent) + LastSeqSent=seq; + MResendQue.lock(); + EQProtocolPacket* packet = 0; + for(itr=resend_que.begin();itr!=resend_que.end();itr++){ + packet = *itr; + if(packet && packet->sequence <= seq){ + safe_delete(packet); + itr = resend_que.erase(itr); + if(itr == resend_que.end()) + break; + } + } + MResendQue.unlock(); + MOutboundQueue.unlock(); +} + +void EQStream::SetNextAckToSend(uint32 seq) +{ + MAcks.lock(); + NextAckToSend=seq; + MAcks.unlock(); +} + +void EQStream::SetLastAckSent(uint32 seq) +{ + MAcks.lock(); + LastAckSent=seq; + MAcks.unlock(); +} + +void EQStream::SetLastSeqSent(uint32 seq) +{ + MOutboundQueue.lock(); + LastSeqSent=seq; + MOutboundQueue.unlock(); +} + +void EQStream::SetStreamType(EQStreamType type) +{ + StreamType=type; + switch (StreamType) { + case LoginStream: + app_opcode_size=1; + compressed=false; + encoded=false; + break; + case EQ2Stream: + app_opcode_size=2; + compressed=false; + encoded=false; + break; + case ChatOrMailStream: + case ChatStream: + case MailStream: + app_opcode_size=1; + compressed=false; + encoded=true; + break; + case ZoneStream: + case WorldStream: + default: + app_opcode_size=2; + compressed=true; + encoded=false; + break; + } +} + +void EQStream::ProcessQueue() +{ + if (OutOfOrderpackets.empty()) { + return; + } + + EQProtocolPacket* qp = NULL; + while ((qp = RemoveQueue(NextInSeq)) != NULL) { + //_log(NET__DEBUG, _L "Processing Queued Packet: Seq=%d" __L, NextInSeq); + ProcessPacket(qp); + delete qp; + //_log(NET__APP_TRACE, _L "OP_Packet Queue size=%d" __L, PacketQueue.size()); + } +} + +EQProtocolPacket* EQStream::RemoveQueue(uint16 seq) +{ + map::iterator itr; + EQProtocolPacket* qp = NULL; + if ((itr = OutOfOrderpackets.find(seq)) != OutOfOrderpackets.end()) { + qp = itr->second; + OutOfOrderpackets.erase(itr); + //_log(NET__APP_TRACE, _L "OP_Packet Queue size=%d" __L, PacketQueue.size()); + } + return qp; +} + +void EQStream::Decay() +{ + MRate.lock(); + uint32 rate=DecayRate; + MRate.unlock(); + if (BytesWritten>0) { + BytesWritten-=rate; + if (BytesWritten<0) + BytesWritten=0; + } + + int count = 0; + MOutboundQueue.lock(); + for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end(); ++sitr, count++) { + if (!(*sitr)->acked && (*sitr)->sent_time > 0 && ((*sitr)->sent_time + retransmittimeout) < Timer::GetCurrentTime2()) { + (*sitr)->sent_time = 0; + LogWrite(PACKET__DEBUG, 9, "Packet", "Timeout exceeded for seq %u. Flagging packet for retransmission", SequencedBase + count); + } + } + MOutboundQueue.unlock(); +} + +void EQStream::AdjustRates(uint32 average_delta) +{ + if (average_delta && (average_delta <= AVERAGE_DELTA_MAX)) { + MRate.lock(); + AverageDelta = average_delta; + RateThreshold = RATEBASE / average_delta; + DecayRate = DECAYBASE / average_delta; + if (BytesWritten > RateThreshold) + BytesWritten = RateThreshold + DecayRate; + MRate.unlock(); + } + else { + AverageDelta = AVERAGE_DELTA_MAX; + } +} diff --git a/source/common/EQStream.h b/source/common/EQStream.h new file mode 100644 index 0000000..3ec6028 --- /dev/null +++ b/source/common/EQStream.h @@ -0,0 +1,373 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _EQPROTOCOL_H +#define _EQPROTOCOL_H + +#include +#include +#include +#include + +#include +#include +#ifndef WIN32 +#include +#endif +#include "EQPacket.h" +#include "Mutex.h" +#include "opcodemgr.h" +#include "misc.h" +#include "Condition.h" +#include "Crypto.h" +#include "zlib.h" +#include "timer.h" +#ifdef WRITE_PACKETS +#include +#endif + +using namespace std; + +typedef enum { + ESTABLISHED, + WAIT_CLOSE, + CLOSING, + DISCONNECTING, + CLOSED +} EQStreamState; + +#define FLAG_COMPRESSED 0x01 +#define FLAG_ENCODED 0x04 + +#define RATEBASE 1048576 // 1 MB +#define DECAYBASE 78642 // RATEBASE/10 + +#ifndef RETRANSMIT_TIMEOUT_MULT +#define RETRANSMIT_TIMEOUT_MULT 3.0 +#endif + +#ifndef RETRANSMIT_TIMEOUT_MAX +#define RETRANSMIT_TIMEOUT_MAX 5000 +#endif + +#ifndef AVERAGE_DELTA_MAX +#define AVERAGE_DELTA_MAX 2500 +#endif + +#pragma pack(1) +struct SessionRequest { + uint32 UnknownA; + uint32 Session; + uint32 MaxLength; +}; + +struct SessionResponse { + uint32 Session; + uint32 Key; + uint8 UnknownA; + uint8 Format; + uint8 UnknownB; + uint32 MaxLength; + uint32 UnknownD; +}; + +//Deltas are in ms, representing round trip times +struct ClientSessionStats { +/*000*/ uint16 RequestID; +/*002*/ uint32 last_local_delta; +/*006*/ uint32 average_delta; +/*010*/ uint32 low_delta; +/*014*/ uint32 high_delta; +/*018*/ uint32 last_remote_delta; +/*022*/ uint64 packets_sent; +/*030*/ uint64 packets_recieved; +/*038*/ +}; + +struct ServerSessionStats { + uint16 RequestID; + uint32 current_time; + uint32 unknown1; + uint32 received_packets; + uint32 unknown2; + uint32 sent_packets; + uint32 unknown3; + uint32 sent_packets2; + uint32 unknown4; + uint32 received_packets2; +}; + +#pragma pack() + +class OpcodeManager; +extern OpcodeManager *EQNetworkOpcodeManager; + +class EQStreamFactory; + +typedef enum { + UnknownStream=0, + LoginStream, + WorldStream, + ZoneStream, + ChatOrMailStream, + ChatStream, + MailStream, + EQ2Stream, +} EQStreamType; + +class EQStream { + protected: + typedef enum { + SeqPast, + SeqInOrder, + SeqFuture + } SeqOrder; + + uint32 received_packets; + uint32 sent_packets; + uint32 remote_ip; + uint16 remote_port; + uint8 buffer[8192]; + unsigned char *oversize_buffer; + uint32 oversize_offset,oversize_length; + unsigned char *rogue_buffer; + uint32 roguebuf_offset,roguebuf_size; + uint8 app_opcode_size; + EQStreamType StreamType; + bool compressed,encoded; + + uint32 retransmittimer; + uint32 retransmittimeout; + //uint32 buffer_len; + + uint16 sessionAttempts; + uint16 reconnectAttempt; + bool streamactive; + + uint32 Session, Key; + uint16 NextInSeq; + uint16 NextOutSeq; + uint16 SequencedBase; //the sequence number of SequencedQueue[0] + uint32 MaxLen; + uint16 MaxSends; + int8 timeout_delays; + + uint8 active_users; //how many things are actively using this + Mutex MInUse; + +#ifdef WRITE_PACKETS + FILE* write_packets = NULL; + char GetChar(uchar in); + void WriteToFile(char* pFormat, ...); + void WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing); + void WritePackets(EQ2Packet* app, bool outgoing); + Mutex MWritePackets; +#endif + + EQStreamState State; + Mutex MState; + + uint32 LastPacket; + Mutex MVarlock; + + EQApplicationPacket* CombinedAppPacket; + Mutex MCombinedAppPacket; + + long LastSeqSent; + Mutex MLastSeqSent; + void SetLastSeqSent(uint32); + + // Ack sequence tracking. + long MaxAckReceived,NextAckToSend,LastAckSent; + long GetMaxAckReceived(); + long GetNextAckToSend(); + long GetLastAckSent(); + void SetMaxAckReceived(uint32 seq); + void SetNextAckToSend(uint32); + void SetLastAckSent(uint32); + + Mutex MAcks; + + // Packets waiting to be sent + queue NonSequencedQueue; + deque SequencedQueue; + map OutOfOrderpackets; + Mutex MOutboundQueue; + + // Packes waiting to be processed + deque InboundQueue; + Mutex MInboundQueue; + + static uint16 MaxWindowSize; + + sint32 BytesWritten; + + Mutex MRate; + sint32 RateThreshold; + sint32 DecayRate; + uint32 AverageDelta; + + EQStreamFactory *Factory; + + public: + Mutex MCombineQueueLock; + bool CheckCombineQueue(); + deque combine_queue; + Timer* combine_timer; + + Crypto* crypto; + int8 EQ2_Compress(EQ2Packet* app, int8 offset = 3); + z_stream stream; + uchar* stream_buffer; + int32 stream_buffer_size; + bool eq2_compressed; + int8 compressed_offset; + int16 client_version; + int16 GetClientVersion(){ return client_version; } + void SetClientVersion(int16 version){ client_version = version; } + void ResetSessionAttempts() { reconnectAttempt = 0; } + bool HasSessionAttempts() { return reconnectAttempt>0; } + EQStream() { init(); remote_ip = 0; remote_port = 0; State = CLOSED; StreamType = UnknownStream; compressed = true; + encoded = false; app_opcode_size = 2;} + EQStream(sockaddr_in addr); + virtual ~EQStream() { + MOutboundQueue.lock(); + SetState(CLOSED); + MOutboundQueue.unlock(); + RemoveData(); + safe_delete(crypto); + safe_delete(combine_timer); + safe_delete(resend_que_timer); + safe_delete_array(oversize_buffer); + safe_delete_array(rogue_buffer); + deque::iterator cmb; + MCombineQueueLock.lock(); + for (cmb = combine_queue.begin(); cmb != combine_queue.end(); cmb++){ + safe_delete(*cmb); + } + MCombineQueueLock.unlock(); + deflateEnd(&stream); + map::iterator oop; + for (oop = OutOfOrderpackets.begin(); oop != OutOfOrderpackets.end(); oop++){ + safe_delete(oop->second); + } +#ifdef WRITE_PACKETS + if (write_packets) + fclose(write_packets); +#endif + } + inline void SetFactory(EQStreamFactory *f) { Factory=f; } + void init(bool resetSession = true); + void SetMaxLen(uint32 length) { MaxLen=length; } + int8 getTimeoutDelays(){ return timeout_delays; } + void addTimeoutDelay(){ timeout_delays++; } + void EQ2QueuePacket(EQ2Packet* app, bool attempted_combine = false); + void PreparePacket(EQ2Packet* app, int8 offset = 0); + void UnPreparePacket(EQ2Packet* app); + void EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset); + void FlushCombinedPacket(); + void SendPacket(EQApplicationPacket *p); + void QueuePacket(EQProtocolPacket *p); + void SendPacket(EQProtocolPacket *p); + vector convert(EQApplicationPacket *p); + void NonSequencedPush(EQProtocolPacket *p); + void SequencedPush(EQProtocolPacket *p); + + Mutex MResendQue; + Mutex MCompressData; + dequeresend_que; + void CheckResend(int eq_fd); + + void AckPackets(uint16 seq); + void Write(int eq_fd); + + void SetActive(bool val) { streamactive = val; } + + void WritePacket(int fd,EQProtocolPacket *p); + + void EncryptPacket(uchar* data, int16 size); + uint32 GetKey() { return Key; } + void SetKey(uint32 k) { Key=k; } + void SetSession(uint32 s) { Session=s; } + void SetLastPacketTime(uint32 t) {LastPacket=t;} + + void Process(const unsigned char *data, const uint32 length); + void ProcessPacket(EQProtocolPacket *p, EQProtocolPacket* lastp=NULL); + + bool ProcessEmbeddedPacket(uchar* pBuffer, uint16 length, int8 opcode = OP_Packet); + bool HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset = 2, int16 length = 0); + + EQProtocolPacket * ProcessEncryptedPacket(EQProtocolPacket *p); + EQProtocolPacket * ProcessEncryptedData(uchar* data, int32 size, int16 opcode); + + virtual void DispatchPacket(EQApplicationPacket *p) { p->DumpRaw(); } + + void SendSessionResponse(); + void SendSessionRequest(); + void SendDisconnect(bool setstate = true); + void SendAck(uint16 seq); + void SendOutOfOrderAck(uint16 seq); + + bool CheckTimeout(uint32 now, uint32 timeout=30) { return (LastPacket && (now-LastPacket) > timeout); } + bool Stale(uint32 now, uint32 timeout=30) { return (LastPacket && (now-LastPacket) > timeout); } + + void InboundQueuePush(EQApplicationPacket *p); + EQApplicationPacket *PopPacket(); // InboundQueuePop + void InboundQueueClear(); + + void OutboundQueueClear(); + bool HasOutgoingData(); + void SendKeyRequest(); + int16 processRSAKey(EQProtocolPacket *p, uint16 subpacket_length = 0); + void RemoveData() { InboundQueueClear(); OutboundQueueClear(); if (CombinedAppPacket) delete CombinedAppPacket; } + + // + inline bool IsInUse() { bool flag; MInUse.lock(); flag=(active_users>0); MInUse.unlock(); return flag; } + inline void PutInUse() { MInUse.lock(); active_users++; MInUse.unlock(); } + inline void ReleaseFromUse() { MInUse.lock(); if(active_users > 0) active_users--; MInUse.unlock(); } + + static SeqOrder CompareSequence(uint16 expected_seq, uint16 seq); + + inline EQStreamState GetState() { return State; } + inline void SetState(EQStreamState state) { MState.lock(); State = state; MState.unlock(); } + + inline uint32 GetRemoteIP() { return remote_ip; } + inline uint32 GetrIP() { return remote_ip; } + inline uint16 GetRemotePort() { return remote_port; } + inline uint16 GetrPort() { return remote_port; } + + + static EQProtocolPacket *Read(int eq_fd, sockaddr_in *from); + + void Close() { SendDisconnect(); } + bool CheckActive() { return (GetState()==ESTABLISHED); } + bool CheckClosed() { return GetState()==CLOSED; } + void SetOpcodeSize(uint8 s) { app_opcode_size = s; } + void SetStreamType(EQStreamType t); + inline const EQStreamType GetStreamType() const { return StreamType; } + + void ProcessQueue(); + EQProtocolPacket* RemoveQueue(uint16 seq); + + void Decay(); + void AdjustRates(uint32 average_delta); + Timer* resend_que_timer; +}; + +#endif diff --git a/source/common/EQStreamFactory.cpp b/source/common/EQStreamFactory.cpp new file mode 100644 index 0000000..965865e --- /dev/null +++ b/source/common/EQStreamFactory.cpp @@ -0,0 +1,444 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "EQStreamFactory.h" +#include "Log.h" + +#ifdef WIN32 + #include + #include + #include + #include + #include +#else + #include + #include + #include + #include + #include + #include +#endif +#include +#include +#include +#include "op_codes.h" +#include "EQStream.h" +#include "packet_dump.h" +#ifdef WORLD + #include "../WorldServer/client.h" +#endif +using namespace std; + +#ifdef WORLD + extern ClientList client_list; +#endif +ThreadReturnType EQStreamFactoryReaderLoop(void *eqfs) +{ + if(eqfs){ + EQStreamFactory *fs=(EQStreamFactory *)eqfs; + fs->ReaderLoop(); + } + THREAD_RETURN(NULL); +} + +ThreadReturnType EQStreamFactoryWriterLoop(void *eqfs) +{ + if(eqfs){ + EQStreamFactory *fs=(EQStreamFactory *)eqfs; + fs->WriterLoop(); + } + THREAD_RETURN(NULL); +} + +ThreadReturnType EQStreamFactoryCombinePacketLoop(void *eqfs) +{ + if(eqfs){ + EQStreamFactory *fs=(EQStreamFactory *)eqfs; + fs->CombinePacketLoop(); + } + THREAD_RETURN(NULL); +} + +EQStreamFactory::EQStreamFactory(EQStreamType type, int port) +{ + StreamType=type; + Port=port; + listen_ip_address = 0; +} + +void EQStreamFactory::Close() +{ + CheckTimeout(true); + Stop(); + if (sock != -1) { +#ifdef WIN32 + closesocket(sock); +#else + close(sock); +#endif + sock = -1; + } +} +bool EQStreamFactory::Open() +{ +struct sockaddr_in address; +#ifndef WIN32 + pthread_t t1, t2, t3; +#endif + /* Setup internet address information. + This is used with the bind() call */ + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(Port); +#if defined(LOGIN) || defined(MINILOGIN) + if(listen_ip_address) + address.sin_addr.s_addr = inet_addr(listen_ip_address); + else + address.sin_addr.s_addr = htonl(INADDR_ANY); +#else + address.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + /* Setting up UDP port for new clients */ + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + return false; + } + + if (::bind(sock, (struct sockaddr *) &address, sizeof(address)) < 0) { + //close(sock); + sock=-1; + return false; + } + #ifdef WIN32 + unsigned long nonblock = 1; + ioctlsocket(sock, FIONBIO, &nonblock); + #else + fcntl(sock, F_SETFL, O_NONBLOCK); + #endif + //moved these because on windows the output was delayed and causing the console window to look bad +#ifdef LOGIN + LogWrite(LOGIN__DEBUG, 0, "Login", "Starting factory Reader"); + LogWrite(LOGIN__DEBUG, 0, "Login", "Starting factory Writer"); +#elif WORLD + LogWrite(WORLD__DEBUG, 0, "World", "Starting factory Reader"); + LogWrite(WORLD__DEBUG, 0, "World", "Starting factory Writer"); +#endif + #ifdef WIN32 + _beginthread(EQStreamFactoryReaderLoop,0, this); + _beginthread(EQStreamFactoryWriterLoop,0, this); + _beginthread(EQStreamFactoryCombinePacketLoop,0, this); + #else + pthread_create(&t1,NULL,EQStreamFactoryReaderLoop,this); + pthread_create(&t2,NULL,EQStreamFactoryWriterLoop,this); + pthread_create(&t3,NULL,EQStreamFactoryCombinePacketLoop,this); + pthread_detach(t1); + pthread_detach(t2); + pthread_detach(t3); + #endif + return true; +} + +EQStream *EQStreamFactory::Pop() +{ + if (!NewStreams.size()) + return NULL; + +EQStream *s=NULL; + //cout << "Pop():Locking MNewStreams" << endl; + MNewStreams.lock(); + if (NewStreams.size()) { + s=NewStreams.front(); + NewStreams.pop(); + s->PutInUse(); + } + MNewStreams.unlock(); + //cout << "Pop(): Unlocking MNewStreams" << endl; + + return s; +} + +void EQStreamFactory::Push(EQStream *s) +{ + //cout << "Push():Locking MNewStreams" << endl; + MNewStreams.lock(); + NewStreams.push(s); + MNewStreams.unlock(); + //cout << "Push(): Unlocking MNewStreams" << endl; +} + +void EQStreamFactory::ReaderLoop() +{ +fd_set readset; +map::iterator stream_itr; +int num; +int length; +unsigned char buffer[2048]; +sockaddr_in from; +int socklen=sizeof(sockaddr_in); +timeval sleep_time; + ReaderRunning=true; + while(sock!=-1) { + MReaderRunning.lock(); + if (!ReaderRunning) + break; + MReaderRunning.unlock(); + + FD_ZERO(&readset); + FD_SET(sock,&readset); + + sleep_time.tv_sec=30; + sleep_time.tv_usec=0; + if ((num=select(sock+1,&readset,NULL,NULL,&sleep_time))<0) { + // What do we wanna do? + } else if (num==0) + continue; + + if (FD_ISSET(sock,&readset)) { +#ifdef WIN32 + if ((length=recvfrom(sock,(char*)buffer,sizeof(buffer),0,(struct sockaddr*)&from,(int *)&socklen))<2) +#else + if ((length=recvfrom(sock,buffer,2048,0,(struct sockaddr *)&from,(socklen_t *)&socklen))<2) +#endif + { + // What do we wanna do? + } else { + char temp[25]; + sprintf(temp,"%u.%d",ntohl(from.sin_addr.s_addr),ntohs(from.sin_port)); + MStreams.lock(); + if ((stream_itr=Streams.find(temp))==Streams.end() || buffer[1]==OP_SessionRequest) { + MStreams.unlock(); + if (buffer[1]==OP_SessionRequest) { + if(stream_itr != Streams.end() && stream_itr->second) + stream_itr->second->SetState(CLOSED); + EQStream *s=new EQStream(from); + s->SetFactory(this); + s->SetStreamType(StreamType); + Streams[temp]=s; + WriterWork.Signal(); + Push(s); + s->Process(buffer,length); + s->SetLastPacketTime(Timer::GetCurrentTime2()); + } + } else { + EQStream *curstream = stream_itr->second; + //dont bother processing incoming packets for closed connections + if(curstream->CheckClosed()) + curstream = NULL; + else + curstream->PutInUse(); + MStreams.unlock(); + + if(curstream) { + curstream->Process(buffer,length); + curstream->SetLastPacketTime(Timer::GetCurrentTime2()); + curstream->ReleaseFromUse(); + } + } + } + } + } +} + +void EQStreamFactory::CheckTimeout(bool remove_all) +{ + //lock streams the entire time were checking timeouts, it should be fast. + MStreams.lock(); + + unsigned long now=Timer::GetCurrentTime2(); + map::iterator stream_itr; + + for(stream_itr=Streams.begin();stream_itr!=Streams.end();) { + EQStream *s = stream_itr->second; + EQStreamState state = s->GetState(); + + if (state==CLOSING && !s->HasOutgoingData()) { + stream_itr->second->SetState(CLOSED); + state = CLOSED; + } else if (s->CheckTimeout(now, STREAM_TIMEOUT)) { + const char* stateString; + switch (state){ + case ESTABLISHED: + stateString = "Established"; + break; + case CLOSING: + stateString = "Closing"; + break; + case CLOSED: + stateString = "Closed"; + break; + case WAIT_CLOSE: + stateString = "Wait-Close"; + break; + default: + stateString = "Unknown"; + break; + } + LogWrite(WORLD__DEBUG, 0, "World", "Timeout up!, state=%s (%u)", stateString, state); + if (state==ESTABLISHED) { + s->Close(); + } + else if (state == WAIT_CLOSE) { + s->SetState(CLOSING); + state = CLOSING; + } + else if (state == CLOSING) { + //if we time out in the closing state, just give up + s->SetState(CLOSED); + state = CLOSED; + } + } + //not part of the else so we check it right away on state change + if (remove_all || state==CLOSED) { + if (!remove_all && s->getTimeoutDelays()<2) { + s->addTimeoutDelay(); + //give it a little time for everybody to finish with it + } else { + //everybody is done, we can delete it now + +#ifdef LOGIN + LogWrite(LOGIN__DEBUG, 0, "Login", "Removing connection..."); +#else + LogWrite(WORLD__DEBUG, 0, "World", "Removing connection..."); +#endif + map::iterator temp=stream_itr; + stream_itr++; + //let whoever has the stream outside delete it + #ifdef WORLD + client_list.RemoveConnection(temp->second); + #endif + EQStream* stream = temp->second; + Streams.erase(temp); + delete stream; + continue; + } + } + + stream_itr++; + } + MStreams.unlock(); +} + +void EQStreamFactory::CombinePacketLoop(){ + deque combine_que; + CombinePacketRunning = true; + bool packets_waiting = false; + while(sock!=-1) { + if (!CombinePacketRunning) + break; + MStreams.lock(); + map::iterator stream_itr; + for(stream_itr=Streams.begin();stream_itr!=Streams.end();stream_itr++) { + if(!stream_itr->second){ + continue; + } + if(stream_itr->second->combine_timer && stream_itr->second->combine_timer->Check()) + combine_que.push_back(stream_itr->second); + } + EQStream* stream = 0; + packets_waiting = false; + while(combine_que.size()){ + stream = combine_que.front(); + if(stream->CheckActive()){ + if(!stream->CheckCombineQueue()) + packets_waiting = true; + } + combine_que.pop_front(); + } + MStreams.unlock(); + if(!packets_waiting) + Sleep(25); + + Sleep(1); + } +} + +void EQStreamFactory::WriterLoop() +{ +map::iterator stream_itr; +vector wants_write; +vector::iterator cur,end; +deque resend_que; +bool decay=false; +uint32 stream_count; + +Timer DecayTimer(20); + + WriterRunning=true; + DecayTimer.Enable(); + while(sock!=-1) { + Timer::SetCurrentTime(); + //if (!havework) { + //WriterWork.Wait(); + //} + MWriterRunning.lock(); + if (!WriterRunning) + break; + MWriterRunning.unlock(); + + wants_write.clear(); + + decay=DecayTimer.Check(); + + //copy streams into a seperate list so we dont have to keep + //MStreams locked while we are writting + MStreams.lock(); + for(stream_itr=Streams.begin();stream_itr!=Streams.end();stream_itr++) { + // If it's time to decay the bytes sent, then let's do it before we try to write + if(!stream_itr->second){ + Streams.erase(stream_itr); + break; + } + if (decay) + stream_itr->second->Decay(); + + if (stream_itr->second->HasOutgoingData()) { + stream_itr->second->PutInUse(); + wants_write.push_back(stream_itr->second); + } + if(stream_itr->second->resend_que_timer->Check()) + resend_que.push_back(stream_itr->second); + } + MStreams.unlock(); + + //do the actual writes + cur = wants_write.begin(); + end = wants_write.end(); + for(; cur != end; cur++) { + (*cur)->Write(sock); + (*cur)->ReleaseFromUse(); + } + while(resend_que.size()){ + resend_que.front()->CheckResend(sock); + resend_que.pop_front(); + } + Sleep(10); + + MStreams.lock(); + stream_count=Streams.size(); + MStreams.unlock(); + if (!stream_count) { + //cout << "No streams, waiting on condition" << endl; + WriterWork.Wait(); + //cout << "Awake from condition, must have a stream now" << endl; + } + } +} + + diff --git a/source/common/EQStreamFactory.h b/source/common/EQStreamFactory.h new file mode 100644 index 0000000..9dce3c7 --- /dev/null +++ b/source/common/EQStreamFactory.h @@ -0,0 +1,86 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _EQSTREAMFACTORY_H + +#define _EQSTREAMFACTORY_H + +#include +#include +#include "../common/EQStream.h" +#include "../common/Condition.h" +#include "../common/opcodemgr.h" +#include "../common/timer.h" + +#define STREAM_TIMEOUT 45000 //in ms + +class EQStreamFactory { + private: + int sock; + int Port; + + bool ReaderRunning; + Mutex MReaderRunning; + bool WriterRunning; + Mutex MWriterRunning; + bool CombinePacketRunning; + Mutex MCombinePacketRunning; + + Condition WriterWork; + + EQStreamType StreamType; + + queue NewStreams; + Mutex MNewStreams; + + map Streams; + Mutex MStreams; + + + + Timer *DecayTimer; + + public: + char* listen_ip_address; + void CheckTimeout(bool remove_all = false); + EQStreamFactory(EQStreamType type) { ReaderRunning=false; WriterRunning=false; StreamType=type; } + EQStreamFactory(EQStreamType type, int port); + ~EQStreamFactory(){ + safe_delete_array(listen_ip_address); + } + + EQStream *Pop(); + void Push(EQStream *s); + + bool loadPublicKey(); + bool Open(); + bool Open(unsigned long port) { Port=port; return Open(); } + void Close(); + void ReaderLoop(); + void WriterLoop(); + void CombinePacketLoop(); + void Stop() { StopReader(); StopWriter(); StopCombinePacket(); } + void StopReader() { MReaderRunning.lock(); ReaderRunning=false; MReaderRunning.unlock(); } + void StopWriter() { MWriterRunning.lock(); WriterRunning=false; MWriterRunning.unlock(); WriterWork.Signal(); } + void StopCombinePacket() { MCombinePacketRunning.lock(); CombinePacketRunning=false; MCombinePacketRunning.unlock(); } + void SignalWriter() { WriterWork.Signal(); } + +}; + +#endif diff --git a/source/common/GlobalHeaders.h b/source/common/GlobalHeaders.h new file mode 100644 index 0000000..98b7458 --- /dev/null +++ b/source/common/GlobalHeaders.h @@ -0,0 +1,58 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +//Character Creation Replies, put in globals so name filter can return proper responses +#define UNKNOWNERROR_REPLY 0 +#define CREATESUCCESS_REPLY 1 +#define NOSERVERSAVAIL_REPLY 2 +#define CREATEPENDING_REPLY 3 +#define MAXCHARSALLOWED_REPLY 4 +#define INVALIDRACE_REPLY 5 +#define INVALIDCITY_REPLY 6 +#define INVALIDCLASS_REPLY 7 +#define INVALIDGENDER_REPLY 8 +#define INVALIDFIRST_LVL_REPLY 9 +#define BADNAMELENGTH_REPLY 10 +#define NAMEINVALID_REPLY 11 +#define NAMEFILTER_REPLY 12 // name_filter reply (bad word or blocked words) +#define NAMETAKEN_REPLY 13 +#define OVERLOADEDSERVER_REPLY 14 +#define UNKNOWNERROR_REPLY2 15 +#define INVALIDFEATURES1_REPLY 16 +#define INVALIDFEATURES2_REPLY 17 +#define INVALIDRACE_APPEARANCE_REPLY 18 + +#define PLAY_ERROR_PROBLEM 0 +#define PLAY_ERROR_ZONE_DOWN 4 +#define PLAY_ERROR_CHAR_NOT_LOADED 5 +#define PLAY_ERROR_CHAR_NOT_FOUND 6 +#define PLAY_ERROR_ACCOUNT_IN_USE 7 +#define PLAY_ERROR_SERVER_TIMEOUT 8 +#define PLAY_ERROR_SERVER_SHUTDOWN 9 +#define PLAY_ERROR_LOADING_ERROR 10 +#define PLAY_ERROR_EXCHANGE_SERVER 11 +#define PLAY_ERROR_REGION_SERVER 12 +#define PLAY_ERROR_CLASS_INVALID 13 +#define PLAY_ERROR_TOO_MANY_CHARACTERS 14 +#define PLAY_ERROR_EOF_EXP_NOT_FOUND 15 +#define PLAY_ERROR_UNKNOWN_RESPONSE 16 +#define PLAY_ERROR_UNKNOWN 17 +#define PLAY_ERROR_ACCOUNT_BANNED 18 +#define PLAY_ERROR_PROHIBITED 19 diff --git a/source/common/JsonParser.cpp b/source/common/JsonParser.cpp new file mode 100644 index 0000000..cca065a --- /dev/null +++ b/source/common/JsonParser.cpp @@ -0,0 +1,97 @@ +#include "JsonParser.h" + +JsonParser::JsonParser(const std::string &filename) { + is_loaded = false; + try { + boost::property_tree::read_json(filename, pt); + parseTree(pt, ""); + is_loaded = true; + } catch (const boost::property_tree::json_parser_error &e) { + std::cerr << "Error reading JSON file: " << e.what() << std::endl; + } +} + +bool JsonParser::convertStringToUnsignedChar(const std::string& str, unsigned char& result) { + unsigned long ul; + try { + ul = std::stoul(str); + } catch (const std::invalid_argument&) { + return false; // Not a valid number + } catch (const std::out_of_range&) { + return false; // Number is too large for unsigned long + } + + if (ul > std::numeric_limits::max()) { + return false; // Number is too large for unsigned short + } + + result = static_cast(ul); + return true; +} + +bool JsonParser::convertStringToUnsignedShort(const std::string& str, unsigned short& result) { + unsigned long ul; + try { + ul = std::stoul(str); + } catch (const std::invalid_argument&) { + return false; // Not a valid number + } catch (const std::out_of_range&) { + return false; // Number is too large for unsigned long + } + + if (ul > std::numeric_limits::max()) { + return false; // Number is too large for unsigned short + } + + result = static_cast(ul); + return true; +} + +bool JsonParser::convertStringToUnsignedInt(const std::string& str, unsigned int& result) { + unsigned long ul; + try { + ul = std::stoul(str); + } catch (const std::invalid_argument&) { + return false; // Not a valid number + } catch (const std::out_of_range&) { + return false; // Number is too large for unsigned long + } + + if (ul > std::numeric_limits::max()) { + return false; // Number is too large for unsigned short + } + + result = static_cast(ul); + return true; +} + +bool JsonParser::convertStringToUnsignedLong(const std::string& str, unsigned long& result) { + unsigned long ul; + try { + ul = std::stoul(str); + } catch (const std::invalid_argument&) { + return false; // Not a valid number + } catch (const std::out_of_range&) { + return false; // Number is too large for unsigned long + } + + if (ul > std::numeric_limits::max()) { + return false; // Number is too large for unsigned short + } + + result = ul; + return true; +} + +void JsonParser::parseTree(const boost::property_tree::ptree &tree, const std::string &path) { + for (const auto &node : tree) { + std::string currentPath = path.empty() ? node.first : path + "." + node.first; + if (node.second.empty()) { + std::string name = currentPath; + boost::algorithm::to_lower(name); + values[name] = node.second.get_value(); + } else { + parseTree(node.second, currentPath); + } + } +} \ No newline at end of file diff --git a/source/common/JsonParser.h b/source/common/JsonParser.h new file mode 100644 index 0000000..f92b07d --- /dev/null +++ b/source/common/JsonParser.h @@ -0,0 +1,33 @@ + +#include +#include +#include +#include +#include +#include +#include + +class JsonParser { +public: + JsonParser(const std::string &filename); + + std::string getValue(const std::string &path) const { + auto it = values.find(path); + if (it != values.end()) { + return it->second; + } + return ""; + } + + static bool convertStringToUnsignedChar(const std::string& str, unsigned char& result); + static bool convertStringToUnsignedShort(const std::string& str, unsigned short& result); + static bool convertStringToUnsignedInt(const std::string& str, unsigned int& result); + static bool convertStringToUnsignedLong(const std::string& str, unsigned long& result); + bool IsLoaded() { return is_loaded; } +private: + boost::property_tree::ptree pt; + std::map values; + + void parseTree(const boost::property_tree::ptree &tree, const std::string &path); + bool is_loaded; +}; diff --git a/source/common/Log.cpp b/source/common/Log.cpp new file mode 100644 index 0000000..0c59aa9 --- /dev/null +++ b/source/common/Log.cpp @@ -0,0 +1,615 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Log.h" +#include "xmlParser.h" +#include "types.h" +#include +#include +#include +#include +#include +#include +#include +#include "../WorldServer/World.h" +#include "../WorldServer/client.h" +#include "../WorldServer/zoneserver.h" + +extern ZoneList zone_list; + +#ifdef _WIN32 + #include + #ifndef snprintf + #define snprintf sprintf_s + #endif +#include + #include +#else +#endif + +#define LOG_CATEGORY(category) #category, +const char *log_category_names[NUMBER_OF_LOG_CATEGORIES] = { + #include "LogTypes.h" +}; + +#define LOG_TYPE(category, type, level, color, enabled, logfile, console, client, str) { level, color, enabled, logfile, console, client, LOG_ ##category, #category "__" #type, ( strlen(str)>0 ) ? str : #category "__" #type }, +static LogTypeStatus real_log_type_info[NUMBER_OF_LOG_TYPES+1] = +{ + #include "LogTypes.h" + { 0, 0, false, false, false, false, NUMBER_OF_LOG_CATEGORIES, "BAD TYPE", "Bad Name" } /* dummy trailing record */ +}; + +LogTypeStatus *log_type_info = real_log_type_info; + +//make these rules? +#define LOG_CYCLE 100 //milliseconds between each batch of log writes +#define LOGS_PER_CYCLE 50 //amount of logs to write per cycle + +#define LOG_DIR "logs" + +#if defined LOGIN +#define EXE_NAME "login" +#elif defined WORLD +#define EXE_NAME "world" +#elif defined PARSER +#define EXE_NAME "parser" +#elif defined PATCHER +#define EXE_NAME "patcher" +#else +#define EXE_NAME "whatprogyourunning" +#endif + +#define DATE_MAX 8 +#define LOG_NAME_MAX 32 + +struct logq_t { + LogType log_type; + char date[DATE_MAX + 1]; + char name[LOG_NAME_MAX + 1]; + char *text; + struct logq_t *next; + struct logq_t *prev; +}; + +//doubly linked list of logs +static logq_t head; +static logq_t tail; +static int num_logqs = 0; +static Mutex mlogqs; + +//loop until.... +static bool looping = false; + +//because our code has LogWrite's before main(), make sure any of those do the +//call to LogStart if it hasn't been called already... +static bool start_called = false; + +static void SetConsoleColor(int color) { +#ifdef _WIN32 + HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + + if (handle == NULL || handle == INVALID_HANDLE_VALUE) + return; +#endif + + switch (color) { + case FOREGROUND_WHITE: + case FOREGROUND_WHITE_BOLD: + case FOREGROUND_RED: + case FOREGROUND_RED_BOLD: + case FOREGROUND_GREEN: + case FOREGROUND_GREEN_BOLD: + case FOREGROUND_BLUE: + case FOREGROUND_BLUE_BOLD: + case FOREGROUND_YELLOW: + case FOREGROUND_YELLOW_BOLD: + case FOREGROUND_CYAN: + case FOREGROUND_CYAN_BOLD: + case FOREGROUND_MAGENTA: + case FOREGROUND_MAGENTA_BOLD: +#ifdef _WIN32 + SetConsoleTextAttribute(handle, color); +#else + printf("\033[%i;%i;40m", color > 100 ? 1 : 0, color > 100 ? color - 100 : color); +#endif + break; + default: +#ifdef _WIN32 + SetConsoleTextAttribute(handle, FOREGROUND_WHITE_BOLD); +#else + printf("\033[0;37;40m"); +#endif + break; + } +} + +static FILE * OpenLogFile() { + char file[FILENAME_MAX + 1]; + struct stat st; + struct tm *tm; + time_t now; + FILE *f; + + now = time(NULL); + tm = localtime(&now); + + //make sure the logs directory exists + if (stat(LOG_DIR, &st) != 0) { +#ifdef _WIN32 + if (!CreateDirectory(LOG_DIR, NULL)) { + fprintf(stderr, "Unable to create directory '%s'\n", LOG_DIR); + return stderr; + } +#else + if (mkdir(LOG_DIR, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { + fprintf(stderr, "Unable to create direcotry '%s': %s\n", LOG_DIR, strerror(errno)); + return stderr; + } +#endif + } + +#ifdef NO_PIDLOG + snprintf(file, FILENAME_MAX, LOG_DIR"/%04i-%02i-%02i_eq2" EXE_NAME ".log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); +#else + snprintf(file, FILENAME_MAX, LOG_DIR"/%04i-%02i-%02i_eq2" EXE_NAME "_%04i.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, getpid()); +#endif + + if ((f = fopen(file, "a")) == NULL) { + fprintf(stderr, "Could not open '%s' for writing: %s\n", file, strerror(errno)); + return stderr; + } + + return f; +} + +static void WriteQueuedLogs(int count) { + logq_t pending_head, pending_tail, *logq, *tmp; + int i = 0; + FILE *f; + + pending_head.next = &pending_tail; + pending_tail.prev = &pending_head; + + //loop through our queued logs and store at most `count` logs into a temporary list + //since io functions are expensive, we'll write from a temporary list so we don't hold the + //write lock of the main list for a long period of time + mlogqs.writelock(); + logq = head.next; + + while (head.next != &tail) { + //first remove the log from the master list + logq = head.next; + logq->next->prev = &head; + head.next = logq->next; + + //now insert it into the temporary list + tmp = pending_tail.prev; + tmp->next = logq; + logq->prev = tmp; + logq->next = &pending_tail; + pending_tail.prev = logq; + --num_logqs; + + logq = logq->next; + + //if we have a limit, check it + if (count > 0 && ++i == count) + break; + } + + //if we have no logs to write, we're done + if ((logq = pending_head.next) == &pending_tail) + { + mlogqs.releasewritelock(); + return; + } + + while (logq != &pending_tail) { + if (log_type_info[logq->log_type].console) { + SetConsoleColor(FOREGROUND_WHITE_BOLD); + printf("%s ", logq->date); + SetConsoleColor(log_type_info[logq->log_type].color); + printf("%s ", log_type_info[logq->log_type].display_name); + SetConsoleColor(FOREGROUND_WHITE_BOLD); + printf("%-10s: ", logq->name); + SetConsoleColor(log_type_info[logq->log_type].color); + printf("%s\n", logq->text); + SetConsoleColor(-1); + fflush(stdout); + } + + if (log_type_info[logq->log_type].logfile) { + f = OpenLogFile(); + + if (f != stderr || (f == stderr && !log_type_info[logq->log_type].console)) { + fprintf(f, "%s %s %s: %s\n", logq->date, log_type_info[logq->log_type].display_name, logq->name, logq->text); + fflush(f); + if (f != stderr) + fclose(f); + } + } + +#if defined WORLD + if (log_type_info[logq->log_type].client) { + // eventually output logging to the client who "subscribed" to the logger + // in-game, they type: + // /logsys add WORLD__DEBUG 5 + // to watch world debug loggers of level 5 or less + } +#endif + + tmp = logq; + logq = logq->next; + + mlogqs.releasewritelock(); + + free(tmp->text); + free(tmp); + } +} + +ThreadReturnType LogLoop(void *args) { + while (looping) { + WriteQueuedLogs(LOGS_PER_CYCLE); + Sleep(LOG_CYCLE); + } + + THREAD_RETURN(NULL); +} + +void LogStart() { + if (start_called) + return; + + //initialize the doubly linked list + head.prev = NULL; + head.next = &tail; + tail.prev = &head; + tail.next = NULL; + + mlogqs.SetName("logqueue"); + looping = true; + +#ifdef _WIN32 + _beginthread(LogLoop, 0, NULL); +#else + pthread_t thread; + pthread_create(&thread, NULL, LogLoop, NULL); + pthread_detach(thread); +#endif + + start_called = true; +} + +void LogStop() { + looping = false; + WriteQueuedLogs(-1); + start_called = false; +} + +static void LogQueueAdd(LogType log_type, char *text, int len, const char *cat_text = NULL) { + logq_t *logq; + struct tm *tm; + time_t now; + + if ((logq = (logq_t *)calloc(1, sizeof(logq_t))) == NULL) { + free(text); + fprintf(stderr, "%s: %u: Unable to allocate %zu bytes\n", __FUNCTION__, __LINE__, sizeof(logq_t)); + return; + } + + if ((logq->text = (char *)calloc(len + 1, sizeof(char))) == NULL) { + free(text); + free(logq); + fprintf(stderr, "%s: %u: Unable to allocate %i bytes\n", __FUNCTION__, __LINE__, len + 1); + return; + } + + now = time(NULL); + tm = localtime(&now); + + logq->log_type = log_type; + snprintf(logq->date, DATE_MAX + 1, "%02i:%02i:%02i", tm->tm_hour, tm->tm_min, tm->tm_sec); + strncpy(logq->name, cat_text == NULL || cat_text[0] == '\0' ? log_type_info[log_type].name : cat_text, LOG_NAME_MAX); + strncpy(logq->text, text, len); + free(text); + + if (!start_called) + LogStart(); + + //insert at the end + mlogqs.writelock(); + tail.prev->next = logq; + logq->prev = tail.prev; + logq->next = &tail; + tail.prev = logq; + ++num_logqs; + mlogqs.releasewritelock(); +} + +int8 GetLoggerLevel(LogType type) +{ + return log_type_info[type].level; +} + +// JA: horrific hack for Parser, since queued logging keeps crashing between parses. +#ifndef PARSER +void LogWrite(LogType type, int8 log_level, const char *cat_text, const char *fmt, ...) { + int count, size = 64; + char *buf; + va_list ap; + + // if there is no formatting, or the logger is DISABLED + // or the log_level param exceeds the minimum allowed value, abort logwrite + if (!log_type_info[type].enabled || (log_level > 0 && log_type_info[type].level < log_level)) + return; + + while (true) { + if ((buf = (char *)malloc(size)) == NULL) { + fprintf(stderr, "%s: %i: Unable to allocate %i bytes\n", __FUNCTION__, __LINE__, size); + return; + } + + va_start(ap, fmt); + count = vsnprintf(buf, size, fmt, ap); + va_end(ap); + + if (count > -1 && count < size) + break; + + free(buf); + if (count > 1) + size = count + 1; + else + size *= 2; + } + + LogQueueAdd(type, buf, count, cat_text); +} +#else +void LogWrite(LogType type, int8 log_level, const char *cat_text, const char *format, ...) +{ + // if there is no formatting, or the logger is DISABLED + // or the log_level param exceeds the minimum allowed value, abort logwrite + if ( !format || !log_type_info[type].enabled || (log_level > 0 && log_type_info[type].level < log_level) ) + return; + + time_t clock; + struct tm *tm; + + char buffer[LOG_BUFFER_SIZE], date[32]; + va_list args; + FILE *f; + size_t cat_text_len = 0; + + memset(buffer, 0, sizeof(buffer)); + memset(date, 0, sizeof(date)); + + va_start(args, format); + vsnprintf(buffer, sizeof(buffer) - 1, format, args); + va_end(args); + + time(&clock); + tm = localtime(&clock); + snprintf(date, sizeof(date)-1, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); + // DateString(date, sizeof(date)); + + cat_text_len = strlen(cat_text); + //if( strlen(cat_text) == 0 ) // cat_text was blank + // cat_text = (char*)log_type_info[type].name; + + /* write to the log file? */ + if (log_type_info[type].logfile) + { + char exename[200] = ""; + + #ifdef LOGIN + snprintf(exename, sizeof(exename), "login"); + #endif + #ifdef WORLD + snprintf(exename, sizeof(exename), "world"); + #endif + #ifdef PARSER + snprintf(exename, sizeof(exename), "parser"); + #endif + #ifdef PATCHER + snprintf(exename, sizeof(exename), "patcher"); + #endif + + char filename[200], log_header[200] = ""; + + #ifndef NO_PIDLOG + snprintf(filename, sizeof(filename)-1, "logs/%04d-%02d-%02d_eq2%s_%04i.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, exename, getpid()); + #else + snprintf(filename, sizeof(filename)-1, "logs/%04d-%02d-%02d_eq2%s.log", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, exename); + #endif + + f=fopen(filename, "r"); + if( !f ) + snprintf(log_header, sizeof(log_header), "===[ New log '%s' started ]===\n\n", filename); + else + fclose (f); + + f = fopen(filename, "a"); + if (f) { + if( strlen(log_header) > 0 ) + fprintf(f, "%s\n", log_header); + fprintf(f, "%s %s %s: %s\n", date, log_type_info[type].display_name, cat_text, buffer); + fclose(f); + } + } + + /* write to the console? */ + if (log_type_info[type].console) + { + #ifdef _WIN32 + ColorizeLog(log_type_info[type].color, date, log_type_info[type].display_name, cat_text_len == 0 ? log_type_info[type].name : cat_text, (string)buffer); + #else + printf("%s %s %s: %s\n", date, log_type_info[type].display_name, cat_text_len == 0 ? log_type_info[type].name : cat_text, buffer); + #endif + } +} + +void +ColorizeLog(int color, char *date, const char *display_name, const char *category, string buffer) +{ + #ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + if (console == INVALID_HANDLE_VALUE) { + printf("%s %s %s: %s\n", date, display_name, category, buffer); + return; + } + printf("%s ", date); + SetConsoleTextAttribute(console, color); + printf("%s ", display_name); + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); + printf("%s: ", category); + SetConsoleTextAttribute(console, color); + printf("%s\n", buffer.c_str()); + SetConsoleTextAttribute(console, FOREGROUND_WHITE); + #endif +} + +#endif + +LogTypeStatus * +GetLogTypeStatus(const char *category, const char *type) { + char combined[256]; + int i; + + memset(combined, 0, sizeof(combined)); + snprintf(combined, sizeof(combined) - 1, "%s__%s", category, type); + + for (i = 0; i < NUMBER_OF_LOG_TYPES; i++) { + if (strcasecmp(log_type_info[i].name, combined) == 0) + return &log_type_info[i]; + } + + return &log_type_info[NUMBER_OF_LOG_TYPES]; +} + +void +ProcessLogConfig(XMLNode node) { + int i; + const char *category, *type, *level, *color, *enabled, *logs; + LogTypeStatus *lfs; + XMLNode child; + + category = node.getAttribute("Category"); + if (!category) { + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing a Category"); + return; + } + + for (i = 0; i < node.nChildNode("ConfigType"); i++) { + child = node.getChildNode("ConfigType", i); + type = child.getAttribute("Type"); + + if (!type) { + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing a Type"); + continue; + } + + lfs = GetLogTypeStatus(category, type); + level = child.getAttribute("Level"); + enabled = child.getAttribute("Enabled"); + color = child.getAttribute("Color"); + logs = child.getAttribute("Logs"); + + if (!logs) { + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Config missing 'Logs' attribute to specify which log(s) to write to"); + continue; + } + if (!IsNumber(logs)) { + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Attribute 'Logs' must be a number. See LogTypes.h for the valid types."); + continue; + } + + if (enabled) { + if (!strcasecmp("true", enabled) || !strcasecmp("on", enabled)) + lfs->enabled = true; + else if (!strcasecmp("false", enabled) || !strcasecmp("off", enabled)) + lfs->enabled = false; + else + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Log setting 'Enabled' has invalid value '%s'. 'true'/'on' or 'false'/'off' are valid values", enabled); + } + + if (IsNumber(level)) + lfs->level = atoi(level); + else + lfs->level = 0; + + if (color) { + if (IsNumber(color)) + lfs->color = atoi(color); + else if (!strcasecmp("White", color)) + lfs->color = FOREGROUND_WHITE; + else if (!strcasecmp("Green", color)) + lfs->color = FOREGROUND_GREEN; + else if (!strcasecmp("Yellow", color)) + lfs->color = FOREGROUND_YELLOW; + else if (!strcasecmp("Red", color)) + lfs->color = FOREGROUND_RED; + else if (!strcasecmp("Blue", color)) + lfs->color = FOREGROUND_BLUE; + else if (!strcasecmp("Cyan", color)) + lfs->color = FOREGROUND_CYAN; + else if (!strcasecmp("Magenta", color)) + lfs->color = FOREGROUND_MAGENTA; + else if (!strcasecmp("WhiteBold", color)) + lfs->color = FOREGROUND_WHITE_BOLD; + else if (!strcasecmp("GreenBold", color)) + lfs->color = FOREGROUND_GREEN_BOLD; + else if (!strcasecmp("YellowBold", color)) + lfs->color = FOREGROUND_YELLOW_BOLD; + else if (!strcasecmp("RedBold", color)) + lfs->color = FOREGROUND_RED_BOLD; + else if (!strcasecmp("BlueBold", color)) + lfs->color = FOREGROUND_BLUE_BOLD; + else if (!strcasecmp("CyanBold", color)) + lfs->color = FOREGROUND_CYAN_BOLD; + else if (!strcasecmp("MagentaBold", color)) + lfs->color = FOREGROUND_MAGENTA_BOLD; + else + LogWrite(MISC__WARNING, 0, "Misc", "Error parsing log config. Log setting 'Color' has invalid value '%s'", color); + } + + // JA: something was wrong here, lfs->logfile or console always was true, even if bit was off. Will ask Scatman about it someday. + lfs->logfile = (atoi(logs) & LOG_LOGFILE); + lfs->console = (atoi(logs) & LOG_CONSOLE); + lfs->client = (atoi(logs) & LOG_CLIENT); + } +} + +bool +LogParseConfigs() { + XMLNode main_node; + int i; + + main_node = XMLNode::openFileHelper("log_config.xml", "EQ2EmuLogConfigs"); + if (main_node.isEmpty()) { + LogWrite(MISC__WARNING, 0, "Misc", "Unable to parse the file 'log_config.xml' or it does not exist. Default values will be used"); + return false; + } + + for (i = 0; i < main_node.nChildNode("LogConfig"); i++) + ProcessLogConfig(main_node.getChildNode("LogConfig", i)); + + return true; +} diff --git a/source/common/Log.h b/source/common/Log.h new file mode 100644 index 0000000..8a7e6a6 --- /dev/null +++ b/source/common/Log.h @@ -0,0 +1,69 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef LOG_H_ +#define LOG_H_ + +#include +#include "../WorldServer/client.h" + +#define LOG_BUFFER_SIZE 4096 + +#define LOG_CATEGORY(category) LOG_ ##category , +enum LogCategory +{ + #include "LogTypes.h" + NUMBER_OF_LOG_CATEGORIES +}; + +#define LOG_TYPE(category, type, level, color, enabled, logfile, console, client, str) category##__##type , +enum LogType +{ + #include "LogTypes.h" + NUMBER_OF_LOG_TYPES +}; + +extern const char* log_category_names[NUMBER_OF_LOG_CATEGORIES]; + +struct LogTypeStatus +{ + int8 level; + int color; + bool enabled; + bool logfile; + bool console; + bool client; + LogCategory category; + const char *name; + const char *display_name; +}; + +extern LogTypeStatus* log_type_info; + +void LogStart(); +void LogStop(); +int8 GetLoggerLevel(LogType type); +void LogWrite(LogType type, int8 log_level, const char *cat_text, const char *fmt, ...); +#ifdef PARSER + void ColorizeLog(int color, char *date, const char *display_name, const char *category, string buffer); +#endif + +bool LogParseConfigs(); + +#endif diff --git a/source/common/LogTypes.h b/source/common/LogTypes.h new file mode 100644 index 0000000..615904b --- /dev/null +++ b/source/common/LogTypes.h @@ -0,0 +1,519 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef LOG_CATEGORY + #define LOG_CATEGORY(name) +#endif + +#ifndef LOG_TYPE + #define LOG_TYPE(category, type, level, color, enabled, logfile, console, client, str) +#endif + +#ifndef ENABLED + #define ENABLED true +#endif + +#ifndef DISABLED + #define DISABLED false +#endif + +#ifdef _WIN32 + #define FOREGROUND_WHITE (FOREGROUND_RED |FOREGROUND_GREEN | FOREGROUND_BLUE) + #define FOREGROUND_WHITE_BOLD (FOREGROUND_RED |FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY) + #define FOREGROUND_RED_BOLD (FOREGROUND_RED | FOREGROUND_INTENSITY) + #define FOREGROUND_GREEN_BOLD (FOREGROUND_GREEN | FOREGROUND_INTENSITY) + #define FOREGROUND_BLUE_BOLD (FOREGROUND_BLUE | FOREGROUND_INTENSITY) + #define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN) + #define FOREGROUND_YELLOW_BOLD (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY) + #define FOREGROUND_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE) + #define FOREGROUND_CYAN_BOLD (FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY) + #define FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE) + #define FOREGROUND_MAGENTA_BOLD (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY) +#else + #define FOREGROUND_WHITE 37 + #define FOREGROUND_WHITE_BOLD 137 + #define FOREGROUND_RED 31 + #define FOREGROUND_RED_BOLD 131 + #define FOREGROUND_GREEN 32 + #define FOREGROUND_GREEN_BOLD 132 + #define FOREGROUND_BLUE 34 + #define FOREGROUND_BLUE_BOLD 134 + #define FOREGROUND_YELLOW 33 + #define FOREGROUND_YELLOW_BOLD 133 + #define FOREGROUND_CYAN 36 + #define FOREGROUND_CYAN_BOLD 136 + #define FOREGROUND_MAGENTA 35 + #define FOREGROUND_MAGENTA_BOLD 135 +#endif + + + +#define LOG_LOGFILE 1 +#define LOG_CONSOLE 2 +#define LOG_CLIENT 4 /* not yet using */ + +/* + Legend for str output (optional): + I : Information messages + W : Warning messages + E : Error messages + D : Debug messages + P : DumpPacket/PrintPacket messages - should NEVER go to Client channel!!! + T : Low-level debug tracing messages - should NEVER go to Client channel!!! + + Note: If str = null, output #catagory__#type to logs +*/ + + +/*** SYSTEM Loggers ******************************************************************************/ +// Logging specific to general events within the World code +LOG_CATEGORY(WORLD) +LOG_TYPE(WORLD, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") // Information messages (minimum output) +LOG_TYPE(WORLD, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") // Warning messages +LOG_TYPE(WORLD, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") // Error messages (should always be enabled) +LOG_TYPE(WORLD, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") // Debug messages (enabled during alpha dev) +LOG_TYPE(WORLD, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") // DumpPacket/PrintPacket messages +LOG_TYPE(WORLD, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") // Low-level debug tracing messages + +// LoginServer and MiniLogin events +LOG_CATEGORY(LOGIN) +LOG_TYPE(LOGIN, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(LOGIN, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(LOGIN, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(LOGIN, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(LOGIN, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(LOGIN, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// PatchServer (DB auto-patcher) events +LOG_CATEGORY(PATCHER) +LOG_TYPE(PATCHER, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PATCHER, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PATCHER, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PATCHER, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PATCHER, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PATCHER, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// PacketParser events +LOG_CATEGORY(PARSER) +LOG_TYPE(PARSER, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PARSER, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PARSER, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PARSER, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PARSER, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PARSER, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// World/Login/Patcher/Parser Initialization loggers +LOG_CATEGORY(INIT) +LOG_TYPE(INIT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(INIT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(INIT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(INIT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(INIT, LOGIN_INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(INIT, LOGIN_WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(INIT, LOGIN_ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(INIT, LOGIN_DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(INIT, PATCHER_INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(INIT, PATCHER_WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(INIT, PATCHER_ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(INIT, PATCHER_DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") + +// General DB logging +LOG_CATEGORY(DATABASE) +LOG_TYPE(DATABASE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(DATABASE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(DATABASE, QUERY, 0, FOREGROUND_CYAN, DISABLED, DISABLED, DISABLED, DISABLED, "Q") +LOG_TYPE(DATABASE, RESULT, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "R") +LOG_TYPE(DATABASE, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(DATABASE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") + +// Logging Mutex code +LOG_CATEGORY(MUTEX) +LOG_TYPE(MUTEX, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(MUTEX, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(MUTEX, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(MUTEX, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(MUTEX, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(MUTEX, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// anything else... including a special DEV type "TODO" +LOG_CATEGORY(MISC) +LOG_TYPE(MISC, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(MISC, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(MISC, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(MISC, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(MISC, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(MISC, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") +LOG_TYPE(MISC, TODO, 0, FOREGROUND_YELLOW_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "M") + + + +/*** NETWORK Loggers *****************************************************************************/ +// Client Communications Logging +LOG_CATEGORY(CCLIENT) +LOG_TYPE(CCLIENT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(CCLIENT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(CCLIENT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(CCLIENT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(CCLIENT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(CCLIENT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Logging Net code +LOG_CATEGORY(NET) +LOG_TYPE(NET, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(NET, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(NET, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(NET, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NET, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(NET, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Logging opcodes as they are encountered +LOG_CATEGORY(OPCODE) +LOG_TYPE(OPCODE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(OPCODE, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(OPCODE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(OPCODE, DEBUG, 0, FOREGROUND_MAGENTA_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(OPCODE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(OPCODE, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Special category for dumping out excessive DumpPacket or Opcode debugging entries - All DISABLED by default! +LOG_CATEGORY(PACKET) +LOG_TYPE(PACKET, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PACKET, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PACKET, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PACKET, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PACKET, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PACKET, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + + +/*** PLAYER Loggers ******************************************************************************/ +// Events related to character progress +LOG_CATEGORY(PLAYER) +LOG_TYPE(PLAYER, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PLAYER, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PLAYER, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PLAYER, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PLAYER, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PLAYER, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + + +/*** SUBSYSTEM Loggers ***************************************************************************/ +// Achievements Logging +LOG_CATEGORY(ACHIEVEMENT) +LOG_TYPE(ACHIEVEMENT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(ACHIEVEMENT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(ACHIEVEMENT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(ACHIEVEMENT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(ACHIEVEMENT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(ACHIEVEMENT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Chat Logging +LOG_CATEGORY(CHAT) +LOG_TYPE(CHAT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(CHAT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(CHAT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(CHAT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(CHAT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(CHAT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Collection generated events +LOG_CATEGORY(COLLECTION) +LOG_TYPE(COLLECTION, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(COLLECTION, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(COLLECTION, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(COLLECTION, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(COLLECTION, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(COLLECTION, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events related to combat,aggro, hate, melee, damages, etc. +LOG_CATEGORY(COMBAT) +LOG_TYPE(COMBAT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(COMBAT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(COMBAT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(COMBAT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(COMBAT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(COMBAT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events related to commands (slash commands, UI commands, etc) +LOG_CATEGORY(COMMAND) +LOG_TYPE(COMMAND, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(COMMAND, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(COMMAND, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(COMMAND, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(COMMAND, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(COMMAND, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Faction-related events, adjustments, querying, etc. +LOG_CATEGORY(FACTION) +LOG_TYPE(FACTION, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(FACTION, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(FACTION, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(FACTION, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(FACTION, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(FACTION, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Guild events, members, logging, permissions, recruiting, etc. +LOG_CATEGORY(GUILD) +LOG_TYPE(GUILD, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(GUILD, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(GUILD, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(GUILD, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(GUILD, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(GUILD, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Group events, members, permissions, etc. +LOG_CATEGORY(GROUP) +LOG_TYPE(GROUP, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(GROUP, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(GROUP, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(GROUP, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(GROUP, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(GROUP, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Item events, stats, appearances, loading/reloading, etc. +LOG_CATEGORY(ITEM) +LOG_TYPE(ITEM, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(ITEM, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(ITEM, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(ITEM, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(ITEM, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(ITEM, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Loot events, loot lists, rules, smart loot +LOG_CATEGORY(LOOT) +LOG_TYPE(LOOT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(LOOT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(LOOT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(LOOT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(LOOT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(LOOT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events that occur within the LUA subsystem +LOG_CATEGORY(LUA) +LOG_TYPE(LUA, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(LUA, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(LUA, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(LUA, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(LUA, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(LUA, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Merchant events, buy/sell/broker, faction merchants, etc. +LOG_CATEGORY(MERCHANT) +LOG_TYPE(MERCHANT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(MERCHANT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(MERCHANT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(MERCHANT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(MERCHANT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(MERCHANT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// NPC events, stats, appearances, movement, gear, abilities, etc. +LOG_CATEGORY(NPC) +LOG_TYPE(NPC, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(NPC, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(NPC, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(NPC, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, COMBAT, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, SPELLS, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, AI, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, DAMAGE, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(NPC, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// What is that NPC thinking?! ...etc. +LOG_CATEGORY(NPC_AI) +LOG_TYPE(NPC_AI, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(NPC_AI, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(NPC_AI, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(NPC_AI, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(NPC_AI, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(NPC_AI, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// PET events, stats, appearances, movement, gear, abilities, etc. +LOG_CATEGORY(PET) +LOG_TYPE(PET, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(PET, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(PET, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(PET, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, COMBAT, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, SPELLS, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, AI, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, DAMAGE, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(PET, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(PET, TRACE, 0, FOREGROUND_GREEN, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Quest generated events +LOG_CATEGORY(QUEST) +LOG_TYPE(QUEST, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(QUEST, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(QUEST, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(QUEST, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(QUEST, REWARD, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(QUEST, STEP, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(QUEST, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(QUEST, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Recipes in the world +LOG_CATEGORY(RECIPE) +LOG_TYPE(RECIPE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(RECIPE, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(RECIPE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(RECIPE, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(RECIPE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(RECIPE, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Rules in the world +LOG_CATEGORY(RULESYS) +LOG_TYPE(RULESYS, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(RULESYS, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(RULESYS, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(RULESYS, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(RULESYS, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(RULESYS, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// The Skill system, books, scribing, stats, usage +LOG_CATEGORY(SKILL) +LOG_TYPE(SKILL, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(SKILL, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(SKILL, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(SKILL, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(SKILL, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(SKILL, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// The Spell system, books, scribing, stats, usage +LOG_CATEGORY(SPELL) +LOG_TYPE(SPELL, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(SPELL, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(SPELL, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(SPELL, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(SPELL, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(SPELL, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// The Crafting system, recipies, reactions, progress, etc. +LOG_CATEGORY(TRADESKILL) +LOG_TYPE(TRADESKILL, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(TRADESKILL, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(TRADESKILL, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(TRADESKILL, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(TRADESKILL, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(TRADESKILL, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// The Transportation system, teleporters, mounts, etc. +LOG_CATEGORY(TRANSPORT) +LOG_TYPE(TRANSPORT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(TRANSPORT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(TRANSPORT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(TRANSPORT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(TRANSPORT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(TRANSPORT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + +/*** SPAWN Loggers *******************************************************************************/ +// General Spawn events, location, placement, grouping +LOG_CATEGORY(SPAWN) +LOG_TYPE(SPAWN, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(SPAWN, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(SPAWN, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(SPAWN, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(SPAWN, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(SPAWN, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to interactable objects in the world +LOG_CATEGORY(OBJECT) +LOG_TYPE(OBJECT, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(OBJECT, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(OBJECT, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(OBJECT, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(OBJECT, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(OBJECT, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Signs in the world +LOG_CATEGORY(SIGN) +LOG_TYPE(SIGN, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(SIGN, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(SIGN, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(SIGN, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(SIGN, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(SIGN, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Widgets in the world +LOG_CATEGORY(WIDGET) +LOG_TYPE(WIDGET, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(WIDGET, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(WIDGET, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(WIDGET, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(WIDGET, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(WIDGET, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Events relating to Groundspawns in the world +LOG_CATEGORY(GROUNDSPAWN) +LOG_TYPE(GROUNDSPAWN, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(GROUNDSPAWN, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(GROUNDSPAWN, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(GROUNDSPAWN, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(GROUNDSPAWN, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(GROUNDSPAWN, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + + +/*** ZONE Loggers ********************************************************************************/ +// Zone-related events, status, messaging, access +LOG_CATEGORY(ZONE) +LOG_TYPE(ZONE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(ZONE, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(ZONE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(ZONE, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(ZONE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(ZONE, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +// Instance loading/reloading, etc. +LOG_CATEGORY(INSTANCE) +LOG_TYPE(INSTANCE, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(INSTANCE, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(INSTANCE, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(INSTANCE, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(INSTANCE, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(INSTANCE, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + +/*** MAP Loggers ********************************************************************************/ +// Map-related events, status, messaging, access +LOG_CATEGORY(MAP) +LOG_TYPE(MAP, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(MAP, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(MAP, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(MAP, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(MAP, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(MAP, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + + +/*** Region Map Loggers ********************************************************************************/ +// RegionMap-related events, status, messaging, access +LOG_CATEGORY(REGION) +LOG_TYPE(REGION, INFO, 0, FOREGROUND_WHITE_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "I") +LOG_TYPE(REGION, WARNING, 0, FOREGROUND_YELLOW_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "W") +LOG_TYPE(REGION, ERROR, 0, FOREGROUND_RED_BOLD, ENABLED, ENABLED, ENABLED, DISABLED, "E") +LOG_TYPE(REGION, DEBUG, 0, FOREGROUND_GREEN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "D") +LOG_TYPE(REGION, PACKET, 0, FOREGROUND_CYAN_BOLD, DISABLED, DISABLED, DISABLED, DISABLED, "P") +LOG_TYPE(REGION, TRACE, 0, FOREGROUND_YELLOW, DISABLED, DISABLED, DISABLED, DISABLED, "T") + +#undef LOG_TYPE +#undef LOG_CATEGORY +#undef ENABLED +#undef DISABLED diff --git a/source/common/MiscFunctions.cpp b/source/common/MiscFunctions.cpp new file mode 100644 index 0000000..f4d5bbf --- /dev/null +++ b/source/common/MiscFunctions.cpp @@ -0,0 +1,980 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +#include "../common/Log.h" +#include "MiscFunctions.h" +#include +#include +#include +#include + +#ifndef WIN32 +#include +#include +#endif +#include +#include +#ifdef WIN32 + #include +#endif +#include "../common/timer.h" +#include "../common/seperator.h" +#include "../common/packet_dump.h" +#include + +using namespace std; + +#ifndef PATCHER +extern map EQOpcodeVersions; +#endif + +#ifdef WIN32 + #include + #include + + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#else + #include + #include + #include + #include + #include +#ifdef FREEBSD //Timothy Whitman - January 7, 2003 + #include + #include + #endif + #include + #include + #include + #include +#endif + +void CoutTimestamp(bool ms) { + time_t rawtime; + struct tm* gmt_t; + time(&rawtime); + gmt_t = gmtime(&rawtime); + + struct timeval read_time; + gettimeofday(&read_time,0); + + cout << (gmt_t->tm_year + 1900) << "/" << setw(2) << setfill('0') << (gmt_t->tm_mon + 1) << "/" << setw(2) << setfill('0') << gmt_t->tm_mday << " " << setw(2) << setfill('0') << gmt_t->tm_hour << ":" << setw(2) << setfill('0') << gmt_t->tm_min << ":" << setw(2) << setfill('0') << gmt_t->tm_sec; + if (ms) + cout << "." << setw(3) << setfill('0') << (read_time.tv_usec / 1000); + cout << " GMT"; +} + +string loadInt32String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_32BitString* eq_string){ + buffer += *pos; + int32 size = *(int32*)buffer; + if((size + *pos + sizeof(int16)) > buffer_size){ + cout << "Error in loadInt32String: Corrupt packet.\n"; + return string(""); + } + buffer += sizeof(int32); + string ret((char*)buffer, 0, size); + if(eq_string){ + eq_string->size = size; + eq_string->data = ret; + } + *pos += (size + sizeof(int32)); + return ret; +} +string loadInt16String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_16BitString* eq_string){ + buffer += *pos; + int16 size = *(int16*)buffer; + if((size + *pos + sizeof(int16))> buffer_size){ + cout << "Error in loadInt16String: Corrupt packet.\n"; + return string(""); + } + buffer += sizeof(int16); + string ret((char*)buffer, 0, size); + if(eq_string){ + eq_string->size = size; + eq_string->data = ret; + } + *pos += (size + sizeof(int16)); + return ret; +} +string loadInt8String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_8BitString* eq_string){ + buffer += *pos; + int8 size = *(int8*)buffer; + if((size + *pos + sizeof(int16)) > buffer_size){ + cout << "Error in loadInt8String: Corrupt packet.\n"; + return string(""); + } + buffer += sizeof(int8); + string ret((char*)buffer, 0, size); + if(eq_string){ + eq_string->size = size; + eq_string->data = ret; + } + *pos += (size + sizeof(int8)); + return ret; +} + +sint16 storeInt32String(uchar* buffer, int16 buffer_size, string in_str){ + sint16 string_size = in_str.length(); + if((string_size + sizeof(int32)) > buffer_size) + return -1; + memcpy(buffer, &string_size, sizeof(int32)); + buffer += sizeof(int32); + memcpy(buffer, in_str.c_str(), string_size); + buffer += string_size; + return (buffer_size - (string_size + sizeof(int32))); +} +sint16 storeInt16String(uchar* buffer, int16 buffer_size, string in_str){ + sint16 string_size = in_str.length(); + if((string_size + sizeof(int16)) > buffer_size) + return -1; + memcpy(buffer, &string_size, sizeof(int16)); + buffer += sizeof(int16); + memcpy(buffer, in_str.c_str(), string_size); + buffer += string_size; + return (buffer_size - (string_size + sizeof(int16))); +} +sint16 storeInt8String(uchar* buffer, int16 buffer_size, string in_str){ + sint16 string_size = in_str.length(); + if((string_size + sizeof(int8)) > buffer_size) + return -1; + memcpy(buffer, &string_size, sizeof(int8)); + buffer += sizeof(int8); + memcpy(buffer, in_str.c_str(), string_size); + buffer += string_size; + return (buffer_size - (string_size + sizeof(int8))); +} + + +sint32 filesize(FILE* fp) { +#ifdef WIN32 + return _filelength(_fileno(fp)); +#else + struct stat file_stat; + fstat(fileno(fp), &file_stat); + return (sint32) file_stat.st_size; +#endif +} + +int32 ResolveIP(const char* hostname, char* errbuf) { +#ifdef WIN32 + static InitWinsock ws; +#endif + if (errbuf) + errbuf[0] = 0; + if (hostname == 0) { + if (errbuf) + snprintf(errbuf, ERRBUF_SIZE, "ResolveIP(): hostname == 0"); + return 0; + } + struct sockaddr_in server_sin; +#ifdef WIN32 + PHOSTENT phostent = NULL; +#else + struct hostent *phostent = NULL; +#endif + server_sin.sin_family = AF_INET; + if ((phostent = gethostbyname(hostname)) == NULL) { +#ifdef WIN32 + if (errbuf) + snprintf(errbuf, ERRBUF_SIZE, "Unable to get the host name. Error: %i", WSAGetLastError()); +#else + if (errbuf) + snprintf(errbuf, ERRBUF_SIZE, "Unable to get the host name. Error: %s", strerror(errno)); +#endif + return 0; + } +#ifdef WIN32 + memcpy ((char FAR *)&(server_sin.sin_addr), phostent->h_addr, phostent->h_length); +#else + memcpy ((char*)&(server_sin.sin_addr), phostent->h_addr, phostent->h_length); +#endif + return server_sin.sin_addr.s_addr; +} + +#ifdef WIN32 +InitWinsock::InitWinsock() { + WORD version = MAKEWORD (1,1); + WSADATA wsadata; + WSAStartup (version, &wsadata); +} + +InitWinsock::~InitWinsock() { + WSACleanup(); +} + +#endif + +#ifndef WIN32 +const char * itoa(int value) { + static char temp[_ITOA_BUFLEN]; + memset(temp, 0, _ITOA_BUFLEN); + snprintf(temp, _ITOA_BUFLEN,"%d", value); + return temp; +} + + +char * itoa(int value, char *result, int base) { + char *ptr1, *ptr2; + char c; + int tmp_value; + + //need a valid base + if (base < 2 || base > 36) { + *result = '\0'; + return result; + } + + ptr1 = ptr2 = result; + do { + tmp_value = value; + value /= base; + + *ptr1++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)]; + } + while (value > 0); + + //apply a negative sign if need be + if (tmp_value < 0) + *ptr1++ = '-'; + + *ptr1-- = '\0'; + while (ptr2 < ptr1) { + c = *ptr1; + *ptr1-- = *ptr2; + *ptr2++ = c; + } + + return result; +} +#endif + +/* + * solar: generate a random integer in the range low-high + * this should be used instead of the rand()%limit method + */ +int MakeRandomInt(int low, int high) +{ + return (int)MakeRandomFloat((double)low, (double)high + 0.999); +} +int32 hextoi(char* num) { + int len = strlen(num); + if (len < 3) + return 0; + + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + return 0; + + int32 ret = 0; + int mul = 1; + for (int i=len-1; i>=2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') + ret += ((num[i] - 'A') + 10) * mul; + else if (num[i] >= 'a' && num[i] <= 'f') + ret += ((num[i] - 'a') + 10) * mul; + else if (num[i] >= '0' && num[i] <= '9') + ret += (num[i] - '0') * mul; + else + return 0; + mul *= 16; + } + return ret; +} + +int64 hextoi64(char* num) { + int len = strlen(num); + if (len < 3) + return 0; + + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + return 0; + + int64 ret = 0; + int mul = 1; + for (int i=len-1; i>=2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') + ret += ((num[i] - 'A') + 10) * mul; + else if (num[i] >= 'a' && num[i] <= 'f') + ret += ((num[i] - 'a') + 10) * mul; + else if (num[i] >= '0' && num[i] <= '9') + ret += (num[i] - '0') * mul; + else + return 0; + mul *= 16; + } + return ret; +} + +float MakeRandomFloat(float low, float high) +{ +#ifdef _WIN32 + thread_local bool seeded = false; +#else + static bool seeded = false; +#endif + + float diff = high - low; + + if(!diff) return low; + if(diff < 0) + diff = 0 - diff; + + if(!seeded) + { + srand(time(0) * (time(0) % (int)diff)); + seeded = true; + } + + return (rand() / (float)RAND_MAX * diff + (low > high ? high : low)); +} + +int32 GenerateEQ2Color(float* r, float* g, float* b){ + int8 rgb[4] = {0}; + rgb[0] = (int8)((*r)*255); + rgb[1] = (int8)((*b)*255); + rgb[2] = (int8)((*g)*255); + int32 color = 0; + memcpy(&color, rgb, sizeof(int32)); + return color; +} +int32 GenerateEQ2Color(float* rgb[3]){ + return GenerateEQ2Color(rgb[0], rgb[1], rgb[2]); +} +int8 MakeInt8(float* input){ + float input2 = *input; + if(input2 < 0) + input2 *= -1; + return (int8)(input2*255); +} + +vector* SplitString(string str, char delim){ + vector* results = new vector; + int32 pos; + while((pos = str.find_first_of(delim))!= str.npos){ + if(pos > 0){ + results->push_back(str.substr(0,pos)); + } + if(str.length() > pos) + str = str.substr(pos+1); + else + break; + } + if(str.length() > 0) + results->push_back(str); + return results; +} + +bool Unpack(uchar* data, uchar* dst, int16 dstLen, int16 version, bool reverse){ + int32 srcLen = 0; + memcpy(&srcLen, data, sizeof(int32)); + return Unpack(srcLen, data + 4, dst, dstLen, version, reverse); +} +bool Unpack(int32 srcLen, uchar* data, uchar* dst, int16 dstLen, int16 version, bool reverse) { +// int32 srcLen = 0; +// memcpy(&srcLen, data, sizeof(int32)); +// data+=4; + if(reverse) + Reverse(data, srcLen); + int16 pos = 0; + int16 real_pos = 0; + while(srcLen && pos < dstLen) { + if(srcLen >= 0 && !srcLen--) + return false; + int8 code = data[real_pos++]; + + if(code >= 128) { + for(int8 index=0; index<7; index++) { + if(code & 1) { + if(pos >= dstLen) + return false; + if(srcLen >= 0 && !srcLen--) + return false; + dst[pos++] = data[real_pos++]; + } else { + if(pos < dstLen) dst[pos++] = 0; + } + code >>= 1; + } + } else { + if(pos + code > dstLen) + return false; + memset(dst+pos, 0, code); + pos+=code; + } + } + return srcLen <= 0; +} + +int32 Pack(uchar* data, uchar* src, int16 srcLen, int16 dstLen, int16 version, bool reverse) { + int16 real_pos = 4; + int32 pos = 0; + int32 code = 0; + int codePos = 0; + int codeLen = 0; + int8 zeroLen = 0; + memset(data,0,dstLen); + if (version > 1 && version <= 374) + reverse = false; + while(pos < srcLen) { + if(src[pos] || codeLen) { + if(!codeLen) { + /*if(zeroLen > 5) { + data[real_pos++] = zeroLen; + zeroLen = 0; + } + else if(zeroLen >= 1 && zeroLen<=5){ + for(;zeroLen>0;zeroLen--) + codeLen++; + }*/ + if (zeroLen) { + data[real_pos++] = zeroLen; + zeroLen = 0; + } + codePos = real_pos; + code = 0; + data[real_pos++] = 0; + } + if(src[pos]) { + data[real_pos++] = src[pos]; + code |= 0x80; + } + code >>= 1; + codeLen++; + + if(codeLen == 7) { + data[codePos] = int8(0x80 | code); + codeLen = 0; + } + } else { + if(zeroLen == 0x7F) { + data[real_pos++] = zeroLen; + zeroLen = 0; + } + zeroLen++; + } + pos++; + } + if(codeLen) { + code >>= (7 - codeLen); + data[codePos] = int8(0x80 | code); + } else if(zeroLen) { + data[real_pos++] = zeroLen; + } + if(reverse) + Reverse(data + 4, real_pos - 4); + int32 dataLen = real_pos - 4; + memcpy(&data[0], &dataLen, sizeof(int32)); + return dataLen + 4; +} +void Reverse(uchar* input, int32 srcLen){ + int16 real_pos = 0; + int16 orig_pos = 0; + int8 reverse_count = 0; + while(srcLen > 0 && srcLen < 0xFFFFFFFF){ // XXX it was >=0 before. but i think it was a bug + int8 code = input[real_pos++]; + srcLen--; + if(code >= 128) { + for(int8 index=0; index<7; index++) { + if(code & 1) { + if(srcLen >= 0 && !srcLen--) + return; + real_pos++; + reverse_count++; + } + code >>= 1; + } + } + if(reverse_count > 0){ + int8 tmp_data[8] = {0}; + for(int8 i=0;i 0){ + ret = atoul(input.c_str()); + } + } + catch(...){} + return ret; +} + +int64 ParseLongLongValue(string input){ + int64 ret = 0xFFFFFFFFFFFFFFFF; + try{ + if(input.length() > 0){ +#ifdef WIN32 + ret = _strtoui64(input.c_str(), NULL, 10); +#else + ret = strtoull(input.c_str(), 0, 10); +#endif + } + } + catch(...){} + return ret; +} + +map TranslateBrokerRequest(string request){ + map ret; + string key; + string value; + int32 start_pos = 0; + int32 end_pos = 0; + int32 pos = request.find("="); + bool str_val = false; + while(pos < 0xFFFFFFFF){ + str_val = false; + key = request.substr(start_pos, pos-start_pos); + if(request.find("|", pos) == pos+1){ + pos++; + end_pos = request.find("|", pos+1); + str_val = true; + } + else + end_pos = request.find(" ", pos); + if(end_pos < 0xFFFFFFFF){ + value = request.substr(pos+1, end_pos-pos-1); + start_pos = end_pos+1; + if(str_val){ + start_pos++; + ret[key] = ToLower(value); + } + else + ret[key] = value; + pos = request.find("=", start_pos); + } + else{ + value = request.substr(pos+1); + if(str_val){ + start_pos++; + ret[key] = ToLower(value); + } + else + ret[key] = value; + break; + } + } + return ret; +} + +int8 CheckOverLoadSize(int32 val){ + int8 ret = 1; + if(val >= 0xFFFF) //int32 + ret = sizeof(int16) + sizeof(int32); + else if(val >= 0xFF) + ret = sizeof(int8) + sizeof(int16); + return ret; +} + +int8 DoOverLoad(int32 val, uchar* data){ + int8 ret = 1; + if(val >= 0xFFFF){ //int32 + memset(data, 0xFF, sizeof(int16)); + memcpy(data + sizeof(int16), &val, sizeof(int32)); + ret = sizeof(int16) + sizeof(int32); + } + else if(val >= 0xFF){ //int16 + memset(data, 0xFF, sizeof(int8)); + memcpy(data + sizeof(int8), &val, sizeof(int16)); + ret = sizeof(int8) + sizeof(int16); + } + else + memcpy(data, &val, sizeof(int8)); + return ret; +} + +/* Treats contiguous spaces as one space. */ +int32 CountWordsInString(const char* text) { + int32 words = 0; + if (text && strlen(text) > 0) { + bool on_word = false; + for (int32 i = 0; i < strlen(text); i++) { + char letter = text[i]; + if (on_word && !((letter >= 48 && letter <= 57) || (letter >= 65 && letter <= 90) || (letter >= 97 && letter <= 122))) + on_word = false; + else if (!on_word && ((letter >= 48 && letter <= 57) || (letter >= 65 && letter <= 90) || (letter >= 97 && letter <= 122))){ + on_word = true; + words++; + } + } + } + return words; +} + +bool IsNumber(const char *num) { + size_t len, i; + + if (!num) + return false; + + len = strlen(num); + if (len == 0) + return false; + + for (i = 0; i < len; i++) { + if (!isdigit(num[i])) + return false; + } + + return true; +} + +void PrintSep(Seperator *sep, const char *name) { + int32 i = 0; + + LogWrite(MISC__DEBUG, 0, "Misc", "Printing sep %s", name ? name : "No Name"); + if (!sep) + LogWrite(MISC__DEBUG, 0, "Misc", "\tSep is null"); + else { + while (sep->arg[i] && strlen(sep->arg[i]) > 0) { + LogWrite(MISC__DEBUG, 0, "Misc", "\t%i => %s", i, sep->arg[i]); + i++; + } + } +} + +#define INI_IGNORE(c) (c == '\n' || c == '\r' || c == '#') + +static bool INIGoToSection(FILE *f, const char *section) { + size_t size = strlen(section) + 3; + char line[256], *buf, *tmp; + bool found = false; + + if ((buf = (char *)malloc(size)) == NULL) { + fprintf(stderr, "%s: %u: Unable to allocate %zu bytes\n", __FUNCTION__, __LINE__, size); + return false; + } + + sprintf(buf, "[%s]", section); + + while (fgets(line, sizeof(line), f) != NULL) { + if (INI_IGNORE(line[0])) + continue; + + if (line[0] == '[') { + if ((tmp = strstr(line, "\n")) != NULL) + *tmp = '\0'; + if ((tmp = strstr(line, "\r")) != NULL) + *tmp = '\0'; + + if (strcasecmp(buf, line) == 0) { + found = true; + break; + } + } + } + + free(buf); + return found; +} + +static char * INIFindValue(FILE *f, const char *section, const char *property) { + char line[256], *key, *val; + + if (section != NULL && !INIGoToSection(f, section)) + return NULL; + + while (fgets(line, sizeof(line), f) != NULL) { + if (INI_IGNORE(line[0])) + continue; + + if (section != NULL && line[0] == '[') + return NULL; + + if ((key = strtok(line, "=")) == NULL) + continue; + + if (strcasecmp(key, property) == 0) { + val = strtok(NULL, "\n\r"); + + if (val == NULL) + return NULL; + + return strdup(val); + } + } + + return NULL; +} + +bool INIReadInt(FILE *f, const char *section, const char *property, int *out) { + char *value; + + rewind(f); + + if ((value = INIFindValue(f, section, property)) == NULL) + return false; + + if (!IsNumber(value)) { + free(value); + return false; + } + + *out = atoi(value); + free(value); + + return true; +} + +bool INIReadBool(FILE *f, const char *section, const char *property, bool *out) { + char *value; + + rewind(f); + + if ((value = INIFindValue(f, section, property)) == NULL) + return false; + + *out = (strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0 || strcasecmp(value, "on") == 0 || strcasecmp(value, "yes") == 0); + free(value); + + return true; + +} +string GetDeviceName(string device) { + if (device == "chemistry_table") + device = "Chemistry Table"; + else if (device == "work_desk") + device = "Engraved Desk"; + else if (device == "forge") + device = "Forge"; + else if (device == "stove and keg") + device = "Stove & Keg"; + else if (device == "sewing_table") + device = "Sewing Table & Mannequin"; + else if (device == "woodworking_table") + device = "Woodworking Table"; + else if (device == "work_bench") + device = "Work Bench"; + else if (device == "crafting_intro_anvil") + device = "Mender's Anvil"; + return device; +} + +int32 GetDeviceID(string device) { + if (device == "Chemistry Table") + return 3; + else if (device == "Engraved Desk") + return 4; + else if (device == "Forge") + return 2; + else if (device == "Stove & Keg") + return 7; + else if (device == "Sewing Table & Mannequin") + return 1; + else if (device == "Woodworking Table") + return 6; + else if (device == "Work Bench") + return 5; + else if (device == "Mender's Anvil") + return 0xFFFFFFFF; + return 0; +} +int16 GetItemPacketType(int32 version) { + int16 item_version; + if (version >= 64707) + item_version = 0x5CFE; + else if (version >= 63119) + item_version = 0x56FE; + else if (version >= 60024) + item_version = 0x51FE; + else if (version >= 57107) + item_version = 0x4CFE; + else if (version >= 57048) + item_version = 0x48FE; + else if (version >= 1199) + item_version = 0x44FE; + else if (version >= 1195) + item_version = 0x40FE; + else if (version >= 1193) + item_version = 0x3FFE; + else if (version >= 1190) + item_version = 0x3EFE; + else if (version >= 1188) + item_version = 0x3DFE; + else if (version >= 1096) + item_version = 0x35FE; + else if (version >= 1027) + item_version = 0x31FE; + else if (version >= 1008) + item_version = 0x2CFE; + else if (version >= 927) + item_version = 0x23FE; + else if (version >= 893) + item_version = 0x22FE; + else if (version >= 860) + item_version = 0x20FE; + else if (version > 546) + item_version = 0x1CFE; + else + item_version = 0; + + return item_version; +} + +#ifndef PATCHER +int16 GetOpcodeVersion(int16 version) { + int16 ret = version; + int16 version1 = 0; + int16 version2 = 0; + map::iterator itr; + for (itr = EQOpcodeVersions.begin(); itr != EQOpcodeVersions.end(); itr++) { + version1 = itr->first; + version2 = itr->second; + if (version >= version1 && version <= version2) { + ret = version1; + break; + } + } + + return ret; +} +#endif + +void SleepMS(int32 milliseconds) { +#if defined(_WIN32) + Sleep(milliseconds); +#else + usleep(milliseconds * 1000); +#endif +} + +size_t +strlcpy(char *dst, const char *src, size_t size) { + char *d = dst; + const char *s = src; + size_t n = size; + + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + if (n == 0) { + if (size != 0) + *d = '\0'; + while (*s++) + ; + } + + return(s - src - 1); +} + +float short_to_float(const ushort x) { // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits + const uint32 e = (x & 0x7C00) >> 10; // exponent + const uint32 m = (x & 0x03FF) << 13; // mantissa + const uint32 v = as_uint((float)m) >> 23; // evil log2 bit hack to count leading zeros in denormalized format + return as_float((x & 0x8000) << 16 | (e != 0) * ((e + 112) << 23 | m) | ((e == 0) & (m != 0)) * ((v - 37) << 23 | ((m << (150 - v)) & 0x007FE000))); // sign : normalized : denormalized +} + +uint32 float_to_int(const float x) { // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits + const uint32 b = as_uint(x) + 0x00001000; // round-to-nearest-even: add last bit after truncated mantissa + const uint32 e = (b & 0x7F800000) >> 23; // exponent + const uint32 m = b & 0x007FFFFF; // mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding + return (b & 0x80000000) >> 16 | (e > 112)* ((((e - 112) << 10) & 0x7C00) | m >> 13) | ((e < 113) & (e > 101))* ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) | (e > 143) * 0x7FFF; // sign : normalized : denormalized : saturate +} + +uint32 as_uint(const float x) { + return *(uint32*)&x; +} + +float as_float(const uint32 x) { + return *(float*)&x; +} + +// Function to get the current timestamp in milliseconds +int64 getCurrentTimestamp() { + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(now.time_since_epoch()); + return duration.count(); +} + +std::tuple convertTimestampDuration(int64 total_seconds) { + std::chrono::milliseconds duration(total_seconds); + // Convert to days, hours, minutes, and seconds + auto days = std::chrono::duration_cast>>(duration); + duration -= days; + + auto hours = std::chrono::duration_cast(duration); + duration -= hours; + + auto minutes = std::chrono::duration_cast(duration); + duration -= minutes; + + auto seconds = std::chrono::duration_cast(duration); + + // Return the result as a tuple + return std::make_tuple(days.count(), hours.count(), minutes.count(), seconds.count()); +} \ No newline at end of file diff --git a/source/common/MiscFunctions.h b/source/common/MiscFunctions.h new file mode 100644 index 0000000..8a5db4f --- /dev/null +++ b/source/common/MiscFunctions.h @@ -0,0 +1,187 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef MISCFUNCTIONS_H +#define MISCFUNCTIONS_H + +#include "types.h" +#include "seperator.h" +#include +#include +#include +#include +#include + +#ifndef ERRBUF_SIZE +#define ERRBUF_SIZE 1024 +#endif + +//int MakeAnyLenString(char** ret, const char* format, ...); +int32 hextoi(char* num); +int64 hextoi64(char* num); +sint32 filesize(FILE* fp); +int32 ResolveIP(const char* hostname, char* errbuf = 0); +void CoutTimestamp(bool ms = true); +//char* strn0cpy(char* dest, const char* source, int32 size); + // return value =true if entire string(source) fit, false if it was truncated +//bool strn0cpyt(char* dest, const char* source, int32 size); +string loadInt32String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_32BitString* eq_string = NULL); +string loadInt16String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_16BitString* eq_string = NULL); +string loadInt8String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_8BitString* eq_string = NULL); +sint16 storeInt32String(uchar* buffer, int16 buffer_size, string in_str); +sint16 storeInt16String(uchar* buffer, int16 buffer_size, string in_str); +sint16 storeInt8String(uchar* buffer, int16 buffer_size, string in_str); +int MakeRandomInt(int low, int high); +float MakeRandomFloat(float low, float high); + +float TransformToFloat(sint16 data, int8 bits); +sint16 TransformFromFloat(float data, int8 bits); + +int32 GenerateEQ2Color(float r, float g, float b); +int32 GenerateEQ2Color(float* rgb[3]); +void SetColor(EQ2_Color* color, long data); +//void CreateEQ2Color(EQ2_Color* color, uchar* data, int16* size); +int8 MakeInt8(uchar* data, int16* size); +int8 MakeInt8(float* input); +bool Unpack(int32 srcLen, uchar* data, uchar* dst, int16 dstLen, int16 version = 0, bool reverse = true); +bool Unpack(uchar* data, uchar* dst, int16 dstLen, int16 version = 0, bool reverse = true); +int32 Pack(uchar* data, uchar* src, int16 srcLen, int16 dstLen, int16 version = 0, bool reverse = true); +void Reverse(uchar* input, int32 srcLen); +void Encode(uchar* dst, uchar* src, int16 len); +void Decode(uchar* dst, uchar* src, int16 len); +string ToUpper(string input); +string ToLower(string input); +int32 ParseIntValue(string input); +int64 ParseLongLongValue(string input); +map TranslateBrokerRequest(string request); +void MovementDecode(uchar* dst, uchar* newval, uchar* orig, int16 len); +vector* SplitString(string str, char delim); +int8 DoOverLoad(int32 val, uchar* data); +int8 CheckOverLoadSize(int32 val); +int32 CountWordsInString(const char* text); +bool IsNumber(const char *num); +void PrintSep(Seperator *sep, const char *name = 0); +string GetDeviceName(string device); +int32 GetDeviceID(string device); +///Gets the packet type for the given version +///The client version +int16 GetItemPacketType(int32 version); +///Gets the opcode version_range1 from the clients version +///The client version +int16 GetOpcodeVersion(int16 version); +void SleepMS(int32 milliseconds); +size_t strlcpy(char *dst, const char *src, size_t size); + +float short_to_float(const ushort x); +uint32 float_to_int(const float x); +uint32 as_uint(const float x); +float as_float(const uint32 x); + +int64 getCurrentTimestamp(); +std::tuple convertTimestampDuration(int64 total_seconds); + +bool INIReadBool(FILE *f, const char *section, const char *property, bool *out); +bool INIReadInt(FILE *f, const char *section, const char *property, int *out); + +static bool IsPrivateAddress(uint32_t ip) +{ + uint8_t b1, b2;//, b3, b4; + b1 = (uint8_t)(ip >> 24); + b2 = (uint8_t)((ip >> 16) & 0x0ff); + //b3 = (uint8_t)((ip >> 8) & 0x0ff); + //b4 = (uint8_t)(ip & 0x0ff); + + // 10.x.y.z + if (b1 == 10) + return true; + + // 172.16.0.0 - 172.31.255.255 + if ((b1 == 172) && (b2 >= 16) && (b2 <= 31)) + return true; + + // 192.168.0.0 - 192.168.255.255 + if ((b1 == 192) && (b2 == 168)) + return true; + + return false; +} + +template void AddData(Type input, string* datastring){ + if(datastring) + datastring->append((char*)&input, sizeof(input)); +} + +template void AddData(Type input, int32 array_size, string* datastring){ + if(array_size>0){ + for(int32 i=0;i class AutoDelete { +public: + AutoDelete(T** iVar, T* iSetTo = 0) { + init(iVar, iSetTo); + } + AutoDelete() {} + void init(T** iVar, T* iSetTo = 0) + { + pVar = iVar; + if (iSetTo) + *pVar = iSetTo; + } + ~AutoDelete() { + safe_delete(*pVar); + } +private: + T** pVar; +}; + +class VersionRange { +public: + VersionRange(int32 in_min_version, int32 in_max_version) + { + min_version = in_min_version; + max_version = in_max_version; + } + int32 GetMinVersion() { return min_version; } + int32 GetMaxVersion() { return max_version; } +private: + int32 min_version; + int32 max_version; +}; +#endif + + diff --git a/source/common/Mutex.cpp b/source/common/Mutex.cpp new file mode 100644 index 0000000..732e451 --- /dev/null +++ b/source/common/Mutex.cpp @@ -0,0 +1,361 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/Log.h" +#include "../common/debug.h" +#include "../common/Mutex.h" + +Mutex::Mutex() { + readers = 0; + mlocked = false; + writing = false; + name = ""; +#ifdef DEBUG + stack.clear(); +#endif + //CSLock is a pointer so we can use a different attribute type on create + CSLock = new CriticalSection(MUTEX_ATTRIBUTE_RECURSIVE); +} + +Mutex::~Mutex() { + safe_delete(CSLock); +#ifdef DEBUG + stack.clear(); +#endif +} + +void Mutex::SetName(string in_name) { +#ifdef DEBUG + name = in_name; +#endif +} + +void Mutex::lock() { +#ifdef DEBUG + int i = 0; +#endif + if (name.length() > 0) { + while (mlocked) { +#ifdef DEBUG + if (i > MUTEX_TIMEOUT_MILLISECONDS) { + LogWrite(MUTEX__ERROR, 0, "Mutex", "Possible deadlock attempt by '%s'!", name.c_str()); + return; + } + i++; +#endif + Sleep(1); + } + } + mlocked = true; + CSLock->lock(); +} + +bool Mutex::trylock() { + return CSLock->trylock(); +} + +void Mutex::unlock() { + CSLock->unlock(); + mlocked = false; +} + +void Mutex::readlock(const char* function, int32 line) { +#ifdef DEBUG + int32 i = 0; +#endif + while (true) { + //Loop until there isn't a writer, then we can read! + CSRead.lock(); + if (!writing) { + readers++; + CSRead.unlock(); +#ifdef DEBUG + CSStack.lock(); + if (function) + stack[(string)function]++; + CSStack.unlock(); +#endif + return; + } + CSRead.unlock(); +#ifdef DEBUG + if (i > MUTEX_TIMEOUT_MILLISECONDS) { + LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out waiting for a readlock!", name.c_str(), function ? function : "name_not_provided", line); + LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:"); + map::iterator itr; + CSStack.lock(); + for (itr = stack.begin(); itr != stack.end(); itr++) { + if (itr->second > 0 && itr->first.length() > 0) + LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second); + } + CSStack.unlock(); + i = 0; + continue; + } + i++; +#endif + Sleep(1); + } +} + +void Mutex::releasereadlock(const char* function, int32 line) { + //Wait for the readcount lock + CSRead.lock(); + //Lower the readcount by one, when readcount is 0 writers may start writing + readers--; + CSRead.unlock(); +#ifdef DEBUG + CSStack.lock(); + if (function) { + map::iterator itr = stack.find((string)function); + if (itr != stack.end()) { + if (--(itr->second) == 0) { + stack.erase(itr); + } + } + } + CSStack.unlock(); +#endif +} + +bool Mutex::tryreadlock(const char* function) { + //This returns true if able to instantly obtain a readlock, false if not + CSRead.lock(); + if (!writing) { + readers++; + CSRead.unlock(); + } + else { + CSRead.unlock(); + return false; + } + +#ifdef DEBUG + CSStack.lock(); + if (function) + stack[(string)function]++; + CSStack.unlock(); +#endif + + return true; +} + +void Mutex::writelock(const char* function, int32 line) { + //Wait until the writer lock becomes available, then we can be the only writer! +#ifdef DEBUG + int32 i = 0; +#endif + while (!CSWrite.trylock()) { +#ifdef DEBUG + if (i > MUTEX_TIMEOUT_MILLISECONDS) { + LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out waiting on another writelock!", name.c_str(), function ? function : "name_not_provided", line); + LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:"); + map::iterator itr; + CSStack.lock(); + for (itr = stack.begin(); itr != stack.end(); itr++) { + if (itr->second > 0 && itr->first.length() > 0) + LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second); + } + CSStack.unlock(); + i = 0; + continue; + } + i++; +#endif + Sleep(1); + } + waitReaders(function, line); +#ifdef DEBUG + CSStack.lock(); + if (function) + stack[(string)function]++; + CSStack.unlock(); +#endif +} + +void Mutex::releasewritelock(const char* function, int32 line) { + //Wait for the readcount lock + CSRead.lock(); + //Readers are aloud again + writing = false; + CSRead.unlock(); + //Allow other writers to write + CSWrite.unlock(); +#ifdef DEBUG + CSStack.lock(); + if (function) { + map::iterator itr = stack.find((string)function); + if (itr != stack.end()) { + if (--(itr->second) == 0) { + stack.erase(itr); + } + } + } + CSStack.unlock(); +#endif +} + +bool Mutex::trywritelock(const char* function) { + //This returns true if able to instantly obtain a writelock, false if not + if (CSWrite.trylock()) { + CSRead.lock(); + if (readers == 0) + writing = true; + CSRead.unlock(); + if (!writing) { + CSWrite.unlock(); + return false; + } + } + else + return false; + +#ifdef DEBUG + CSStack.lock(); + if (function) + stack[(string)function]++; + CSStack.unlock(); +#endif + + return true; +} + +void Mutex::waitReaders(const char* function, int32 line) +{ + //Wait for all current readers to stop, then we can write! +#ifdef DEBUG + int32 i = 0; +#endif + while (true) + { + CSRead.lock(); + if (readers == 0) + { + writing = true; + CSRead.unlock(); + break; + } + CSRead.unlock(); +#ifdef DEBUG + if (i > MUTEX_TIMEOUT_MILLISECONDS) { + LogWrite(MUTEX__ERROR, 0, "Mutex", "The mutex %s called from %s at line %u timed out while waiting on readers!", name.c_str(), function ? function : "name_not_provided", line); + LogWrite(MUTEX__ERROR, 0, "Mutex", "The following functions had locks:"); + map::iterator itr; + CSStack.lock(); + for (itr = stack.begin(); itr != stack.end(); itr++) { + if (itr->second > 0 && itr->first.length() > 0) + LogWrite(MUTEX__ERROR, 0, "Mutex", "%s, number of locks = %u", itr->first.c_str(), itr->second); + } + CSStack.unlock(); + i = 0; + continue; + } + i++; +#endif + Sleep(1); + } +} + +LockMutex::LockMutex(Mutex* in_mut, bool iLock) { + mut = in_mut; + locked = iLock; + if (locked) { + mut->lock(); + } +} + +LockMutex::~LockMutex() { + if (locked) { + mut->unlock(); + } +} + +void LockMutex::unlock() { + if (locked) + mut->unlock(); + locked = false; +} + +void LockMutex::lock() { + if (!locked) + mut->lock(); + locked = true; +} + +CriticalSection::CriticalSection(int attribute) { +#ifdef WIN32 + InitializeCriticalSection(&CSMutex); +#else + pthread_mutexattr_init(&type_attribute); + switch (attribute) + { + case MUTEX_ATTRIBUTE_FAST: + pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_FAST_NP); + break; + case MUTEX_ATTRIBUTE_RECURSIVE: + pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_RECURSIVE_NP); + break; + case MUTEX_ATTRIBUTE_ERRORCHK: + pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_ERRORCHECK_NP); + break; + default: + LogWrite(MUTEX__DEBUG, 0, "Critical Section", "Invalid mutex attribute type! Using PTHREAD_MUTEX_FAST_NP"); + pthread_mutexattr_settype(&type_attribute, PTHREAD_MUTEX_FAST_NP); + break; + } + pthread_mutex_init(&CSMutex, &type_attribute); +#endif +} + +CriticalSection::~CriticalSection() { +#ifdef WIN32 + DeleteCriticalSection(&CSMutex); +#else + pthread_mutex_destroy(&CSMutex); + pthread_mutexattr_destroy(&type_attribute); +#endif +} + +void CriticalSection::lock() { + //Waits for a lock on this critical section +#ifdef WIN32 + EnterCriticalSection(&CSMutex); +#else + pthread_mutex_lock(&CSMutex); +#endif +} + +void CriticalSection::unlock() { + //Gets rid of one of the current thread's locks on this critical section +#ifdef WIN32 + LeaveCriticalSection(&CSMutex); +#else + pthread_mutex_unlock(&CSMutex); +#endif +} + +bool CriticalSection::trylock() { + //Returns true if able to instantly get a lock on this critical section, false if not +#ifdef WIN32 + return TryEnterCriticalSection(&CSMutex); +#else + return (pthread_mutex_trylock(&CSMutex) == 0); +#endif +} + diff --git a/source/common/Mutex.h b/source/common/Mutex.h new file mode 100644 index 0000000..ae96333 --- /dev/null +++ b/source/common/Mutex.h @@ -0,0 +1,103 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef MYMUTEX_H +#define MYMUTEX_H +#ifdef WIN32 + #include + #include +#else + #include + #include "../common/unix.h" +#endif +#include "../common/types.h" +#include +#include + +#define MUTEX_ATTRIBUTE_FAST 1 +#define MUTEX_ATTRIBUTE_RECURSIVE 2 +#define MUTEX_ATTRIBUTE_ERRORCHK 3 +#define MUTEX_TIMEOUT_MILLISECONDS 10000 + +class CriticalSection { +public: + CriticalSection(int attribute = MUTEX_ATTRIBUTE_FAST); + ~CriticalSection(); + void lock(); + void unlock(); + bool trylock(); +private: +#ifdef WIN32 + CRITICAL_SECTION CSMutex; +#else + pthread_mutex_t CSMutex; + pthread_mutexattr_t type_attribute; +#endif +}; + +class Mutex { +public: + Mutex(); + ~Mutex(); + + void lock(); + void unlock(); + bool trylock(); + + void readlock(const char* function = 0, int32 line = 0); + void releasereadlock(const char* function = 0, int32 line = 0); + bool tryreadlock(const char* function = 0); + + void writelock(const char* function = 0, int32 line = 0); + void releasewritelock(const char* function = 0, int32 line = 0); + bool trywritelock(const char* function = 0); + + void waitReaders(const char* function = 0, int32 line = 0); + + void SetName(string in_name); +private: + CriticalSection CSRead; + CriticalSection CSWrite; + CriticalSection* CSLock; + +#ifdef DEBUG //Used for debugging only + CriticalSection CSStack; + map stack; +#endif + + int readers; + bool writing; + volatile bool mlocked; + string name; +}; + + +class LockMutex { +public: + LockMutex(Mutex* in_mut, bool iLock = true); + ~LockMutex(); + void unlock(); + void lock(); +private: + bool locked; + Mutex* mut; +}; + + +#endif diff --git a/source/common/PacketStruct.cpp b/source/common/PacketStruct.cpp new file mode 100644 index 0000000..4de8120 --- /dev/null +++ b/source/common/PacketStruct.cpp @@ -0,0 +1,2738 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include +#include +#include "PacketStruct.h" +#include "ConfigReader.h" +#include "../common/debug.h" +#include "MiscFunctions.h" +#include "Log.h" + +#ifdef WORLD +#include "../WorldServer/Items/Items.h" +#include "../WorldServer/Player.h" +#include "../WorldServer/World.h" +#endif + +extern ConfigReader configReader; +using namespace std; + +DataStruct::DataStruct() { + item_size = 0; + type = 0; + type2 = 0; + length = 1; + if_flag_set = false; + if_flag_not_set = false; + if_set = false; + if_not_set = false; + if_not_equals = false; + if_equals = false; + is_set = false; + optional = false; + oversized = 0; + oversized_byte = 0; + add = false; + addType = 0; + maxArraySize = 0; + default_value = 0; +} +DataStruct::DataStruct(DataStruct* data_struct) { + type = data_struct->GetType(); + type2 = data_struct->GetType2(); + length = data_struct->GetLength(); + name = data_struct->GetName(); + array_size_variable = data_struct->array_size_variable; + default_value = data_struct->default_value; + add = true; + addType = type; + oversized = data_struct->GetOversized(); + oversized_byte = data_struct->GetOversizedByte(); + maxArraySize = data_struct->GetMaxArraySize(); + if_flag_set = false; + if_flag_not_set = false; + if_set = false; + if_not_set = false; + if_not_equals = false; + if_equals = false; + optional = false; + if (data_struct->GetIfSet()) + SetIfSetVariable(data_struct->GetIfSetVariable()); + if (data_struct->GetIfNotSet()) + SetIfNotSetVariable(data_struct->GetIfNotSetVariable()); + if (data_struct->GetIfNotEquals()) + SetIfNotEqualsVariable(data_struct->GetIfNotEqualsVariable()); + if (data_struct->GetIfEquals()) + SetIfEqualsVariable(data_struct->GetIfEqualsVariable()); + if (data_struct->GetIfFlagSet()) + SetIfFlagSetVariable(data_struct->GetIfFlagSetVariable()); + if (data_struct->GetIfFlagNotSet()) + SetIfFlagNotSetVariable(data_struct->GetIfFlagNotSetVariable()); + item_size = 0; + is_set = false; +} +DataStruct::DataStruct(const char* new_name, const char* new_type, int32 new_length, const char* new_type2) { + name = string(new_name); + type = 0; + type2 = 0; + SetType(new_type, &type); + if (new_type2) + SetType(new_type2, &type2); + length = new_length; + add = true; + addType = type; + if_set = false; + if_not_set = false; + is_set = false; + if_not_equals = false; + item_size = 0; +} +const char* DataStruct::GetArraySizeVariable() { + return array_size_variable.c_str(); +} +void DataStruct::SetArraySizeVariable(const char* new_name) { + array_size_variable = string(new_name); +} +void DataStruct::SetType(const char* new_type, int8* output_type) { + if (strlen(new_type) > 3 && strncasecmp("int", new_type, 3) == 0) { + if (strncasecmp("int8", new_type, 4) == 0) + *output_type = DATA_STRUCT_INT8; + else if (strncasecmp("int16", new_type, 5) == 0) + *output_type = DATA_STRUCT_INT16; + else if (strncasecmp("int32", new_type, 5) == 0) + *output_type = DATA_STRUCT_INT32; + else if (strncasecmp("int64", new_type, 5) == 0) + *output_type = DATA_STRUCT_INT64; + } + else if (strlen(new_type) > 4 && strncasecmp("sint", new_type, 4) == 0) { + if (strncasecmp("sint8", new_type, 5) == 0) + *output_type = DATA_STRUCT_SINT8; + else if (strncasecmp("sint16", new_type, 6) == 0) + *output_type = DATA_STRUCT_SINT16; + else if (strncasecmp("sint32", new_type, 6) == 0) + *output_type = DATA_STRUCT_SINT32; + else if (strncasecmp("sint64", new_type, 6) == 0) + *output_type = DATA_STRUCT_SINT64; + } + else if (strlen(new_type) == 4 && strncasecmp("char", new_type, 4) == 0) + *output_type = DATA_STRUCT_CHAR; + else if (strlen(new_type) == 5 && strncasecmp("float", new_type, 5) == 0) + *output_type = DATA_STRUCT_FLOAT; + else if (strlen(new_type) == 6 && strncasecmp("double", new_type, 6) == 0) + *output_type = DATA_STRUCT_DOUBLE; + else if (strlen(new_type) >= 5 && strncasecmp("EQ2_", new_type, 4) == 0) { + if (strncasecmp("EQ2_8", new_type, 5) == 0) + *output_type = DATA_STRUCT_EQ2_8BIT_STRING; + else if (strncasecmp("EQ2_16", new_type, 6) == 0) + *output_type = DATA_STRUCT_EQ2_16BIT_STRING; + else if (strncasecmp("EQ2_32", new_type, 6) == 0) + *output_type = DATA_STRUCT_EQ2_32BIT_STRING; + else if (strncasecmp("EQ2_E", new_type, 5) == 0) + *output_type = DATA_STRUCT_EQUIPMENT; + else if (strncasecmp("EQ2_C", new_type, 5) == 0) + *output_type = DATA_STRUCT_COLOR; + else if (strncasecmp("EQ2_I", new_type, 5) == 0) + *output_type = DATA_STRUCT_ITEM; + } + else if (strlen(new_type) >= 5) { + if (strncasecmp("Array", new_type, 5) == 0) + *output_type = DATA_STRUCT_ARRAY; + } + else + LogWrite(PACKET__ERROR, 0, "Packet", "Invalid Type: %s", new_type); +} +DataStruct::DataStruct(const char* new_name, int32 new_length) { + name = string(new_name); + length = new_length; + if_set = false; + if_not_set = false; + is_set = false; + item_size = 0; +} +DataStruct::DataStruct(const char* new_name, int8 new_type, int32 new_length, int8 new_type2) { + name = string(new_name); + type = new_type; + length = new_length; + type2 = new_type2; + addType = type; + if_set = false; + if_not_set = false; + is_set = false; + item_size = 0; +} +void DataStruct::SetType(int8 new_type) { + type = new_type; + addType = type; +} +void DataStruct::SetMaxArraySize(int8 size) { + maxArraySize = size; +} +void DataStruct::SetOversized(int8 val) { + oversized = val; +} +void DataStruct::SetDefaultValue(int8 new_val) { + default_value = new_val; +} +void DataStruct::SetName(const char* new_name) { + name = string(new_name); +} +void DataStruct::SetLength(int32 new_length) { + length = new_length; +} +void DataStruct::SetOversizedByte(int8 val) { + oversized_byte = val; +} +void DataStruct::SetItemSize(int32 val) { + item_size = val; + if(item_size) + is_set = true; + else + is_set = false; +} +void DataStruct::SetIfEqualsVariable(const char* variable) { + if (variable) { + if_equals = true; + if_equals_variable = string(variable); + } + else + if_equals = false; +} +void DataStruct::SetIfNotEqualsVariable(const char* variable) { + if (variable) { + if_not_equals = true; + if_not_equals_variable = string(variable); + } + else + if_not_equals = false; +} +void DataStruct::SetIfFlagNotSetVariable(const char* variable) { + if (variable) { + if_flag_not_set = true; + if_flag_not_set_variable = string(variable); + } + else + if_flag_not_set = false; +} +void DataStruct::SetIfFlagSetVariable(const char* variable) { + if (variable) { + if_flag_set = true; + if_flag_set_variable = string(variable); + } + else + if_flag_set = false; +} +void DataStruct::SetIfSetVariable(const char* variable) { + if (variable) { + if_set = true; + if_set_variable = string(variable); + } + else + if_set = false; +} +void DataStruct::SetIfNotSetVariable(const char* variable) { + if (variable) { + if_not_set = true; + if_not_set_variable = string(variable); + } + else + if_not_set = false; +} +void DataStruct::SetIsSet(bool val) { + is_set = val; +} +bool DataStruct::IsSet() { + return is_set; +} +void DataStruct::SetIsOptional(bool val) { + optional = val; +} +bool DataStruct::IsOptional() { + return optional; +} +int32 DataStruct::GetItemSize() { + return item_size; +} +bool DataStruct::GetIfSet() { + return if_set; +} +const char* DataStruct::GetIfSetVariable() { + if (if_set_variable.length() > 0) + return if_set_variable.c_str(); + return 0; +} +bool DataStruct::GetIfNotSet() { + return if_not_set; +} +const char* DataStruct::GetIfNotSetVariable() { + if (if_not_set_variable.length() > 0) + return if_not_set_variable.c_str(); + return 0; +} +bool DataStruct::GetIfEquals() { + return if_equals; +} +const char* DataStruct::GetIfEqualsVariable() { + if (if_equals_variable.length() > 0) + return if_equals_variable.c_str(); + return 0; +} +bool DataStruct::GetIfNotEquals() { + return if_not_equals; +} +const char* DataStruct::GetIfNotEqualsVariable() { + if (if_not_equals_variable.length() > 0) + return if_not_equals_variable.c_str(); + return 0; +} +bool DataStruct::GetIfFlagSet() { + return if_flag_set; +} +const char* DataStruct::GetIfFlagSetVariable() { + if (if_flag_set_variable.length() > 0) + return if_flag_set_variable.c_str(); + return 0; +} +bool DataStruct::GetIfFlagNotSet() { + return if_flag_not_set; +} +const char* DataStruct::GetIfFlagNotSetVariable() { + if (if_flag_not_set_variable.length() > 0) + return if_flag_not_set_variable.c_str(); + return 0; +} +int8 DataStruct::GetDefaultValue() { + return default_value; +} +int8 DataStruct::GetType() { + return type; +} +int8 DataStruct::GetType2() { + return type2; +} +const char* DataStruct::GetName() { + return name.c_str(); +} +int8 DataStruct::GetOversized() { + return oversized; +} +int8 DataStruct::GetOversizedByte() { + return oversized_byte; +} +int8 DataStruct::GetMaxArraySize() { + return maxArraySize; +} +int32 DataStruct::GetLength() { + return length; +} +string DataStruct::GetStringName() { + return name; +} +bool DataStruct::AddToStruct() { + return add; +} +void DataStruct::SetAddToStruct(bool val) { + add = val; +} +int8 DataStruct::GetAddType() { + return addType; +} +void DataStruct::SetAddType(int8 new_type) { + addType = new_type; +} +string DataStruct::AppendVariable(string orig, const char* val) { + if (!val) + return orig; + if(orig.length() == 0) + return string(val); + if (orig.find(",") < 0xFFFFFFFF) { //has more than one already + string valstr = string(val); + vector* varnames = SplitString(orig, ','); + if (varnames) { + for (int32 i = 0; i < varnames->size(); i++) { + if (valstr.compare(varnames->at(i)) == 0) { + return orig; //already in the variable, no need to append + } + } + safe_delete(varnames); + } + } + return orig.append(",").append(val); +} +int32 DataStruct::GetDataSizeInBytes() { + int32 ret = 0; + switch (type) { + case DATA_STRUCT_INT8: + ret = length * sizeof(int8); + break; + case DATA_STRUCT_INT16: + ret = length * sizeof(int16); + break; + case DATA_STRUCT_INT32: + ret = length * sizeof(int32); + break; + case DATA_STRUCT_INT64: + ret = length * sizeof(int64); + break; + case DATA_STRUCT_SINT8: + ret = length * sizeof(sint8); + break; + case DATA_STRUCT_SINT16: + ret = length * sizeof(sint16); + break; + case DATA_STRUCT_SINT32: + ret = length * sizeof(sint32); + break; + case DATA_STRUCT_SINT64: + ret = length * sizeof(sint64); + break; + case DATA_STRUCT_FLOAT: + ret = length * sizeof(float); + break; + case DATA_STRUCT_DOUBLE: + ret = length * sizeof(double); + break; + case DATA_STRUCT_ARRAY: + // Array elements won't have a size so get out now to avoid the warning. + break; + default: + LogWrite(PACKET__WARNING, 0, "DataStruct", "Tried retrieving a data size from an unsupported data struct type in GetDataSizeInBytes()"); + break; + } + return ret; +} + +PacketStruct::PacketStruct(PacketStruct* packet, int16 in_client_version) { + parent = packet->parent; + client_version = in_client_version; + vector::iterator itr2; + name = packet->name; + + for (itr2 = packet->structs.begin(); itr2 != packet->structs.end(); itr2++) { + add(new DataStruct(*itr2)); + } + vector::iterator itr; + for (itr = packet->flags.begin(); itr != packet->flags.end(); itr++) { + AddFlag((*itr).c_str()); + } + sub_packet = false; + opcode = packet->opcode; + version = packet->version; + opcode_type = packet->opcode_type; + sub_packet_size = 1; + + + addPacketArrays(packet); +} + +PacketStruct::PacketStruct() { + parent = 0; + opcode = OP_Unknown; + opcode_type = string(""); +} + +PacketStruct::PacketStruct(PacketStruct* packet, bool sub) { + vector::iterator itr2; + + for (itr2 = packet->structs.begin(); itr2 != packet->structs.end(); itr2++) { + add(new DataStruct(*itr2)); + } + vector::iterator itr; + for (itr = packet->flags.begin(); itr != packet->flags.end(); itr++) { + AddFlag((*itr).c_str()); + } + sub_packet = sub; + opcode = packet->opcode; + version = packet->version; + opcode_type = packet->opcode_type; + name = packet->name; + sub_packet_size = 0; + parent = 0; +} +PacketStruct::~PacketStruct() { + deleteDataStructs(&structs); + deleteDataStructs(&orig_structs); + deletePacketArrays(this); + struct_map.clear(); + struct_data.clear(); + flags.clear(); +} + +void PacketStruct::deleteDataStructs(vector* data_structs) { + if ( !data_structs || data_structs->size() == 0 ) + return; + + DataStruct* data = 0; + vector::iterator itr; + for (itr = data_structs->begin(); itr != data_structs->end(); itr++) { + data = *itr; + void* ptr = GetStructPointer(data); + + // stop the struct_data from growing with old data/ptr info, memory leaking and eventual buffer overflow (crash) + map::iterator datastr = struct_data.find(data); + if (datastr != struct_data.end()) + struct_data.erase(datastr); + + switch (data->GetType()) { + case DATA_STRUCT_EQ2_8BIT_STRING: { + EQ2_8BitString* real_ptr = (EQ2_8BitString*)ptr; + safe_delete(real_ptr); + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + EQ2_16BitString* real_ptr = (EQ2_16BitString*)ptr; + safe_delete(real_ptr); + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + EQ2_32BitString* real_ptr = (EQ2_32BitString*)ptr; + safe_delete(real_ptr); + break; + } + case DATA_STRUCT_EQUIPMENT: { + EQ2_EquipmentItem* real_ptr = (EQ2_EquipmentItem*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_DOUBLE: { + double* real_ptr = (double*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_FLOAT: { + float* real_ptr = (float*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_INT8: { + int8* real_ptr = (int8*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_INT16: { + int16* real_ptr = (int16*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_INT32: { + int32* real_ptr = (int32*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_INT64: { + int64* real_ptr = (int64*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_SINT8: { + sint8* real_ptr = (sint8*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_SINT16: { + sint16* real_ptr = (sint16*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_SINT32: { + sint32* real_ptr = (sint32*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_SINT64: { + sint64* real_ptr = (sint64*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_ITEM: { + uchar* real_ptr = (uchar*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_CHAR: { + char* real_ptr = (char*)ptr; + safe_delete_array(real_ptr); + break; + } + case DATA_STRUCT_COLOR: { + EQ2_Color* real_ptr = (EQ2_Color*)ptr; + safe_delete_array(real_ptr); + break; + } + } + ptr = 0; + safe_delete(data); + } +} +void PacketStruct::deletePacketArrays(PacketStruct* packet) { + if (!packet) + return; + vector::iterator itr; + + for (itr = packet->arrays.begin(); itr != packet->arrays.end(); itr++) + safe_delete(*itr); + packet->arrays.clear(); + + for (itr = packet->orig_packets.begin(); itr != packet->orig_packets.end(); itr++) + safe_delete(*itr); + packet->orig_packets.clear(); +} + +void PacketStruct::renameSubstructArray(const char* substruct, int32 index) { + vector::iterator itr; + char tmp[10] = { 0 }; + sprintf(tmp, "%i", index); + for (itr = arrays.begin(); itr != arrays.end(); itr++) { + (*itr)->SetName(string(substruct).append("_").append((*itr)->GetName()).append("_").append(tmp).c_str()); + } +} + +void PacketStruct::addPacketArrays(PacketStruct* packet) { + if (!packet) + return; + vector::iterator itr; + + for (itr = packet->arrays.begin(); itr != packet->arrays.end(); itr++) { + PacketStruct* tmp = new PacketStruct(*itr, true); + tmp->addPacketArrays(*itr); + add(tmp); + } +} + +bool PacketStruct::IsStringValueType(string in_name, int32 index) { + DataStruct* data = findStruct(in_name.c_str(), index); + switch (data->GetType()) { + case DATA_STRUCT_CHAR: + case DATA_STRUCT_EQ2_8BIT_STRING: + case DATA_STRUCT_EQ2_16BIT_STRING: + case DATA_STRUCT_EQ2_32BIT_STRING: + return true; + } + return false; +} + +bool PacketStruct::IsColorValueType(string in_name, int32 index) { + DataStruct* data = findStruct(in_name.c_str(), index); + if (data->GetType() == DATA_STRUCT_COLOR) + return true; + else + return false; +} +void PacketStruct::setColor(DataStruct* data, int8 red, int8 green, int8 blue, int32 index = 0) { + if (data && data->GetType() == DATA_STRUCT_COLOR) { + EQ2_Color* color = (EQ2_Color*)GetStructPointer(data); + color[index].blue = blue; + color[index].red = red; + color[index].green = green; + if (blue > 0 || green > 0 || red > 0) + data->SetIsSet(true); + } +} +void PacketStruct::setEquipment(DataStruct* data, int16 type, int8 c_red, int8 c_blue, int8 c_green, int8 h_red, int8 h_blue, int8 h_green, int32 index) { + if (data) { + EQ2_EquipmentItem* equipment = (EQ2_EquipmentItem*)GetStructPointer(data); + EQ2_Color* color = (EQ2_Color*)&equipment[index].color; + EQ2_Color* highlight = (EQ2_Color*)&equipment[index].highlight; + equipment[index].type = type; + color->blue = c_blue; + color->red = c_red; + color->green = c_green; + highlight->blue = h_blue; + highlight->red = h_red; + highlight->green = h_green; + if (c_red > 0 || c_blue > 0 || c_green > 0 || h_red > 0 || h_blue > 0 || h_green > 0) + data->SetIsSet(true); + } +} +void PacketStruct::add(PacketStruct* packet_struct) { + packet_struct->parent = this; + arrays.push_back(packet_struct); +} +const char* PacketStruct::GetOpcodeType() { + return opcode_type.c_str(); +} + +void PacketStruct::SetOpcodeType(const char* in_type) { + if (in_type) + opcode_type = string(in_type); + else + opcode_type = string(""); +} + +void PacketStruct::setDataType(DataStruct* data_struct, sint8 data, int32 index) { + if (data_struct) { + sint8* ptr = (sint8*)GetStructPointer(data_struct); + ptr[index] = data; + if (data != 0 && data != -1) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, sint16 data, int32 index) { + if (data_struct) { + sint16* ptr = (sint16*)GetStructPointer(data_struct); + ptr[index] = data; + if (data != 0 && data != -1) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, sint32 data, int32 index) { + if (data_struct) { + sint32* ptr = (sint32*)GetStructPointer(data_struct); + ptr[index] = data; + if (data != 0 && data != -1) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, sint64 data, int32 index) { + if (data_struct) { + sint64* ptr = (sint64*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, char data, int32 index) { + if (data_struct) { + char* ptr = (char*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, int8 data, int32 index) { + if (data_struct) { + int8* ptr = (int8*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, int16 data, int32 index) { + if (data_struct) { + int16* ptr = (int16*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, int32 data, int32 index) { + if (data_struct) { + int32* ptr = (int32*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, int64 data, int32 index) { + if (data_struct) { + int64* ptr = (int64*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, float data, int32 index) { + if (data_struct) { + float* ptr = (float*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setDataType(DataStruct* data_struct, double data, int32 index) { + if (data_struct) { + double* ptr = (double*)GetStructPointer(data_struct); + ptr[index] = data; + if (data > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setData(DataStruct* data_struct, EQ2_8BitString* input_string, int32 index, bool use_second_type) { + if (data_struct) { + EQ2_8BitString* tmp = (EQ2_8BitString*)GetStructPointer(data_struct); + tmp->data = input_string->data; + tmp->size = input_string->data.length(); + if (input_string->data.length() > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setData(DataStruct* data_struct, EQ2_16BitString* input_string, int32 index, bool use_second_type) { + if (data_struct) { + EQ2_16BitString* tmp = (EQ2_16BitString*)GetStructPointer(data_struct); + tmp->data = input_string->data; + tmp->size = input_string->data.length(); + if (input_string->data.length() > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::setData(DataStruct* data_struct, EQ2_32BitString* input_string, int32 index, bool use_second_type) { + if (data_struct) { + EQ2_32BitString* tmp = (EQ2_32BitString*)GetStructPointer(data_struct); + tmp->data = input_string->data; + tmp->size = input_string->data.length(); + if (input_string->data.length() > 0) + data_struct->SetIsSet(true); + } +} +void PacketStruct::add(DataStruct* data) { + structs.push_back(data); + struct_map[data->GetStringName()] = data; + switch (data->GetType()) { + case DATA_STRUCT_INT8: { + struct_data[data] = new int8[data->GetLength()]; + int8* ptr = (int8*)GetStructPointer(data); + if (data->GetLength() > 1) { + int8 default_val = data->GetDefaultValue(); + memset(ptr, default_val, data->GetLength() * sizeof(int8)); + } + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_INT16: { + struct_data[data] = new int16[data->GetLength()]; + int16* ptr = (int16*)GetStructPointer(data); + if (data->GetLength() > 1) { + int8 default_val = data->GetDefaultValue(); + memset(ptr, default_val, data->GetLength() * sizeof(int16)); + } + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_INT32: { + struct_data[data] = new int32[data->GetLength()]; + int32* ptr = (int32*)GetStructPointer(data); + if (data->GetLength() > 1) { + int8 default_val = data->GetDefaultValue(); + memset(ptr, default_val, data->GetLength() * sizeof(int32)); + } + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_INT64: { + struct_data[data] = new int64[data->GetLength()]; + int64* ptr = (int64*)GetStructPointer(data); + if (data->GetLength() > 1) { + int8 default_val = data->GetDefaultValue(); + memset(ptr, default_val, data->GetLength() * sizeof(int64)); + } + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_SINT8: { + struct_data[data] = new sint8[data->GetLength()]; + sint8* ptr = (sint8*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(sint8)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_SINT16: { + struct_data[data] = new sint16[data->GetLength()]; + sint16* ptr = (sint16*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(sint16)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_SINT32: { + struct_data[data] = new sint32[data->GetLength()]; + sint32* ptr = (sint32*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(sint32)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_SINT64: { + struct_data[data] = new sint64[data->GetLength()]; + sint64* ptr = (sint64*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(sint64)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_CHAR: { + struct_data[data] = new char[data->GetLength()]; + char* ptr = (char*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(char)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_FLOAT: { + struct_data[data] = new float[data->GetLength()]; + float* ptr = (float*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(float)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_DOUBLE: { + struct_data[data] = new double[data->GetLength()]; + double* ptr = (double*)GetStructPointer(data); + if (data->GetLength() > 1) + memset(ptr, 0, data->GetLength() * sizeof(double)); + else + ptr[0] = 0; + break; + } + case DATA_STRUCT_ARRAY: { + data->SetLength(0); + break; + } + case DATA_STRUCT_EQ2_8BIT_STRING: { + string name2 = data->GetStringName(); + for (int32 i = 1; i < data->GetLength(); i++) { + DataStruct* new_data = new DataStruct(data); + char blah[10] = { 0 }; + sprintf(blah, "%i", i); + name2.append("_").append(blah); + new_data->SetName(name2.c_str()); + new_data->SetLength(1); + EQ2_8BitString* tmp = new EQ2_8BitString; + tmp->size = 0; + struct_data[new_data] = tmp; + structs.push_back(new_data); + } + data->SetLength(1); + EQ2_8BitString* tmp = new EQ2_8BitString; + tmp->size = 0; + struct_data[data] = tmp; + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + string name2 = data->GetStringName(); + for (int32 i = 1; i < data->GetLength(); i++) { + DataStruct* new_data = new DataStruct(data); + char blah[10] = { 0 }; + sprintf(blah, "%i", i); + name2.append("_").append(blah); + new_data->SetName(name2.c_str()); + new_data->SetLength(1); + EQ2_16BitString* tmp = new EQ2_16BitString; + tmp->size = 0; + struct_data[new_data] = tmp; + structs.push_back(new_data); + } + data->SetLength(1); + EQ2_16BitString* tmp = new EQ2_16BitString; + tmp->size = 0; + struct_data[data] = tmp; + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + string name2 = data->GetStringName(); + for (int32 i = 1; i < data->GetLength(); i++) { + DataStruct* new_data = new DataStruct(data); + char blah[10] = { 0 }; + sprintf(blah, "%i", i); + name2.append("_").append(blah); + new_data->SetName(name2.c_str()); + new_data->SetLength(1); + EQ2_32BitString* tmp = new EQ2_32BitString; + tmp->size = 0; + struct_data[new_data] = tmp; + structs.push_back(new_data); + } + data->SetLength(1); + EQ2_32BitString* tmp = new EQ2_32BitString; + tmp->size = 0; + struct_data[data] = tmp; + break; + } + case DATA_STRUCT_COLOR: { + struct_data[data] = new EQ2_Color[data->GetLength()]; + EQ2_Color* ptr = (EQ2_Color*)GetStructPointer(data); + for (int16 i = 0; i < data->GetLength(); i++) { + ptr[i].red = 0; + ptr[i].blue = 0; + ptr[i].green = 0; + } + break; + } + case DATA_STRUCT_EQUIPMENT: { + struct_data[data] = new EQ2_EquipmentItem[data->GetLength()]; + EQ2_EquipmentItem* ptr = (EQ2_EquipmentItem*)GetStructPointer(data); + for (int16 i = 0; i < data->GetLength(); i++) { + memset(&ptr[i].color, 0, sizeof(ptr[i].color)); + memset(&ptr[i].highlight, 0, sizeof(ptr[i].highlight)); + ptr[i].type = 0; + } + break; + } + case DATA_STRUCT_ITEM: { + struct_data[data] = new uchar[10000]; + char* ptr = (char*)GetStructPointer(data); + memset(ptr, 0, 10000); + break; + } + } +} +void PacketStruct::remove(DataStruct* data) { + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + if (data == (*itr)) { + structs.erase(itr); + safe_delete(data); + return; + } + } +} +DataStruct* PacketStruct::findStruct(const char* name, int32 index) { + return findStruct(name, index, index); +} + +DataStruct* PacketStruct::findStruct(const char* name, int32 index1, int32 index2) { + DataStruct* data = 0; + + if (struct_map.count(string(name)) > 0) { + data = struct_map[string(name)]; + if (data && index2 < data->GetLength()) + return data; + } + vector::iterator itr; + + PacketStruct* packet = 0; + vector::iterator itr2; + string name2 = string(name); + if (index1 < 0xFFFF) { + char blah[10] = { 0 }; + sprintf(blah, "_%i", index1); + name2.append(blah); + } + if (struct_map.count(name2) > 0) { + data = struct_map[name2]; + if (data && index2 < data->GetLength()) + return data; + } + for (itr2 = arrays.begin(); itr2 != arrays.end(); itr2++) { + packet = *itr2; + data = packet->findStruct(name, index1, index2); + if (data != 0) + return data; + } + return 0; +} +void PacketStruct::remove(const char* name) { + DataStruct* data = 0; + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + data = *itr; + if (strcmp(name, data->GetName()) == 0) { + structs.erase(itr); + safe_delete(data); + return; + } + } +} +string* PacketStruct::serializeString() { + serializePacket(); + return getDataString(); +} +void PacketStruct::setSmallString(DataStruct* data_struct, const char* text, int32 index) { + EQ2_8BitString* string_data = new EQ2_8BitString; + string_data->data = string(text); + string_data->size = string_data->data.length(); + setData(data_struct, string_data, index); + safe_delete(string_data); +} +void PacketStruct::setMediumString(DataStruct* data_struct, const char* text, int32 index) { + EQ2_16BitString* string_data = new EQ2_16BitString; + string_data->data = string(text); + string_data->size = string_data->data.length(); + setData(data_struct, string_data, index); + safe_delete(string_data); +} +void PacketStruct::setLargeString(DataStruct* data_struct, const char* text, int32 index) { + EQ2_32BitString* string_data = new EQ2_32BitString; + string_data->data = string(text); + string_data->size = string_data->data.length(); + setData(data_struct, string_data, index); + safe_delete(string_data); +} +void PacketStruct::setSmallStringByName(const char* name, const char* text, int32 index) { + setSmallString(findStruct(name, index), text, index); +} +void PacketStruct::setMediumStringByName(const char* name, const char* text, int32 index) { + setMediumString(findStruct(name, index), text, index); +} +void PacketStruct::setLargeStringByName(const char* name, const char* text, int32 index) { + setLargeString(findStruct(name, index), text, index); +} + +bool PacketStruct::GetVariableIsSet(const char* name) { + DataStruct* ds2 = findStruct(name, 0); + if (!ds2 || !ds2->IsSet()) + return false; + return true; +} + +bool PacketStruct::GetVariableIsNotSet(const char* name) { + DataStruct* ds2 = findStruct(name, 0); + if (ds2 && ds2->IsSet()) + return false; + return true; +} + +bool PacketStruct::CheckFlagExists(const char* name) { + vector::iterator itr; + for (itr = flags.begin(); itr != flags.end(); itr++) { + if (*itr == string(name)) + return true; + } + return false; +} + +void PacketStruct::AddFlag(const char* name) { + if (flags.size() > 0) { + vector::iterator itr; + for (itr = flags.begin(); itr != flags.end(); itr++) { + if (*itr == string(name)) + return; + } + } + flags.push_back(string(name)); +} + +bool PacketStruct::LoadPacketData(uchar* data, int32 data_len, bool create_color) { + loadedSuccessfully = true; + DataStruct* data_struct = 0; + try { + InitializeLoadData(data, data_len); + vector::iterator itr; + + for (itr = structs.begin(); itr != structs.end(); itr++) { + data_struct = *itr; + if (!data_struct->AddToStruct()) + continue; + + if (data_struct->GetIfSet() && data_struct->GetIfSetVariable()) { + string varname = string(data_struct->GetIfSetVariable()); + if (varname.find(",") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, ','); + if (varnames) { + bool should_continue = true; + for (int32 i = 0; i < varnames->size(); i++) { + if (GetVariableIsSet(varnames->at(i).c_str())) { + should_continue = false; + break; + } + } + safe_delete(varnames); + if (should_continue) + continue; + } + } + else { + // Check to see if the variable contains %i, if it does assume we are in an array + // and get the current index from the end of the data struct's name + char name[250] = { 0 }; + if (varname.find("%i") < 0xFFFFFFFF) { + vector* varnames = SplitString(data_struct->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + sprintf(name, varname.c_str(), index); + } + else + strcpy(name, varname.c_str()); + + if (!GetVariableIsSet(name)) + continue; + } + } + if (data_struct->GetIfNotSet() && data_struct->GetIfNotSetVariable()) { + string varname = string(data_struct->GetIfNotSetVariable()); + if (varname.find(",") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, ','); + if (varnames) { + bool should_continue = false; + for (int32 i = 0; i < varnames->size(); i++) { + if (!GetVariableIsNotSet(varnames->at(i).c_str())) { + should_continue = true; + break; + } + } + safe_delete(varnames); + if (should_continue) + continue; + } + } + else { + // Check to see if the variable contains %i, if it does assume we are in an array + // and get the current index from the end of the data struct's name + char name[250] = { 0 }; + if (varname.find("%i") < 0xFFFFFFFF) { + vector* varnames = SplitString(data_struct->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + sprintf(name, varname.c_str(), index); + } + else + strcpy(name, varname.c_str()); + + if (!GetVariableIsNotSet(name)) + continue; + } + } + // Quick implementaion of IfVariableNotEquals + // probably not what it was intended for as it currently just checks to see if the given variable equals 1 + // should probably change it so you can define what the variable should or shouldn't equal + // + // ie: IfVariableNotEquals="stat_type_%i=1" + // would be a check to make sure that stat_type_%i does not equal 1 and if it does exclude this element + if (data_struct->GetIfNotEquals() && data_struct->GetIfNotEqualsVariable()) { + // Get the variable name + string varname = string(data_struct->GetIfNotEqualsVariable()); + char name[250] = { 0 }; + // Check to see if the variable has %i in the name, if it does assume we are in an array and get the current + // index and replace it + if (varname.find("%i") < 0xFFFFFFFF) { + // Get the current index by getting the number at the end of the name + vector* varnames = SplitString(data_struct->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + + string substr = "stat_type"; + if (strncmp(varname.c_str(), substr.c_str(), strlen(substr.c_str())) == 0) { + // adorn_stat_subtype 18 chars + string temp = varname.substr(12); + char temp2[20] = { 0 }; + int index2 = atoi(temp.c_str()); + itoa(index2, temp2, 10); + varname = varname.substr(0, 12).append(temp2).append("_%i"); + } + sprintf(name, varname.c_str(), index); + safe_delete(varnames); + } + else + strcpy(name, varname.c_str()); + + // Get the data for the variable + int16 value = 0; + DataStruct* data_struct2 = findStruct(name, 0); + value = getType_int16(data_struct2); + // Hack for items as it is the only struct that currently uses IfVariableNotEquals + if (value == 1) + continue; + } + // copy and paste of the code above for IfEquals + if (data_struct->GetIfEquals() && data_struct->GetIfEqualsVariable()) { + // Get the variable name + string varname = string(data_struct->GetIfEqualsVariable()); + char name[250] = { 0 }; + // Check to see if the variable has %i in the name, if it does assume we are in an array and get the current + // index and replace it + if (varname.find("%i") < 0xFFFFFFFF) { + // Get the current index by getting the number at the end of the name + vector* varnames = SplitString(data_struct->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + + string substr = "stat_type"; + if (strncmp(varname.c_str(), substr.c_str(), strlen(substr.c_str())) == 0) { + // adorn_stat_subtype 18 chars + string temp = varname.substr(12); + char temp2[20] = { 0 }; + int index2 = atoi(temp.c_str()); + itoa(index2, temp2, 10); + varname = varname.substr(0, 12).append(temp2).append("_%i"); + } + sprintf(name, varname.c_str(), index); + safe_delete(varnames); + } + else + strcpy(name, varname.c_str()); + + // Get the data for the variable + int16 value = 0; + DataStruct* data_struct2 = findStruct(name, 0); + value = getType_int16(data_struct2); + // Hack for items as it is the only struct that currently uses IfVariableNotEquals + if (value != 1) + continue; + } + + // The following is tailored to items as they are the only structs that use type2 + // if more type2's are needed outside of the item stats array we need to think up an element + // to determine when to use type2 over type. + // Added checks for set stats and adorn stats - Zcoretri + bool useType2 = false; + if (data_struct->GetType2() > 0) { + int16 type = 0; + char name[250] = { 0 }; + vector* varnames = SplitString(data_struct->GetName(), '_'); + string struct_name = data_struct->GetName(); + if (struct_name.find("set") < 0xFFFFFFFF) { + string tmp = "set_stat_type"; + struct_name.replace(0, 9, tmp); + sprintf(name, "%s", struct_name.c_str()); + } + else if (struct_name.find("adorn") < 0xFFFFFFFF) { + string tmp = "adorn_stat_type"; + struct_name.replace(0, 9, tmp); + sprintf(name, "%s", struct_name.c_str()); + } + else { + // set name to stat_type_# (where # is the current index of the array we are in) + sprintf(name, "%s_%s", "stat_type", varnames->at(varnames->size() - 1).c_str()); + } + // Look up the value for stat_type + DataStruct* data_struct2 = findStruct(name, 0); + type = getType_int16(data_struct2); + // If stat_type == 6 we use a float, else we use sint16 + if (type != 6 && type != 7) + useType2 = true; + safe_delete(varnames); + } + if (!StructLoadData(data_struct, GetStructPointer(data_struct), data_struct->GetLength(), useType2, create_color)) + { + loadedSuccessfully = false; + break; + } + } + } + catch (...) { + loadedSuccessfully = false; + } + return loadedSuccessfully; +} +bool PacketStruct::StructLoadData(DataStruct* data_struct, void* data, int32 len, bool useType2, bool create_color) { + int8 type = 0; + if (useType2) { + type = data_struct->GetType2(); + // Need to change the data the struct expects to type2 + data_struct->SetType(type); + LogWrite(PACKET__DEBUG, 7, "Items", "Using type2 = %i", type); + } + else + type = data_struct->GetType(); + + switch (type) { + case DATA_STRUCT_INT8: + LoadData((int8*)data, len); + data_struct->SetIsSet(*((int8*)data) > 0); + break; + case DATA_STRUCT_INT16: + if (data_struct->GetOversized() > 0) { + LoadData((int8*)data, len); + if (getType_int16(data_struct) == data_struct->GetOversizedByte()) { + LoadData((int16*)data, len); + } + } + else { + LoadData((int16*)data, len); + } + data_struct->SetIsSet(*((int16*)data) > 0); + break; + case DATA_STRUCT_INT32: + if (data_struct->GetOversized() > 0) { + LoadData((int8*)data, len); + if (getType_int32(data_struct) == data_struct->GetOversizedByte()) { + LoadData((int32*)data, len); + } + else + LoadData((int8*)data, len); + } + else { + LoadData((int32*)data, len); + } + data_struct->SetIsSet(*((int32*)data) > 0); + break; + case DATA_STRUCT_INT64: + LoadData((int64*)data, len); + data_struct->SetIsSet(*((int64*)data) > 0); + break; + case DATA_STRUCT_SINT8: + LoadData((sint8*)data, len); + data_struct->SetIsSet(*((sint8*)data) > 0); + break; + case DATA_STRUCT_SINT16: + if (data_struct->GetOversized() > 0) { + LoadData((sint8*)data, len); + sint8 val = (sint8)getType_sint16(data_struct); + if (val < 0) //necessary because when using memcpy from a smaller data type to a larger one, the sign is lost + setData(data_struct, val, 0); + if (getType_sint16(data_struct) == data_struct->GetOversizedByte()) + LoadData((sint16*)data, len); + } + else + LoadData((sint16*)data, len); + data_struct->SetIsSet(*((sint16*)data) > 0); + break; + case DATA_STRUCT_SINT32: + LoadData((sint32*)data, len); + data_struct->SetIsSet(*((sint32*)data) > 0); + break; + case DATA_STRUCT_SINT64: + LoadData((sint64*)data, len); + data_struct->SetIsSet(*((sint64*)data) > 0); + break; + case DATA_STRUCT_CHAR: + LoadData((char*)data, len); + data_struct->SetIsSet(true); + break; + case DATA_STRUCT_FLOAT: + LoadData((float*)data, len); + data_struct->SetIsSet(*((float*)data) > 0); + break; + case DATA_STRUCT_DOUBLE: + LoadData((double*)data, len); + data_struct->SetIsSet(*((double*)data) > 0); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: { + LoadDataString((EQ2_8BitString*)data); + data_struct->SetIsSet(((EQ2_8BitString*)data)->data.length() > 0); + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + LoadDataString((EQ2_16BitString*)data); + data_struct->SetIsSet(((EQ2_16BitString*)data)->data.length() > 0); + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + LoadDataString((EQ2_32BitString*)data); + data_struct->SetIsSet(((EQ2_32BitString*)data)->data.length() > 0); + break; + } + case DATA_STRUCT_COLOR: { + // lets not do this again, DoF behaves differently than AoM, DoF is not compatible with CreateEQ2Color + //if (strcmp(GetName(), "CreateCharacter") == 0 || strcmp(GetName(), "WS_SubmitCharCust") == 0) + if(create_color) + CreateEQ2Color((EQ2_Color*)data); + else + LoadData((EQ2_Color*)data, len); + break; + } + case DATA_STRUCT_EQUIPMENT: { + LoadData((EQ2_EquipmentItem*)data); + break; + } + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(data_struct, 0); + if (size > 0xFFFF || size > GetLoadLen()-GetLoadPos()) { + LogWrite(PACKET__WARNING, 1, "Packet", "Possible corrupt packet while loading struct array, orig array size: %u in struct name %s, data name %s, load_len %u, load_pos %u", size, GetName(), (data_struct && data_struct->GetName()) ? data_struct->GetName() : "??", GetLoadLen(), GetLoadPos()); + return false; + } + PacketStruct* ps = GetPacketStructByName(data_struct->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + if (data_struct->GetMaxArraySize() > 0 && size > data_struct->GetMaxArraySize()) + size = data_struct->GetMaxArraySize(); + ps->reAddAll(size); + } + if (ps && size > 0) { + //for(int i=0;i 0;i++){ + if(ps->LoadPacketData(GetLoadBuffer() + GetLoadPos(), GetLoadLen() - GetLoadPos(), create_color)) { + SetLoadPos(GetLoadPos() + ps->GetLoadPos()); + } + //} + } + break; + } + default: { + data_struct->SetIsSet(false); + } + } + + return true; +} +PacketStruct* PacketStruct::GetPacketStructByName(const char* name) { + PacketStruct* ps = 0; + vector::iterator itr; + for (itr = arrays.begin(); itr != arrays.end(); itr++) { + ps = *itr; + if (strcmp(ps->GetName(), name) == 0) + return ps; + ps = ps->GetPacketStructByName(name); + if (ps) + return ps; + } + return 0; +} + +void PacketStruct::reAddAll(int32 length) { + vector::iterator itr; + DataStruct* ds = 0; + PacketStruct* ps = 0; + vector::iterator packet_itr; + if (orig_structs.size() == 0) + orig_structs = structs; + else + deleteDataStructs(&structs); + structs.clear(); + + if (orig_packets.size() == 0 && arrays.size() > 0) + orig_packets = arrays; + else { + for (packet_itr = arrays.begin(); packet_itr != arrays.end(); packet_itr++) { + ps = *packet_itr; + safe_delete(ps); + } + } + arrays.clear(); + + for (int16 i = 0; i < length; i++) { + for (packet_itr = orig_packets.begin(); packet_itr != orig_packets.end(); packet_itr++) { + ps = *packet_itr; + PacketStruct* new_packet = new PacketStruct(ps, true); + char tmp[20] = { 0 }; + sprintf(tmp, "_%i", i); + string name = string(new_packet->GetName()); + name.append(tmp); + new_packet->SetName(name.c_str()); + add(new_packet); + } + for (itr = orig_structs.begin(); itr != orig_structs.end(); itr++) { + ds = *itr; + DataStruct* new_data = new DataStruct(ds); + char tmp[20] = { 0 }; + sprintf(tmp, "_%i", i); + string name = new_data->GetStringName(); + if (IsSubPacket() && parent->IsSubPacket()) { + string parent_name = string(GetName()); + try { + if (parent_name.rfind("_") < 0xFFFFFFFF) + sprintf(tmp, "%i_%i", atoi(parent_name.substr(parent_name.rfind("_") + 1).c_str()), i); + } + catch (...) { + sprintf(tmp, "_%i", i); + } + } + name.append(tmp); + new_data->SetName(name.c_str()); + if (new_data->GetType() == DATA_STRUCT_ARRAY) { + string old_size_arr = string(new_data->GetArraySizeVariable()); + new_data->SetArraySizeVariable(old_size_arr.append(tmp).c_str()); + } + add(new_data); + } + } + sub_packet_size = length; +} +int32 PacketStruct::GetArraySize(DataStruct* data_struct, int32 index) { + if (data_struct) { + const char* name = data_struct->GetArraySizeVariable(); + return GetArraySize(name, index); + } + return 0; +} +int32 PacketStruct::GetArraySize(const char* name, int32 index) { + int32 ret = 0; + DataStruct* ds = findStruct(name, index); + if (ds) { + if (ds->GetType() == DATA_STRUCT_INT8) { + int8* tmp = (int8*)GetStructPointer(ds); + ret = *tmp; + } + else if (ds->GetType() == DATA_STRUCT_INT16) { + int16* tmp = (int16*)GetStructPointer(ds); + ret = *tmp; + } + else if (ds->GetType() == DATA_STRUCT_INT32) { + int32* tmp = (int32*)GetStructPointer(ds); + ret = *tmp; + } + else if (ds->GetType() == DATA_STRUCT_INT64) { + int64* tmp = (int64*)GetStructPointer(ds); + ret = *tmp; + } + } + return ret; +} + +void PacketStruct::UpdateArrayByArrayLength(DataStruct* data_struct, int32 index, int32 size) { + if (data_struct) { + PacketStruct* packet = 0; + DataStruct* data = 0; + vector::iterator itr; + + for (itr = structs.begin(); itr != structs.end(); itr++) { + data = *itr; + if (strcmp(data->GetArraySizeVariable(), data_struct->GetName()) == 0) { + packet = GetPacketStructByName(data->GetName()); + if (packet) + packet->reAddAll(size); + return; + } + } + vector::iterator itr2; + for (itr2 = arrays.begin(); itr2 != arrays.end(); itr2++) { + packet = *itr2; + packet->UpdateArrayByArrayLength(data_struct, index, size); + } + } +} + +void PacketStruct::UpdateArrayByArrayLengthName(const char* name, int32 index, int32 size) { + UpdateArrayByArrayLength(findStruct(name, index), index, size); +} +int32 PacketStruct::GetArraySizeByName(const char* name, int32 index) { + DataStruct* ds1 = findStruct(name, index); + return GetArraySize(ds1, index); +} + +int16 PacketStruct::GetOpcodeValue(int16 client_version) { + int16 opcode = 0xFFFF; + bool client_cmd = false; + int16 OpcodeVersion = 0; +#ifndef LOGIN + if (GetOpcode() == OP_ClientCmdMsg && strlen(GetOpcodeType()) > 0 && !IsSubPacket()) + client_cmd = true; +#endif + if (client_cmd) { + EmuOpcode sub_opcode = EQOpcodeManager[0]->NameSearch(GetOpcodeType()); + if (sub_opcode != OP_Unknown) { //numbers should be used at OpcodeTypes, define them! + OpcodeVersion = GetOpcodeVersion(client_version); + if (EQOpcodeManager.count(OpcodeVersion) > 0) { + opcode = EQOpcodeManager[OpcodeVersion]->EmuToEQ(sub_opcode); + if (opcode == 0xCDCD) { + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find valid opcode for opcode: %s and client_version: %i", EQOpcodeManager[OpcodeVersion]->EmuToName(sub_opcode), client_version); + } + } + } + } + else { + OpcodeVersion = GetOpcodeVersion(client_version); + if (EQOpcodeManager.count(OpcodeVersion) > 0) { + opcode = EQOpcodeManager[OpcodeVersion]->EmuToEQ(GetOpcode()); + if (opcode == 0xCDCD) { + LogWrite(PACKET__ERROR, 0, "Packet", "Could not find valid opcode for opcode: %s and client_version: %i", EQOpcodeManager[OpcodeVersion]->EmuToName(GetOpcode()), client_version); + } + } + } +#ifndef LOGIN + if(opcode == 0) + opcode = 0xFFFF; +#endif + return opcode; +} + +void PacketStruct::serializePacket(bool clear) { + if (clear) + Clear(); + bool client_cmd = false; + string client_data; +#ifndef LOGIN + if (GetOpcode() == OP_ClientCmdMsg && strlen(GetOpcodeType()) > 0 && !IsSubPacket()) + client_cmd = true; +#endif + DataStruct* data = 0; + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + data = *itr; + if (data->IsOptional())//this would be false if the datastruct WAS optional, but was set + continue; + if (data->AddToStruct()) { + if (data->GetIfFlagNotSet() && CheckFlagExists(data->GetIfFlagNotSetVariable())) + continue; + if (data->GetIfFlagSet() && !CheckFlagExists(data->GetIfFlagSetVariable())) + continue; + if (data->GetIfSet() && data->GetIfSetVariable()) { + string varname = string(data->GetIfSetVariable()); + if (varname.find(",") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, ','); + if (varnames) { + bool should_continue = true; + for (int32 i = 0; i < varnames->size(); i++) { + if (GetVariableIsSet(varnames->at(i).c_str())) { + should_continue = false; + break; + } + } + safe_delete(varnames); + if (should_continue) + continue; + } + } + else { + if (!GetVariableIsSet(varname.c_str())) + continue; + } + } + if (data->GetIfNotSet() && data->GetIfNotSetVariable()) { + string varname = string(data->GetIfNotSetVariable()); + if (varname.find(",") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, ','); + if (varnames) { + bool should_continue = false; + for (int32 i = 0; i < varnames->size(); i++) { + if (!GetVariableIsNotSet(varnames->at(i).c_str())) { + should_continue = true; + break; + } + } + safe_delete(varnames); + if (should_continue) + continue; + } + } + else { + // Check to see if the variable contains %i, if it does assume we are in an array + // and get the current index from the end of the data struct's name + char name[250] = { 0 }; + if (varname.find("%i") < 0xFFFFFFFF) { + vector* varnames = SplitString(varname, '_'); + vector* indexes = SplitString(data->GetName(), '_'); + int index = 0; + if (indexes->size() > 0) + index = atoi(indexes->at(indexes->size() - 1).c_str()); + + sprintf(name, varname.c_str(), index); + } + else + strcpy(name, varname.c_str()); + if (!GetVariableIsNotSet(name)) + continue; + } + } + // Quick implementaion of IfVariableNotEquals + // probably not what it was intended for as it currently just checks to see if the given variable equals 1 + // should probably change it so you can define what the variable should or shouldn't equal + // + // ie: IfVariableNotEquals="stat_type_%i=1" + // would be a check to make sure that stat_type_%i does not equal 1 and if it does exclude this element + if (data->GetIfNotEquals() && data->GetIfNotEqualsVariable()) { + // Get the variable name + string varname = string(data->GetIfNotEqualsVariable()); + char name[250] = { 0 }; + // Check to see if the variable has %i in the name, if it does assume we are in an array and get the current + // index and replace it + if (varname.find("%i") < 0xFFFFFFFF) { + // Get the current index by getting the number at the end of the name + vector* varnames = SplitString(data->GetName(), '_'); + int index = atoi(varnames->at(varnames->size() - 1).c_str()); + + string substr = "stat_type"; + if (strncmp(varname.c_str(), substr.c_str(), strlen(substr.c_str())) == 0) { + // adorn_stat_subtype 18 chars + string temp = varname.substr(12); + char temp2[20] = { 0 }; + int index2 = atoi(temp.c_str()); + itoa(index2, temp2, 10); + varname = varname.substr(0, 12).append(temp2).append("_%i"); + } + sprintf(name, varname.c_str(), index); + safe_delete(varnames); + } + else + strcpy(name, varname.c_str()); + + // Get the data for the variable + int16 value = 0; + DataStruct* data_struct2 = findStruct(name, 0); + value = getType_int16(data_struct2); + // Hack for items as it is the only struct that currently uses IfVariableNotEquals + if (value == 1) + continue; + } + // copy and paste of the code above for IfEquals + if (data->GetIfEquals() && data->GetIfEqualsVariable()) { + // Get the variable name + string varname = string(data->GetIfEqualsVariable()); + char name[250] = { 0 }; + // Check to see if the variable has %i in the name, if it does assume we are in an array and get the current + // index and replace it + if (varname.find("%i") < 0xFFFFFFFF) { + // Get the current index by getting the number at the end of the name + vector* varnames = SplitString(data->GetName(), '_'); + int index = 0; + if (varnames) + index = atoi(varnames->at(varnames->size() - 1).c_str()); + + string substr = "stat_type"; + if (strncmp(varname.c_str(), substr.c_str(), strlen(substr.c_str())) == 0) { + // adorn_stat_subtype 18 chars + string temp = varname.substr(12); + char temp2[20] = { 0 }; + int index2 = atoi(temp.c_str()); + itoa(index2, temp2, 10); + varname = varname.substr(0, 12).append(temp2).append("_%i"); + } + sprintf(name, varname.c_str(), index); + safe_delete(varnames); + } + else + strcpy(name, varname.c_str()); + + // Get the data for the variable + int16 value = 0; + DataStruct* data_struct2 = findStruct(name, 0); + value = getType_int16(data_struct2); + // Hack for items as it is the only struct that currently uses IfVariableNotEquals + if (value != 1) + continue; + } + if (client_cmd) + AddSerializedData(data, 0, &client_data); + else + AddSerializedData(data); + } + } +#ifndef LOGIN + if (client_cmd) { + int16 opcode_val = GetOpcodeValue(client_version); + Clear(); + int32 size = client_data.length() + 3; //gotta add the opcode and oversized + int8 oversized = 0xFF; + int16 OpcodeVersion = GetOpcodeVersion(client_version); + if (opcode_val == EQOpcodeManager[OpcodeVersion]->EmuToEQ(OP_EqExamineInfoCmd) && client_version > 561) + size += (size - 9); + if (client_version <= 374) { + if (size >= 255) { + StructAddData(oversized, sizeof(int8), 0); + StructAddData(size, sizeof(int16), 0); + } + else { + StructAddData(size, sizeof(int8), 0); + } + StructAddData(oversized, sizeof(int8), 0); + StructAddData(opcode_val, sizeof(int16), 0); + } + else { + StructAddData(size, sizeof(int32), 0); + StructAddData(oversized, sizeof(int8), 0); + StructAddData(opcode_val, sizeof(int16), 0); + } + AddData(client_data); + } +#endif +} +int32 PacketStruct::GetTotalPacketSize() { + int32 retSize = 0; + DataStruct* data = 0; + vector::iterator itr; + EQ2_8BitString* tmp1 = 0; + EQ2_16BitString* tmp2 = 0; + EQ2_32BitString* tmp3 = 0; + for (itr = structs.begin(); itr != structs.end(); itr++) { + data = *itr; + switch (data->GetType()) { + case DATA_STRUCT_INT8: + case DATA_STRUCT_SINT8: + case DATA_STRUCT_CHAR: + retSize += (1 * data->GetLength()); + break; + case DATA_STRUCT_SINT16: + case DATA_STRUCT_INT16: + retSize += (2 * data->GetLength()); + break; + case DATA_STRUCT_INT32: + case DATA_STRUCT_SINT32: + case DATA_STRUCT_FLOAT: + case DATA_STRUCT_DOUBLE: + retSize += (4 * data->GetLength()); + break; + case DATA_STRUCT_SINT64: + case DATA_STRUCT_INT64: + retSize += (8 * data->GetLength()); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: + tmp1 = (EQ2_8BitString*)GetStructPointer(data); + if (tmp1) { + for (int16 i = 0; i < data->GetLength(); i++) + retSize += tmp1[i].data.length(); + } + retSize += (1 * data->GetLength()); + break; + case DATA_STRUCT_EQ2_16BIT_STRING: { + tmp2 = (EQ2_16BitString*)GetStructPointer(data); + if (tmp2) { + for (int16 i = 0; i < data->GetLength(); i++) + retSize += tmp2[i].data.length(); + } + retSize += (2 * data->GetLength()); + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + tmp3 = (EQ2_32BitString*)GetStructPointer(data); + if (tmp3) { + for (int16 i = 0; i < data->GetLength(); i++) + retSize += tmp3[i].data.length(); + } + retSize += (4 * data->GetLength()); + break; + } + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(data, 0); + PacketStruct* ps = GetPacketStructByName(data->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps && size > 0) + retSize += ps->GetTotalPacketSize(); + break; + } + case DATA_STRUCT_COLOR: { + retSize += ((sizeof(int8) * 3) * data->GetLength()); + break; + } + case DATA_STRUCT_EQUIPMENT: { + retSize += ((((sizeof(int8) * 3) * 2) + sizeof(int16)) * data->GetLength()); + break; + } + } + } + return retSize; +} + +void PacketStruct::AddSerializedData(DataStruct* data, int32 index, string* datastring) { + switch (data->GetAddType()) { + case DATA_STRUCT_INT8: + StructAddData((int8*)GetStructPointer(data), data->GetLength(), sizeof(int8), datastring); + break; + case DATA_STRUCT_INT16: + if (data->GetOversized() > 0) { + if (*((int16*)GetStructPointer(data)) >= data->GetOversized()) { + StructAddData(data->GetOversizedByte(), sizeof(int8), datastring); + StructAddData((int16*)GetStructPointer(data), data->GetLength(), sizeof(int16), datastring); + } + else + StructAddData((int8*)GetStructPointer(data), data->GetLength(), sizeof(int8), datastring); + } + else + StructAddData((int16*)GetStructPointer(data), data->GetLength(), sizeof(int16), datastring); + break; + case DATA_STRUCT_INT32: + if (data->GetOversized() > 0) { + if (*((int32*)GetStructPointer(data)) >= data->GetOversized()) { + StructAddData(data->GetOversizedByte(), sizeof(int8), datastring); + StructAddData((int32*)GetStructPointer(data), data->GetLength(), sizeof(int32), datastring); + } + else + StructAddData((int16*)GetStructPointer(data), data->GetLength(), sizeof(int16), datastring); + } + else + StructAddData((int32*)GetStructPointer(data), data->GetLength(), sizeof(int32), datastring); + break; + case DATA_STRUCT_INT64: + StructAddData((int64*)GetStructPointer(data), data->GetLength(), sizeof(int64), datastring); + break; + case DATA_STRUCT_SINT8: + StructAddData((sint8*)GetStructPointer(data), data->GetLength(), sizeof(sint8), datastring); + break; + case DATA_STRUCT_SINT16: + if (data->GetOversized() > 0) { + sint16 val = *((sint16*)GetStructPointer(data)); + if (val >= data->GetOversized() || val <= (data->GetOversized() * -1)) { + StructAddData(data->GetOversizedByte(), sizeof(sint8), datastring); + StructAddData((sint16*)GetStructPointer(data), data->GetLength(), sizeof(sint16), datastring); + } + else + StructAddData((sint8*)GetStructPointer(data), data->GetLength(), sizeof(sint8), datastring); + } + else + StructAddData((sint16*)GetStructPointer(data), data->GetLength(), sizeof(sint16), datastring); + break; + case DATA_STRUCT_SINT32: + StructAddData((sint32*)GetStructPointer(data), data->GetLength(), sizeof(sint32), datastring); + break; + case DATA_STRUCT_SINT64: + StructAddData((sint64*)GetStructPointer(data), data->GetLength(), sizeof(sint64), datastring); + break; + case DATA_STRUCT_CHAR: + StructAddData((char*)GetStructPointer(data), data->GetLength(), sizeof(char), datastring); + break; + case DATA_STRUCT_FLOAT: + StructAddData((float*)GetStructPointer(data), data->GetLength(), sizeof(float), datastring); + break; + case DATA_STRUCT_DOUBLE: + StructAddData((double*)GetStructPointer(data), data->GetLength(), sizeof(double), datastring); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: { + for (int16 i = 0; i < data->GetLength(); i++) { + EQ2_8BitString* ds = (EQ2_8BitString*)GetStructPointer(data); + AddDataString(ds[i], datastring); + } + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + for (int16 i = 0; i < data->GetLength(); i++) { + EQ2_16BitString* ds = (EQ2_16BitString*)GetStructPointer(data); + AddDataString(ds[i], datastring); + } + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + for (int16 i = 0; i < data->GetLength(); i++) { + EQ2_32BitString* ds = (EQ2_32BitString*)GetStructPointer(data); + AddDataString(ds[i], datastring); + } + break; + } + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(data, 0); + PacketStruct* ps = GetPacketStructByName(data->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps && size > 0) { + ps->serializePacket(); + string data = *(ps->getDataString()); + AddData(data, datastring); + } + break; + } + case DATA_STRUCT_COLOR: { + StructAddData((EQ2_Color*)GetStructPointer(data), data->GetLength(), sizeof(EQ2_Color), datastring); + break; + } + case DATA_STRUCT_EQUIPMENT: { + StructAddData((EQ2_EquipmentItem*)GetStructPointer(data), data->GetLength(), sizeof(EQ2_EquipmentItem), datastring); + break; + } + case DATA_STRUCT_ITEM: { + //DumpPacket((uchar*)GetStructPointer(data), data->GetItemSize()); + AddCharArray((char*)GetStructPointer(data), data->GetItemSize(), datastring); + break; + } + } +} + +int8 PacketStruct::getType_int8_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_int8(data_struct, index, force); +} + +int16 PacketStruct::getType_int16_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_int16(data_struct, index, force); +} + +int32 PacketStruct::getType_int32_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_int32(data_struct, index, force); +} + +int64 PacketStruct::getType_int64_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_int64(data_struct, index, force); +} + +sint8 PacketStruct::getType_sint8_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_sint8(data_struct, index, force); +} + +sint16 PacketStruct::getType_sint16_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_sint16(data_struct, index, force); +} + +sint32 PacketStruct::getType_sint32_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_sint32(data_struct, index, force); +} + +sint64 PacketStruct::getType_sint64_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_sint64(data_struct, index, force); +} + +float PacketStruct::getType_float_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_float(data_struct, index, force); +} + +double PacketStruct::getType_double_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_double(data_struct, index, force); +} + +char PacketStruct::getType_char_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_char(data_struct, index, force); +} + +EQ2_8BitString PacketStruct::getType_EQ2_8BitString_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_8BitString(data_struct, index, force); +} + +EQ2_16BitString PacketStruct::getType_EQ2_16BitString_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_16BitString(data_struct, index, force); +} + +EQ2_32BitString PacketStruct::getType_EQ2_32BitString_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_32BitString(data_struct, index, force); +} + +EQ2_Color PacketStruct::getType_EQ2_Color_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_Color(data_struct, index, force); +} + +EQ2_EquipmentItem PacketStruct::getType_EQ2_EquipmentItem_ByName(const char* name, int32 index, bool force) { + DataStruct* data_struct = findStruct(name, index); + return getType_EQ2_EquipmentItem(data_struct, index, force); +} + +int8 PacketStruct::getType_int8(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_INT8) || force)) { + int8* ptr = (int8*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +int16 PacketStruct::getType_int16(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_INT16) || force)) { + int16* ptr = (int16*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +int32 PacketStruct::getType_int32(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_INT32) || force)) { + int32* ptr = (int32*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +int64 PacketStruct::getType_int64(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_INT64) || force)) { + int64* ptr = (int64*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +sint8 PacketStruct::getType_sint8(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_SINT8) || force)) { + sint8* ptr = (sint8*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +sint16 PacketStruct::getType_sint16(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_SINT16) || force)) { + sint16* ptr = (sint16*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +sint32 PacketStruct::getType_sint32(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_SINT32) || force)) { + sint32* ptr = (sint32*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +sint64 PacketStruct::getType_sint64(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_SINT64) || force)) { + sint64* ptr = (sint64*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +float PacketStruct::getType_float(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_FLOAT) || force)) { + float* ptr = (float*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +double PacketStruct::getType_double(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_DOUBLE) || force)) { + double* ptr = (double*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +char PacketStruct::getType_char(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_CHAR) || force)) { + char* ptr = (char*)GetStructPointer(data_struct); + return ptr[index]; + } + return 0; +} +EQ2_8BitString PacketStruct::getType_EQ2_8BitString(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_EQ2_8BIT_STRING) || force)) { + EQ2_8BitString* ptr = (EQ2_8BitString*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_8BitString ret; + ret.size = 0; + return ret; +} +EQ2_16BitString PacketStruct::getType_EQ2_16BitString(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_EQ2_16BIT_STRING) || force)) { + EQ2_16BitString* ptr = (EQ2_16BitString*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_16BitString ret; + ret.size = 0; + return ret; +} +EQ2_32BitString PacketStruct::getType_EQ2_32BitString(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_EQ2_32BIT_STRING) || force)) { + EQ2_32BitString* ptr = (EQ2_32BitString*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_32BitString ret; + ret.size = 0; + return ret; +} +EQ2_Color PacketStruct::getType_EQ2_Color(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_COLOR) || force)) { + EQ2_Color* ptr = (EQ2_Color*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_Color ret; + ret.blue = 0; + ret.red = 0; + ret.green = 0; + return ret; +} +EQ2_EquipmentItem PacketStruct::getType_EQ2_EquipmentItem(DataStruct* data_struct, int32 index, bool force) { + if (data_struct && ((data_struct->GetType() == DATA_STRUCT_EQUIPMENT) || force)) { + EQ2_EquipmentItem* ptr = (EQ2_EquipmentItem*)GetStructPointer(data_struct); + return ptr[index]; + } + EQ2_EquipmentItem ret; + ret.color.blue = 0; + ret.color.red = 0; + ret.color.green = 0; + ret.highlight.blue = 0; + ret.highlight.red = 0; + ret.highlight.green = 0; + ret.type = 0; + return ret; +} + +bool PacketStruct::SetOpcode(const char* new_opcode) { + opcode = EQOpcodeManager[0]->NameSearch(new_opcode); + if (opcode == OP_Unknown) { +#ifndef MINILOGIN + LogWrite(PACKET__ERROR, 0, "Packet", "Warning: PacketStruct '%s' uses an unknown opcode named '%s', this struct cannot be serialized directly.", GetName(), new_opcode); +#endif + return false; + } + return true; +} + +EQ2Packet* PacketStruct::serialize() { + serializePacket(); + + if (GetOpcode() != OP_Unknown) + return new EQ2Packet(GetOpcode(), getData(), getDataSize()); + else { +#ifndef MINILOGIN + LogWrite(PACKET__ERROR, 0, "Packet", "Warning: PacketStruct '%s' uses an unknown opcode and cannot be serialized directly.", GetName()); +#endif + return 0; + } +} + +EQ2Packet* PacketStruct::serializeCountPacket(int16 version, int8 offset, uchar* orig_packet, uchar* xor_packet) { + string* packet_data = serializeString(); + uchar* data = (uchar*)packet_data->c_str(); + int32 size = packet_data->size(); + uchar* packed_data = new uchar[size + 1000]; // this size + 20 is poorly defined, depending on the packet data, we could use a additional length of 1K+ + memset(packed_data, 0, size + 1000); + if (orig_packet && xor_packet) { + memcpy(xor_packet, data + 6, size - 6 - offset); + Encode(xor_packet, orig_packet, size - 6 - offset); + size = Pack(packed_data, xor_packet, size - 6 - offset, size + 1000, version); + } + else + size = Pack(packed_data, data + 6, packet_data->size() - 6 - offset, packet_data->size() + 1000, version); + uchar* combined = new uchar[size + sizeof(int16) + offset]; + memset(combined, 0, size + sizeof(int16) + offset); + uchar* ptr = combined; + memcpy(ptr, data, sizeof(int16)); + ptr += sizeof(int16); + memcpy(ptr, packed_data, size); + if (offset > 0) { + ptr += size; + uchar* ptr2 = data; + ptr2 += packet_data->size() - offset; + memcpy(ptr, ptr2, offset); + } + EQ2Packet* app = new EQ2Packet(GetOpcode(), combined, size + sizeof(int16) + offset); + safe_delete_array(packed_data); + safe_delete_array(combined); + return app; +} + +bool PacketStruct::IsSubPacket() { + return sub_packet; +} +void PacketStruct::IsSubPacket(bool new_val) { + sub_packet = new_val; +} +int32 PacketStruct::GetSubPacketSize() { + return sub_packet_size; +} +void PacketStruct::SetSubPacketSize(int32 new_size) { + sub_packet_size = new_size; +} +void* PacketStruct::GetStructPointer(DataStruct* data_struct, bool erase) { + try { + map::iterator tmpitr = struct_data.find(data_struct); + if (tmpitr != struct_data.end()) { + if (erase) + struct_data.erase(data_struct); + return tmpitr->second; + } + else { + PacketStruct* packet = 0; + vector::iterator itr2; + for (itr2 = arrays.begin(); itr2 != arrays.end(); itr2++) { + packet = *itr2; + if (packet) { + void* tmp = packet->GetStructPointer(data_struct, erase); + if (tmp != 0) + return tmp; + } + } + } + } + catch (...) { + cout << "Caught Exception...\n"; + } + return 0; +} + +vector PacketStruct::GetDataStructs() { + vector ret; + DataStruct* ds = 0; + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + ds = *itr; + if (ds->GetType() == DATA_STRUCT_ARRAY) { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps) { + vector ret2 = ps->GetDataStructs(); + vector::iterator itr2; + for (itr2 = ret2.begin(); itr2 != ret2.end(); itr2++) { + ret.push_back(*itr2); + } + } + } + else if (ds->GetLength() == 0 && ds->GetType() == DATA_STRUCT_ARRAY) { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps) { + vector ret2 = ps->GetDataStructs(); + vector::iterator itr2; + for (itr2 = ret2.begin(); itr2 != ret2.end(); itr2++) { + ret.push_back(*itr2); + } + } + } + else + ret.push_back(ds); + } + return ret; +} + +void PacketStruct::PrintPacket() { + DataStruct* ds = 0; + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + ds = *itr; + if (!ds->AddToStruct()) + continue; + for (int16 i = 0; i < ds->GetLength(); i++) { + cout << "Name: " << ds->GetName() << " \tIndex: " << i << " \tType: "; + switch (ds->GetType()) { + case DATA_STRUCT_INT8: + printf("int8\t\tData: %i", getType_int8_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_INT16: + printf("int16\t\tData: %i", getType_int16_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_INT32: + printf("int32\t\tData: %u", getType_int32_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_INT64: + printf("int64\t\tData: %llu", getType_int64_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_SINT8: + printf("sint8\t\tData: %i", getType_sint8_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_SINT16: + printf("sint16\t\tData: %i", getType_sint16_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_SINT32: + printf("sint32\t\tData: %i", getType_sint32_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_SINT64: + printf("sint64\t\tData: %lli", getType_sint64_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_CHAR: + printf("char\t\tData: %c", getType_char_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_FLOAT: + printf("float\t\tData: %f", getType_float_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_DOUBLE: + printf("double\t\tData: %f", getType_double_ByName(ds->GetName(), i)); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: + printf("EQ2_8BitString\tData: %s", getType_EQ2_8BitString_ByName(ds->GetName(), i).data.c_str()); + break; + case DATA_STRUCT_EQ2_16BIT_STRING: + printf("EQ2_16BitString\tData: %s", getType_EQ2_16BitString_ByName(ds->GetName(), i).data.c_str()); + break; + case DATA_STRUCT_EQ2_32BIT_STRING: { + printf("EQ2_32BitString\tData: %s", getType_EQ2_32BitString_ByName(ds->GetName(), i).data.c_str()); + break; + } + case DATA_STRUCT_ITEM: { + if (ds->GetItemSize() > 0) { + DumpPacket((uchar*)GetStructPointer(ds), ds->GetItemSize()); + } + break; + } + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps) { + cout << "Array:\tData: \n"; + ps->PrintPacket(); + } + break; + } + case DATA_STRUCT_COLOR: { + cout.unsetf(ios_base::dec); + cout.setf(ios_base::hex); + printf("EQ2_Color\tData: "); + EQ2_Color tmp = getType_EQ2_Color_ByName(ds->GetName(), i); + printf("R: %i", tmp.red); + printf(", G: %i", tmp.green); + printf(", B: %i", tmp.blue); + break; + } + case DATA_STRUCT_EQUIPMENT: { + cout.unsetf(ios_base::dec); + cout.setf(ios_base::hex); + printf("EQ2_EquipmentItem\tData: "); + EQ2_EquipmentItem tmp = getType_EQ2_EquipmentItem_ByName(ds->GetName(), i); + printf("type: "); + printf(" ,color R: %i", tmp.color.red); + printf(" ,color G: %i", tmp.color.green); + printf(" ,color B: %i", tmp.color.blue); + printf(" ,hl R: %i", tmp.highlight.red); + printf(" ,hl G: %i", tmp.highlight.green); + printf(" ,hl B: %i", tmp.highlight.blue); + break; + } + } + cout << endl; + } + if (ds->GetLength() == 0 && ds->GetType() == DATA_STRUCT_ARRAY) { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && ps->GetSubPacketSize() != size) { + ps->reAddAll(size); + } + if (ps) { + cout << "Array:\tData: \n"; + ps->PrintPacket(); + } + } + } +} + +void PacketStruct::LoadFromPacketStruct(PacketStruct* packet, char* substruct_name) { + vector::iterator itr; + DataStruct* ds = 0; + char name[512]; + + //scatman (1/30/2012): these declarations are here to get rid of a linux compile error "taking address of temporary" + EQ2_8BitString str8; + EQ2_16BitString str16; + EQ2_32BitString str32; + EQ2_EquipmentItem equip; + + for (itr = structs.begin(); itr != structs.end(); itr++) { + ds = *itr; + for (int16 i = 0; i < ds->GetLength(); i++) { + memset(name, 0, sizeof(name)); + + if (substruct_name) + snprintf(name, sizeof(name), "%s_%s_0", substruct_name, ds->GetName()); + else + strncpy(name, ds->GetName(), sizeof(name)); + name[sizeof(name) - 1] = '\0'; + + switch (ds->GetType()) { + case DATA_STRUCT_INT8: + setData(ds, packet->getType_int8_ByName(name, i), i); + break; + case DATA_STRUCT_SINT8: + setData(ds, packet->getType_sint8_ByName(name, i), i); + break; + case DATA_STRUCT_CHAR: + setData(ds, packet->getType_char_ByName(name, i), i); + break; + case DATA_STRUCT_SINT16: + setData(ds, packet->getType_sint16_ByName(name, i), i); + break; + case DATA_STRUCT_INT16: + setData(ds, packet->getType_int16_ByName(name, i), i); + break; + case DATA_STRUCT_INT32: + setData(ds, packet->getType_int32_ByName(name, i), i); + break; + case DATA_STRUCT_INT64: + setData(ds, packet->getType_int64_ByName(name, i), i); + break; + case DATA_STRUCT_SINT32: + setData(ds, packet->getType_sint32_ByName(name, i), i); + break; + case DATA_STRUCT_SINT64: + setData(ds, packet->getType_sint64_ByName(name, i), i); + break; + case DATA_STRUCT_FLOAT: + setData(ds, packet->getType_float_ByName(name, i), i); + break; + case DATA_STRUCT_DOUBLE: + setData(ds, packet->getType_double_ByName(name, i), i); + break; + case DATA_STRUCT_EQ2_8BIT_STRING: + str8 = packet->getType_EQ2_8BitString_ByName(name, i); + setData(ds, &str8, i); + break; + case DATA_STRUCT_EQ2_16BIT_STRING: + str16 = packet->getType_EQ2_16BitString_ByName(name, i); + setData(ds, &str16, i); + break; + case DATA_STRUCT_EQ2_32BIT_STRING: + str32 = packet->getType_EQ2_32BitString_ByName(name, i); + setData(ds, &str32, i); + break; + case DATA_STRUCT_ARRAY: { + int32 size = GetArraySize(ds, 0); + PacketStruct* ps = GetPacketStructByName(ds->GetName()); + if (ps && size > 0) + ps->LoadFromPacketStruct(packet, substruct_name); + break; + } + case DATA_STRUCT_COLOR: + setColor(ds, packet->getType_EQ2_Color_ByName(name, i), i); + break; + case DATA_STRUCT_EQUIPMENT: + equip = packet->getType_EQ2_EquipmentItem_ByName(name, i); + setEquipmentByName(ds->GetName(), &equip, i); + break; + default: + break; + } + } + } +} + +#ifdef WORLD +void PacketStruct::setItem(DataStruct* ds, Item* item, Player* player, int32 index, sint8 offset, bool loot_item, bool make_empty_item_packet, bool inspect) { + if (!ds) + return; + uchar* ptr = (uchar*)GetStructPointer(ds); + + if(!item) { + if(make_empty_item_packet) { + if(client_version <= 373) { + // for player inspection this will offset the parts of the packet that have no items + uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]); + ds->SetItemSize(sizeOfArray); + memcpy(ptr, bogusItemBuffer, sizeOfArray); + } + else if(client_version <= 561) { + // for player inspection this will offset the parts of the packet that have no items + uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x8C,0x5A,0xF1,0xD2,0x8C,0x5A,0xF1,0xD2,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00}; + int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]); + ds->SetItemSize(sizeOfArray); + memcpy(ptr, bogusItemBuffer, sizeOfArray); + } + else { + // for player inspection this will offset the parts of the packet that have no items + uchar bogusItemBuffer[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00 /*0x68 was item flags*/,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + int sizeOfArray = sizeof(bogusItemBuffer) / sizeof(bogusItemBuffer[0]); + ds->SetItemSize(sizeOfArray); + memcpy(ptr, bogusItemBuffer, sizeOfArray); + } + } + return; + } + PacketStruct* packet = item->PrepareItem(client_version, false, loot_item, inspect); + if (packet) { + int16 item_version = GetItemPacketType(packet->GetVersion()); + // newer clients can handle the item structure without the loot_item flag set to true, older clients like DoF need a smaller subpacket of item + item->serialize(packet, true, player, item_version, 0, (packet->GetVersion() <= 561) ? loot_item : false, inspect); + + string* generic_string_data = packet->serializeString(); + int32 size = generic_string_data->length(); // had to set to 81 + int32 actual_length = size; + + if(client_version <= 373 && make_empty_item_packet && inspect) { + size += 2; // end padding for the specialized item (used for player inspection) + } + else if(offset > 12) { + size += 6; // end padding for the specialized item (used for player inspection) + } + //DumpPacket((uchar*)generic_string_data->c_str(), size); + if (size <= 13) + { + ds->SetIsSet(false); + return; + } + size -= (9 + offset); + if (client_version > 561 && item->IsBag() == false && item->IsBauble() == false && item->IsFood() == false && (offset == 0 || offset == -1 || offset == 2)) + size = (size * 2) - 5; + uchar* out_data = new uchar[size + 1]; + memset(out_data, 0, size+1); + uchar* out_ptr = out_data; + memcpy(out_ptr, (uchar*)generic_string_data->c_str() + (9 + offset), actual_length - (9 + offset)); + //DumpPacket((uchar*)generic_string_data->c_str() + (9 + offset), size); + //without these it will prompt for your character name + if (offset == 0 || offset == -1 || offset == 2) { + if (client_version <= 561 && item->details.count > 0) + out_data[0] = item->details.count; + else + out_data[0] = 1; + } + // + out_ptr += generic_string_data->length() - (10 + offset); + if (client_version > 561 && item->IsBag() == false && item->IsBauble() == false && item->IsFood() == false && (offset == 0 || offset == -1 || offset == 2)) { + out_data[4] = 0x80; + memcpy(out_ptr, (uchar*)generic_string_data->c_str() + (13 + offset), generic_string_data->length() - (13 + offset)); + } + ds->SetItemSize(size); + memcpy(ptr, out_data, size); + safe_delete_array(out_data); + delete packet; + } + //DumpPacket(ptr2, ds->GetItemSize()); +} +void PacketStruct::setItemByName(const char* name, Item* item, Player* player, int32 index, sint8 offset, bool loot_item, bool make_empty_item_packet, bool inspect) { + setItem(findStruct(name, index), item, player, index, offset, loot_item, make_empty_item_packet, inspect); +} +void PacketStruct::setItemArrayDataByName(const char* name, Item* item, Player* player, int32 index1, int32 index2, sint8 offset, bool loot_item, bool make_empty_item_packet, bool inspect) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", index1); + string name2 = string(name).append(tmp); + setItem(findStruct(name2.c_str(), index1, index2), item, player, index2, offset, loot_item, make_empty_item_packet, inspect); +} + +void PacketStruct::ResetData() { + vector::iterator itr; + for (itr = structs.begin(); itr != structs.end(); itr++) { + DataStruct* ds = *itr; + void* ptr = GetStructPointer(ds); + if (!ptr) + continue; + switch (ds->GetType()) + { + case DATA_STRUCT_EQ2_8BIT_STRING: { + EQ2_8BitString* real_ptr = (EQ2_8BitString*)ptr; + real_ptr->size = 0; + real_ptr->data.clear(); + break; + } + case DATA_STRUCT_EQ2_16BIT_STRING: { + EQ2_16BitString* real_ptr = (EQ2_16BitString*)ptr; + real_ptr->size = 0; + real_ptr->data.clear(); + break; + } + case DATA_STRUCT_EQ2_32BIT_STRING: { + EQ2_32BitString* real_ptr = (EQ2_32BitString*)ptr; + real_ptr->size = 0; + real_ptr->data.clear(); + break; + } + case DATA_STRUCT_EQUIPMENT: { + memset(ptr, 0, sizeof(EQ2_EquipmentItem) * ds->GetLength()); + break; + } + case DATA_STRUCT_DOUBLE: { + memset(ptr, 0, sizeof(double) * ds->GetLength()); + break; + } + case DATA_STRUCT_FLOAT: { + memset(ptr, 0, sizeof(float) * ds->GetLength()); + break; + } + case DATA_STRUCT_INT8: { + memset(ptr, 0, sizeof(int8) * ds->GetLength()); + break; + } + case DATA_STRUCT_INT16: { + memset(ptr, 0, sizeof(int16) * ds->GetLength()); + break; + } + case DATA_STRUCT_INT32: { + memset(ptr, 0, sizeof(int32) * ds->GetLength()); + break; + } + case DATA_STRUCT_INT64: { + memset(ptr, 0, sizeof(int64) * ds->GetLength()); + break; + } + case DATA_STRUCT_SINT8: { + memset(ptr, 0, sizeof(sint8) * ds->GetLength()); + break; + } + case DATA_STRUCT_SINT16: { + memset(ptr, 0, sizeof(sint16) * ds->GetLength()); + break; + } + case DATA_STRUCT_SINT32: { + memset(ptr, 0, sizeof(sint32) * ds->GetLength()); + break; + } + case DATA_STRUCT_SINT64: { + memset(ptr, 0, sizeof(sint64) * ds->GetLength()); + break; + } + case DATA_STRUCT_ITEM: { + memset(ptr, 0, 10000 * ds->GetLength()); + break; + } + case DATA_STRUCT_CHAR: { + memset(ptr, 0, sizeof(char) * ds->GetLength()); + break; + } + case DATA_STRUCT_COLOR: { + memset(ptr, 0, sizeof(EQ2_Color) * ds->GetLength()); + break; + } + } + } + vector::iterator itr2; + for (itr2 = arrays.begin(); itr2 != arrays.end(); itr2++) + (*itr2)->ResetData(); +} +#endif diff --git a/source/common/PacketStruct.h b/source/common/PacketStruct.h new file mode 100644 index 0000000..98bca7d --- /dev/null +++ b/source/common/PacketStruct.h @@ -0,0 +1,513 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef __EQ2_PACKETSTRUCT__ +#define __EQ2_PACKETSTRUCT__ +#include "types.h" +#include "DataBuffer.h" +#include "opcodemgr.h" + +#include +#include +#ifdef WORLD +class Item; +class Player; +#endif +extern mapEQOpcodeManager; +using namespace std; + +#define DATA_STRUCT_NONE 0 +#define DATA_STRUCT_INT8 1 +#define DATA_STRUCT_INT16 2 +#define DATA_STRUCT_INT32 3 +#define DATA_STRUCT_INT64 4 +#define DATA_STRUCT_FLOAT 5 +#define DATA_STRUCT_DOUBLE 6 +#define DATA_STRUCT_COLOR 7 +#define DATA_STRUCT_SINT8 8 +#define DATA_STRUCT_SINT16 9 +#define DATA_STRUCT_SINT32 10 +#define DATA_STRUCT_CHAR 11 +#define DATA_STRUCT_EQ2_8BIT_STRING 12 +#define DATA_STRUCT_EQ2_16BIT_STRING 13 +#define DATA_STRUCT_EQ2_32BIT_STRING 14 +#define DATA_STRUCT_EQUIPMENT 15 +#define DATA_STRUCT_ARRAY 16 +#define DATA_STRUCT_ITEM 17 +#define DATA_STRUCT_SINT64 18 + +class DataStruct { +public: + DataStruct(); + DataStruct(DataStruct* data_struct); + DataStruct(const char* new_name, int8 new_type, int32 new_length = 1, int8 new_type2 = DATA_STRUCT_NONE); + DataStruct(const char* new_name, const char* new_type, int32 new_length = 1, const char* new_type2 = 0); + DataStruct(const char* new_name, int32 new_length); + void SetType(const char* new_type, int8* output_type); + void SetType(int8 new_type); + void SetName(const char* new_name); + void SetLength(int32 new_length); + void SetArraySizeVariable(const char* new_name); + void SetDefaultValue(int8 new_val); + void SetMaxArraySize(int8 size); + void SetOversized(int8 val); + void SetOversizedByte(int8 val); + void SetAddToStruct(bool val); + void SetAddType(int8 new_type); + void SetPackedIndex(int8 new_index); + void SetPackedSizeVariable(const char* new_name); + void SetPacked(const char* value); + void SetItemSize(int32 val); + void SetIfSetVariable(const char* variable); + void SetIfNotSetVariable(const char* variable); + void SetIfEqualsVariable(const char* variable); + void SetIfNotEqualsVariable(const char* variable); + void SetIfFlagSetVariable(const char* variable); + void SetIfFlagNotSetVariable(const char* variable); + void SetIsSet(bool val); + void SetIsOptional(bool val); + + int8 GetPackedIndex(); + const char* GetPackedSizeVariable(); + const char* GetArraySizeVariable(); + int8 GetDefaultValue(); + int8 GetOversized(); + int8 GetOversizedByte(); + int8 GetMaxArraySize(); + int8 GetType(); + int8 GetType2(); + const char* GetName(); + string GetStringName(); + int32 GetLength(); + bool AddToStruct(); + int8 GetAddType(); + int32 GetItemSize(); + bool GetIfSet(); + const char* GetIfSetVariable(); + bool GetIfNotSet(); + const char* GetIfNotSetVariable(); + bool GetIfEquals(); + const char* GetIfEqualsVariable(); + bool GetIfNotEquals(); + const char* GetIfNotEqualsVariable(); + bool GetIfFlagSet(); + const char* GetIfFlagSetVariable(); + bool GetIfFlagNotSet(); + const char* GetIfFlagNotSetVariable(); + bool IsSet(); + bool IsOptional(); + int32 GetDataSizeInBytes(); + string AppendVariable(string orig, const char* val); + void AddIfSetVariable(const char* val) { + if (val) { + if_set_variable = AppendVariable(if_set_variable, val); + is_set = true; + } + } + void AddIfNotSetVariable(const char* val) { + if (val) { + if_not_set_variable = AppendVariable(if_not_set_variable, val); + if_not_set = true; + } + } + +private: + bool is_set; + bool if_not_set; + bool optional; + bool if_set; + bool if_not_equals; + bool if_equals; + bool if_flag_set; + bool if_flag_not_set; + string if_flag_not_set_variable; + string if_flag_set_variable; + string if_not_equals_variable; + string if_equals_variable; + string if_not_set_variable; + string if_set_variable; + int8 oversized; + int8 oversized_byte; + bool add; + int8 addType; + int8 maxArraySize; + string array_size_variable; + string name; + int8 type; + int8 default_value; + int8 type2; + int32 length; + int32 item_size; +}; +class PacketStruct : public DataBuffer { +public: + PacketStruct(); + PacketStruct(PacketStruct* packet, bool sub); + PacketStruct(PacketStruct* packet, int16 in_client_version); + ~PacketStruct(); + void add(DataStruct* data); + void reAddAll(int32 length); + void add(PacketStruct* packet_struct); + void addPacketArrays(PacketStruct* packet); + void deletePacketArrays(PacketStruct* packet); + void deleteDataStructs(vector* data_structs); + void setSmallStringByName(const char* name, const char* text, int32 index = 0); + void setMediumStringByName(const char* name, const char* text, int32 index = 0); + void setLargeStringByName(const char* name, const char* text, int32 index = 0); + void setSmallString(DataStruct* data_struct, const char* text, int32 index = 0); + void setMediumString(DataStruct* data_struct, const char* text, int32 index = 0); + void setLargeString(DataStruct* data_struct, const char* text, int32 index = 0); + void renameSubstructArray(const char* substruct, int32 index); + template void setSubstructSubstructDataByName(const char* substruct_name1, const char* substruct_name2, const char* name, Data data, int32 substruct_index1 = 0, int32 substruct_index2 = 0, int32 index = 0) { + char tmp[15] = { 0 }; + sprintf(tmp, "_%i_%i", substruct_index1, substruct_index2); + string name2 = string(substruct_name1).append("_").append(substruct_name2).append("_").append(name).append(tmp); + setData(findStruct(name2.c_str(), index), data, index); + } + template void setSubstructDataByName(const char* substruct_name, const char* name, Data data, int32 substruct_index = 0, int32 index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + setData(findStruct(name2.c_str(), index), data, index); + } + template void setSubstructColorByName(const char* substruct_name, const char* name, Data data, int32 substruct_index = 0, int32 index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + setColor(findStruct(name2.c_str(), index), data, index); + } + template void setSubstructArrayDataByName(const char* substruct_name, const char* name, Data data, int32 index = 0, int32 substruct_index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + setData(findStruct(name2.c_str(), substruct_index, index), data, index); + } + template void setSubstructArrayColorByName(const char* substruct_name, const char* name, Data data, int32 substruct_index = 0, int32 index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + setColor(findStruct(name2.c_str(), index, substruct_index), data, index); + } + template void setDataByName(const char* name, Data data, int32 index = 0, bool use_second_type = false) { + setData(findStruct(name, index), data, index, use_second_type); + } + template void setDataByName(const char* name, Data* data, int32 index = 0, bool use_second_type = false) { + setData(findStruct(name, index), data, index, use_second_type); + } + template void setSubArrayDataByName(const char* name, Data data, int32 index1 = 0, int32 index2 = 0, int32 index3 = 0) { + char tmp[20] = { 0 }; + sprintf(tmp, "%i_%i", index1, index2); + string name2 = string(name).append(tmp); + setData(findStruct(name2.c_str(), index2, index3), data, index3); + } + template void setArrayDataByName(const char* name, Data data, int32 index1 = 0, int32 index2 = 0, bool use_second_type = false) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", index1); + string name2 = string(name).append(tmp); + setData(findStruct(name2.c_str(), index1, index2), data, index2, use_second_type); + } + void setArrayAddToPacketByName(const char* name, bool new_val, int32 index1 = 0, int32 index2 = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", index1); + string name2 = string(name).append(tmp); + DataStruct* data = findStruct(name2.c_str(), index2); + if (data) + data->SetAddToStruct(new_val); + } + void setAddToPacketByName(const char* name, bool new_val, int32 index = 0) { + DataStruct* data = findStruct(name, index); + if (data) + data->SetAddToStruct(new_val); + } + void setAddTypePacketByName(const char* name, int8 new_val, int32 index = 0) { + DataStruct* data = findStruct(name, index); + if (data) + data->SetAddType(new_val); + } + const char* GetOpcodeType(); + bool IsSubPacket(); + void IsSubPacket(bool new_val); + int32 GetSubPacketSize(); + void SetSubPacketSize(int32 new_size); + void SetOpcodeType(const char* opcodeType); + int32 GetArraySizeByName(const char* name, int32 index); + int32 GetArraySize(DataStruct* data_struct, int32 index); + int32 GetArraySize(const char* name, int32 index); + void LoadFromPacketStruct(PacketStruct* packet, char* substruct_name = 0); + bool GetVariableIsSet(const char* name); + bool GetVariableIsNotSet(const char* name); + + int8 getType_int8_ByName(const char* name, int32 index = 0, bool force = false); + int16 getType_int16_ByName(const char* name, int32 index = 0, bool force = false); + int32 getType_int32_ByName(const char* name, int32 index = 0, bool force = false); + int64 getType_int64_ByName(const char* name, int32 index = 0, bool force = false); + sint8 getType_sint8_ByName(const char* name, int32 index = 0, bool force = false); + sint16 getType_sint16_ByName(const char* name, int32 index = 0, bool force = false); + sint32 getType_sint32_ByName(const char* name, int32 index = 0, bool force = false); + sint64 getType_sint64_ByName(const char* name, int32 index = 0, bool force = false); + float getType_float_ByName(const char* name, int32 index = 0, bool force = false); + double getType_double_ByName(const char* name, int32 index = 0, bool force = false); + char getType_char_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_8BitString getType_EQ2_8BitString_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_16BitString getType_EQ2_16BitString_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_32BitString getType_EQ2_32BitString_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_Color getType_EQ2_Color_ByName(const char* name, int32 index = 0, bool force = false); + EQ2_EquipmentItem getType_EQ2_EquipmentItem_ByName(const char* name, int32 index = 0, bool force = false); + + int8 getType_int8(DataStruct* data_struct, int32 index = 0, bool force = false); + int16 getType_int16(DataStruct* data_struct, int32 index = 0, bool force = false); + int32 getType_int32(DataStruct* data_struct, int32 index = 0, bool force = false); + int64 getType_int64(DataStruct* data_struct, int32 index = 0, bool force = false); + sint8 getType_sint8(DataStruct* data_struct, int32 index = 0, bool force = false); + sint16 getType_sint16(DataStruct* data_struct, int32 index = 0, bool force = false); + sint32 getType_sint32(DataStruct* data_struct, int32 index = 0, bool force = false); + sint64 getType_sint64(DataStruct* data_struct, int32 index = 0, bool force = false); + float getType_float(DataStruct* data_struct, int32 index = 0, bool force = false); + double getType_double(DataStruct* data_struct, int32 index = 0, bool force = false); + char getType_char(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_8BitString getType_EQ2_8BitString(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_16BitString getType_EQ2_16BitString(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_32BitString getType_EQ2_32BitString(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_Color getType_EQ2_Color(DataStruct* data_struct, int32 index = 0, bool force = false); + EQ2_EquipmentItem getType_EQ2_EquipmentItem(DataStruct* data_struct, int32 index = 0, bool force = false); + + void setDataType(DataStruct* data_struct, char data, int32 index); + void setDataType(DataStruct* data_struct, int8 data, int32 index); + void setDataType(DataStruct* data_struct, int16 data, int32 index); + void setDataType(DataStruct* data_struct, int32 data, int32 index); + void setDataType(DataStruct* data_struct, int64 data, int32 index); + void setDataType(DataStruct* data_struct, sint8 data, int32 index); + void setDataType(DataStruct* data_struct, sint16 data, int32 index); + void setDataType(DataStruct* data_struct, sint32 data, int32 index); + void setDataType(DataStruct* data_struct, sint64 data, int32 index); + void setDataType(DataStruct* data_struct, float data, int32 index); + void setDataType(DataStruct* data_struct, double data, int32 index); + void setData(DataStruct* data_struct, EQ2_8BitString* input_string, int32 index, bool use_second_type = false); + void setData(DataStruct* data_struct, EQ2_16BitString* input_string, int32 index, bool use_second_type = false); + void setData(DataStruct* data_struct, EQ2_32BitString* input_string, int32 index, bool use_second_type = false); + + template void setData(DataStruct* data_struct, Data* data, int32 index, bool use_second_type = false) { + if (!data_struct) + return; + data_struct->SetIsOptional(false); + int8 type_to_use = (use_second_type) ? data_struct->GetType2() : data_struct->GetType(); + if (type_to_use >= DATA_STRUCT_EQ2_8BIT_STRING && type_to_use <= DATA_STRUCT_EQ2_32BIT_STRING) { + if (type_to_use == DATA_STRUCT_EQ2_8BIT_STRING) { + setSmallString(data_struct, data, index); + } + else if (type_to_use == DATA_STRUCT_EQ2_16BIT_STRING) { + setMediumString(data_struct, data, index); + } + else { + setLargeString(data_struct, data, index); + } + } + else { + if (data_struct && index == 0 && data_struct->GetLength() > 1) { + if (type_to_use == DATA_STRUCT_CHAR) { + for (int32 i = 0; data && i < data_struct->GetLength() && i < strlen(data); i++) + setData(data_struct, data[i], i); + } + else { + for (int32 i = 0; i < data_struct->GetLength(); i++) + setData(data_struct, data[i], i); + } + } + else + setData(data_struct, *data, index); + } + } + template void setData(DataStruct* data_struct, Data data, int32 index, bool use_second_type = false) { + if (data_struct && index < data_struct->GetLength()) { + data_struct->SetIsOptional(false); + int8 type_to_use = (use_second_type) ? data_struct->GetType2() : data_struct->GetType(); + if (use_second_type) { + // Need to figure out why type2 always seems to be 205 + // since only items use type2 for now just hardcoded the value needed (BAD!!!) + //type_to_use = DATA_STRUCT_SINT16; // 9; + data_struct->SetType(type_to_use); + } + switch (type_to_use) { + case DATA_STRUCT_INT8: + setDataType(data_struct, (int8)data, index); + break; + case DATA_STRUCT_INT16: + setDataType(data_struct, (int16)data, index); + break; + case DATA_STRUCT_INT32: + setDataType(data_struct, (int32)data, index); + break; + case DATA_STRUCT_INT64: + setDataType(data_struct, (int64)data, index); + break; + case DATA_STRUCT_SINT8: + setDataType(data_struct, (sint8)data, index); + break; + case DATA_STRUCT_SINT16: + setDataType(data_struct, (sint16)data, index); + break; + case DATA_STRUCT_SINT32: + setDataType(data_struct, (sint32)data, index); + break; + case DATA_STRUCT_SINT64: + setDataType(data_struct, (sint64)data, index); + break; + case DATA_STRUCT_CHAR: + setDataType(data_struct, (char)data, index); + break; + case DATA_STRUCT_FLOAT: + setDataType(data_struct, (float)data, index); + break; + case DATA_STRUCT_DOUBLE: + setDataType(data_struct, (double)data, index); + break; + case DATA_STRUCT_COLOR: + setColor(data_struct, *((EQ2_Color*)&data), index); + break; + case DATA_STRUCT_EQUIPMENT: + setEquipmentByName(data_struct, *((EQ2_EquipmentItem*)&data), index); + break; + case DATA_STRUCT_ITEM: + break; + } + } + } + + template void setSubArrayLengthByName(const char* name, Data data, int32 index1 = 0, int32 index2 = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", index1); + string name2 = string(name).append(tmp); + DataStruct* data_struct = findStruct(name2.c_str(), index2); + setData(data_struct, data, index2); + UpdateArrayByArrayLength(data_struct, index2, data); + } + template void setArrayLengthByName(const char* name, Data data, int32 index = 0) { + DataStruct* data_struct = findStruct(name, index); + setData(data_struct, data, index); + UpdateArrayByArrayLength(data_struct, index, data); + } + template void setSubstructArrayLengthByName(const char* substruct_name, const char* name, Data data, int32 substruct_index = 0, int32 index = 0) { + char tmp[10] = { 0 }; + sprintf(tmp, "_%i", substruct_index); + string name2 = string(substruct_name).append("_").append(name).append(tmp); + + DataStruct* data_struct = findStruct(name2.c_str(), index); + setData(data_struct, data, index); + UpdateArrayByArrayLength(data_struct, index, data); + } + void UpdateArrayByArrayLengthName(const char* name, int32 index, int32 size); + void UpdateArrayByArrayLength(DataStruct* data_struct, int32 index, int32 size); + bool StructLoadData(DataStruct* data_struct, void* data, int32 len, bool useType2 = false, bool create_color = false); + bool LoadPacketData(uchar* data, int32 data_len, bool create_color = false); + bool CheckFlagExists(const char* name); + + void setColorByName(const char* name, EQ2_Color* data, int32 index = 0) { + if (data) + setColorByName(name, data->red, data->green, data->blue, index); + } + void setColorByName(const char* name, EQ2_Color data, int32 index = 0) { + setColorByName(name, data.red, data.green, data.blue, index); + } + void setColor(DataStruct* data_struct, EQ2_Color data, int32 index = 0) { + if (data_struct) { + EQ2_Color* ptr = (EQ2_Color*)struct_data[data_struct]; + ptr[index] = data; + } + } + void setColorByName(const char* name, int8 red, int8 green, int8 blue, int32 index = 0) { + setColor(findStruct(name, index), red, green, blue, index); + } + void setColor(DataStruct* data, int8 red, int8 green, int8 blue, int32 index); + void setEquipmentByName(DataStruct* data_struct, EQ2_EquipmentItem data, int32 index = 0) { + if (data_struct) { + EQ2_EquipmentItem* ptr = (EQ2_EquipmentItem*)struct_data[data_struct]; + ptr[index] = data; + } + } +#ifdef WORLD + void setItem(DataStruct* ds, Item* item, Player* player, int32 index, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false, bool inspect = false); + void setItemByName(const char* name, Item* item, Player* player, int32 index = 0, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false, bool inspect = false); + void setItemArrayDataByName(const char* name, Item* item, Player* player, int32 index1 = 0, int32 index2 = 0, sint8 offset = 0, bool loot_item = false, bool make_empty_item_packet = false, bool inspect = false); +#endif + void setEquipmentByName(const char* name, EQ2_EquipmentItem data, int32 index = 0) { + setEquipmentByName(findStruct(name, index), data, index); + } + void setEquipmentByName(const char* name, EQ2_EquipmentItem* data, int32 size) { + DataStruct* data_struct = findStruct(name, 0); + if (data_struct) { + for (int32 i = 0; i < size; i++) + setEquipmentByName(data_struct, data[i], i); + } + } + void setEquipmentByName(const char* name, int32 type, int8 c_red, int8 c_blue, int8 c_green, int8 h_red, int8 h_blue, int8 h_green, int32 index = 0) { + setEquipment(findStruct(name, index), type, c_red, c_blue, c_green, h_red, h_blue, h_green, index); + } + void setEquipment(DataStruct* data, int16 type, int8 c_red, int8 c_blue, int8 c_green, int8 h_red, int8 h_blue, int8 h_green, int32 index); + void remove(DataStruct* data); + vector* getStructs() { return &structs; } + DataStruct* findStruct(const char* name, int32 index); + DataStruct* findStruct(const char* name, int32 index1, int32 index2); + void remove(const char* name); + void remove(int32 position); + void serializePacket(bool clear = true); + + void AddSerializedData(DataStruct* data, int32 index = 0, string* datastring = 0); + EQ2Packet* serialize(); + EQ2Packet* serializeCountPacket(int16 version, int8 offset = 0, uchar* orig_packet = 0, uchar* xor_packet = 0); + string* serializeString(); + int32 GetVersion() { return version; } + void SetVersion(int32 in_version) { version = in_version; } + bool SetOpcode(const char* new_opcode); + EmuOpcode GetOpcode() { return opcode; } + int16 GetOpcodeValue(int16 client_version); + const char* GetName() { return name.c_str(); } + void SetName(const char* in_name) { name = string(in_name); } + bool LoadedSuccessfully() { return loadedSuccessfully; } + bool IsStringValueType(string in_name, int32 index); + bool IsColorValueType(string in_name, int32 index); + int32 GetTotalPacketSize(); + PacketStruct* GetPacketStructByName(const char* name); + void* GetStructPointer(DataStruct* data_struct, bool erase = false); + void PrintPacket(); + string GetSQLQuery(const char* table_name); + vector GetDataStructs(); + void AddPackedData(); + void ResetData(); + void AddFlag(const char* name); + +private: + PacketStruct* parent; + int32 sub_packet_size; + string opcode_type; + bool sub_packet; + bool loadedSuccessfully; + string name; + EmuOpcode opcode; + int16 version; + int16 client_version; + vector arrays; + vector flags; + map struct_data; + map packed_data; + map struct_map; + vector structs; + vector orig_structs; + vector orig_packets; +}; +#endif \ No newline at end of file diff --git a/source/common/RC4.cpp b/source/common/RC4.cpp new file mode 100644 index 0000000..15d9f78 --- /dev/null +++ b/source/common/RC4.cpp @@ -0,0 +1,93 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "RC4.h" +#include + +static bool g_bInitStateInitialized = false; +static uchar g_byInitState[256]; + +RC4::RC4(int64 nKey) +{ + if( !g_bInitStateInitialized ) + { + for(int16 i = 0; i < 256; i++ ) + g_byInitState[i] = i; + } + Init(nKey); +} + +RC4::~RC4() +{ +} + +void RC4::Init(int64 nKey) +{ + memcpy(m_state, g_byInitState, 256); + m_x = 0; + m_y = 0; + + ulong dwKeyIndex = 0; + ulong dwStateIndex = 0; + uchar* pKey = (uchar*)&nKey; + for(int16 i = 0; i < 256; i++ ) + { + ulong dwTemp = m_state[i]; + dwStateIndex += pKey[dwKeyIndex] + dwTemp; + dwStateIndex &= 0xFF; + m_state[i] = m_state[dwStateIndex]; + m_state[dwStateIndex] = (uchar)dwTemp; + dwKeyIndex++; + dwKeyIndex &= 7; + } +} + +// A = m_state[X + 1] +// B = m_state[Y + A] +// C ^= m_state[(A + B)] + +// X = 20 +// Y = ? +// C = 0 +// m_state[(A + B)] = Cypher Byte + +void RC4::Cypher(uchar* pBuffer, int32 nLength) +{ + int32 nOffset = 0; + uchar byKey1 = m_x; + uchar byKey2 = m_y; + if( nLength > 0 ) + { + do + { + byKey1++; + uchar byKeyVal1 = m_state[byKey1]; + + byKey2 += byKeyVal1; + uchar byKeyVal2 = m_state[byKey2]; + + m_state[byKey1] = byKeyVal2; + m_state[byKey2] = byKeyVal1; + + pBuffer[nOffset++] ^= m_state[(byKeyVal1 + byKeyVal2) & 0xFF]; + } while( nOffset < nLength ); + } + m_x = byKey1; + m_y = byKey2; +} diff --git a/source/common/RC4.h b/source/common/RC4.h new file mode 100644 index 0000000..52a3ea9 --- /dev/null +++ b/source/common/RC4.h @@ -0,0 +1,38 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _EQ2_RC4_H +#define _EQ2_RC4_H +#include "../common/types.h" +class RC4 +{ +public: + RC4(int64 nKey); + ~RC4(); + + void Init(int64 nKey); + void Cypher(uchar* pData, int32 nLen); + +private: + uchar m_state[256]; + uchar m_x; + uchar m_y; +}; +#endif + diff --git a/source/common/TCPConnection.cpp b/source/common/TCPConnection.cpp new file mode 100644 index 0000000..81493b2 --- /dev/null +++ b/source/common/TCPConnection.cpp @@ -0,0 +1,1729 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" + +#include +using namespace std; +#include +#include +#include +using namespace std; + +#include "TCPConnection.h" +#include "../common/servertalk.h" +#include "../common/timer.h" +#include "../common/packet_dump.h" +#include "Log.h" + +#ifdef FREEBSD //Timothy Whitman - January 7, 2003 +#define MSG_NOSIGNAL 0 +#endif + +#ifdef WIN32 +InitWinsock winsock; +#endif + +#define LOOP_GRANULARITY 3 //# of ms between checking our socket/queues +#define SERVER_LOOP_GRANULARITY 3 //# of ms between checking our socket/queues + +#define TCPN_DEBUG 0 +#define TCPN_DEBUG_Console 0 +#define TCPN_DEBUG_Memory 0 +#define TCPN_LOG_PACKETS 0 +#define TCPN_LOG_RAW_DATA_OUT 0 +#define TCPN_LOG_RAW_DATA_IN 0 + +TCPConnection::TCPNetPacket_Struct* TCPConnection::MakePacket(ServerPacket* pack, int32 iDestination) { + sint32 size = sizeof(TCPNetPacket_Struct) + pack->size; + if (pack->compressed) { + size += 4; + } + if (iDestination) { + size += 4; + } + TCPNetPacket_Struct* tnps = (TCPNetPacket_Struct*) new uchar[size]; + tnps->size = size; + tnps->opcode = pack->opcode; + *((int8*) &tnps->flags) = 0; + uchar* buffer = tnps->buffer; + if (pack->compressed) { + tnps->flags.compressed = 1; + *((sint32*) buffer) = pack->InflatedSize; + buffer += 4; + } + if (iDestination) { + tnps->flags.destination = 1; + *((sint32*) buffer) = iDestination; + buffer += 4; + } + memcpy(buffer, pack->pBuffer, pack->size); + return tnps; +} + +TCPConnection::TCPConnection(bool iOldFormat, TCPServer* iRelayServer, eTCPMode iMode) { + id = 0; + Server = iRelayServer; + if (Server) + RelayServer = true; + else + RelayServer = false; + RelayLink = 0; + RelayCount = 0; + RemoteID = 0; + pOldFormat = iOldFormat; + ConnectionType = Outgoing; + TCPMode = iMode; + pState = TCPS_Ready; + pFree = false; + pEcho = false; + sock = 0; + rIP = 0; + rPort = 0; + keepalive_timer = new Timer(SERVER_TIMEOUT); + timeout_timer = new Timer(SERVER_TIMEOUT * 2); + recvbuf = 0; + sendbuf = 0; + pRunLoop = false; + charAsyncConnect = 0; + pAsyncConnect = false; + connection_socket = 0; + recvbuf_size = 0; + recvbuf_used = 0; + recvbuf_echo = 0; + sendbuf_size = 0; + sendbuf_used = 0; +#if TCPN_DEBUG_Memory >= 7 + cout << "Constructor #1 on outgoing TCP# " << GetID() << endl; +#endif +} + +TCPConnection::TCPConnection(TCPServer* iServer, SOCKET in_socket, int32 irIP, int16 irPort, bool iOldFormat) { + Server = iServer; + RelayLink = 0; + RelayServer = false; + RelayCount = 0; + RemoteID = 0; + id = Server->GetNextID(); + ConnectionType = Incomming; + pOldFormat = iOldFormat; + TCPMode = modePacket; + pState = TCPS_Connected; + pFree = false; + pEcho = false; + sock = 0; + connection_socket = in_socket; + rIP = irIP; + rPort = irPort; + keepalive_timer = new Timer(SERVER_TIMEOUT); + timeout_timer = new Timer(SERVER_TIMEOUT * 2); + recvbuf = 0; + sendbuf = 0; + pRunLoop = false; + charAsyncConnect = 0; + pAsyncConnect = false; + recvbuf_size = 0; + recvbuf_used = 0; + recvbuf_echo = 0; + sendbuf_size = 0; + sendbuf_used = 0; +#if TCPN_DEBUG_Memory >= 7 + cout << "Constructor #2 on outgoing TCP# " << GetID() << endl; +#endif +} + +TCPConnection::TCPConnection(TCPServer* iServer, TCPConnection* iRelayLink, int32 iRemoteID, int32 irIP, int16 irPort) { + Server = iServer; + RelayLink = iRelayLink; + RelayServer = true; + id = Server->GetNextID(); + RelayCount = 0; + RemoteID = iRemoteID; + if (!RemoteID) + ThrowError("Error: TCPConnection: RemoteID == 0 on RelayLink constructor"); + pOldFormat = false; + ConnectionType = Incomming; + TCPMode = modePacket; + pState = TCPS_Connected; + pFree = false; + pEcho = false; + sock = 0; + connection_socket = 0; + rIP = irIP; + rPort = irPort; + keepalive_timer = 0; + timeout_timer = 0; + recvbuf = 0; + sendbuf = 0; + pRunLoop = false; + charAsyncConnect = 0; + pAsyncConnect = false; + recvbuf_size = 0; + recvbuf_used = 0; + recvbuf_echo = 0; + sendbuf_size = 0; + sendbuf_used = 0; +#if TCPN_DEBUG_Memory >= 7 + cout << "Constructor #3 on outgoing TCP# " << GetID() << endl; +#endif +} + +TCPConnection::~TCPConnection() { + Disconnect(); + ClearBuffers(); + if (ConnectionType == Outgoing) { + MRunLoop.lock(); + pRunLoop = false; + MRunLoop.unlock(); + MLoopRunning.lock(); + MLoopRunning.unlock(); +#if TCPN_DEBUG_Memory >= 6 + cout << "Deconstructor on outgoing TCP# " << GetID() << endl; +#endif + } +#if TCPN_DEBUG_Memory >= 5 + else { + cout << "Deconstructor on incomming TCP# " << GetID() << endl; + } +#endif + safe_delete(keepalive_timer); + safe_delete(timeout_timer); + safe_delete_array(recvbuf); + safe_delete_array(sendbuf); + safe_delete_array(charAsyncConnect); +} + +void TCPConnection::SetState(int8 in_state) { + MState.lock(); + pState = in_state; + MState.unlock(); +} + +int8 TCPConnection::GetState() { + int8 ret; + MState.lock(); + ret = pState; + MState.unlock(); + return ret; +} + +void TCPConnection::Free() { + if (ConnectionType == Outgoing) { + ThrowError("TCPConnection::Free() called on an Outgoing connection"); + } +#if TCPN_DEBUG_Memory >= 5 + cout << "Free on TCP# " << GetID() << endl; +#endif + Disconnect(); + pFree = true; +} + +bool TCPConnection::SendPacket(ServerPacket* pack, int32 iDestination) { + LockMutex lock(&MState); + if (!Connected()) + return false; + eTCPMode tmp = GetMode(); + if (tmp != modePacket && tmp != modeTransition) + return false; + if (RemoteID) + return RelayLink->SendPacket(pack, RemoteID); + else { + TCPNetPacket_Struct* tnps = MakePacket(pack, iDestination); + if (tmp == modeTransition) { + InModeQueuePush(tnps); + } + else { +#if TCPN_LOG_PACKETS >= 1 + if (pack && pack->opcode != 0) { + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Logging outgoing TCP packet. OPCode: 0x" << hex << setw(4) << setfill('0') << pack->opcode << dec << ", size: " << setw(5) << setfill(' ') << pack->size << " " << inet_ntoa(in) << ":" << GetrPort() << endl; +#if TCPN_LOG_PACKETS == 2 + if (pack->size >= 32) + DumpPacket(pack->pBuffer, 32); + else + DumpPacket(pack); +#endif +#if TCPN_LOG_PACKETS >= 3 + DumpPacket(pack); +#endif + } +#endif + ServerSendQueuePushEnd((uchar**) &tnps, tnps->size); + } + } + return true; +} + +bool TCPConnection::SendPacket(TCPNetPacket_Struct* tnps) { + LockMutex lock(&MState); + if (RemoteID) + return false; + if (!Connected()) + return false; + eTCPMode tmp = GetMode(); + if (tmp == modeTransition) { + TCPNetPacket_Struct* tnps2 = (TCPNetPacket_Struct*) new uchar[tnps->size]; + memcpy(tnps2, tnps, tnps->size); + InModeQueuePush(tnps2); + return true; + } + if (GetMode() != modePacket) + return false; +#if TCPN_LOG_PACKETS >= 1 + if (tnps && tnps->opcode != 0) { + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Logging outgoing TCP NetPacket. OPCode: 0x" << hex << setw(4) << setfill('0') << tnps->opcode << dec << ", size: " << setw(5) << setfill(' ') << tnps->size << " " << inet_ntoa(in) << ":" << GetrPort(); + if (pOldFormat) + cout << " (OldFormat)"; + cout << endl; +#if TCPN_LOG_PACKETS == 2 + if (tnps->size >= 32) + DumpPacket((uchar*) tnps, 32); + else + DumpPacket((uchar*) tnps, tnps->size); +#endif +#if TCPN_LOG_PACKETS >= 3 + DumpPacket((uchar*) tnps, tnps->size); +#endif + } +#endif + ServerSendQueuePushEnd((const uchar*) tnps, tnps->size); + return true; +} + +bool TCPConnection::Send(const uchar* data, sint32 size) { + if (!Connected()) + return false; + if (GetMode() != modeConsole) + return false; + if (!size) + return true; + ServerSendQueuePushEnd(data, size); + return true; +} + +void TCPConnection::InModeQueuePush(TCPNetPacket_Struct* tnps) { + MSendQueue.lock(); + InModeQueue.push(tnps); + MSendQueue.unlock(); +} + +void TCPConnection::ServerSendQueuePushEnd(const uchar* data, sint32 size) { + MSendQueue.lock(); + if (sendbuf == 0) { + sendbuf = new uchar[size]; + sendbuf_size = size; + sendbuf_used = 0; + } + else if (size > (sendbuf_size - sendbuf_used)) { + sendbuf_size += size + 1024; + uchar* tmp = new uchar[sendbuf_size]; + memcpy(tmp, sendbuf, sendbuf_used); + safe_delete_array(sendbuf); + sendbuf = tmp; + } + memcpy(&sendbuf[sendbuf_used], data, size); + sendbuf_used += size; + MSendQueue.unlock(); +} + +void TCPConnection::ServerSendQueuePushEnd(uchar** data, sint32 size) { + MSendQueue.lock(); + if (sendbuf == 0) { + sendbuf = *data; + sendbuf_size = size; + sendbuf_used = size; + MSendQueue.unlock(); + *data = 0; + return; + } + if (size > (sendbuf_size - sendbuf_used)) { + sendbuf_size += size; + uchar* tmp = new uchar[sendbuf_size]; + memcpy(tmp, sendbuf, sendbuf_used); + safe_delete_array(sendbuf); + sendbuf = tmp; + } + memcpy(&sendbuf[sendbuf_used], *data, size); + sendbuf_used += size; + MSendQueue.unlock(); + delete[] (TCPNetPacket_Struct*)*data; +} + +void TCPConnection::ServerSendQueuePushFront(uchar* data, sint32 size) { + MSendQueue.lock(); + if (sendbuf == 0) { + sendbuf = new uchar[size]; + sendbuf_size = size; + sendbuf_used = 0; + } + else if (size > (sendbuf_size - sendbuf_used)) { + sendbuf_size += size; + uchar* tmp = new uchar[sendbuf_size]; + memcpy(&tmp[size], sendbuf, sendbuf_used); + safe_delete_array(sendbuf); + sendbuf = tmp; + } + memcpy(sendbuf, data, size); + sendbuf_used += size; + MSendQueue.unlock(); +} + +bool TCPConnection::ServerSendQueuePop(uchar** data, sint32* size) { + bool ret; + if (!MSendQueue.trylock()) + return false; + if (sendbuf) { + *data = sendbuf; + *size = sendbuf_used; + sendbuf = 0; + ret = true; + } + else { + ret = false; + } + MSendQueue.unlock(); + return ret; +} + +ServerPacket* TCPConnection::PopPacket() { + ServerPacket* ret; + if (!MOutQueueLock.trylock()) + return 0; + ret = OutQueue.pop(); + MOutQueueLock.unlock(); + return ret; +} + +char* TCPConnection::PopLine() { + char* ret; + if (!MOutQueueLock.trylock()) + return 0; + ret = (char*) LineOutQueue.pop(); + MOutQueueLock.unlock(); + return ret; +} + +void TCPConnection::OutQueuePush(ServerPacket* pack) { + MOutQueueLock.lock(); + OutQueue.push(pack); + MOutQueueLock.unlock(); +} + +void TCPConnection::LineOutQueuePush(char* line) { +#if defined(GOTFRAGS) && 0 + if (strcmp(line, "**CRASHME**") == 0) { + int i = 0; + cout << (5 / i) << endl; + } +#endif + if (strcmp(line, "**PACKETMODE**") == 0) { + MSendQueue.lock(); + safe_delete_array(sendbuf); + if (TCPMode == modeConsole) + Send((const uchar*) "\0**PACKETMODE**\r", 16); + TCPMode = modePacket; + TCPNetPacket_Struct* tnps = 0; + while ((tnps = InModeQueue.pop())) { + SendPacket(tnps); + safe_delete_array(tnps); + } + MSendQueue.unlock(); + safe_delete_array(line); + return; + } + MOutQueueLock.lock(); + LineOutQueue.push(line); + MOutQueueLock.unlock(); +} + +void TCPConnection::Disconnect(bool iSendRelayDisconnect) { + if (connection_socket != INVALID_SOCKET && connection_socket != 0) { + MState.lock(); + if (pState == TCPS_Connected || pState == TCPS_Disconnecting || pState == TCPS_Disconnected) + SendData(); + pState = TCPS_Closing; + MState.unlock(); + shutdown(connection_socket, 0x01); + shutdown(connection_socket, 0x00); +#ifdef WIN32 + closesocket(connection_socket); +#else + close(connection_socket); +#endif + connection_socket = 0; + rIP = 0; + rPort = 0; + ClearBuffers(); + } + SetState(TCPS_Ready); + if (RelayLink) { + RelayLink->RemoveRelay(this, iSendRelayDisconnect); + RelayLink = 0; + } +} + +bool TCPConnection::GetAsyncConnect() { + bool ret; + MAsyncConnect.lock(); + ret = pAsyncConnect; + MAsyncConnect.unlock(); + return ret; +} + +bool TCPConnection::SetAsyncConnect(bool iValue) { + bool ret; + MAsyncConnect.lock(); + ret = pAsyncConnect; + pAsyncConnect = iValue; + MAsyncConnect.unlock(); + return ret; +} + +void TCPConnection::AsyncConnect(char* irAddress, int16 irPort) { + if (ConnectionType != Outgoing) { + // If this code runs, we got serious problems + // Crash and burn. + ThrowError("TCPConnection::AsyncConnect() call on a Incomming connection object!"); + return; + } + if (GetState() != TCPS_Ready) + return; + MAsyncConnect.lock(); + if (pAsyncConnect) { + MAsyncConnect.unlock(); + return; + } + pAsyncConnect = true; + safe_delete_array(charAsyncConnect); + charAsyncConnect = new char[strlen(irAddress) + 1]; + strcpy(charAsyncConnect, irAddress); + rPort = irPort; + MAsyncConnect.unlock(); + if (!pRunLoop) { + pRunLoop = true; +#ifdef WIN32 + _beginthread(TCPConnectionLoop, 0, this); +#else + pthread_t thread; + pthread_create(&thread, NULL, TCPConnectionLoop, this); + pthread_detach(thread); +#endif + } + return; +} + +void TCPConnection::AsyncConnect(int32 irIP, int16 irPort) { + if (ConnectionType != Outgoing) { + // If this code runs, we got serious problems + // Crash and burn. + ThrowError("TCPConnection::AsyncConnect() call on a Incomming connection object!"); + return; + } + if (GetState() != TCPS_Ready) + return; + MAsyncConnect.lock(); + if (pAsyncConnect) { + MAsyncConnect.unlock(); + return; + } + pAsyncConnect = true; + safe_delete(charAsyncConnect); + rIP = irIP; + rPort = irPort; + MAsyncConnect.unlock(); + if (!pRunLoop) { + pRunLoop = true; +#ifdef WIN32 + _beginthread(TCPConnectionLoop, 0, this); +#else + pthread_t thread; + pthread_create(&thread, NULL, TCPConnectionLoop, this); + pthread_detach(thread); +#endif + } + return; +} + +bool TCPConnection::Connect(char* irAddress, int16 irPort, char* errbuf) { + if (errbuf) + errbuf[0] = 0; + int32 tmpIP = ResolveIP(irAddress); + if (!tmpIP) { + if (errbuf) { +#ifdef WIN32 + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): Couldnt resolve hostname. Error: %i", WSAGetLastError()); +#else + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): Couldnt resolve hostname. Error #%i: %s", errno, strerror(errno)); +#endif + } + return false; + } + return Connect(tmpIP, irPort, errbuf); +} + +bool TCPConnection::Connect(int32 in_ip, int16 in_port, char* errbuf) { + if (errbuf) + errbuf[0] = 0; + if (ConnectionType != Outgoing) { + // If this code runs, we got serious problems + // Crash and burn. + ThrowError("TCPConnection::Connect() call on a Incomming connection object!"); + return false; + } + MState.lock(); + if (pState == TCPS_Ready) { + pState = TCPS_Connecting; + } + else { + MState.unlock(); + SetAsyncConnect(false); + return false; + } + MState.unlock(); + if (!pRunLoop) { + pRunLoop = true; +#ifdef WIN32 + _beginthread(TCPConnectionLoop, 0, this); +#else + pthread_t thread; + pthread_create(&thread, NULL, TCPConnectionLoop, this); + pthread_detach(thread); +#endif + } + + connection_socket = INVALID_SOCKET; + struct sockaddr_in server_sin; + // struct in_addr in; + + if ((connection_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET || connection_socket == 0) { +#ifdef WIN32 + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): Allocating socket failed. Error: %i", WSAGetLastError()); +#else + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): Allocating socket failed. Error: %s", strerror(errno)); +#endif + SetState(TCPS_Ready); + SetAsyncConnect(false); + return false; + } + server_sin.sin_family = AF_INET; + server_sin.sin_addr.s_addr = in_ip; + server_sin.sin_port = htons(in_port); + + // Establish a connection to the server socket. +#ifdef WIN32 + if (connect(connection_socket, (PSOCKADDR) &server_sin, sizeof (server_sin)) == SOCKET_ERROR) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): connect() failed. Error: %i", WSAGetLastError()); + closesocket(connection_socket); + connection_socket = 0; + SetState(TCPS_Ready); + SetAsyncConnect(false); + return false; + } +#else + if (connect(connection_socket, (struct sockaddr *) &server_sin, sizeof (server_sin)) == SOCKET_ERROR) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::Connect(): connect() failed. Error: %s", strerror(errno)); + close(connection_socket); + connection_socket = 0; + SetState(TCPS_Ready); + SetAsyncConnect(false); + return false; + } +#endif + int bufsize = 64 * 1024; // 64kbyte recieve buffer, up from default of 8k + setsockopt(connection_socket, SOL_SOCKET, SO_RCVBUF, (char*) &bufsize, sizeof(bufsize)); +#ifdef WIN32 + unsigned long nonblocking = 1; + ioctlsocket(connection_socket, FIONBIO, &nonblocking); +#else + fcntl(connection_socket, F_SETFL, O_NONBLOCK); +#endif + + SetEcho(false); + MSendQueue.lock(); + ClearBuffers(); + TCPMode = modePacket; + + MSendQueue.unlock(); + + rIP = in_ip; + rPort = in_port; + SetState(TCPS_Connected); + SetAsyncConnect(false); + return true; +} + +void TCPConnection::ClearBuffers() { + LockMutex lock1(&MSendQueue); + LockMutex lock2(&MOutQueueLock); + LockMutex lock3(&MRunLoop); + LockMutex lock4(&MState); + safe_delete_array(recvbuf); + safe_delete_array(sendbuf); + ServerPacket* pack = 0; + while ((pack = PopPacket())) + safe_delete(pack); + TCPNetPacket_Struct* tnps = 0; + while ((tnps = InModeQueue.pop())) + safe_delete(tnps); + char* line = 0; + while ((line = LineOutQueue.pop())) + safe_delete_array(line); + keepalive_timer->Start(); + timeout_timer->Start(); +} + +bool TCPConnection::CheckNetActive() { + MState.lock(); + if (pState == TCPS_Connected || pState == TCPS_Disconnecting) { + MState.unlock(); + return true; + } + MState.unlock(); + return false; +} + +bool TCPConnection::Process() { + char errbuf[TCPConnection_ErrorBufferSize]; + if (!CheckNetActive()) { + if (ConnectionType == Outgoing) { + if (GetAsyncConnect()) { + if (charAsyncConnect) + rIP = ResolveIP(charAsyncConnect); + Connect(rIP, rPort); + } + } + if (GetState() == TCPS_Disconnected) { + Disconnect(); + return false; + } + else if (GetState() == TCPS_Connecting) + return true; + else + return false; + } + if (!SendData(errbuf)) { + struct in_addr in; + in.s_addr = GetrIP(); + cout << inet_ntoa(in) << ":" << GetrPort() << ": " << errbuf << endl; + return false; + } + if (!Connected()) + return false; + if (!RecvData(errbuf)) { + struct in_addr in; + in.s_addr = GetrIP(); + cout << inet_ntoa(in) << ":" << GetrPort() << ": " << errbuf << endl; + return false; + } + return true; +} + +bool TCPConnection::RecvData(char* errbuf) { + if (errbuf) + errbuf[0] = 0; + if (!Connected()) { + return false; + } + + int status = 0; + if (recvbuf == 0) { + recvbuf = new uchar[5120]; + recvbuf_size = 5120; + recvbuf_used = 0; + recvbuf_echo = 0; + } + else if ((recvbuf_size - recvbuf_used) < 2048) { + uchar* tmpbuf = new uchar[recvbuf_size + 5120]; + memcpy(tmpbuf, recvbuf, recvbuf_used); + recvbuf_size += 5120; + safe_delete_array(recvbuf); + recvbuf = tmpbuf; + if (recvbuf_size >= MaxTCPReceiveBufferSize) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::RecvData(): recvbuf_size >= MaxTCPReceiveBufferSize"); + return false; + } + } + + status = recv(connection_socket, (char *) &recvbuf[recvbuf_used], (recvbuf_size - recvbuf_used), 0); + if (status >= 1) { +#if TCPN_LOG_RAW_DATA_IN >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Read " << status << " bytes from network. (recvbuf_used = " << recvbuf_used << ") " << inet_ntoa(in) << ":" << GetrPort(); + if (pOldFormat) + cout << " (OldFormat)"; + cout << endl; +#if TCPN_LOG_RAW_DATA_IN == 2 + sint32 tmp = status; + if (tmp > 32) + tmp = 32; + DumpPacket(&recvbuf[recvbuf_used], status); +#elif TCPN_LOG_RAW_DATA_IN >= 3 + DumpPacket(&recvbuf[recvbuf_used], status); +#endif +#endif + recvbuf_used += status; + timeout_timer->Start(); + if (!ProcessReceivedData(errbuf)) + return false; + } + else if (status == SOCKET_ERROR) { +#ifdef WIN32 + if (!(WSAGetLastError() == WSAEWOULDBLOCK)) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::RecvData(): Error: %i", WSAGetLastError()); + return false; + } +#else + if (!(errno == EWOULDBLOCK)) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::RecvData(): Error: %s", strerror(errno)); + return false; + } +#endif + } + if ((TCPMode == modePacket || TCPMode == modeTransition) && timeout_timer->Check()) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::RecvData(): Connection timeout"); + return false; + } + + return true; +} + + +bool TCPConnection::GetEcho() { + bool ret; + MEcho.lock(); + ret = pEcho; + MEcho.unlock(); + return ret; +} + +void TCPConnection::SetEcho(bool iValue) { + MEcho.lock(); + pEcho = iValue; + MEcho.unlock(); +} + +bool TCPConnection::ProcessReceivedData(char* errbuf) { + if (errbuf) + errbuf[0] = 0; + if (!recvbuf) + return true; + if (TCPMode == modePacket) { + //if (pOldFormat) + // return ProcessReceivedDataAsOldPackets(errbuf); + //else + return ProcessReceivedDataAsPackets(errbuf); + } + else { +#if TCPN_DEBUG_Console >= 4 + if (recvbuf_used) { + cout << "Starting Processing: recvbuf=" << recvbuf_used << endl; + DumpPacket(recvbuf, recvbuf_used); + } +#endif + for (int i=0; i < recvbuf_used; i++) { + if (GetEcho() && i >= recvbuf_echo) { + Send(&recvbuf[i], 1); + recvbuf_echo = i + 1; + } + switch(recvbuf[i]) { + case 0: { // 0 is the code for clear buffer + if (i==0) { + recvbuf_used--; + recvbuf_echo--; + memcpy(recvbuf, &recvbuf[1], recvbuf_used); + i = -1; + } else { + if (i == recvbuf_used) { + safe_delete_array(recvbuf); + i = -1; + } + else { + uchar* tmpdel = recvbuf; + recvbuf = new uchar[recvbuf_size]; + memcpy(recvbuf, &tmpdel[i+1], recvbuf_used-i); + recvbuf_used -= i + 1; + recvbuf_echo -= i + 1; + safe_delete(tmpdel); + i = -1; + } + } +#if TCPN_DEBUG_Console >= 5 + cout << "Removed 0x00" << endl; + if (recvbuf_used) { + cout << "recvbuf left: " << recvbuf_used << endl; + DumpPacket(recvbuf, recvbuf_used); + } + else + cout << "recbuf left: None" << endl; +#endif + break; + } + case 10: + case 13: // newline marker + { + if (i==0) { // empty line + recvbuf_used--; + recvbuf_echo--; + memcpy(recvbuf, &recvbuf[1], recvbuf_used); + i = -1; + } else { + char* line = new char[i+1]; + memset(line, 0, i+1); + memcpy(line, recvbuf, i); +#if TCPN_DEBUG_Console >= 3 + cout << "Line Out: " << endl; + DumpPacket((uchar*) line, i); +#endif + //line[i] = 0; + uchar* tmpdel = recvbuf; + recvbuf = new uchar[recvbuf_size]; + recvbuf_used -= i+1; + recvbuf_echo -= i+1; + memcpy(recvbuf, &tmpdel[i+1], recvbuf_used); +#if TCPN_DEBUG_Console >= 5 + cout << "i+1=" << i+1 << endl; + if (recvbuf_used) { + cout << "recvbuf left: " << recvbuf_used << endl; + DumpPacket(recvbuf, recvbuf_used); + } + else + cout << "recbuf left: None" << endl; +#endif + safe_delete(tmpdel); + if (strlen(line) > 0) + LineOutQueuePush(line); + else + safe_delete_array(line); + if (TCPMode == modePacket) { + return ProcessReceivedDataAsPackets(errbuf); + } + i = -1; + } + break; + } + case 8: // backspace + { + if (i==0) { // nothin to backspace + recvbuf_used--; + recvbuf_echo--; + memcpy(recvbuf, &recvbuf[1], recvbuf_used); + i = -1; + } else { + uchar* tmpdel = recvbuf; + recvbuf = new uchar[recvbuf_size]; + memcpy(recvbuf, tmpdel, i-1); + memcpy(&recvbuf[i-1], &tmpdel[i+1], recvbuf_used-i); + recvbuf_used -= 2; + recvbuf_echo -= 2; + safe_delete(tmpdel); + i -= 2; + } + break; + } + } + } + if (recvbuf_used < 0) + safe_delete_array(recvbuf); + } + return true; +} + +bool TCPConnection::ProcessReceivedDataAsPackets(char* errbuf) { + if (errbuf) + errbuf[0] = 0; + sint32 base = 0; + sint32 size = 0; + uchar* buffer; + sint32 sizeReq = sizeof(TCPNetPacket_Struct); + ServerPacket* pack = 0; + while ((recvbuf_used - base) >= size) { + TCPNetPacket_Struct* tnps = (TCPNetPacket_Struct*) &recvbuf[base]; + buffer = tnps->buffer; + size = tnps->size; + + if (size < sizeReq || recvbuf_used < sizeReq || size >= MaxTCPReceiveBufferSize) { +#if TCPN_DEBUG_Memory >= 1 + cout << "TCPConnection[" << GetID() << "]::ProcessReceivedDataAsPackets(): size[" << size << "] >= MaxTCPReceiveBufferSize" << endl; +#endif + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(): size provided %i, recvbuf_used %i, checks failed: struct_size < %i || recvbuf_used < sizeReq || size >= MaxTCPReceiveBufferSize", size, recvbuf_used, sizeReq); + return false; + } + if ((recvbuf_used - base) >= size) { + // ok, we got enough data to make this packet! + safe_delete(pack); + pack = new ServerPacket; + pack->size = size - sizeof(TCPNetPacket_Struct); + // read headers + pack->opcode = tnps->opcode; + if (tnps->flags.compressed) { + sizeReq += 4; + if(size < sizeReq || recvbuf_used < sizeReq) + { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(Flags.Compressed): size provided %i, recvbuf_used %i, checks failed: struct_size < %i || recvbuf_used < sizeReq", size, recvbuf_used, sizeReq); + safe_delete(pack); + return false; + } + pack->compressed = true; + pack->InflatedSize = *((sint32*)buffer); + pack->size -= 4; + buffer += 4; + } + if (tnps->flags.destination) { + sizeReq += 4; + if(size < sizeReq || recvbuf_used < sizeReq) + { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(Flags.Destination): size provided %i, recvbuf_used %i, checks failed: struct_size < %i || recvbuf_used < sizeReq", size, recvbuf_used, sizeReq); + safe_delete(pack); + return false; + } + pack->destination = *((sint32*)buffer); + pack->size -= 4; + buffer += 4; + } + // end read headers + if (pack->size > 0) { + if (tnps->flags.compressed) { + // Lets decompress the packet here + pack->compressed = false; + if(pack->InflatedSize < MaxTCPReceiveBufferSize) + { + pack->pBuffer = new uchar[pack->InflatedSize]; + pack->size = InflatePacket(buffer, pack->size, pack->pBuffer, pack->InflatedSize); + if(!pack->size) + { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(InflatePacket): size provided %i, recvbuf_used: %i, sizeReq: %i, could not inflate packet", size, recvbuf_used, sizeReq); + + safe_delete(pack); + return false; + } + } + else + { + cout << "Invalid inflated packet." << endl; + safe_delete(pack); + return false; + } + } + else { + pack->pBuffer = new uchar[pack->size]; + memcpy(pack->pBuffer, buffer, pack->size); + } + } + if (pack->opcode == 0) { + if (pack->size) { +#if TCPN_DEBUG >= 2 + cout << "Received TCP Network layer packet" << endl; +#endif + ProcessNetworkLayerPacket(pack); + } +#if TCPN_DEBUG >= 5 + else { + cout << "Received TCP keepalive packet. (opcode=0)" << endl; + } +#endif + } + else { +#if TCPN_LOG_PACKETS >= 1 + if (pack && pack->opcode != 0) { + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Logging incoming TCP packet. OPCode: 0x" << hex << setw(4) << setfill('0') << pack->opcode << dec << ", size: " << setw(5) << setfill(' ') << pack->size << " " << inet_ntoa(in) << ":" << GetrPort() << endl; +#if TCPN_LOG_PACKETS == 2 + if (pack->size >= 32) + DumpPacket(pack->pBuffer, 32); + else + DumpPacket(pack); +#endif +#if TCPN_LOG_PACKETS >= 3 + DumpPacket(pack); +#endif + } +#endif + if (RelayServer && Server && pack->destination) { + TCPConnection* con = Server->GetConnection(pack->destination); + if (!con) { +#if TCPN_DEBUG >= 1 + cout << "Error relaying packet: con = 0" << endl; +#endif + } + else{ + con->OutQueuePush(pack); + pack = 0; + } + } + else{ + OutQueuePush(pack); + pack = 0; + } + } + base += size; + size = 7; + } + } + safe_delete(pack); + if (base != 0) { + if (base >= recvbuf_used) { + safe_delete_array(recvbuf); + } + else { + uchar* tmpbuf = new uchar[recvbuf_size - base]; + memcpy(tmpbuf, &recvbuf[base], recvbuf_used - base); + safe_delete_array(recvbuf); + recvbuf = tmpbuf; + recvbuf_used -= base; + recvbuf_size -= base; + } + } + return true; +} + +bool TCPConnection::ProcessReceivedDataAsOldPackets(char* errbuf) { + sint32 base = 0; + sint32 size = 4; + uchar* buffer; + ServerPacket* pack = 0; + while ((recvbuf_used - base) >= size) { + buffer = &recvbuf[base]; + memcpy(&size, &buffer[2], 2); + if (size >= MaxTCPReceiveBufferSize) { +#if TCPN_DEBUG_Memory >= 1 + cout << "TCPConnection[" << GetID() << "]::ProcessReceivedDataAsPackets(): size[" << size << "] >= MaxTCPReceiveBufferSize" << endl; +#endif + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::ProcessReceivedDataAsPackets(): size >= MaxTCPReceiveBufferSize"); + return false; + } + if ((recvbuf_used - base) >= size) { + // ok, we got enough data to make this packet! + pack = new ServerPacket; + memcpy(&pack->opcode, &buffer[0], 2); + pack->size = size - 4; + + LogWrite(MISC__TODO, 1, "TODO", "Checksum or size check or something similar\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__); + /* + if () { // TODO: Checksum or size check or something similar + // Datastream corruption, get the hell outta here! + delete pack; + return false; + } + */ + + if (pack->size > 0) { + pack->pBuffer = new uchar[pack->size]; + memcpy(pack->pBuffer, &buffer[4], pack->size); + } + if (pack->opcode == 0) { + // keepalive, no need to process + safe_delete(pack); + } + else { +#if TCPN_LOG_PACKETS >= 1 + if (pack && pack->opcode != 0) { + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Logging incoming TCP OldPacket. OPCode: 0x" << hex << setw(4) << setfill('0') << pack->opcode << dec << ", size: " << setw(5) << setfill(' ') << pack->size << " " << inet_ntoa(in) << ":" << GetrPort() << endl; +#if TCPN_LOG_PACKETS == 2 + if (pack->size >= 32) + DumpPacket(pack->pBuffer, 32); + else + DumpPacket(pack); +#endif +#if TCPN_LOG_PACKETS >= 3 + DumpPacket(pack); +#endif + } +#endif + OutQueuePush(pack); + } + base += size; + size = 4; + } + } + if (base != 0) { + if (base >= recvbuf_used) { + safe_delete_array(recvbuf); + } + else { + uchar* tmpbuf = new uchar[recvbuf_size - base]; + memcpy(tmpbuf, &recvbuf[base], recvbuf_used - base); + safe_delete_array(recvbuf); + recvbuf = tmpbuf; + recvbuf_used -= base; + recvbuf_size -= base; + } + } + return true; +} + +void TCPConnection::ProcessNetworkLayerPacket(ServerPacket* pack) { + int8 opcode = pack->pBuffer[0]; + + + /** disabling RELAY capabilities, this functionality is poorly implemented + even if such a feature needs to be re-used need authentication BEFORE allowing relay to take place + secondly we need to protect the LS accepting new connections as bogus data can be passed to open + fake TCP connections + opcode 0 is OK, that is Keep-Alive + **/ + + if (opcode > 0) + { + Disconnect(); + return; + } + + int8* data = &pack->pBuffer[1]; + switch (opcode) { + case 0: { + break; + } + case 1: { // Switch to RelayServer mode + if (pack->size != 1) { + SendNetErrorPacket("New RelayClient: wrong size, expected 1"); + break; + } + if (RelayServer) { + SendNetErrorPacket("Switch to RelayServer mode when already in RelayServer mode"); + break; + } + if (RemoteID) { + SendNetErrorPacket("Switch to RelayServer mode by a Relay Client"); + break; + } + if (ConnectionType != Incomming) { + SendNetErrorPacket("Switch to RelayServer mode on outgoing connection"); + break; + } +#if TCPC_DEBUG >= 3 + struct in_addr in; + in.s_addr = GetrIP(); + cout << "Switching to RelayServer mode: " << inet_ntoa(in) << ":" << GetPort() << endl; +#endif + RelayServer = true; + break; + } + case 2: { // New Relay Client + if (!RelayServer) { + SendNetErrorPacket("New RelayClient when not in RelayServer mode"); + break; + } + if (pack->size != 11) { + SendNetErrorPacket("New RelayClient: wrong size, expected 11"); + break; + } + if (ConnectionType != Incomming) { + SendNetErrorPacket("New RelayClient: illegal on outgoing connection"); + break; + } + TCPConnection* con = new TCPConnection(Server, this, *((int32*) data), *((int32*) &data[4]), *((int16*) &data[8])); + Server->AddConnection(con); + RelayCount++; + break; + } + case 3: { // Delete Relay Client + if (!RelayServer) { + SendNetErrorPacket("Delete RelayClient when not in RelayServer mode"); + break; + } + if (pack->size != 5) { + SendNetErrorPacket("Delete RelayClient: wrong size, expected 5"); + break; + } + TCPConnection* con = Server->GetConnection(*((int32*)data)); + if (con) { + if (ConnectionType == Incomming) { + if (con->GetRelayLink() != this) { + SendNetErrorPacket("Delete RelayClient: RelayLink != this"); + break; + } + } + con->Disconnect(false); + } + break; + } + case 255: { +#if TCPC_DEBUG >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + cout "Received NetError: '"; + if (pack->size > 1) + cout << (char*) data; + cout << "': " << inet_ntoa(in) << ":" << GetPort() << endl; +#endif + break; + } + } +} + +void TCPConnection::SendNetErrorPacket(const char* reason) { +#if TCPC_DEBUG >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + cout "NetError: '"; + if (reason) + cout << reason; + cout << "': " << inet_ntoa(in) << ":" << GetPort() << endl; +#endif + ServerPacket* pack = new ServerPacket(0); + pack->size = 1; + if (reason) + pack->size += strlen(reason) + 1; + pack->pBuffer = new uchar[pack->size]; + memset(pack->pBuffer, 0, pack->size); + pack->pBuffer[0] = 255; + strcpy((char*) &pack->pBuffer[1], reason); + SendPacket(pack); + safe_delete(pack); +} + +void TCPConnection::RemoveRelay(TCPConnection* relay, bool iSendRelayDisconnect) { + if (iSendRelayDisconnect) { + ServerPacket* pack = new ServerPacket(0, 5); + pack->pBuffer[0] = 3; + *((int32*) &pack->pBuffer[1]) = relay->GetRemoteID(); + SendPacket(pack); + safe_delete(pack); + } + RelayCount--; +} + +bool TCPConnection::SendData(char* errbuf) { + if (errbuf) + errbuf[0] = 0; + /************ Get first send packet on queue and send it! ************/ + uchar* data = 0; + sint32 size = 0; + int status = 0; + if (ServerSendQueuePop(&data, &size)) { +#ifdef WIN32 + status = send(connection_socket, (const char *) data, size, 0); +#else + status = send(connection_socket, data, size, MSG_NOSIGNAL); + if(errno==EPIPE) status = SOCKET_ERROR; +#endif + if (status >= 1) { +#if TCPN_LOG_RAW_DATA_OUT >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Wrote " << status << " bytes to network. " << inet_ntoa(in) << ":" << GetrPort(); + if (pOldFormat) + cout << " (OldFormat)"; + cout << endl; +#if TCPN_LOG_RAW_DATA_OUT == 2 + sint32 tmp = status; + if (tmp > 32) + tmp = 32; + DumpPacket(data, status); +#elif TCPN_LOG_RAW_DATA_OUT >= 3 + DumpPacket(data, status); +#endif +#endif + keepalive_timer->Start(); + if (status < (signed)size) { +#if TCPN_LOG_RAW_DATA_OUT >= 1 + struct in_addr in; + in.s_addr = GetrIP(); + CoutTimestamp(true); + cout << ": Pushed " << (size - status) << " bytes back onto the send queue. " << inet_ntoa(in) << ":" << GetrPort(); + if (pOldFormat) + cout << " (OldFormat)"; + cout << endl; +#endif + // If there's network congestion, the number of bytes sent can be less than + // what we tried to give it... Push the extra back on the queue for later + ServerSendQueuePushFront(&data[status], size - status); + } + else if (status > (signed)size) { + ThrowError("TCPConnection::SendData(): WTF! status > size"); + return false; + } + // else if (status == size) {} + } + else { + ServerSendQueuePushFront(data, size); + } + + safe_delete_array(data); + if (status == SOCKET_ERROR) { +#ifdef WIN32 + if (WSAGetLastError() != WSAEWOULDBLOCK) +#else + if (errno != EWOULDBLOCK) +#endif + { + if (errbuf) { +#ifdef WIN32 + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::SendData(): send(): Errorcode: %i", WSAGetLastError()); +#else + snprintf(errbuf, TCPConnection_ErrorBufferSize, "TCPConnection::SendData(): send(): Errorcode: %s", strerror(errno)); +#endif + } + return false; + } + } + } + if (TCPMode == modePacket && keepalive_timer->Check()) { + ServerPacket* pack = new ServerPacket(0, 0); + SendPacket(pack); + safe_delete(pack); +#if TCPN_DEBUG >= 5 + cout << "Sending TCP keepalive packet. (timeout=" << timeout_timer->GetRemainingTime() << " remaining)" << endl; +#endif + } + return true; +} + +ThreadReturnType TCPConnectionLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("TCPConnectionLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + TCPConnection* tcpc = (TCPConnection*) tmp; + tcpc->MLoopRunning.lock(); + while (tcpc->RunLoop()) { + Sleep(LOOP_GRANULARITY); + if (tcpc->GetState() != TCPS_Ready) { + if (!tcpc->Process()) { + tcpc->Disconnect(); + } + } + else if (tcpc->GetAsyncConnect()) { + if (tcpc->charAsyncConnect) + tcpc->Connect(tcpc->charAsyncConnect, tcpc->GetrPort()); + else + tcpc->Connect(tcpc->GetrIP(), tcpc->GetrPort()); + tcpc->SetAsyncConnect(false); + } + else + Sleep(10); + } + tcpc->MLoopRunning.unlock(); + + THREAD_RETURN(NULL); +} + +bool TCPConnection::RunLoop() { + bool ret; + MRunLoop.lock(); + ret = pRunLoop; + MRunLoop.unlock(); + return ret; +} + + + + + +TCPServer::TCPServer(int16 in_port, bool iOldFormat) { + NextID = 1; + pPort = in_port; + sock = 0; + pOldFormat = iOldFormat; + list = new LinkedList; + pRunLoop = true; +#ifdef WIN32 + _beginthread(TCPServerLoop, 0, this); +#else + pthread_t thread; + pthread_create(&thread, NULL, &TCPServerLoop, this); + pthread_detach(thread); +#endif +} + +TCPServer::~TCPServer() { + MRunLoop.lock(); + pRunLoop = false; + MRunLoop.unlock(); + MLoopRunning.lock(); + MLoopRunning.unlock(); + + while (NewQueue.pop()); // the objects are deleted with the list, clear this queue so it doesnt try to delete them again + safe_delete(list); +} + +bool TCPServer::RunLoop() { + bool ret; + MRunLoop.lock(); + ret = pRunLoop; + MRunLoop.unlock(); + return ret; +} + +ThreadReturnType TCPServerLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("TCPServerLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + TCPServer* tcps = (TCPServer*) tmp; + tcps->MLoopRunning.lock(); + while (tcps->RunLoop()) { + Sleep(SERVER_LOOP_GRANULARITY); + tcps->Process(); + } + tcps->MLoopRunning.unlock(); + + THREAD_RETURN(NULL); +} + +void TCPServer::Process() { + CheckInQueue(); + ListenNewConnections(); + LinkedListIterator iterator(*list); + + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData()->IsFree() && (!iterator.GetData()->CheckNetActive())) { +#if _DEBUG + LogWrite(NET__DEBUG, 0, "Net", "EQStream Connection deleted."); +#endif + iterator.RemoveCurrent(); + } + else { + if (!iterator.GetData()->Process()) + iterator.GetData()->Disconnect(); + iterator.Advance(); + } + } +} + +void TCPServer::ListenNewConnections() { + SOCKET tmpsock; + struct sockaddr_in from; + struct in_addr in; + unsigned int fromlen; + + TCPConnection* con; + + from.sin_family = AF_INET; + fromlen = sizeof(from); + LockMutex lock(&MSock); + if (!sock) + return; + + // Check for pending connects +#ifdef WIN32 + unsigned long nonblocking = 1; + while ((tmpsock = accept(sock, (struct sockaddr*) &from, (int *) &fromlen)) != INVALID_SOCKET) { + ioctlsocket (tmpsock, FIONBIO, &nonblocking); +#else + while ((tmpsock = accept(sock, (struct sockaddr*) &from, &fromlen)) != INVALID_SOCKET) { + fcntl(tmpsock, F_SETFL, O_NONBLOCK); +#endif + int bufsize = 64 * 1024; // 64kbyte recieve buffer, up from default of 8k + setsockopt(tmpsock, SOL_SOCKET, SO_RCVBUF, (char*) &bufsize, sizeof(bufsize)); + in.s_addr = from.sin_addr.s_addr; + + // New TCP connection + con = new TCPConnection(this, tmpsock, in.s_addr, ntohs(from.sin_port), pOldFormat); +#if TCPN_DEBUG >= 1 + cout << "New TCP connection: " << inet_ntoa(in) << ":" << con->GetrPort() << endl; +#endif + AddConnection(con); + } +} + +bool TCPServer::Open(int16 in_port, char* errbuf) { + if (errbuf) + errbuf[0] = 0; + LockMutex lock(&MSock); + if (sock != 0) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "Listening socket already open"); + return false; + } + if (in_port != 0) { + pPort = in_port; + } + +#ifdef WIN32 + SOCKADDR_IN address; + unsigned long nonblocking = 1; +#else + struct sockaddr_in address; +#endif + int reuse_addr = 1; + + // Setup internet address information. + // This is used with the bind() call + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(pPort); + address.sin_addr.s_addr = htonl(INADDR_ANY); + + // Setting up TCP port for new TCP connections + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "socket(): INVALID_SOCKET"); + return false; + } + + // Quag: dont think following is good stuff for TCP, good for UDP + // Mis: SO_REUSEADDR shouldn't be a problem for tcp--allows you to restart + // without waiting for conns in TIME_WAIT to die + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &reuse_addr, sizeof(reuse_addr)); + + + if (::bind(sock, (struct sockaddr *) &address, sizeof(address)) < 0) { +#ifdef WIN32 + closesocket(sock); +#else + close(sock); +#endif + sock = 0; + if (errbuf) + sprintf(errbuf, "bind(): <0"); + return false; + } + + int bufsize = 64 * 1024; // 64kbyte recieve buffer, up from default of 8k + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*) &bufsize, sizeof(bufsize)); +#ifdef WIN32 + ioctlsocket (sock, FIONBIO, &nonblocking); +#else + fcntl(sock, F_SETFL, O_NONBLOCK); +#endif + + if (listen(sock, SOMAXCONN) == SOCKET_ERROR) { +#ifdef WIN32 + closesocket(sock); + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "listen() failed, Error: %d", WSAGetLastError()); +#else + close(sock); + if (errbuf) + snprintf(errbuf, TCPConnection_ErrorBufferSize, "listen() failed, Error: %s", strerror(errno)); +#endif + sock = 0; + return false; + } + + return true; +} + +void TCPServer::Close() { + LockMutex lock(&MSock); + if (sock) { +#ifdef WIN32 + closesocket(sock); +#else + close(sock); +#endif + } + sock = 0; +} + +bool TCPServer::IsOpen() { + MSock.lock(); + bool ret = (bool) (sock != 0); + MSock.unlock(); + return ret; +} + +TCPConnection* TCPServer::NewQueuePop() { + TCPConnection* ret; + MNewQueue.lock(); + ret = NewQueue.pop(); + MNewQueue.unlock(); + return ret; +} + +void TCPServer::AddConnection(TCPConnection* con) { + list->Append(con); + MNewQueue.lock(); + NewQueue.push(con); + MNewQueue.unlock(); +} + +TCPConnection* TCPServer::GetConnection(int32 iID) { + LinkedListIterator iterator(*list); + + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData()->GetID() == iID) + return iterator.GetData(); + iterator.Advance(); + } + return 0; +} + +void TCPServer::SendPacket(ServerPacket* pack) { + TCPConnection::TCPNetPacket_Struct* tnps = TCPConnection::MakePacket(pack); + SendPacket(&tnps); +} + +void TCPServer::SendPacket(TCPConnection::TCPNetPacket_Struct** tnps) { + MInQueue.lock(); + InQueue.push(*tnps); + MInQueue.unlock(); + tnps = 0; +} + +void TCPServer::CheckInQueue() { + LinkedListIterator iterator(*list); + TCPConnection::TCPNetPacket_Struct* tnps = 0; + + while (( tnps = InQueuePop() )) { + iterator.Reset(); + while(iterator.MoreElements()) { + if (iterator.GetData()->GetMode() != modeConsole && iterator.GetData()->GetRemoteID() == 0) + iterator.GetData()->SendPacket(tnps); + iterator.Advance(); + } + safe_delete(tnps); + } +} + +TCPConnection::TCPNetPacket_Struct* TCPServer::InQueuePop() { + TCPConnection::TCPNetPacket_Struct* ret; + MInQueue.lock(); + ret = InQueue.pop(); + MInQueue.unlock(); + return ret; +} + + diff --git a/source/common/TCPConnection.h b/source/common/TCPConnection.h new file mode 100644 index 0000000..ae47526 --- /dev/null +++ b/source/common/TCPConnection.h @@ -0,0 +1,277 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef TCP_CONNECTION_H +#define TCP_CONNECTION_H +/* + Parent classes for interserver TCP Communication. + -Quagmire +*/ + +#ifdef WIN32 + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + + #include +#else + #include + #include + #include + #include + #include + #include + #include + #include + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 + #include "unix.h" + +#endif + +#include "types.h" +#include "Mutex.h" +#include "linked_list.h" +#include "queue.h" +#include "servertalk.h" +#include "timer.h" +#include "MiscFunctions.h" + +class TCPServer; + +#define TCPConnection_ErrorBufferSize 1024 +#define MaxTCPReceiveBufferSize 524288 + +#define TCPS_Ready 0 +#define TCPS_Connecting 1 +#define TCPS_Connected 100 +#define TCPS_Disconnecting 200 +#define TCPS_Disconnected 201 +#define TCPS_Closing 250 +#define TCPS_Error 255 + +#ifndef DEF_eConnectionType +#define DEF_eConnectionType +enum eConnectionType {Incomming, Outgoing}; +#endif + +#ifdef WIN32 + void TCPServerLoop(void* tmp); + void TCPConnectionLoop(void* tmp); +#else + void* TCPServerLoop(void* tmp); + void* TCPConnectionLoop(void* tmp); +#endif + +enum eTCPMode { modeConsole, modeTransition, modePacket }; +class TCPConnection { +public: +#pragma pack(1) + struct TCPNetPacket_Struct { + int32 size; + struct { + int8 + compressed : 1, + destination : 1, + flag3 : 1, + flag4 : 1, + flag5 : 1, + flag6 : 1, + flag7 : 1, + flag8 : 1; + } flags; + int16 opcode; + uchar buffer[0]; + }; +#pragma pack() + + static TCPNetPacket_Struct* MakePacket(ServerPacket* pack, int32 iDestination = 0); + + TCPConnection(TCPServer* iServer, SOCKET iSock, int32 irIP, int16 irPort, bool iOldFormat = false); + TCPConnection(bool iOldFormat = false, TCPServer* iRelayServer = 0, eTCPMode iMode = modePacket); // for outgoing connections + TCPConnection(TCPServer* iServer, TCPConnection* iRelayLink, int32 iRemoteID, int32 irIP, int16 irPort); // for relay connections + virtual ~TCPConnection(); + + // Functions for outgoing connections + bool Connect(char* irAddress, int16 irPort, char* errbuf = 0); + bool Connect(int32 irIP, int16 irPort, char* errbuf = 0); + void AsyncConnect(char* irAddress, int16 irPort); + void AsyncConnect(int32 irIP, int16 irPort); + virtual void Disconnect(bool iSendRelayDisconnect = true); + + virtual bool SendPacket(ServerPacket* pack, int32 iDestination = 0); + virtual bool SendPacket(TCPNetPacket_Struct* tnps); + bool Send(const uchar* data, sint32 size); + + char* PopLine(); + ServerPacket* PopPacket(); // OutQueuePop() + inline int32 GetrIP() { return rIP; } + inline int16 GetrPort() { return rPort; } + virtual int8 GetState(); + eTCPMode GetMode() { return TCPMode; } + inline bool Connected() { return (GetState() == TCPS_Connected); } + inline bool ConnectReady() { return (bool) (GetState() == TCPS_Ready && ConnectionType == Outgoing); } + void Free(); // Inform TCPServer that this connection object is no longer referanced + + inline int32 GetID() { return id; } + inline bool IsRelayServer() { return RelayServer; } + inline int32 GetRemoteID() { return RemoteID; } + inline TCPConnection* GetRelayLink() { return RelayLink; } + + bool GetEcho(); + void SetEcho(bool iValue); +protected: + friend class TCPServer; + virtual bool Process(); + void SetState(int8 iState); + inline bool IsFree() { return pFree; } + bool CheckNetActive(); + +#ifdef WIN32 + friend void TCPConnectionLoop(void* tmp); +#else + friend void* TCPConnectionLoop(void* tmp); +#endif + SOCKET sock; + bool RunLoop(); + Mutex MLoopRunning; + Mutex MAsyncConnect; + bool GetAsyncConnect(); + bool SetAsyncConnect(bool iValue); + char* charAsyncConnect; + +#ifdef WIN32 + friend class TCPConnection; +#endif + void OutQueuePush(ServerPacket* pack); + void RemoveRelay(TCPConnection* relay, bool iSendRelayDisconnect); +private: + void ProcessNetworkLayerPacket(ServerPacket* pack); + void SendNetErrorPacket(const char* reason = 0); + TCPServer* Server; + TCPConnection* RelayLink; + int32 RemoteID; + sint32 RelayCount; + + bool pOldFormat; + bool SendData(char* errbuf = 0); + bool RecvData(char* errbuf = 0); + bool ProcessReceivedData(char* errbuf = 0); + bool ProcessReceivedDataAsPackets(char* errbuf = 0); + bool ProcessReceivedDataAsOldPackets(char* errbuf = 0); + void ClearBuffers(); + + bool pAsyncConnect; + + eConnectionType ConnectionType; + eTCPMode TCPMode; + bool RelayServer; + Mutex MRunLoop; + bool pRunLoop; + + SOCKET connection_socket; + int32 id; + int32 rIP; + int16 rPort; // host byte order + bool pFree; + + Mutex MState; + int8 pState; + + void LineOutQueuePush(char* line); + MyQueue LineOutQueue; + MyQueue OutQueue; + Mutex MOutQueueLock; + + Timer* keepalive_timer; + Timer* timeout_timer; + + uchar* recvbuf; + sint32 recvbuf_size; + sint32 recvbuf_used; + + sint32 recvbuf_echo; + bool pEcho; + Mutex MEcho; + + void InModeQueuePush(TCPNetPacket_Struct* tnps); + MyQueue InModeQueue; + Mutex MSendQueue; + uchar* sendbuf; + sint32 sendbuf_size; + sint32 sendbuf_used; + bool ServerSendQueuePop(uchar** data, sint32* size); + void ServerSendQueuePushEnd(const uchar* data, sint32 size); + void ServerSendQueuePushEnd(uchar** data, sint32 size); + void ServerSendQueuePushFront(uchar* data, sint32 size); +}; + +class TCPServer { +public: + TCPServer(int16 iPort = 0, bool iOldFormat = false); + virtual ~TCPServer(); + + bool Open(int16 iPort = 0, char* errbuf = 0); // opens the port + void Close(); // closes the port + bool IsOpen(); + inline int16 GetPort() { return pPort; } + + TCPConnection* NewQueuePop(); + + void SendPacket(ServerPacket* pack); + void SendPacket(TCPConnection::TCPNetPacket_Struct** tnps); +protected: +#ifdef WIN32 + friend void TCPServerLoop(void* tmp); +#else + friend void* TCPServerLoop(void* tmp); +#endif + void Process(); + bool RunLoop(); + Mutex MLoopRunning; + + friend class TCPConnection; + inline int32 GetNextID() { return NextID++; } + void AddConnection(TCPConnection* con); + TCPConnection* GetConnection(int32 iID); +private: + void ListenNewConnections(); + + int32 NextID; + bool pOldFormat; + + Mutex MRunLoop; + bool pRunLoop; + + Mutex MSock; + SOCKET sock; + int16 pPort; + + Mutex MNewQueue; + MyQueue NewQueue; + + void CheckInQueue(); + Mutex MInQueue; + TCPConnection::TCPNetPacket_Struct* InQueuePop(); + MyQueue InQueue; + + LinkedList* list; +}; +#endif diff --git a/source/common/Web/WebServer.cpp b/source/common/Web/WebServer.cpp new file mode 100644 index 0000000..861ec86 --- /dev/null +++ b/source/common/Web/WebServer.cpp @@ -0,0 +1,336 @@ +#include "WebServer.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../version.h" + +#ifdef WORLD + #include "../../WorldServer/WorldDatabase.h" + extern WorldDatabase database; +#endif +#ifdef LOGIN + #include "../../LoginServer/LoginDatabase.h" + extern LoginDatabase database; +#endif + +#ifdef WIN32 + #include + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include + #include "../unix.h" +#endif + +ThreadReturnType RunWebServer (void* tmp); + +static std::string keypasswd = ""; + +void web_handle_version(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree pt; + + // Add key-value pairs to the property tree + pt.put("eq2emu_process", std::string(EQ2EMU_MODULE)); + pt.put("version", std::string(CURRENT_VERSION)); + pt.put("compile_date", std::string(COMPILE_DATE)); + pt.put("compile_time", std::string(COMPILE_TIME)); + + // Create an output string stream to hold the JSON string + std::ostringstream oss; + + // Write the property tree to the output string stream as JSON + boost::property_tree::write_json(oss, pt); + + // Get the JSON string from the output string stream + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void web_handle_root(const http::request& req, http::response& res) { + res.set(http::field::content_type, "text/html"); + res.body() = "Hello!"; + res.prepare_payload(); +} + +// this function is called to obtain password info about an encrypted key +std::string WebServer::my_password_callback( + std::size_t max_length, // the maximum length for a password + ssl::context::password_purpose purpose ) // for_reading or for_writing +{ + return keypasswd; +} + +//void handle_root(const http::request& req, http::response& res); + +WebServer::WebServer(const std::string& address, unsigned short port, const std::string& cert_file, const std::string& key_file, const std::string& key_password, const std::string& hardcode_user, const std::string& hardcode_password) + : ioc_(1), + ssl_ctx_(ssl::context::tlsv13_server), + acceptor_(ioc_, {boost_net::ip::make_address(address), port}) { + keypasswd = key_password; + // Initialize SSL context + if(cert_file.size() < 1 || key_file.size() < 1) { + is_ssl = false; + } + else { + ssl_ctx_.set_password_callback(my_password_callback); + ssl_ctx_.use_certificate_chain_file(cert_file); + ssl_ctx_.use_private_key_file(key_file, ssl::context::file_format::pem); + is_ssl = true; + } + keypasswd = ""; // reset no longer needed + // Initialize some test credentials + if(hardcode_user.size() > 0 && hardcode_password.size() > 0) + credentials_[hardcode_user] = hardcode_password; + + register_route("/", web_handle_root); + register_route("/version", web_handle_version); +} + +WebServer::~WebServer() { + ioc_.stop(); +} + +ThreadReturnType RunWebServer (void* tmp) { + if(tmp == nullptr) { + THREAD_RETURN(NULL); + } + WebServer* ws = (WebServer*)tmp; + ws->start(); + THREAD_RETURN(NULL); +} + +void WebServer::start() { + do_accept(); + ioc_.run(); +} + +void WebServer::run() { + pthread_t thread; + pthread_create(&thread, NULL, RunWebServer, this); + pthread_detach(thread); +} + + +void WebServer::register_route(const std::string& uri, std::function&, http::response&)> handler, bool auth_req) { + int32 status = database.NoAuthRoute((char*)uri.c_str()); // overrides the default hardcode settings via DB + if(status == 0) { + auth_req = false; + } + if(auth_req) { + routes_[uri] = handler; + } + else { + noauth_routes_[uri] = handler; + } + route_required_status_[uri] = status; +} + +void WebServer::do_accept() { + acceptor_.async_accept( + [this](beast::error_code ec, tcp::socket socket) { + this->on_accept(ec, std::move(socket)); + }); +} + +void WebServer::on_accept(beast::error_code ec, tcp::socket socket) { + if (!ec) { + if(is_ssl) { + std::thread(&WebServer::do_session_ssl, this, std::move(socket)).detach(); + } + else { + std::thread(&WebServer::do_session, this, std::move(socket)).detach(); + } + } + do_accept(); +} + +void WebServer::do_session_ssl(tcp::socket socket) { + try { + ssl::stream stream(std::move(socket), ssl_ctx_); + stream.handshake(ssl::stream_base::server); + + bool close = false; + beast::flat_buffer buffer; + + while (!close) { + http::request req; + + http::read(stream, buffer, req); + + // Send the response + handle_request(std::move(req), [&](auto&& response) { + if (response.need_eof()) { + close = true; + } + http::write(stream, response); + }); + + if (close) break; + } + + beast::error_code ec; + socket.shutdown(tcp::socket::shutdown_send, ec); + } + catch (const std::exception& e) { + // irrelevant spam for now really + } +} + +void WebServer::do_session(tcp::socket socket) { + try { + bool close = false; + beast::flat_buffer buffer; + + while (!close) { + http::request req; + http::read(socket, buffer, req); + + // Send the response + handle_request(std::move(req), [&](auto&& response) { + if (response.need_eof()) { + close = true; + } + http::write(socket, response); + }); + + if (close) break; + } + + beast::error_code ec; + socket.shutdown(tcp::socket::shutdown_send, ec); + } + catch (const std::exception& e) { + // irrelevant spam for now really + } +} + + +template +void WebServer::handle_request(http::request>&& req, std::function&&)> send) { + auto it = noauth_routes_.find(req.target().to_string()); + if (it != noauth_routes_.end()) { + http::response res{http::status::ok, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + it->second(req, res); + return send(std::move(res)); + } + int32 user_status = 0; + std::string session_id = authenticate(req, &user_status); + if (session_id.size() < 1) { + http::response res{http::status::unauthorized, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::www_authenticate, "Basic realm=\"example\""); + res.body() = "Unauthorized"; + res.prepare_payload(); + return send(std::move(res)); + } + + auto status_it = route_required_status_.find(req.target().to_string()); + if (status_it != route_required_status_.end()) { + if(status_it->second > 0 && status_it->second != 0xFFFFFFFF && status_it->second > user_status) { + http::response res{http::status::unauthorized, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.body() = "Unauthorized status"; + res.prepare_payload(); + return send(std::move(res)); + } + } + + it = routes_.find(req.target().to_string()); + if (it != routes_.end()) { + http::response res{http::status::ok, req.version()}; + res.set(http::field::set_cookie, "session_id=" + session_id); + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + it->second(req, res); + return send(std::move(res)); + } +/* + + http::response res{http::status::not_found, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.body() = "Not Found"; + res.prepare_payload(); + return send(std::move(res)); + */ + return send(http::response{http::status::bad_request, req.version()}); +} + + +std::string WebServer::authenticate(const http::request& req, int32* user_status) { + auto it = req.find(http::field::cookie); + if (it != req.end()) { + std::istringstream cookie_stream(it->value().to_string()); + std::string session_id; + std::getline(cookie_stream, session_id, '='); + if (session_id == "session_id") { + std::string id; + std::getline(cookie_stream, id); + if (sessions_.find(id) != sessions_.end()) { + if(sessions_status_.find(id) != sessions_status_.end()) { + *user_status = sessions_status_[id]; + } + return id; + } + } + } + + it = req.find(http::field::authorization); + if (it != req.end()) { + std::string auth_header = it->value().to_string(); + if (auth_header.substr(0, 6) == "Basic ") { + std::string encoded_credentials = auth_header.substr(6); + std::string decoded_credentials; + decoded_credentials.resize(boost::beast::detail::base64::decoded_size(encoded_credentials.size())); + auto result = boost::beast::detail::base64::decode( + &decoded_credentials[0], + encoded_credentials.data(), + encoded_credentials.size() + ); + decoded_credentials.resize(result.first); + + std::istringstream credentials_stream(decoded_credentials); + std::string username, password; + std::getline(credentials_stream, username, ':'); + std::getline(credentials_stream, password); + int32 out_status = 0; + if ((credentials_.find(username) != credentials_.end() && credentials_[username] == password) || (database.AuthenticateWebUser((char*)username.c_str(),(char*)password.c_str(), &out_status) > 0)) { + std::string session_id = generate_session_id(); + sessions_[session_id] = username; + sessions_status_[session_id] = out_status; + *user_status = out_status; + return session_id; + } + } + } + + return std::string(""); +} + +std::string WebServer::generate_session_id() { + static std::mt19937 rng{std::random_device{}()}; + static std::uniform_int_distribution<> dist(0, 15); + std::string session_id; + for (int i = 0; i < 32; ++i) { + session_id += "0123456789abcdef"[dist(rng)]; + } + return session_id; +} + +// Explicit template instantiation +template void WebServer::handle_request>( + http::request>>&&, + std::function&&)> +); \ No newline at end of file diff --git a/source/common/Web/WebServer.h b/source/common/Web/WebServer.h new file mode 100644 index 0000000..e30ae90 --- /dev/null +++ b/source/common/Web/WebServer.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../types.h" + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace boost_net = boost::asio; // from +namespace ssl = boost::asio::ssl; // from +using tcp = boost_net::ip::tcp; // from + +class WebServer { +public: + WebServer(const std::string& address, unsigned short port, const std::string& cert_file, const std::string& key_file, const std::string& key_password, const std::string& hardcode_user, const std::string& hardcode_password); + ~WebServer(); + + void run(); + void start(); + + void register_route(const std::string& uri, std::function&, http::response&)> handler, bool auth_required = true); +private: + bool is_ssl; + static std::string my_password_callback(std::size_t max_length, ssl::context::password_purpose purpose); + void do_accept(); + void on_accept(beast::error_code ec, tcp::socket socket); + void do_session_ssl(tcp::socket socket); + void do_session(tcp::socket socket); + + template + void handle_request(http::request>&& req, std::function&&)> send); + + std::string authenticate(const http::request& req, int32* user_status = 0); + std::string generate_session_id(); + + boost_net::io_context ioc_; + ssl::context ssl_ctx_; + tcp::acceptor acceptor_; + std::unordered_map sessions_; // session_id -> username + std::unordered_map sessions_status_; // session_id -> status + + std::unordered_map credentials_; // username -> password + std::unordered_map route_required_status_; // route -> status + std::unordered_map&, http::response&)>> routes_; + std::unordered_map&, http::response&)>> noauth_routes_; +}; diff --git a/source/common/Web/WebServer.o b/source/common/Web/WebServer.o new file mode 100644 index 0000000000000000000000000000000000000000..39f6920696f2feaa595d856b4797c9607520d8d9 GIT binary patch literal 10894152 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 z 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

CBj{H4(+a#y`-rz%~5;7_>p^e}jV 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�V%k`wUV&h3LMyx#uZ2@&T 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

SNBt2*a{|bV7>kN3*-E!t2MBWl$T`Sp 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

1SK|91yPY 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±|TyCi3hP&J_$sO);he_@Tmpfc?BV2BTq#S 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 z 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

vj+1pylze&54UHDYZg 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

%#!4 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ⅈj(BM99UDx40PS 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¥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৯#N z-b<|>5r~Uts|(O^eIQ^^)_Ue)yv@e9>S@lpN2>RFT~xXI1E6`Q;!S})djj>75ZnoS z=2#B3<6VgAq`wCIiEhfr!e88~%Nles0OZjJU>F9?#=T&>Gc$ea!L)3f7?fQcN7B?L;Hdc2BJiRs0_#A?L9V?1kWJ8?JN>*9H z=?sl=I(=Xs!>RdI>_9$n)dTfSGIo%PMjhk7Ahz<5K3yVLfJyU!C11(MqQmktHQok( z7_2Wd;O0mZxeHYfCmcbKCKj6L+Kf2!3wtFOqs{to8EnMgm5@Jm2(J0JsPdJhG> zQPv?5!c5eJg|-|V$ffZ4TEZBq7HcKoPA|wktv#X@g`7Q*hyFZWf@_B;%L|x=BRGl! z3x)B(u7(f+djN_1ME6y)15T((Cd?CfjHOnUM`>PV&Y`rLy)X)E^?I?vgntJjOo;IAP3U`P_(lDef(Zkm315i53xHg+D1H;=m z)}o%b`Y3E5tY@~Q{4@5Ec3qQ2zS<||*J14Uhk;rUmUCfOrPGq> z4Jd7n)`inDPFEXX-OR4jS{wS|c=ai+#)4Gd zX9>#U3Gvxzd<1xrf5pabd>hP02+9gHF^XrS@i`hQ7kJ|B+oi+1y`ASN@B}OQ<|ActN6$ajy$lcNVzJ^9&cJ^fUqE zbbkO*oE$YE9sl$#JO7!~#rPcNKd4IP1!|XK<#(at9WtIq!}ro5L15Xzw7CSK{8UR9 zhkf`5B@+X&SS%+|@!MFhBr&!@Vfi}IQt~3y7m)k-GnuPj(e_LWv8cijaM`8oX;eNC z>8>3&BZhg5c-BJq?ZiI5;)+Ghzdn^s>I~sKIukaQN-yl-bc^?pl0(@i}QgukdW!D=z z*{t+iwUBn;P+g%zbgS_Ou;~hgq5)XP!FP&W+Aie?Lkz&+ezBW;0ei5QoFv5yB&}um zi9m>_#GVfUy18+ggl_5we^0XoGWaNFM&X{!Pvlc(;0J+Pswx@JI@7XZgRqR~L2k&F z75hgTxz{ErOe10g7|EpW1@Y>FCK1(8QShTcLCSOa9xl9@|0oVw7ma%ReV}jvC8--tBTK16)G*)D&rrmDt?!$z}L`6kWd8G zR~T3F5Kc9KY)$lj^NbJ6X417Aj5qRB)}@crw=#{?8snd>+CE9&Qi?9Svj6L@>``ZB z|DRRa2c4Du)T-=bvNC$`eB!340wCh#-Hm=ZPP(>=+SYLIutTvK8r@;1AnN(S(Q!6WCbbuUkaK zWJpZ9JvMc`z^EV&}QZf*AFe=Y~o`e24$db6p z@NihTXalB{cwl^%?^|Si{NW?U5+_>mB#dqqcLCaXL0Zc`ukEg9p_>O#WGi^tmf+CiNQbBw zeGNsC62J=Fy2s5G{|tDohdJMM;9V=_IROSe=l1xYn3v@^_bbM@$F-K6vXs=fPe4iC zPD<)|sif8$pJ5!j@v=mr+e%)hih95CD?k&YCFRCH0L8u0_@t$xzND$Be*_h^o>bHe zIjgAk#z!q3^0T%M87PMqi;o1{$|{b@}EHvl;w!fXaG!rDU^ym}3EyLOO5+Ibv}ksEVMU|*?Ny&A3) z@Oj@Nx5Rtgw&e>5xI+XRIUAIb*mUpkl)`Z(j(2Nm&r+msrhD^yKQai3>tDm^eG86~ zij+<(^X~{7r!qKDmCBQ*N27~mqwjDwnmu8&rqP;aMKOWfKpdw`!yd2H%sEN}k!A8g z>rrBEq~@mAC~IPFdQ8jo7{7u(S2DfrW{ z^rs};EDydWuG=fhp8=K&43p*0$b&1~V0-w}esN>)r%=vc32derIL6eSDjLX4Dor zYY){Wm20%jwMgK1M=N)N@911kZ5@W!lE699GI5)+vP@KiGy}9D10rv&#L+yi0d!tj z;D>34-e#Hc=s@r>@&_$3{@;AnS#P9Z+NI7qPv|0z;?JwI(hZO`eGuFnn}ZkfewSRs z-%FjB0Hm`N>)SZ2;8Qq-IPphZ^fv)-nzt9u*A0xvT5SCG0zeJ{L&1x%8|N$9hNh*U z({~R~AVWGFLdSu82P<=-jspRf#06?3I`E|s9X5|9FfrFVMaMtEbh;9`mc;qM))Mml zF%j24X~-EeGdBP^a~rJDH=|!}qd0M{R?)-Zxh}Cog?_X=XgQ4&)t@)@e&iYQeTfKSKSCt!GoSVIcn@ua^a z9_;Mt?+6aP5I$A!^miYSSIYr%QR(k6Wn5qb4UD6!KQE%2cVb(sJ3%>hND1QZ($NI} z55KUlMikMoLBn&|#=J_;}z*F_417=)$@wG?pB> z{WjrWC#9YK&_9OB2vq&AG0_u{hWkX}>tYEq6JV0=6UEqG@uF%Z@Fe-i$iT~APZuh{ zpJzeF^!vmJg@K3W0K$d)zz$U>{2QgBqCd7mQa@l*t^aoC8z^3f3!uBiPE%iTN1XkrVRK!}MU#QW+1va4TNB+`E$JP|1i`_(R8d z7vCq=(DVe>5*6<(M#TdV3FQ*Lt+(QvsNf7{8$q?7B5cfp-&ZfNio=!1sy8 z=#NIR>TasxAMnu7Y9bB)M`%SzFz&95WF@y?V zq>v({k(&p0ROUPxLvciHF4YNEaZz)bG@l0K;8ur84M+wcr7QEPcG&}GrghJ+ETsjp z@`qTth%aP9lWSA)e7|<2xeW*Lbn->RZ8RXt&~;})^=4yTT$G`g>EO2J5>eBwc?5I5 zoZQnqgfmmm%sl0JjVSeMzx7nLXY}5C^Ig^aA-@NaVB=rD@ep`s2>|m&PJyh@*zZE} z1BYSYOTd?GvbmCC#Ff;guqL|5(g=GspU`ixBdf^e$Z|n6&Z2Fk$q~g7>!5o58d3#Q z!e#73pxTHu>}Vdc4+Q^Ma z<=1=Uj$`EZa+Bp$n&BbSe5*XO12>{Qhy;`k8gtaMR}oTGjL~CnmF1DWqiQGL*Bhpz zudmfLnA?ncYrN&PyRCSw8}J7DgX6=AdhM~ZcE~kp`#Tw`h*scV#{%7ep2lLX(RZdQ zy0U?GQ|&bM6qJBwvG@{MoS57hY6ZoE`eEf^(-lsc9K{LW1$}*|bbO46`Bg}EIn*rX ze}zCojJMGS?`5|5Tz^Sa06?d5vuQ>C702O)Ajnt=`xltE=>bQO?FhdR(!s58v0%u= zVd~VZFMP^s=pf7fsb4(kR2lyRkAi8JDn(pOBiRlP6LqBuryECcKTgE~*@dn^+iQQ| zbVUS#qK#t9MG=Of4D1Tm(M!KcM-G8mu=6O=kIV%J-QRh+#5tp|bSTu>v39PEd+Ats zH|>en{#4SY$(#(3;8SBxc+Uy%G$Lwl*OfczTYA@>6O{@(u5E&IqH>mq%CBn%L(hz3 zbA^Z|)CF!B1Ms33A8*!5{k}|Q&_C6T&%Xh5U@6ji!`+;R5W+z?t~*6uh1jqUo?Cpv z|K}M4@RS;b+oeF(YxvG0;_G-3X`GZ6?GCm9oj+mc%2hb zL$Uk?63HPU@M}Q3fSO@Yl)~?Qf@vl4fXoV>JRz3dOUaS*KZfR~fUzTvaH_BLO#wz^4WL34m@aKr|>Y1#}QbCN_cxSZslrE=EuRdL+;V zq|XHy`c*vMK<4bx(q_;9MVnJYzx}Mby(kR`bf?^Q+&g9y74Uhy<f3Q4O$C;>@7{3r7y>CXE zSL*De5!pwJjXy-j#=;YF1i$>CYT1k8%vacoa+w>5kAZyS%DZ_a=?^uU{Lvaw{=*to z{zHu{e?IHy(WTGe%%ES$-pE%LA_TaOKPieg{tTKo8gc&2U`6^gkbg=nf>p;o_FnH* zXgsUN9{1%@(`o$qa|=i?eonh@o;?})4@cAUj0Z*-Ny!%^nvy;OQAxjK%kz+AoEx5J zp)0v`y5V_-)$qK%Fh{u>zJVugGpJEsaLw65M09+Y8RiHTNAN>4R9hY)K`&W}&gQEU*b+E&Pzk zid=XTDKBiku>dcZ68X&nglS?xwguQCmo@Rvf%4n{PRhbS%@WziI{FBN0R-O@ugOFt zr$osm>_ry{F;4icN8uv|MFEi=A(pv;at!BTs@y=o^T80uLmH?@dJ-xDgc$ugM?X#z z9;{HmbY5KwWxZ5j^pzPUNQhC6?s=zJ_-(QHlqfhNmh9)I0R83qdpU}Ls?x%mp{@7h zPPG4mC^(P(7nd)(@6c-~xL=ge!*0Y|FAMy_a_9yGZW|VBpM|~yRRyn2e?M#285RXk zi-mL#KLYP!^n>mPP+VhinKW?fP_1V%WUwcfVW<>{^<8ew;x&xa+c0kqTl}Lsxay;* z;U-wucZ`TS{&`!YH-~6?{t*>wA>dm0d76HT({iqTMO$Z2&G(BNuYf^As%`o*)IbmV zWo?z}K%@++@1IV&Ej>rm`>ZXlAz40U_$7%KP*;$XDUc87 zDE&Tl0ThE9*d3Kmv~ip}HlJvRi(iLmX%?&gKop*%+$v%*K}JCwXC#1Peg~E{_}S+h zy$}W)f-l30eeDknOcibo+|=eRtx|d)&%w%0{E`J1P0D~i53?K zT}pSmGO&JyScM1vCn-7XJRqKtX4dl(xFm6DA$)da5AZKx^x)iLlp-*}*pI`~4Mxooq-fhA zdEF?v0rR@)4xgK7|J!@uGNVRF$U$*S@65bAr5OpoT4FKr-qoAr%}wG zPl1`~Z8cH8~I)&SG+10I2>7cf8n|!U_RK z7| z#FLsyr>A5lwN3F%niKd}VMm`Tg=}u{uHnI(li}Br3=h5mW#G0FsgcjEUj>x87LQXn z^SXjY{un1)P8hst7`D1_F?>r&U|2h)+NYO(UY359j(&c!1Z;4Y)?tp6b{rT=))V~sEx;`o1$WX62fN!9d34s_1qL!y1#2Hnw21gEc}lDq6iY$% z{Rqy*YpH4u@6(SPn)wRWsfU~vhga#ON;#!B60;nV_}nrqHtxv!L6pzrQ_#(C`KdA{ z-eYFw32``r^FC0q+?pHrS|75{mduGX!TB@APxqKtmnl^zNg#wi^E%SYK=7c9k~=r4 zSXvZv`On8IO_0F`z74p!M-eEOX{{H_Q1oW8@m`WAWsoN+n8{xds~}Hce;V*^?3_$B zuA)qRGW%MY$wf0=&CplI++=f1HsoNL*$sS(MPrWY!xTULEj6`(F33qYobjoJ9L(AF zd|QrXewYDuVCEV&Hz6~EnXcl-dTBxv(w?H@n3I$#G-El;*Q*^j|#wP;qxvAx*M+U4G1{C@g0Ed17Ttej))sR2~+`4V0=c_ ztNl6v@a1SnHA))*lh92vGCH8Dct(h0@*RF7;En?zVE?W^L7GU}VjU@84pz@+?VM1# zpb3e*w)E#@nX?YtI?Db_e|;x#b+;%!+`RypuxlFtdlz5lOTfyl1gg=XTyW#bqdkVwKE`Do zy8hol*;qu_bpc=}NZNqC;iSl?Ww1HW;bI*DPk^?eU#G<45Ch2pi$HFf`A<<+^d+Js z4D=3IBdG|eEuWTP2*7P!VZlFJ%wfUvE=erurxJkLQU_XCp||H7A6}eBZ~s%jLT`V1vC5x%p?^do%}*^p1610B z03jQ|Z00HR%XotX##>GwFb#60Uv?wbkgM0lcD4d`t92J?RxSbP3S^i&$jWt#w;lKU zSS-zF#eR!9O|x&E#7bEV;UGYL#Q3fuw}JLmNo;qBA$}fBl~#?J};)v zFtoo{6oL_CbM!ORpk^(`|uDKob>%@mDaBoo#sn zDAc+u;ni^PIU*qLvoZg~olrW}%SpV;^XA%2U=i34!4 zQ?N|nx9}q`KS%^E`Ovb;>32RNF6d(Z_rb8LGm)|?Qx4mfQ?;A z5jGI*;riFajm@GIMB~^eSTEIlW|>4Gv$NS=owVKANhiEF?FseKL??{Lj0mhR!IXF` zpytGs*ZhY-?y(v?JBKa%`|c>ArME7L>HWwhkHwd@FaOPY<7ue-*R8r|XK&863eg|q zlyg%$JCHX;1n!;HD@_-WKk*UYESN%rD%?a9{F&q!m|d>J(SNYH4qVWPSpkqnfLNZ1 zp!FvqLCq3uYpVe185TRRTZ4Ly%p3k@8COVge@6Z=e&#`;z_0xLEX-@1Q~33>;OVpS zRgWWJ%w|(F-Dj7-!ggEuy4*RnOXmo$u=t`VOc4U=tb8r4%)jCGRj*Un0!o1_UhxNO zr9KFBQkI7x?C-KDyl6F`*m>UhGgOXE8SL!^IBl{S%0zsvgP1s7q8c$3%2btE=CUZo z@iir?vDbs?UhaYV09^}HL0hg~bYIgx7kHF;ifg0nm@iXR=4#KoQD>E!n^RS0=H~eA zkWz|XTVDhurCwJ;ijEi%QY@=dQb?s(NFDJIQhG(#!fJvjcO5BalVF{O#Wbl!d2I;# zDPEG3UfOS(jb|1tl}SzCsaj4mvd1+f7b5Trwb>I5dDdx|*EMIfJ8kAe>ax3r;Dat9%CsI$&1u-7g` zB3y;DIO3+v1e}qts%*VS+@Ppom=ketIY5buVQdmDK4`c2m}>F5nP{;`S2l82246y_ z9VwMY+S%G^bHgmH*l)s#If#JoW{j*E?fBXGK?Z|38h65}|qF~J(K%y@7+Xw*80ZdwE@#j-8wzd~Y( zgY{Z}^B*sw2R;buw^u~oJ1QuNuJy6-v?wC3?H&9S>J?+YYPOCH(V&VNrRnd0bEQ0rH{tqeZ@9GNzRM92(A`Blm=#k)B48dWsw2EmZgKMQ=)^Wqx zc}hR0A$lu0km@3Z>>@X_aY6d9-lq-fm3crbj)*m<A;l7N{0WVk8-sxy@hFcD!Gjk8tBqf% z4(tbl*aX2t2rc85seoPsjO!xp{T8}yK6qtA)cNRw`6CQox{8I|52fCLL&DUgsF<%g*)CFl?5f|zHa*FETGYhe?K1)A(zGG5h=|7%I`)TeVpl=ue$)K_I62R{yuCCP7?*i zRK6fqUd;P6V#17l(8fTHocCSeq>Sr^>B}`n#}QoVD}w?I{_&m0K#DFeH#S1mdq?`tOjQy<@Y-am0X>scKsB1{zlK6M4^|jSnf=+J5vI)k`}_bn$;B(O2Xxf< z4rB&-ozQL;GW%gSOdh~&u^54N%0#09>O7Iuu1%d$7|^fQAYSGN66D6vT@7vYzY)n@ zBvvrN%}qZQ6)CaeX0bGIdy&}isMrBbBs?OPcAWf@D7{Y~h6 zwp_8*OT%h;(alb%-prdWQ(KI{+e+o3;CYOlb3)FV4h4+R4*T-%Z?Lhm3YBs7jV#~u zC{6KQQ4tQ57txvv zUN1_K_(ETFrN)Ej##nDtJd$eG%!(JD&`{0XO`{9(QDpCQPlX9}!8klY&Kws{Y7%pd zXvhi1BkI%euW{rocwHWxi^SG^*o}nBjGRMJZD{_Tx5ZW9csxPWZ|>0<_FkwPuoK=P z3f9Y=P=EHfUCj)S*379Xn&aSU`)rzA#=+c?;{Z=_45r?rqYS(@nocFhJ(LrXxzo{( zEK_P!*?$$a&mkQSoltU}tr>XdV8ViT>z6oQr(aO60X5$9r9NA0i zd#RB^w1LusM;TX#@>(-4<#nQOp|#;nASv`A97X=(C_BVQw=!dI8Cg0xmNbV}=1??8 zDVd`LoUUMa{RKx6g*g<}W_EU|d5uUb>zF!4D}Am%+!mU*mgLjd(I`T4dWuf3q`G># zn%-NZ$g(*ByDyT{??lIV}hKt0-3a8h@WwRcIq(BfkqiU=fd!xf`j)QQ93S#=a{%J zi66TFfOcX44M@7|z!`kb%kRKXLHPW6M?`7w;&2fPYMcJ|}L*MdBZ& zUuO`xaI-MJLiC;pdN6`LG6GL)dM1END=^7-8_s;U?bGpYoVZ?V`UKuh3+2M-Tu#5h z1vNNhz5w+<4G{gNL6YNf9sh~J;@=oB`mGO|5!@xAeUpEexjE?b8MMBxu{oI2xV@R* zZB8yuUgoo0)Pn{80%XuBQS@UmZ&+-3LR^mwuO}!-XJxPyFF-d}BVMqt;0mxL)67EV46V)sIEdh$u$j9JZj~I&s}IV&xC;=?y8+w0JxaZGzEy z71SRaMEX^2(|i&!jf;>cK#|;Q6`aqbNTtUS&eyz5hNNj>j)YuxCFuAZ(9upL?H5r0 z8iWKkib@2{sUY(2``tlyzl+CgLty3FXRwF9Byp=}17eG_A_P?4c3EE11KYk$yxSL% zwHho@s z0a5w5ImNpQ>cAg@??seW0acNIL@YRqo;in`*AG&;((;D^TG#HXNaz zG;l?R^ZmIN&WAWqblA=3#jPrcOb1Kkls)6LHXlv^KerV8EARn=nza|z{U>P6>?R?TO zzoJd^Poc$)BcfR?qmKm;$LSzl{Q({dFq;2)84x(Fm$l%G529t)J&XKAa_#8B8K30f zjKT{dbXkJ3PQCYX9twqw?*~!@*IIAh4YI2fbANR)X-V*DONOF9zc8xI8=ITYiIeh$Sz{4nF<5sTqU>Vbfu z@hA0oUPHM$;{u$xZw+z#ynWyR!EY&eCRRd2od-dHsO^=%MPabTReWY>guL5?Psh=- zMzB|ACeqNEj;sP>QJZ@-L+UO#^OQo2=hTeM%8;*0o}mKBMw6{RgBDTn7Nl9?^fIG^ zq{rL8<1LrjW{5Ou^hCyCybm33IgiFo<>)9Cd}@Yz65TeXuc?dvX@i-&qH`&G83lo- z*~DF!FX3`HxI$D6i|~`;7UJ5jkh2|A!K37I3j4SPg>Ru-Zh1sBf~2`~^gB>%7~%X> znD;VXOw+n=G`gNE7M$+y^VyJFddi@$6p} zGU(jNrbb?mUdxzfm-lJl@d`{esYj5CI?<@k0wb;ziP1X489%8tG}BC{H?+|&)`ES% znHsyyD8>Q_8S8^b2-{v@G<8VrZ}iR?t!>VMnw%^^gZxef_17tfT=-Q{eqL-jB{rQA z;YM&|DBAxq9+jium`0qmOZJXSK1}aQ%vCPTPHDBAwz$mAXuK13YNq>aswKw>KlM9h z?(=-M&a9bOA+uoij(4r(IJ0IH+BrAra?#&sVq)k1j7cqp@lN9LmYYo?eQMg0a zxVzlf@tl%0o)dfSt8pgom{lu&ypmzO<)&uUPE9R2uKh`&lF&c7uj1LN0(#ES5zS3` z6B(PfUKCaEKp?T*b>xH5%Y>*5Xnn$tGPDu*fu!e9G9XQj)^v)jVC|(xj7cpuewSxPF_;h(3rF&hka&G+Q z=I5MznUkNhWPi}nKY!=<2R0Xp<(Ei?tv^A;e5+m%8y^?Qd|NyymLTo?Bg6s>;^svu zpo)%&{F7qMUVM5CzFGi`hDDK4a74^YiCS9dM+D&<0DfTNYSh$4HLV%I4X4083}SNd zbI*TPEPn=|hw>8Xf7exUj&?X?C$~-kQW>jbpZC*V4*^1iRXZaSo69~ z8>M53o+d02vg-o7v%}`A7E6hSui2t{CQvEJzLx|xK|Ki6m*CoV2{)pxegL50bKsjU z6F15(<_;%(iTwLsG>-llh5{mau*H&F#2Vt$HiA1O+y$%;FiNBlLUFK>x zU_vh33s~+I1@vn0S~~zSU&A`v7Q9U?MQtZVH9ZLw*0L$Z_lbFD#j0n;0^n{=iQ+C+ zrj`J<9T%bJ#O7fEE>UrU3g@4~&j6^<@r$53q-5otkyPqC?35>Y?m6I194-+%c4j=&KLffRv!nos(+pkXxh`ya*1 zlJ|GY`$wM}dPBz={pp|ixpd}iD60=>u01-#>q&23dcIFrXU=sHlh@9C$LBJjlO3cv zx!|M!jOkxaGdZfWMmFiwQ-cmoAWTlTP~A$2h;9|Da7;^y9i;spkx6HUrG6jWj_)kw zI%~rD`3k@|*O3R)pO46XT>I9jK=KWt${w`_lxgd=#Fcvva`Om|iQ*IO>xB(s! zTiDjomU_e46^|#TE66Z8)&#&Pqu|4>#C^|3lnnfe{ zXY(GzuOsrD3loVe^5e@Wy%iEv1YeX-(?iU*Qa&sBBKoCmesPz&wA!6Ol{#D?c5U9{ z(z2%Vf(?rs?xS#zf^(f`#j>z46ivD5#$BF;y=n=vi3opSLy3Ge;4_K&f_Xp84&T084u-w*t{zeG-^_rZ4T z+~R&r>@tB6*}*b0@W6-QNZ<3{ksrx<=jXtC5!gmIdR|$R^~xCcARO8Gn4bXIFY`Ql zUErr6=c@A#=m_c#uM2#~M>KQvi2W{}SNNX)riILIv7X{}f$ykt(`3O#uiE8xf$zAa zJ!6M;ht~zZV??`S9q!-`_PW4#oYC&sfIAk`VC8PrHjF(3;6R_;#g+=KorhW|2Kr?l_ETM(pZ{y=`< z)}laNQJ|cLKzshn(5{vTNvjRy2hRWqQkWmy8YomN>l%3b$-ZT=dxCuy`uH|Azr8Q< z4A(Aw9(Sz^mLS%8OR*G!T7f^v!^|nZC~gdt7bBN;r?~MMQT&XU_ncH8w9J~!{ zzv*WJ!@qzAr%UA1ui#evw2mA&N#M|t{|>R>0|=HB^OPw=8{sLKVN=}4KZAF12~Ji8 z!)U)Br=}J8!Di2H!*WM{ST2kXKRvwW@mf83Ev#&*m-5M}b4+`ANe07_zEtHWtWBM5UWm#W$=VjYI4lvWt5-rPvty5ES5Qi+ZRyp4X+F1 ziX0iO($C1J;wrNcXjjRfk2Ynw3S0$gq~c~c$H6M5&emEL*mAj0?P!I_i%m7vX@!GDw81Eag4a-HW2qb~ zy$lw4;7-)@8kQTjjJFlBfi#eR0dCAk&_y4S`>uZ5mzW&`b3+7Q@vfK-@CCu+;D-}W z$)VQkKo-lurXsQBRk7|#vUsd}23aqGEx~DiHSI)t`EPKIuY#=MBVs#&egKvXcolp_ zy|*m71x2wlkLwXX;78nsHmLmD(Ar#v8`|g#FN+n*Sy64v5q+J6dHH- z$$_~RvB$?}jG_k3_0e1(kymiCN>CMOwj7H8-@-yP?{y*6?l(8fuEkk~GqY8`(6%y%xFkK=Gub6MOrBDZ`U-ulmIe;)L?Wyqw?dZo6qa(}<8U%^2f{v4v?|H2v# zroh+8sJy;e7{j9YQSk3pz|m8T-`hm-mqiJDN=1U`$V!k5zL|b5*((ZA8gSLS#p09* z;dAp96dbxNt^+x5vvu1GaHI4}JyLrxV4-+$$&bceum7v4|2XcU8ab**tMSRn*LVn+ zV@KzF0H?u{M};xutr5ONCrV>cV4Yu1-r4>(Oo)>l^Wpx2Q z2&^Jc)$%u>_x^*NVK8{Q+)@7%nBe*%vEYaBTkS3pi{ZS=`#;2X@~QU(Zt{x?qb5-9 zC&Ck9e9q_qN>whl^+jk272Iij;yr*c7%%&R&3WJTi&aL;Ir@H$vF!*he(~Wl)HNRi zw$m6qMFo}{*MpWYSd2nOtV~j=yk?XI&j!!R`hM*_a7`ND^seshpTfwOg|wgmD!u$kJ4 z0zdYpklON>xc(LBi82uktcNb&=+z@1ntYF3kv$^B{jyVjd6HEcKc@;s(HYq<|NA|G z?H>KI9sP2~>X+@*FU=nPvKRfb!R{AI`T0v_;=0u|ltsoD(euH(G1Ym5$>FiFgpAK~{}6j&Ray`6X@AhWGq`tac(5Fj zh}~r66>;5&SluWWdZGJx{dwUB>FD}Wa^hcqLM*yhtPE7+blxoPL^rOLe)OrU6h~x6 zpzD2hl0A?Jx}aWV(6fFZ{VBHfH9r*r_qVh56Vf=W&t-h2&iN(4l ztcF1=@)MfrTZ4qb1QoJWyP8HzfNBAQY738;a0rrBoCY1SLEcrJZiKL0t$G^Yl! zn&1Mv0XJEv;3h9>s&+YiP;aM}dtG4Xlzw5oUtA9Wj1kzPs=TPF45ZnMz>TH+%G@h@ ziA@Ax5tqo&F9P9`(*%d1(CdeA%TcLY>9^eKr~P#GC8!451^h=M1u01Pm5PFG{G?U% z7Qf?nE7)(KEud?Jv5R9oW1oOm>+M1JiG`^@AD4@&q#&fC%(WI zr$TVVtq`D+dyRj22(-_}H~eMdokl5$yGx8uJajJaXQUQg@&)$!^M39(KJ{=}-oN|B zMq_swD>%4uz&Hg;-T6j$8Guv2^U#^RAJNu$#>%n%B5{gm-EmR&ZsUUwmF1zLa^qV* zs_38mWqJP!L$&b|vR35%2c=#A^bqMd{N;D@Pl;bdCU4v@-}s|Cl zz0e2M+bYHDMSo!9C<;dA>xPY@GWm|X@CJCpEyfA|8ESagIE&7PX13n=qldG-UCek9 zeTKLjw#)tr#>zPt{tw;`!IrN{{p!_+0-H%`{ict}k)g#7(*>o)Dy8zkBL=>7u`fsW(sQ zqNsR@@nfuwb%TS!VpQD9ih6n(QBb5Yr1^Ei$g)E2wqr zjgt=sFU#q5lbl{Qq4aIWe~R?nnYV z^fNwZ>4p9hg-Ss*`-;5$q&DR=_#>2}JZCe2`vaT84pG@mL6KEw^bgm8Z1$YEy-fP% z^~yDd_CM1$im#$+3j#496P5pIObU2C_;Z_I?LoNUI^u_*+z0oI)q%|= zya8*l4Y2`9-)*ozvG8Uzfx$oXUI;W7Nxc!JUH+-9B$0|<6=f$y!HB5gLo_R%rbXj^N-t2*!J(6cQ}>Uiu1WM(ev^hunnTr|oRt%g1gKxN(^fk6_+) zd7qfSjY1`~{lP3on?CP=x^y8o+fK~xIwXmp)DB90xChw`9OwVP0WKGt-bL(l70cxL zL!ZF^Li6s54aU0%5#EjpJ_OC75zsxqT15JUgiI^FA_~4O8>!v=_v+@|Bf_6$LO0-5 zU#DYDfx_78v+nz7Uh`S8U&YmaMVs+|INvWC-Vj^S1s>$ z!MwK74;0Cq4@>6_&x!dz5DPf&A5$6jgYuIUZC?<4N-PG`Okh)qklz=aAn#(q4={Pk z#SH_P?}4o)*w*`T94!cJT`K(89p#*PL+kaIG5PkW8KF*s-QVdk?f!n4v1`S`uaA$D8=e8tcopXiAoDrtfRoW2tDoI~1}mqdMGB zX*S`qSrtvj`$FNizTRZYlqDi9wI=@2y;WVQRDUSkib8E>M?BWuo@{D~Hnl|}HOCV1 zR4fq+@wa9o+S?h69B8RCLltdZ1HBKJZHexV2>*w=~kwM=>va)1f+XJzb8EtEe z^{4t0KJ&hoR48nko%pgV*4-aVH0|rDi0rR_N369amKcmB?if`4Bb!{W>PaPH(H>J) zht~?5;dp;#s;wW537d7XI zG`H+B_w*kQp~1drqO-C7Ry6Fu-g>mFcff2remqp!8#`u3ySw|)$$g0?XOrxQRdIdv z{7^-_&qUv*V#iaVN~JN@_t5@iry7i^-hrN20%Oq~@1YJo7710O zMP_>puh(OC_hG12QOx%wx*jjwpK{No%4D+JTDY}+{jpyBZN_`!Sfd!>+V(#FBeL&+ zV`_%coVEwd{=V*b+fXFT-M^=o9_5rPs@B@UXd)i%O_30QK=G6v-ogQ0Ts<-2P@@Zn zP?eNBBuQvFMGo@ZxzlXP_;;WsWL8#KBFvI-rgbNGOI<(YrWxyPr&ZM+!wl~mYU{?l z4AsW51UnO$3H(5M@1A`<=%Pqu@GwtL*+unMeaTRb_0f#BM@d9s5t*UN{scxNks88s ziNzpOSu$euVzCJ=vX*+9uyPgEV>X5$+#t*B_kt8NL!q95?o=Ej-X1${O^W)~czZly z_V%?ye5vk33+^yOb=D%{@?1h*sP7%HGmg;m-DM8Xp8SUFGSNDEe zEc9Q?ehfcaHIR&9Qg=krxJb((6aUnLf1Bw$hAuYy22%Y4DW5fH*moLvgKKEu9S*-T zceq9#pfc}ra<|J;i8Uv*%X0TiKP!7H^a^NaY-o@(RJL@h)vk$MQg2OrD%#`NQyY`1 z_J#&~S7{`9d~4aQ4GnUWZD??8vUVF>wWar5+-DcbY$mR5!2f*@(H6yH?Mu-XsCAlb zqSbY>@?`wM*eEsIm$J0WzMk1A`_d$C&AnFM+MSY-m7SXYXd+3FByjvxk+nkcn;jff%$kC>RIT9^)yiAm#gqXitZ1P9=sQji9AfB(XKedfTx1 zrKJyF$|Av_O=Xc_xRPChO+@wKEpaqa-x4wDpC!2?EE-7#+Gh#LNW`%0qL-BiPayTM^iL>Tmvg3{7GBD zD4$F=G|*l}8|$Xco7@srO=mh&=&R3hX>%D%zlMg!?r2YIdvueW9`tjwoCl7V3{^o3 zCB@j%B+=l5A&1r%!LUa{I9r%WelN4H7wzvKA|0{Pu?eg5kEcqajwIGDsLmQBnrNr= zI?PBlJS`t1Sfg~n!B74`d#;r6);3xn?`=!OXp?N~hK`#>d8oru?uaLnDXr({_B44; zoZ&sq+NiBTgph*j(;G_;v~}Tw*@~l6EUAQVg?Sf*AWm1^6V%!qC!M2w^ucimf~L-d zLd1t{95i{3hmz2Z^izoAgmR!|Cp0#5e_y*iLYpL=aV|q`J0T;@JyO4t7u)%&b?IOt z-V-yWJWp$LsIE5scGgm)-Hdi0`i=ik@qRvO!?Z9M?;A+6mKf;3HV#kXrJj7j|wwt~0c?utO=lT*AZ-F+f9v z{i3d;FL5lIXh-LuF-aIisEB$&EaD2v15}Cb2o&T) zd*eQN7G+b3Ei_mQbckHm{nQRja#p>dr4ri08V^VeB;$jzh6YPbdmuK%gND-x?+f-b z$eh&Bkb~MuHPJB_M^mo{wsuKd!NebcwL$XLtMWuE9j4^$Q;!+!i??stTGkQmPR5|z zT3eZ;3%cX5IQ7t3h)gS+E4(Uh??{=cXy?|l%Cv&q(>F+G7+SIo4RM^%Q}NW$q+bhL zBP&T{n$fKRJ&{njCwknB9q)&9>P;zq(AxN&_N1v@3)RpBlGr63lN%->GD+Z6Y)Q=~ z9Kf+FvT^bc8Mot7vK z4J=pM2cS5?KxNM)OO=A^hNDwM121OG7DsL4uPpb+r5|ri*H}+Ko=)bVF`t{B@abyg zTcrQCs^wK?rqm3UlFhQX@^Ky7n^fG%(gN9Rj;61Lr3u-Z%5HrPb|<&t`9MpR+eAc) z8cjz0x94zMcYCU%->hT<&!NL6cB@oqy)}t+?LDrDvE*|ZMnhNzlo=mYO&$UFY?N9ciJcG)yuq^4ca_X#3k3>+Vy^0)* zS=*_`AxJ87+f?N$x;xc{ElnAmWIv>}9$jy%~&GQmw<^l zZaFph3lnCTSZ7~i2*$uRAM_KrK+-0zM%xnK(2ya$8l6V!E!Z*m94l?u?rRxGT-Ti} zzoi6Ms*YoDtWhEV2EK-!URki^ORB~2rg7eoOUG;V(#pgMn1Ay6%s581$Ivk>OZz&Y zIu6On5`yj>gSnmL7JGOs%Ym95*0aJGyYy6G%ZZvzp62W@6m7`sHnR`96ghGvAXl(8%etW4by+?>7_eY6 zA#J*CP@eor-IAY_sEMSMZ7o*bauQrXAef0(6FN@3V+h;jP)|Fb zU>!#!7KBP}g=?S=^`<)7Eek5SH4}+}e*CP)5i6E*>RX|j*l|Kv;1pwXdi~ zHgea?2_En5Adm7<>F%d>-yQ4i#AeBtAu;w;$ePpR>016F(?_m<>mcIrSEkJq9uCV< z%V)YVM=kE*+_dMqXM56K2YHBMWs%=Pu4?{!>SUMQ7?%gx5{m3Wo@#Na#OPGM19!@F ziFWc_@&jy(o$QghL80w0qi@uvsJ@@RK_P1v{m-7kp37-&c@uW8p~~YjMrqNSWkZ_1m82vp43QRFTkaiY9wJWmM!-f5S>6CC{29gNKmI2 zUsq!tDlpkiBN6Fn*i}tv*%15BEgd`UW*|(NJWWc-s5%^YqDQhI-DUtnVm9G$m^*Nm zhk8`>$R4u=|19*#9=2eRt7NZj%ZRj+o%ik*Kvf>2PsG{)c_bgH zUA}3MArLjGE;{!@%ZI8@^DrE;@yHzmy={CN&WVQL$1qxL16Y znx`=FG3c32)9K|^XL$=B-#DW5#XS5ZE#e@=>i;TWbuf(HRT{vNN0LXl& z$zW&?VFIW$`gsq_noL`7tvWgb4OW3><%$O5J2db-xuxL(RNiKjyXIfz)2LOu%PskysAfR9qW3R_V$03X4gHV^EFPpTZwrB$(Ex3#auQuVo zXf}PRz7$-K)B|dc>%P)G@kljX!2J?0?J;^sBjJ$pQ^TQ(Efqls=CN4xfykkw89M_e z2R+AWJDUCGz=@jxS@M#7AOrSHAjE;Fc?<{OzGMCI|cZC+S>IubK0 z+=NYfn7QyEP=o`39f@cU?>2Q5fk2`!6gtqib0F1q2nc1MINE4lsMTL@gE%O_M8Y{p zdW9;|KHF;8RC)+-hNslG^$nntVYVSOiu7!uJg(HZuD}n0uft|#Wly{tF2Gou6UCxD zVlX^(#90DEM%2NJ+1m#g3E^&JL(t^_v>e8!Z<79)p3ZKeTq(`pqy))WALXJQhwN3} zMT$e&&l24SH*+_jQ;juSB^c1Mc4h)@x7_Np+{yA7FKtV0FrF%qF>C({G7ggzMNwFa zi|m24wFpJ+*@LqukC02P3jw`v>vf@;$lCyuO!c+(bxT(sX57KOT}M?AOa=@CK^dV0 zMy_31xCVU;i0<#B6~i4Hs=T|On!Z2U3p+0PDIv8S5~RK}L2gRKQ1B;nC>BlZ4o6by zK~e6KOqU2v4htq|fzD+dhP8OKSgr6Vv098{qzRXObhE^2)w-Q2v|D0M*Lu7A@EE8p z{FBPb?vRl^Fr!*NfrD+N##CUB0~&O{g0!`D#o8Vq$SNr^;Y#4TlCfS`AydcT35VJb zOJlq@MgbX>;mE$8ns60hifYoBcbuAj zsHP>BYU;p`dni^2ND4lE@Gv4&&w(FrQpRA$+3PPwCl5=c^8ScL4RShlcb$PQ02;dvs5PL>t|?hs75R%;xxOG>8Mul;0(f#I86 zRiYY3Y9&Vt1gj^J)+z(RNJN<}{QwZjeVx#h@m}5obA{@VsDQI)^dP+&0@tQg;GshY z3#-^NEl`$qi*$%>EjTJ`$4ui5 zLNvWpMHz<+t2Gr#gp61LvveX4NxdAfzSwbueI}q)LT{J%qwh3;LtFL}V;f=DJ@5`w z5tod^=(wYjkD_bTHiaWI=QZzs89GDDs9L#7cOOl+g z6xYunWFS;DFd=Qt@>IiT7fT96JADc?xElprL0HyCZd=zHGmEI>it}Z1)b~T9==)O` ze$8vJ##`UVV|G)ZxBAH+NUN8!)e`Q`P$`TO1(ytB6?s^*dT1CTzT~lFTeMf|e%Ku? zjc-D7KUHVhEt~Eb!~}45I3I=_<~s`Jq|g|g=>ZZ{mH`#aiS51$ZQ}x(IfA}tM9?mH z60i}_8R!oF&N>3!t(J{0XpTkt_eGAWB|DYzMl_X4vAD(}jzUm#(vdTkizjk0OURKr zEoPkCqaagA#e0Vk*p)O}V{lW&*tEk?9!q&qFl0KtTWUw{R(yMI(xDetTebnEAw9GS zQ><2#b5ovD~w2kL=%f04)#I?1sHQ6~mt`Y{)tkL0`e_t*-BZ zE$c9}l!I+?AL83M&aEBMTx~Ic^$5R5oFcxR{r$W9dRnnj+lCw!2%2fXvHklKyOb+l zZDP^wzB^;7L(yYWX(4Nv*%ME;*`^|2hQ+fHh^NjnJA@h6x0co2>g(x;0Tciyi2`Ed zPh+S;+L@r?Sz`jpa`&i$h5Q0=MtBOAY|JWrhbCz(j-0l^R!etrHliojT^>q5sDGqY zE#!nTS^)(0CiNZ@nCxt0{i114`uyo|HiTLsE)kK%{vFoYCg|0H*=r214oY}=buyd@ z@Myt!6>@~BMS3{Ik=0MP|8_7^&2LBQJj|L8;cdvU>+3gRU+s<|D6Fk3-rerYbcS&; zwr1GoEvI?JG6X?a!wtb{n{DK6m?<^8;zXkZtuEc$9!R|~EVtZlNex4X z6*Y?Nz{56-pf$Lzc^`r;%WFD^HDP3UBRat`>yxaLH)6 zYTcD}+_pC-Q$sMM`OF9r6H&Aw8^yC5h9-y?nNG}{QAaFoUbywTZrfq!MDtEkCMwqyIK}Qjffbq;i9v0cmLU-Y%WT>19!)@V2Wi|#g=P@DqA~;bt!k9K^^UEUBywkF0L4+zXgq)Y`|~` zbr9sA#cA4NGoeF^Gzm)g2xFySrULZ}2i;Pf55dtcM;d(Hhj4<2kfaAym! zG^04x_+WegFSt&kE~AbXUk_*y7@9U+EO?WmFw?H;o;B<8#I)K`?#g}gPK3@$C$<>d z4Lf^ExH)I6Vbcf5hK2EA4G3Dr;s*XJqK8qdZidinY>Ki$O{21rIe0loE%&B~Fl5#+ zD8oe5IF;s%Ymq)BR6`675~V6lvr?H=igvpN*9cWID8!-?fP{3xNVLnWbce%y3(oF+ z-2)Pmr>wkCyDMwGFDj;y_L?d_q$#jzjaHbztoyn{75rahU#ce34ck`FJ`jbbp}C4*B`7qTq^Xh>2#hu0DGU{$*OPOVbpqQ5Q)M6V$HQCe zfn3h&q^ZKj9tGG&d3QpH*5<$IfsqK=__<{O+eX_5$)(;j7Am7{BHC93Q+^Q45yZEa zM0l{-(cK3aTt}ae&eMpjGp&itb42Ga15y%jyYZ)m_ZQk3O!Wn9*>YN)^No!r{Nul=3PezQ^ZyiqQ>RKPUH9D_NW@f4oGc8#dj zUVJqY(>pnlL-K1hPdj9UTHA zz`l4mf-=(H#x_*=WLt^avBIpkxJrl}2D=t~=}iY1yBF?_Mq2JRC-#pT!q2n&jbIJ~ zy>XmJ@Sy#40=d(yN)EIpYg@sDg&k}%4}Gqq&AZH&BXR@5=@i5YHn@|0fMs|m6Qo%-!r05u9=>&hug4_gC&^5;{d+Ek ziYRE>&165IrWi-GCzCW3os*3;G|S6n@3SngJQ;uFm@94^oS~S-9;9_H%6Tcc*}i7m z;nIF=+l<4}H&%2pc3aq+Cr-H#@4z|(y;>Z~1~zf9Np88h%%legs*XX@)ThctoE21lk?;qrRC_v31Y8yaKyvUH#xr^ z)jl@=Vm+Lp5|xJix()$0vQZ9gvtFWiBEF-^KBjPQN$~-ALeNOO5=94tt%HP;HOIRc z6rsYk@Y(==SO_9s;P$axvKK_4GD$!^K}(=R0J;l8KHnkO{r2o?I?zOe=bRB4MtT^F^UK#_k~7x|8Tu?OOgC6MH8jVyYu8fh;crzoW457g za5|-GB#B)pB!4p%n^b4_9aU_l_TfNHu3D8|0gxI*vst+o+F&!1P&cWblLqcWD0?Z` zv>FJ?EpH5)ca!c+SshwhIO7PVQ=q&K@jh$tq$+5GNYa@{+yyM0z8%rYT7#b>kh9Rr0vv13;ixl)}r5~nS?vH~~7gW#YGRt{9p{+}~3#xg2ID0S)gnJA}D2d>)<3vE% zi%dVZ{3pijoW-LG8v-aSjI25w^*Bg-JWk#npkk);DUA#h!+9vI+CZcY3s{n{C=r`_ zr3^1Rh5$iS1eVzd*4gjx!k>FZNS zV=^(gfhCOU+>^xjuADuHVvSV6E*GU4g19Zp6N8}eMAQe7Gl-3y>@G5_33XZ%@?ia8 z3hLN{`z%T5oM$M>DhH;N+}5pLtx|DaFmp0bBU)1-k6{X{5V9eKZWccjta3+xNwd`f z%nvYu!k|Ima#B@XiV6l5M^$X4l&R+!=1eOEZb5Uh8ER$nBg%UuMY*KYm#y}|R(O&g zd8qGIXbylcw9oWMQ{XzYaH{D(RDmxJe%mfgBF`k zGE@zXS}`R^(Z<8i^e<=V6Qm51LK6?QqMX z;{n@Hj=y%qQ#;xEGX!P44_f*}BsO^w>#S`Nb*QLCYwc?>aX@osCE3Ka%&0qj>8X6~ zCd0uUjJQRs-GsDKLnLJA*iuu=gm#x_blqL{(r{AY(qj&=I(k4IxJh%we?2f(vUim& zrlfAP5#muop3y_rlB`IFTJpA|XAlc%8N}`chc2)~oI*^IJZgH#WK4YH*I}&aN9*C4 z+KHw@$&-m}+;}dz40KX&lLm8nj84P2=?D!SXkws52RD-IfmTOc?F2e(k4)Qd7@?Ck z+)(O2P$xIyo2_LP=mJ*5CASEjT)N|}+IDPv449+B(zwIW9eI=M{^D1QlL9!aHY zb06%^P0dGphA8?#QXix%b}2NTKz_oPW6ra{(blOFkyZ>t2Br+K;o2_bU?ZpnP*;ek zI1oJ$K|laQFl@RSYZ|ISwAO$=OLJD$^$O;uIf$}cD*K+ip|Z05g#T!O?UY5A5>n&68@5z73CmBL(Gp){PX` zGB?tUT~h8<#y*bIKG2tPglcoo%5IU8 z)hi+fo!~xSjFH}qt>&6dR}$LPk#xo8%uZCv9g5jWLGYX^Q#m^trO%)@s@7tLoNlE& zE_SX=d4hJngwh3PF@JNe2W30HjRM(dcOx%tEu6HlxwpyO16bw2Ah_~$xY_Jg2%EC~ ztI#zUqrJM6R(D)irFx*2VpxhRaOt#j>Vz>5e^%00oYE$95Txvv^4JH-VHu(+y=4q28d{x}4FVpiRW*i{N+ZEt~B-J&MIasD`RtKz686S*qMGRsb|Sc>U{0d6`Igk;)B zGlxjGPCu0{K7w!^$VZ?};V?{kTmniBwi}C~AQF!sf=hvd4bopq+5#^t7*X-vb@=c> zkjh~WRPU!q+FIo4qX2SrH%w>>+siyT9y}9BHk+OAVrU4{!i9#ACtSC)Ap7yk%3DK@ z6{)~G+z?)=7%^84yuBOmUuD{qViEJI?UvX=J&&DLqx0Wu3N8mHI%yp!5)lqX{NxXK zY4x8x{O>h=RuC*fu}9fwy{$`yVmmCucql955KF*;|`at$uGn{HojB|3D zEcncm_AiX)wuV=g6*PM+RcE@050L>08lq_>sUmQVQm)Zwlflwdn+KiE3_DL0bVkjE zyrv3d(dmHDN|%XXLe0#TZ-sIyJFHSOh+yrDD$munQtZ zHaR+F$SS9b#l)1{ZM3bJhX!gGjSZCry2kd2YSk6?b4UG4Q zU90FVnGmuk-j?ViJO@0`Z4X#&OVi~dqK`t6_&Mq^8GLiq@XB=d61WuZBJizFNa{xr zJ;JmnP;hp(x4&&5mKgHkE4el8B+@J^1NdyUazQmX2xl}F>78sxm;L3i;9A;`(jGx> z3~UGc*Xme5j`acQw6_g1-?zJku$i=xKuAnw%pPr4B)6_D3L@K(#EI3}C!^zB>zLPE zhD%YuIlypLrexH&;2Tc4?MnSctm}k#06an$Rv4W$Dtg%yG155vRkcYKwW%+1Jl2L- zg#^6Eqrn#Cy36%3HHr=ZOSs3E><7x40I8A*YB%IRo)}9ULb%5e0aGzWYxePM3Wb`I z5P8J;)O1ju?;Mj?%MIM7Ty@fM?-Vg^()8WH>BA*4-c5nJcuzmDDGtfyJYI&wmZ{LD zykfz{s46VQcH~IzgC$$#OibHrMl%~)I~*mpXpbG|U`=Z^c^VJrY=J-@SQK~@3JICB z1*Vc#u1=n$9@f%1Ti_@e>zr1=vRgr^iQekIbpz6ua(9xq6a0PyNJ9%^yH?1&SaJgn zR21LJ{SLBDkl;lq+l229f+{C>CORECENYkqG+E8Rph<$<#32f1X?P{#CfV;m?wSZZ z{F(n0$f=36Lsk({=xwfla?cs(DN)LOg&?%lN;hB^0^Ks#(K*u!w&;%c#8b&85Or0+ z1j%SzIA-bL%C?zKq54hgj{_jDb00S|;G7W2FY5QkPc_x{kE@(PoH5O0XTr+vy07F+<85bW!G;hcqiI znFsluE5N+Wn2O`62*q^Ap_!$KGR)c#Cm7IOg@A5WeXX$WOg!2a6%XQ64y?ZRBH1OF ze_F+Jc8Qv`usKK;LCgb!LCdLHjnBXf@9VCtsX)9>H;9fZ@sgNJO(@ z>U$v8%W>=t4YA{JgeO%hW`}Qh!{&yD#_niOYkO2(Oye3iY*z1=Heo6xcZl*9ampv+rHh)l-8z>tKUU-B$Zwc$caV^lUYU%JzLOD zRg|czkiB`XCt;>(TO|KEqv5M6vWW&U6F_hbU%Oyt$0CvVabzoz>1lSR5cAjyM1(Ji zNC3n;_NCXbgHf?oab!~FzyxSTdy?38fRh`I?=n)~KLBWJlb$zv-Sk~`35ixk>wMWrKd ztYNn9vJ8QErp=4X5HF=@IXWvd@k6CM>qEvyi@gdW8_6LcX%UZrEshv|$gw9+opQq% zf4$z|7;i%YyGh8?mmMvXCDjq)i(GpWBqMg&*1NG;r}0hcm9146%c_%Ol)!Hk-@_2Q=V zAS2jG+8ZWjYqkL2wGOQ=hgh?8`mj2RFQe>X>141Tp;<(GPOOQNd@&hIh3c3!(IPA| z2VjARZcQ2El1Qi9mq1cFA5$BL>75Bdv9ouGSr>_ZAlimgRZ^~5^iSH9h!F;(JGn)bv6ET2$yUggo@$@i+Dky?e?q`_Dp|C^`tGsM`D$qo^6_fpy zhZZM2Q^v;Oh`@9do!xAp#N8nip?Yy?5Jyf-DUBQ(wQGxIQ!VBbDcpOdlMOToz<><& zb`C@n?Qlm>ijyd9tFW@C>ect#mV105}9D zTN3FS7K$VmkyBqnlu07k5{qXNKZ3ll)?&_@A{5z}EVKzH8RZjgU!qB3wn;Aufo7b5 z99B6TWprzGe?PePP-;Sk&N;i~oZT`*UzBR4^khr{*^eE2rJWe$v5C(OvI$d8S3czx zP9LyTS}GKn}ghiNg*?lHGo=T__7YNh;SV|hwggiZTu9oxGA9obI3Q!&s_hxfza z&|olybd&<`YcrFDLrfYgwFqffWOc>Ha!o`bL*_S9%~2ght@AkTvUN|DDl*7t1=y9O zjufh~-B=Ec175>^XN4i*cTOKUC-S0%9I;4gt>hXKF*1Yvv)vPq}9d>kTdF&6sTqR2tb=Zi#N zH{y|$k=D1fX;hnqoMOqHy)15f9s9ZqV&Lq?{$b#x90m`hivX^byD})(| zsAc-aHb*F+oQ|$lo(^#xgVISN&2tSq<-;nnV518?A_b6&8<7HP2rM3&)<`vh^I{Rt z#L>=I@V4n?Mefh8Y==ZoN#+1f5`8E6T9M#lFU4Vlq2-`s8G3b`%+*oN8v^&An|`Ox zit&Vj21iu(oGR&y!?TjV65>fLcvGB;7@)M#QxVHoZ`lQ8zce~xcIZyuqX^HTP+Ksr z!%5!@$0UNznMaDEaS3fn7$oA?qE+IsT51@iY1&v2MXWX!8w*N1O1fP&vcG3rTrixX z$=I%H_Q&y}w4yW|yb(zAYeNRh;8y1zrUjho)$)!(3RN;hlzE+rJK^BoJ@FpmPXJ84 z!i34@c)YEz6V|w{IAS;t30Hv?{XZ;B<#PevLdr?T-8W1HFEJwW_2SOb{&8E$3BlpaVCCox$XB*C5{(lXLU zVd*^L81A}G#CpT47{$vVvbMK9nm}hFxVw^DU>ShfqaodhPRIpxN@`H1<;~6Y<>EzD zJZCLrQ-;l!B6=kPRN18`X*sVah6gDk9f4>b#^y|nvy?r3t;qC%JX8E;SO8iC;qgel z)Db`h9yyNdZY6<-M`44S4`E&(YURGWo8lsNwlp!w0wht1-CccThJ-#KXO-5Ez!Ssc zYPoz^+{n~?=q%#JlZu;@DlN+f&x7XaR`T>5VIAQg-Q=42lGaM)0vN9wOt(-%BvADZ zUR?Wgt{MhYLjm&A#_{KhL4c7%)Q?c@EWIMB{9JNvs43>^Dy&yFzJg#rg(M&3Yy{Sq zyKQ=1beh``yCMlD`VSy)tY?XIg(@hKX3b;`&fN}Av#)7TZd-8vN%=7c*pGlzk=#YV zVgRzz&e?n+kP<**q(~%5pHHS>t4~7NZy)H6C4dg^GeN`%^2W9<#e9*z3KCI*B0@|oa#gFbm4nwIHtO-b`nJy@-~fG z8g2W=^c&`;*e;(An8$85m61HxV)iYV-f~POZZCA0uArz6q@Y|JGS%55zg|2E!1d=3xC!>)Q49nxlEv9`B4JR3GLy zMKmNAl0|9lan6y6O%-qkN+UoSVE)#PzdOUo_^qD!2hLWjWSnuA!;1C zsu|*HQ7$QAq#dKWtz*=2*jl^2j!_==Sx}vXLzdwHpdQf3b;itNvFHPlLwn+Emhd=@ zSx12$*HK_fxhf`tF9RwHJ52(l*rOuK9+Eka(^J+N)&T5L8)#<9Qg$eP=II7f>kdk< zMU!9(Q?=0qaSKKZjxsU8t&*-*C0W=4x>6}*v$J5Q(&9q9iQSe=+^&7eCBM@Y3ikEf zZ^b@K$x0#U+XfOK7D<6P2r)uxlI!-&c%(MgpNu1EiH(KRi%^^knJi>ntYvi2OM#Fl zlptbygdx zEkvFQhJ>`!lD36lTU30F6FI4p?JVb{a(qs&V9f`n?eXP^+i!x_bOQhAXwJ{GQ=O%= zsG&FZdgh3m(38+O`6xS_4C&Icr_TeFe?pJJeeeS~^T?$W$59IqL;&%Gw5O0SY)tcz z$wwoxyCX+^|37>0+MGs~bdB;;e7;Of_uFwo1xQE=pN??bp8G5B=3-o;sWVPhSNSEQrStKP!LY$-&1z_J#zzlpJ%$!`E>LKWGAAYk5Rca+Js)ZI5T-{ zKYKMDWTFm~@c{_h^Y6`_?Ah?FcR9w$WyW0tc~A>tI38xsaVAWkmg4<*dJ2&#!O<8m zI`3FfN~)^Q0NTE496@aN&5YN4@v1ii)trSE#d%S1ACDiGcCkeWS3OoUmX<=_vd6dE zMJrwvZ7@woSEh{H&4l3a`cT;I_Lj@P+cV-o)fPuF*7OBXA!|cRh~n?4g25gNVz~Gj zJSN}|)8({39Rt+mrWN0Yl%--lS2g@qsNG%%y7C7Mz9fKQtL|7BJL;-Bd<1>Dh%L&I zk?Td}=pa14d0pT&S^$&QXh1OOVu5+j;G3(g9_$7pyTBJ~hf$}*rSCYRJZn*SMee*6 z*r|ZBYgrlqzBi~*!Gv;X*cLUaPTUqn8>zemHobEMCBE(U-X|1FJIz?@+Y&W`>wATO zPKKh%3T2stP3bv3ksnEmOZn~3J7TPUnJ{6?G?NvdHn~doCBOhN0VNm==T%O}TXltZ ztN{-gO7fU{`AKaMP}p|k0Ci3rD40{>HAS1di^8#Q>yFAuqGU#0OtXj!-LO8xTf$$ zn233eqN?QvQMo;*9uhyOw)!yajIat#t&E&?R^-Oigp%1WCZD7x;PA+@OWy=_H<{fl zQ3z8u!+&tGFMH^&MNIpkkR*EQ z@onAXtjsuwa6?G)Wd`_gi*Nsa)sSSDkYt**!BtyyPH(b{-ej0=@izLB0wf7U;UN37 zH#lOf@jTlV<|j`m6_ujx-bNwG=eUsYCvQ&vZ8|Lqx3(;-8kg6tp ztfF3W*ewx84o&FR`tIBD4bn(P89>3VP$o!P(ohf2sJ-;?4QM^CdBuc7asZa0gof8s z_cqkET9I8kIX{(sNTbx4;4rRTROJ-eXjamll) znzy@xp8WxR=fd)%B;-k0ZngqPHSPv;A-_a*d+a=rV$$P^;u|rusufjk1_Zs|D)74XHt_P+qTD_*3qp|?+ zvOk%C!8ZI)7Aowxp^Jxy@r%Ae#+PFa{q%@b8ZGON1?NY^T9AR?Nne=M#Y#E9stgc$l?9`!>4-uo1@P^UQSUXyaE9}T zzts-6{N5v9Kj+7w?0e=x{`BY{azv5T5_sJdL+?+>?)%-nRk`)>Py$KQ_IsG-ZJ!3& z#`o%|-{vDi|8(M)Mi12`>{guSbO`yu#pmYnzu7O7s$kx?wB_wA!K(G_lxG~tS zj}yl&i8UhCQ0kx%_)D1-!jtrG7=c3f(sd3$^Y$hCSy9-CLDBqOLty#Vwrkht;vywL z;o!Z^mzjR3{Fu=vv1Sb)TU!_$Xj`$GS_N>)nTMgciM5IHkc+_#a6I=R*jD4FOAsSc zp*-?)rHXX74@A&##h&4LAUzUw#7K?oV$tlO@APStD)t~w_PXyBxGu*L7$+mWP|Xx+ z2d|!^hXl}9LC$w@#Y1AfDMln5V)M;~vhFs+g8t6fR2kyI<}4PKe{N{~daBvM<7=wB zW=pr<^&fLl;c$;|gTpf9vZjUIF1YPp&_yW_a9l>cWk@sw{-lZ4%e?85)zSDE6#m4p z1#%#2n=EBJ6=Cci`S?9qoST*!KBa7`f(G$N-H@}#c7j7FQpmA1aR z5P)2Fsavge##DM!71yKRli4unwSNt~iWU zrv9|P#*-u;hd0s_j{fl!t&OO^A~J#E%G}lvgN0qX6aPhm4`$N#~QEQUFWqt&Z-kY(?#{QDxAY@ z$LUlar~Kx)0w@0$p7<>NUv(Z|YN(}z#_U6xU-m&^5{qmez#Hqs!}mkff*>q?4!HC2 zKV41~JkB;CVO}kM*!3A;@c3=rcU7+$oFP}}y~lLC^83XCO+SFlSn(tpQaNKevmaV? zO+ks_u{8cgV8}y47vd?6w<^7dZt#KobWv&W8|c)-K$ul$?k;ZCQ#2~Zk^?hV;?U3C zWGDkBY@p5edoB$^dQ(1IQ=pm?MV~oTCUJRGl)A>heOH<7FTqdDB(;(KX;K}|NdK)iNbFJ4UdO(my`M#hSvGbsr4QUNgu zNXogG{39t-6&PHyXEZeE^B^Le-y{k8%SUH75T}`cgtrGFs7%RCcKE24oP|J0945%r zyU!LF77ukMIiKKKteQZ%9l(@=9Z)%^QtnosY~Fu+h3jJo7<7cT?zzVSIw42B`THRj zHQc~2H?Kjh$Zuts!;nxo0$ldLV52Zu*&OtcR}>mNq`=Yy&x7%yh2e z&0eCXeq(KUz=tUvDy*YF-HCm_8*AL&hr1iz$Wl?zl_A&t+s}=sh(yOj_I17E+`k3{ z6{x*P9XL@|5D_m4_=0e$pf?IvREN`VJ~U~IL43I!sf>09H5?Oq@}Qui_hlys%msN& z4k|{x5%ZTqWwU9~Xm5Ye_C#+uQ%%Gs z(jOhU8xB#2WkK_gzar9Hs0B`WK!+9gF5gqy2E8l<6*O_`q5G3Mxxm)7S|($=6)f>cqh_k?tQ%3t?p zy*{QbL)m~35~_Y=Z;cHPYefp4*e#nkc(Z@OP!F~?Ecd1VFmeZ*v{8 zfb9+42cbxd*YSyM7psyNPmB0(5CApxY0%^vj6o2`A+c^r8+cye4e*7mTl@X`y-O96 z8|X4--Fy|O@fHD(>||F^$4*p@@OTb7MD%w6R4!)2K42%eRf8LfJM~saB?{QO)!Ona zXD?HgPhfqF9%9TbSaE=>{AzUg+-5o>I67A1@thrvZqlRo3y60D#tH-tI6$6@EMK9k zt}9SVN>q*^Ig*`0!Nhga%9;+Cj_CBJ!c|gio^|N$N^*Sqf-T-V?D3BI^?!md_W~i< zvl+zaxg#-AvRH5bzPSLZsqF(g4eB!q%hS6Mc{RVqfJ%I%mSXbW5 z0~{Wx>4>{^CT_Lcm2*{4$G9sL8*r~kz=P_CwlQN($sBRY`rskB(I9Gy?l!$tf+t@C;J(DlX>;<7 zP7sJoX3%w6($=KWLOM=%)8Ekw-~467N*;OE~mlZzgryw zuY;2gRTcNH7&lM4C+~38{%@9xEb`5cM&K_>a@%ZLfhX6MB!fsEtWHT&SJpN;V8=A% z_8a~Oo17azss^^q3P@Oxaw}-BLrlP7K{hTc*^y8TK4$xV`B16Bn+WEQg?y2sMG&RcoKge< zAp)lA?hY5IG<@Ijz*}tDUl0#$_kA$wkm5p@yM>1Z!7T|>=*;$tT>k261$Y6-pl&f| z#r<4k?d)ma1#3ik^UDct`$AsX)vWVS#7bQ$SOYQb;Tisa(N`(#usxzw?sJXkn-}+5 zo?_kvq1I>HRk(svW6W!N;HNuB5K+4NDP%-sm>$ytY=TJxodvG{;U1^AFV0a^eB?VP zz>aq1?}w@Ex`XFz0U;niWU0OU0m10eP~Q4~2DOk|hm0Lekhn77ZGrGh$bC^^)YZj8 z6*MY)nYl3yd^Uho_K0eV$*^u?K%p>item4#1P!olFrST)|0ddJ)T0xajLo3@1Z;U> zQw!Ah`U=4~XbT{uV{-}!X?!pLS7t<}Iuiu7iR)RBUQlFAu34y}%>`FAZUUy?^;6Y? zLpFDmIy^vK8?<~yRH=)Gz&Te@+lBHgw38&BPDEDC!HmeDH8MMh9!VOkiqhgDhF&O{(Kx#JAVg)m2L&b-n(~Iw$;YRGHR;LWBlvEcls_!xtd_izY`=&C zViIaG*5`0kR#NAu*%fk*GbSXm?!nrgjxUKZTbH3wEQf==wvc9cYxK@2qE~J4%6)05 z2NxRBHZzQ1g><$TY@2hKKl)I5gMN`U0NvBw{H;&FEJR({GaIIwcA zxCq`M1eqthzYdpAb@&kf;EE{qG`Hivb6F)Jw*(r1oF`}~SkCiXg&dP_C1bwBt*vDWi7LS_lUd=vIFyy8Dg@)Lk9{3flqqV0nNq z80uiO^*&e94Mj`v)jQGpV{mOM`ds>HzphWEDs->#Qnd}(@L**}SLfE6=5FwTDmXQ% zTJ^0$|0)l+j>n1}39cx1qRyPfa *38MEOM59>vX*Plt%z83Y1V&gEha1As8{d}U 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 z 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%0Af_b2fWved8%YDwqMM@vv-Re-6>ng6833 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

x|Rk%sf>`UTii^Rb6_@ThTWfXis^vs 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 z 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 z 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 z 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

|r%E-B{7->jqq*9rUIKUZf6335haY+DSASe8 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

$Vem-{;?>f*$~HOFZc^eNngV;GwBv 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%0DNq3)#uIdD7}tjqdkefxk81 zQ{IKQh^6y-2A>dLbvqP()m-;|fgkOrymcj3JZygEF~5t825%Nq>vDYf;4ktN@RjG) zZ+duzX{y^<;Zf^oUgU!}GxrIL!{eC!VR`tYW2z_B;eGR~4m5zzHov!N3E$XNb+r?G zR!POJH$3G~#eWF=s@a#0h3{LZdNmcEb%>609(>|R?R%HQ179fL*2A-1(S48Y@NEH_ zF9+Z?i>aQUfSvLDb-|W$Sut0d@Ys$|(aPJnXlSklrV*agMF4q}&Kr7|pRd}&>%ENo` zfN$D&J%^`rDbL@+W2aX?U*R*~D4yYxYMgCdjdWV|Av*km`Fp4Y@E#u8&!>dH`J%kd z06+3p>q|EHq@K!${P5CA)qe@Ne-00KJS)K8JdEh}8t@-;Rj(Stf1Bszw}ST{r}eZm z{QYFbtq=UHc~0X{c&Uo|+;Q+_!P<9CgZJ&OJe&{TYrf}Q0Z;Q=^Lhh3c3;)W9q>0r zHDCUNXL+pr41ynO%VmuB$hW%ar3;ZX`{-_-*i&wMXA5Pqz*>cc4bl3H3%C($>n&di4U zPSCtw0$+Ysb#e_ncaYYRE%5uRln;C1{ZDJWj=~#NRX=Cp3&uxvC+Ic!+bo*D_u-q% zXaRl!Pnbacyn{b`q4D|#Z`4xjOZeo@{A^*K%N+wgB9GRWgzy4OBDs%06+FRi#U~^D zhI#H~cKGhA%AW%8T@|&Cl!VVW^}Hf{+h^6on($nMw2y5B-)er}-Wp!jD~tQQy1+M> z`q>v=u(|60FnDkW<@tDc!l=s6>F~gw8pj3jQv)>LSHe@}(7L=4UT~@Mc_;jp*>4|& zZ!_&r(#PodFTv*y*Sxz84>Z3^c>+&ALhIdYc=FlGtIzPH<~d!z;rUwXe50gr=IyRh zs-JP;C(CM|kPN={fYz^c@a#iWXMEr-%=3*S;rUKRc}p5{?K zcnb5o&F1jQ`L%EC0B@E|$I}zOZng4r5PV7%)x**7$|nAk;fIT=j?IC0jjZ{;6h5ky z>d#vE{Y2VVY=uwVuJw8!Jl;d)|1o&6uiC$zgNFobzi}NNYVI36fIq&V<9`WvrB$B2 zhYx+Hb@n@a+zj;-A*C}v3-!~!A|^a}7v+B<_;U06&(!c3QI)ru;7iQ!qjJC(uG9UW zg7DbQH7=##5nGx2|L|hweZjTh2g>O@8^aU6RX=Uuld7wISNMUIns@!+Yi?=18xGH4 zp5r|Mp8Brl*9>_0yqaGN;S1AfzN~`ZzN~%GCU_R}cc8oAlM1W89fAk<(Y!ka-<(tH z(Pj9AXqp#y;NC-3Z=b^BcT>K-f&cj9?VdTAYAR>mwo9+N8V~OO zO7kl@Jn*u{%M)HIoa$8;_*wH_>AdiFS5>!*!S9;;LgnDUDk#p?;1`wtuKMsJ=I=UN zz&-p_PddV{Ox5`Ig7<%~eg0s0f$OTPW8mAKC=OHLSr;hobK(1IX&jfq_qI|USO*Wh zsCl#vUb4RC#eVpmlNzt%@O`y)zUScs=6JaC=LY;;Ud@+>@DvqvpCK6jFs}0a1AOEv z4cHHOl0fBc#MI9G95hqs8w)=8nBtQdeyO64Ck=enY0XD3_+o#>KPS9UI*o53c!@dM zfBM4bXH%Y3h8K9IysZs)l~){^!0S&{|83z_7O0Wq!9m0{)=8_E!_( zL6zLY?wSezwnLx02>z?J@+ttnV4Ld3X85o-I^W&!?&f)nhvAhUYTQr5(?3w&UV(qw zs5*HUUZl9j{TV#x0UduRd|7(M^9wxHQq^Zy8fSi<8ma5wXmF2`sx$H7ulnk`ECsys z4CQ}%_+9h;U{?5tgt~sr2VYb|{S=3PHt%aF4-d?*aj6c!5~%n$faf!RFVGSm&-~u6 z6Z~az#j`iOVjIPC2z>Zm<>y%VQSNlFT3gb?iGAy0Uggrc+EyS-=FZG zPt-n=hcj=>{ik^m8~!`1;+X_KvcC3F9`KKE6gO}9`C0nhTyW1@8sEb3mc3LrO2gY8 zQU6uonUAPG*MaBElGdFMP2oqP>%7{*uZ-8a*&RM7K@m&nR+ei7i8Xl#I>Ruo`sJP<12R`zYj^_wG`(W))&cJ(K)jD_;zF@Zc zxd;D{M*Fwt@JWSrUT@(|&Uv^y`U(%-sQ834*I|FF*Wd5`=jPEoiVnYOen*r5p7^%% zJ0<)?QeDSofG=yP`H~GDrJUO5hmUQndRPLU@Py)40X}$`)`c4IqH|QY8^Yt6ziVp+ z-?KpVyfb`thAi%Q_JP;^Py6|y@ET2&=i}hH>#F@Uc=D}2?*8Y)6Q);Xcs~+x#mzk)14uYTEt@Z92yi7yYnfvew=J&LbJe|iMHL=bsF5I)4_CG%G zs*m-##o#l|-{sYS{}`|O*$kdENO{`@o+Z56kAqk2qdb`n_iiR%34eJ~@!Sf}Ge>nk z2wq^N`neCUokr_L7<_0F^&cm_GoCdEs-KMTj<2*2F9y%+rT908rwLK}QSdd_wZB>l z?{ro3@g)37T=f$SubV*Yb&L$o<3CbSbut?~zFDu!!v7r7{obbVsOIkkyTB7n&~c80 z2On2F7r^s)Xno%eZxp8dJO|&oRO{Vy_)9PC8)Ij5o^Q@*s?RCm-;!xv&JFLCSLYo|IH_79Kk1D4O8E4)W?sYXaqfcWHNRIp0=LI`8t$Ll+nrap;SGN)&x7IiIN!rRZP)nzgxlkc zn9Z3#J-?`)$AR19Oaf1wMEjHS@N)yT4%UOmGrtoU4?ovL{mg+EJ*xG0JA7+otqYgo zy~b((^A6tG{2f!&?9TJ`G|%P82+tBn^Sv0n*G0v@HoQVtT`%;5r)j2nIu>5nPwUrg zc<#DdX9M5`6KMU~1s@kpaX1aHn@xFs1zxk3?gM;>d!1DLBjj+#=lNpQpP2BXk>!ct zO+V@M2Nb|8=QlDya%`a zhrn(BAK{m`ssE&fobf#1qxfWk+y3*yZU05#v)`-#7VsLQwa#{j+x~~bZU1B8TdQk5 z+5oSyTUd5*be9{c{Ulnfq zZw$Bnw}!vzul^^%vtQQ!XC^$Ud2ZEWcNaX$3~t-khWmF=`&Mw2;W4{u9Q(lS z@r;7oOm0x$JS=X(fl`@aCU{ojD6GS3hE4WDvP{l_ZijI-@OCEWI(9-gtP z_PvGSHg5Ick5=e&+rsVV_J-Tf9Sr}zSaDkp?>f8tuaH{zL!QFlL{WTR z!8e%i8Q;N^&sYCp@F*+fKjG8jDgVQlb)J`hdL4gMc(%C8|2XiYe&%}?cu-tjzj(l- ztWrK_g@0e9<0uS2@YKUSVJg5|cT_+9;JGs?&xgPR&F{fR!F}>;{!W0$sUV*UKV_c# zHya*#mg2S${;s6*b2wpJ+)ePLcT^{Lz~?qqp6`d}SfcgoDBRYg)9_E` zca@jme{!mx--O4Qruy&zzWuq5{{{SMgbeO+d=KAgu6KUGS5?sc!${?v`MG4F?w`bg zf8V3_$>8x5YJPdaFK!9%KA!yW*Pj*7vhdq2l~=Xl&o1luTEWNfGW8bzu($3@kARO} zto&IA@0U;GxCfprh3551_?b(pV>jW|KdX*CgQx4FpH_>_d z!IQ31KG%XjU8a3P19&I%JMd=k2YJ=L4ZLz5eQqcCux~ow9&k_d{(^q+`4hEX41r(x ztobque!IQmHUa+gi1KqP{P7^oi`nr1N@_h_2#js)H;VL-uyud=mp)7poa^+Pm_=~N&o@x$nQA)?t z6&^ZApF0@7r;Y9pOoU(kqT`zfZ*oNQWevQ%%7besy!la$`!V?3Rq5UJ@Cv+ZeK)%v z!wdhBzlXQ0q4-3tsN?;6Hp*nRPYTzpaJjtTioVNL0IuwDxrSA8j@Po(+K0@9$C|6- znGZj)Nc)+k@SBM>UIFl9sr7jW;JuQlJ{*JBNT%aC2mka<>(MoMXl%8A2fv?MTNIhxb3PBL*RAG=z3=yJi#0v_xVnTkAJG;Sp={DPM`Y(UZ=bEIq%@E zS{kq4aGP&2{hZ@o_@44MBfNQO&Fvlrpf2+mc-;M8Epx;}z{Xgyho{`7@{_pSqM!+?S zF4rP>IyHCgM*pkZY2F=%AAbJ#$Xu?|@ab3NSK!gFntD>#8P6oD`~(8SeXC$Jqm3#7FaYH@s82OzwUT!$W?1xcx4?{&3|<=6cTKzn?|*uv&emPwS|E zKG@jllUu6(T!24ctP#Eizh~-d&nC`(rv21u4j(VZ0Ms_RVR4pOzqPKz_Z2I@r;89&R6@{@K|RQx0Uc}fm$E8!s9N{`gI82 zv9|g-5092!@x2T0c~Iy33O=X5`u_?q+(rFIZsQ!U{x1~ggz&lM`T3sk?Q=En^1~;a z_j8qjx9paj_@2jEX>k7a6Q1f~)yzdXyy@~KksdQfR;O*+GpVjbLRkT0Z z0k0T8i#vgi!XLEJ%6199xw@`+?!&WYQ+;?1FI@lcitBQHhwn6hXBV}tbG+tHQvFN} zPdy=uyPx#%Nu~9TychK{Jkd1G$A|Ex#gzY{@I7Y#{1ZNU zi1Imld*`@RkEG*C3eVbF`H&G_;EL)=9(auG%EMCd4@DIJYVd=Xl@Cqe7k_Ggb%LLY zrFlI7zP+{LGY+1~uR69BUdzwhT~7|d(^PY_>pa|Nf%5+@+{0J- z{0ct4qT=%vzGsxKzan>Vj@PsV>OUcT^d22gI(Ul@x?acuUu52&TMVAr)Wgc~FfWZu z19)fizN&Wc^yWElec&T1={kKB{8ZZiU4;Gbf79X5I_mg)nf?CXfBKvG%I5z)&l{fO zZzuoz-^H%ZxJ7-b@x2G{ctH983jQ{#{3m?4dERWaZqEMundbl`hR-yA|LzG7I;?de z2fRQ&-Ipi|FLqY#E5e_fzoV%O&-GII)(SrQkMgZMJjDyee+Yc?BSqRDUU!xDvGd{f zzRFs7jn3K!?tBy~SK+7YX6}+=~Uc@$dmBpFe{p^EZTBPsC zufV;{-<#io`IC=W>U^Kb!051cRLMOp#A{n*yHqlg>9cyk!fmM}^^;%=7DN z!UyH^aG!5Oc&-KNXAu0)EzQSK@OmjVz6;>TJ(ag>;a|<)3-5y0zM%HU;k|P!Kd-!zIimBra zgui>I`MwAKB7)if!S|F9OjJAfceV9=pkczZ}>3CWe>$qkKyVe_CAEN9o`_Hfy}R;B(F2&t`+iKc;z+2c9WNUI^auiuS`L z;AbQ0ILpFknfK6DhA&#EIM;w*xtz{j3F^Ur~&_#E^8nvC#aZT?<0xLkSReawA^n($FUicbr8 z(O=4k?(jjcwH^(F7dO}YQ{cXfR0kHr=iE^qZiMeM-zV;eM@pvpa|WKhv--aczqnm> zBN*P~qw@JHe4D4PN1}{y#=l*Boo{0J+HA__4De?q&Af&$uBmwX!oSQ@J*ffT(o^}| z9RBNphkJax!@spp>-G`w!cX|Kn&v$+=i#sJE3Y2FE8Fk);TPg-UW7C8`y1WA-_IT`sr-)*pZeb0J?@_H z2mMt)^T6wO*S@O^eAPDXhwH${-`04wh5LV3zV(BbU*$d=*Le8NrOK1}@PsAQemy)} zF`d_bxR-gK&N=v+#;ODN;ji;Wb$`HHc!`Uu^RCevXB+1s*R{^ZgO7=<<4*^7JyAcU z;qzPR{zGlJosT`?r9SC=hr_pS)4pN~yr_BZ%0hU&a60~V@L5SUzPsT!+k3dqs*As^-e)Oz^-78n3+Y?g@0A5lySP^dfuLln@&jD)# zkN8Z-XWzSG=c%p3BhkJ}Ddo>(`11}L$Axg;1*&7~;CBD88*cX-C*Zc;UWG?2sl0j& zw{`d(+^&zm;dXtDInFs=ji+fHPY$>1V_x`!pIRS%;U4C_VAbJapOsfl;rC-J&Yj_> z%zHcs!e7kPJRJ{DTuJ+-IdIQxs#mMv5f-TqZ-XD5tNDHyzT4c-zX1PrQ}yQ_-0sst z;B#Xsf4;#tn(Lq_Hs9}5fro{vPBw(w zdeQ;jtD}x*2i(7!&gU?^!DQ9{tMHNabo{sBRoiNPc?e(jRo5NQ;crjqc;3MM6KX$` zaDp?=lg!^ir-$2pX)gGZqRQuz@bMp1pR2)booNcUb*u|~={)Tx2gCQY)%p6vBXrUA z+5))USL}t~sH^X7Pr>c}^CsNx(_X-9oN_N0uE-Oe@%+d&yw7?tJlQKx_X&)K_q(gQu^2vVtmZ`k{84&cU+jg?OQidpN8rEy zXn%Db{v@*Ecpu)#U)Kv^@RWDLyN^Gjzcc=iRx2Np!7IGe`DXfuXNM1Zs{0bY@P&PK z|E&@{dsfxAR&X1)PVgM>R0oE^ZQRDe-%MBgg>W0UmGDsml%MwB)p z@R{co-&gQzAJjhTBxn4;MAh|LTzDh%eQs9x0(1W`FMQ{0tw%NCbNw_f4dF{pXg%!@ zuQOD~IUN4vfyQewd}>dvrvdO&=Dx%+cv|zl(ph+!)B4l#1!kCpHO~+43~yn6htM0|sJM=MGW_A7DDLx} z4bS{s_lq{cx0>&Bcf#Z6Q2jg)KVDIF{yKbkKILI3Jl7hFplJWAM=)YJV1fIH&Ub2K?S@)#nHB4l7kZgW=m3>wLe!V>D7d zi8ReQUYiPOy-ol>o?Yv5T6o`z#mVMcY;6KqPo`$ezT!IcN{!( z7k%DT_!;xQu#NDGo0W$<;E`g`fh}g>Kv2fe}6!^1D8uvhWoeSEBAB1l)&r!bsKlD_e`vCqckFJB>!e5zn`47BG zkot);Q{!XL_fT!E3u)j9l4xDX4)2jy{S=2Mx~%;}5BSrgx*iz_KWVNzhQkZo*E}5y zZxdbngh}wN=J!-H;5lNdZp?#kiJtut#@1C?bGRg#V&Y@ z99mBgz$4S>hlZu^Pvjd z8+g6n+NXVlZ!piX{03iARQ34}e0F1fZlqbx{48?X+*gOMYOVc1JouhS`hF(~ylZa7 zITbwoKpnp)JW+dnt~dNeY~^8gxR-gpVqSQMgSu~17~XNc^0OqoNei_v2fsQ;>wXn@ z{GpnUHR0K-YChJ7k8Q0yX$mjWRrR(tJjyMtQyt+QE~<|8fN!0o_|Jkz+ob)&a(I&6 z+CKyu|GVp{_`m;4p)_~xLHj;)l^;Q9pVG9yi1sn3YuxXneJa!b1=?SapmpRk+MCC7 zx_+a*tA_F+=4>68J^nPN|3t=Zy?tb!C+3Ov9;SU3v~Szi!#!Yy&_1nc?~C?pO#G{( zeLB;=0oqs2uld*x?aj{#UER@s(nRIqP_$2P`X7h(`Kzk_%treRru`DM4>Qll*ogKS zP5Yf_fBT-|ehlq1nf5o(K6#!j?sz_jCpOQ`3xnI=8%Lg_&#~j^YOVR37``gE=0!&M zx(oX6^T7wj*71~uFEGC=r~|KX!hQIzHt_T<6zAUXtW_er|AWEs2N!jJW;XoyR&RIv z#qbC3{+_wZwI6<}xvsyC!9CAv{dx?~b4PXl6+F%W4Oj#-zipgXjZuA$0Uxzo^~w`o zVqiq~ar(e3nmS(^{^et4cl*ll3;k6;JHYG2Rz2(i&ui{uPleB{tZ|tOA9G6m?}RtW zr~S`=@b~rfxi{cmE#wd28HdZiz%!L|pRVfJ*~@^2mIRrum`G2H*+E4)G?<+*EtGd@$iRQJ-ulbYwVWq}v0r+AiwdoR=e zp(^}8b6=j1p zjmv3x)i#=UPvDEq`v*hdjlb!9BP@2t^GiwPVGMZfZOSW8xNYwP?-Nh!NNIS>iQ50v zgwHbXk8T0C`OpD=^`6GpA8y;vgg1V#eA@u8|5f*G&ca{cRR53u;lc29Wi-AqmpJ2D zyR5qmxDvt}me=)cPPomVg7CR1v`?!JpH@ZrP#@m#mg;05xZS@Ef$uJ%^PK}fZSv;> zyy<$?$sceZ^PIGBOPz5jY3gkh_^TP(x5R>Ho2m0n0I&K)`>5pb6-#uT>jhslJF0sC z3jM>2!Trs;Uk{#Tf!6nyaNB=Jc>8k7w_)&J=KVP1;M1RIeyxUA+pfB|8Gd@7;&2gu zzoV{$Zo>UqDo=jHZTrZ}oZ~g&r0z54gfDEVI2VM!@zTDcDSSq5T`#nS`^;AToCN=n zOm$-xyp4JP_&#_$5A}Z(9{f<(J2&7SW`FVkzT}X;FZc<+@m_I>xZD|^HDfft(!sNu z=X~aZ|1kZRf|uN={HzGSn?~c+3SKdr+V_KpB-4EKhqrE|dcF;Q+*k8!AAIFdts^(! zffrTB9>6ok*ZmyV3TJ#a{Z<~PhL5YM`JM@Wq?PtLS>X@HYaf*xKEk{&wIDp?rpCQE zeB&VXUk1J}ht|PL@F!yx|LX9$!xiUx@Qvns@ILU-Z#7T-|KT&?cW)}M*1!*JP@Dtd ztJ-+G2j&z!FsjzIOYkH8<)Luj$f|E)@MkNvPmaCPIlj;B->JcenET1u;cLweyZrDq zU$ySkg3oI!Zv>yQR(U%TzG9EYeImSRZsqM7c$Ei=b0GZUYF#g%f*1c!b>;bp^c)??oSG_6=xBCY__=FXzw{74HTdAIRfnWL+ z-hBdN;FCrv4wK=H%ysx`xV>Mu3vTZ-1i|fnzZ>vQdv#s>0-pbe>eUx`qpN1V2RP## zwSe|resKG{ohIY-0=RAj3sjKsP z3$O4=;~Q;_GoCh2lELp^(Z0(I{@OgREI+*MLbWdo&mKkls21=}vs5R$!|gmB2EYAG z?dz^}9{;Z4nqO`H;a%ZDgS769gqL`4_AT%<7qx$01&`fcb#fCtx_Mv53Ao=x<@p8p zvVP&*|KcUw=IwjZ^CV!2gBP2s4jhnA1ti% zim~1q=l3}@jw#_b&$GaDeO2Aaxxv}b&eK}oOZ>wtz|WcYgS3F#emcUpo8OTRg-;u% z`7#b3DM0ssmceaat%GOt)PD5{+~&y{c=%yD-zRXJC$Hh-N-AEFH#*~H`kNJgWwz#TUiiiZT3@Qb)7((~sSQ6< zL&wn>9_Ug(z2Uj)DxQTYpxRuz(2myexNe^;7;vVYr!YqP@V4q zpPx$i4+p^O*HhlkfDeeK-!CnIS2?6S+zwBWUi-2A@Us!pyT|c1+_rxVPkK)CB2u6; zoE?62 z;dlCI9IL}^|MlU~S1GTC!)^QV@QLkIpI5_onCH)LhUYl1c%FmX_SfK3&F@7+;r8!G z!r)VjD^KEVb;h%SdERvrcscVuMGknO2ijK@fS13Z`x4dRpF>pl>cbaq(7fvbpB$<> zJOIA!j@F$S@S^8+y|VzG%sh{MJG{vmtqc3%E23+EehvO>kLK?^c-zQX-19mNJ}RF2 z{|%3A-ZPS9n=}5~8mca(ftRuM7Jkz_H@gHpK{4GYuMdw9K8pJ|o5QP{_kaw5NA0Nf zVg$URc`w5Pc&Sj;jTP__1>7I&+7B<>M}7?c=9}unJ$R~2it{u0&#szZzv01|wH`&@ z?u>Jn_R60$@WG+FPRs-kUru$h1blWb9Zz|9rUtr>YYrcHLjAOd`$Scp9|2DsUB@{A zzAde%J8&!DsUPV+?Rt2x0Oi$DxOacW{|tQLQpNuXJTy@AHv}GFzK@Qu!x`u7^R({7 zfHz;PJoJR`-lF{Rf!}VXJoJUfjjB3X2_6`%^`a%b-Fscn`=*Y^7hW;5^0^W`f_ZLOOL)W?s-GR8iu@m8s7wb4z z!22ZA{9O-E_g(Fe!kg6B=bnMDHP3^60zckd^*jWAwXfz^gx${gXK1K890MM^uj;2K zywn%vp%477(#_=ypOW6>Km6!V#jPd0lX-q)NBG7CI-W`Jr(?8#o&~?XRQsh(@QD+> z-2=Z9o_B=SkqhvUKH3M~gOBZ}{X+;mM`9i4H+W(5e%L5`obfMg-k+WbzVVC6e|R7B z`|6zVVLz28#o=9o75^&m{^N8#*brW@r}CjaJlrkKm%i{ZRdl^M8a~TBhiwMDbWe@T zGI-6Gsxt@RQOtdSGw^0hweP(Jk5yR5^8#+auL*%*+*)JuAe>Lx0%m}y7 z7s>}euwN6SH@rzLtw$68;WOZ+!gRf`8gBd93=hq#dVUUW+h2p{G0)Zi2#@tc`=uZ7 z7Gni`CjM&4+7lT8$Gp-Z-e`qzmwVr_wdts-G{I7&~ZM8=lP)X zjd;KrpU*usjxpii>MMWp!24g;xD*)WpLYmExi6K?Pt!wkEYZ8 z(kt+v*|a}=4{zT|>;5;m_YfUt;)Bk3)_kt>N)1oCL-VvSJZ(?qLuq*bZ;EF}_}+19 z-wWQZi}GOs{MbYt|4O*`PTl7@120)u$A1;R!Tg=Y5BQ<0+UG|+;@Ld5~ zFY>@&nfF9Dgtsx@ceH{ppQ7WR2zPx}oM*xhnD?jegHKD6$(`rN;GG|6Ul#(8yF&Hh z6Ff#YUALw@6WU+Zg1ZK2dCTD0sEA+CQv^CwJ-i zw!_Dm_fp+~4?d-QdkSB1TKmHoN1W%o!Tc^IAv}tC|9D}zpZWXc((t>Nl|LQfw<$>FQeLDh3BcGb?OJa+B9!>{zN?LjKdA{_t8G^adkAl zdEkAfXus4DzF?&0Z!7phlMfT&TL-HDnec!%%AbAkXli$Wd0#^aJc)VF*C+TY zv!73K%o&G_dEDV~rH2o9dAR>YC3uG-nlH8Be_pF@420jVp#Dd}R~FZLy&gV!mGWde ze7Sjl=pFcX^PGpL@OLLwe_|YWp6|J4TJI9VOZU^dTo~?Wo;O$;o+U`**cNWvcZ2sZ zzk8Vhx9u0ggR*KLdk}8hpM#?yZLK74#dU3b5LH*2hQGx|yA z`DQTh4M_lRRx6WxVwZ-`H{Zusf&ZDW_PyX&%ySe6!z1<4`ZXV(!#tN}1-xJz?Z-~Q zZTk!G@?+Hh2Y8<88Qkaf1AeTo+NVC{jF0U<6MT0h-6yF9xBWMUA6TdLZUTJ#H670k z_|Md;pF82U{XuxTtIFHQaN9lv-u}c9GkE8ynirw)o0WC^anCr<*S1dv-`_;nJ0;-u z_$$EoJXQZK;I@57_`5Q?J{<*rVZPU%1P>mj`W66RnNin+o8c9w>O3#Q?cYn@fls@s z_5CCKta)DOPxy~M+An#Xb;h&FBaLHDcor{t1$fJcy3bGpe&4(gu>(An`5kyqc&BQL zr$5~OedR28?1fB(1-zF@T0qbu+d_cFTsxeLEoSo!}AZoe-Jf6f`V57o8r zO%AuefAEAS_E+4B!1Kh`ah8GKNuzb4F+6q=)r~fAU-LeZaqv=km50;dg+6KDxDCGT zg4WIb@V#fW@4X5?T2kY94_+*X_W$4E&yvX_oOi}Eeo38I2Kc}vYM%{0W}))46#STP zX7~73f={og&ua%Cxk~xa9iBR#>dAO`ts9ElbohqPs;dF;rx&#@Y=#$^s`EMuzixhK zV*j4|Z&dz1H}zUwUDrHA`}3D{JfGlIZ_6WGaK^d6xgQcA-hGbtGil)^K4>4318(pC z7l+%wEAfNd-=j8$7p!H2!o`JRC1J)-^3S$HRJZ}$mbh6i=h@!W)0KCbJ*`|w%g zRQI03PlhQ^Ucsy1Q+~dKH_WH?E(|_$itZ!*gbyC2e!|~yj^ncKssmBs@m6X75F4I$ zz4mE|;5HvT;d{(`cJjd!ZPw=&fk(Ndx={+=W4YF^^6(5jG+uu2iZyjzUmI?ZrvXP(&ONd~w5WQ2ddr1_N_9_PIBzZATARgG62cuDi#(nj#O zxpbeo1-z?yPfa^`hS{2@UErw->O6bF->uN`41hOzqj@m`elUUV^Gt`^crJsVAEo*h z2p=9%<9-mn*wm}@a9?x3=pH;}-z@I@c@1wBP4o8${MSO&=jgYb;~u%3@-rEH_-XBD zyx{B2`ycbev$oYfuq@m+$lHCKb>KEnn!vBV*E%>HZrhKChrU!EE{9*qu6nW#ezcnE zz&3cI>+*f@M`0SrTkyF@w4ZzoPn1CWI@fJyoRgd1zeR(e3a|N?4*ocn>VOY?ta(pd zF?icF+9#BQAFQu=(GGsp{EnqNymCe5!yNd8I__a~ErtJ?sQB!H_r0ze>U)qo z@bYCfPoKiC<<{}L?m6RTk3Sl`xp{9bmkK-1h$np5c(<{~4ZTr}8A?184lN`e=Wi0G@H7 z_C@L7sdH)l^?`q{pt|Y{FPci%O_kwC=IQ#S2i&$F2+x{YpSuv=>Yer>tKg02>2puO zubJO7T!4@Gqk8oYzV@=#op125Z56ll51sLRomBg=tndT3b$wR>Uc0}>w+8&vVXf~y z;kNxC_?E@W!{u<>em(p^pxR%8-!ktNy$w$~UB~kSZrewE@2-I;Ngil$Z^EwG1W}Z*}5N_KC z!;`epIveh>Gj6thboh1iUcz+nRO`&X5uVsQP}>)7+gFBrHC5iWg8Q52Zgqx7II8g) z4Y%zl!<%N%{v-fy+i!*sKdw4<5`N*l>ii}6mZ_Rw!SD&@`AZ+*XIiKpCVk?Jf1|a^ zleF;iiPe88_yqHw#7gktmsNi{!N*_I{=YZ8er+AcQuvF?s;g_^t4;km4u9K1<8mGz z&RrdZ9fO@-BWqB4Q|`-hvzW)c@yrNUi0D+e8)+}^B3H< zkNn&jx3CW8x)^TT`@ol)=hIe!H`%ZF*MaZI>g~>xesFuAYApPUxeq@T9@G3?*d}cf$8n(f0yZ;E~p7A9WYLu#5VS`qCMPkhfY# z;=$KnQhW-)tHjj#mV_tq*81BHK00Sa_X&50$5^ZNXgPdCk^hgbvw)K7*t&4zPH=aJ z!QI{6-QC^Y-QDF8EI=T*yGw9)cXtW=Q+?|Ey?d{zS?j$dA0N9;ovM5Lc2DZ4qD{Y!jVRjL0K@i`Oa@#r)0kR@eZVm|Zl&!<>YeyWF6FZm(;*+%?geK}5#5`U6i@-Gvw zb4>Cd5$_&f)+@|&|NiLnrYbM|d5)H8ybWzcy!#QkE^0-5!Ejl}!Nl_>mU)dP{-^z% z$ZFzG?ENS<5>Hz+iMO6h#Gl#svEL$oWT>p~Z{qRo_f>^?=|A7pXQcjg#7pg$K4&GK z`m^-867f!(C8rkgZMkJX_aVNuhMcd45dU^ej_Vo3<8G35TtNKDY}x1k5YG}t9{0Bs zPtYc~x1X1ZXT2-O??d8ML&*MrPh5ZB8upccKQq-2;mwapyg(G`TRI2NO1z_eJ+>V2 zNsZ(@R*iUu&GI(H`ZJBaIj;Z6`A zXzw$8{JaYT|byNPjXBuhvVhA4(HX zQ$Zf5Diiqeu|2Go<%l=;SEb*?T zz1zWam3U@*AJ@0U-wl&}^_6&zmvTOj^3K2i^X%hz9O8wy$vS2xJ}bNI|J=mKW|4hf znRv5Sa=vOvT;D&_o_K>P@_eK(@x+TIXEN~>gXKK7ka+pI(udW=N4=Hv^HJh&GRk@W zI`M*w<@JHP#QQ{$efXGo+&uC+&r9N|BFOdON8+#S_Xvl0@4qg}BM_gmM)pr`;_4SB zp2oiaur+b@I}=}*L>|`;6MryUo)4ZT-p;Muq7_!?Q4 zM#SUT&n>nhUhBK;+d;%LPLp*RMf^$!dHre;@nAN81@RX4b&0*iy9||aj}p)LL+Zan zyk~s54tzqqOe47t`Axi43OTOBeD?4E&oJ^hm6*8tsfkYwEq-C*T2E=>+1^O~O^ECF zKDQx0FqYImg!ugr^7_&k;$3pf@xFw3<-C%!ium@ka$U5W_{Q*Z9y?4t-7Gl|JRts8 zW*PSd@lE69`YOa1|32SrAjeAt;x$uBKa&&J`!WR(Z}&*{c`@R7bIW+;h|jv1)_XuU zBOYyyoKM;j|7NfIhY^oDM~=I3#2Y>pzLfZb3347-O}xTVxo$Z~{BAnJXSbauMl7T`+v8Q)ER+zEPLPb6vU(2@2O8uykK#8-ddJ;YWw{xRf$Kp z*UxQEnSJve<+X3UHM88@-)|H#B&hm-Y9Ks>$u{l*^o@SO@2UaKkJf_sw zjQHu!a@=(z{=0^p2Zj;9aa>-%oksjZX5mYTr+FjmwUPMCNOImgNc=zh`sW4Wi|qZ} z9uO~Yzeni<@t-f`d18p~{_8T;ey>?{;)}-0ag>~R+I`Z`Y{bVUmFt9J#7`ENzEvY0 zBD-AIjU|3MLU`|qU@`H`b{$s|?_5)ki(SN%e3i$$L&P6_OzzFUPrS%WIo_WW&+}30 z4Ew{sZ`-fQb$%4$OG?V)W((r;a?5eif%tIyexd2a=U0(=%_qJlvK+tLiMKs0k0bku zXSbifctHH!KzaW4g80(ua(x^2r~iDj+s_9@A)exd)R}?!Kbd9Sa}eLuL5|;w#HSvU z{aKUvxUG`kjreqXpZR{oi=UAF`9I?CyGh^vBHp{Zp~rfM|>;$ya(~Uspa^Z zP5jeH*`JGvCu%L{!}G)!hm^<7>%>pwl6C(=yiZj*E`Afwy;hE+WWW6T(`c>KpN{zW znzGMp5x%WnYgUr_wh<2_@t%jo=lm`E{3Y>) zBV?Z>{Ov#AhxTzZCGj}+zHL>ApSPc{tV6u>1UYYyAU?TcTJJubK)g&xFZOIF-rnB7 zVn6W=2jzAC+r*pN``bSz-l)7BFTaWJsv~uV@yLEO|8G7=Ry2(CEhF*r9p&*oC-EoS zl;yn`}eK% zY&qWI62FpCcn0EIRte8RJkK2Ia~a|vD@i?7h`+MmgV~07nANf$x)5(aS{|1t5kKG9 z_L=y`=hDyZ#Ix3x>(BkfM}Ls>>Lua_JTl)~#0Q;~$J0;5w+)m1@E`FlP35{PUI_pG zXDcf_8Sy_d%5_&^;yc^O>#?PY|Fci_XG`K$?E6kT5^rB4nfG{^PduKzzrl9m#TH9F zdx^(vAjj!R;%nB(`R%IZ=IY)&<16eP)#K?O!@mzPyU6+NcuaqO(!OqUfw<>UI&aeI zSpNR2pK|@MA+|qHK2G|xi+Hh4az5!5$KQVvTzEiSe}1N~JU&(>J~FoK^ZLZ~dzF&K z^Us;lTb}=>Cw{q!oIkVL`=gk@WzPSLM@#*C68h)V>?wTdJLH@Ejgy9fSY@L!NWpt!8>C-8@W|C{_CWm0&H4VPQ`X8MyUhp+zVmYY8{$3;hb zAG3tu=Kw!7`N>boc`OU~ee(G7^N}AcxBULD1o-Fj`TRxX4_+jXQ-2XpW3P8N6A!s9 zoj2?t@#weZapXMl?#tzPzeRkxeIMpC;tkr%dH56YZ{-4H5_$ah=f-tuy*vW(7q6sm zv55cgf0Cb+ctLxdrYAnMl*}s^@g%9G4@HUpJx#6$suM42zyJCi@tDWub-A0wS8SB& zJ|SLeot!t`S#BQ9wx$=?{Qpn#qa>H}TbR85eM?|p&yPlYzI~k`5%DCSj4xzfbBRCcBKvJA@%t_0xL8B{(kaet~2ivf7(sIVE*F2GU?~d{~xR`hr`#mokh}YgN&s&cWPu5?q+piLD@=>k_o)Pa*L5|19_WnI) z70lz#hz+T{Cy6%1tJIap)2_s$2b1HaAMy3`Ieu!ee`t4WzdOMK| z_&D$@TCVfDbu6jZuSHz>va&dPay0pyQ`{5|CBzI_k=81SPPm-@9H^-Ec9)@3yK ztsVY+@JE4v5%@^pFDOUXz?uJs=&Yby45Y7y7`F2qN_^y;2I zkUt#qmjNFJd>7^DxX+2}xFJgV_c{JzdEOMka#QtC$j=3Q2=I!OQz^fkhueZb82lmR z&q*W4^;qJ1yq_cyojIe;?+ttb@Ls?-QGW6Ea{acOc*Ov@p1cG(Js~Gj zS*hRD(*t-)%gvvfb@`B0j=NICb-pbjr#s{<1l|q!W=GBm@VkP4&*6UtzYF-0?Dq+1 z{aR;S;(ET#YPsn{XUHi-zUDLpzZ3X99R5h~JA%J}d_BJ{Cw|Vp|7tJfbby>&Fn%{!>?BVjfz8CSE_Hk`6@p|@q6J}Vh z+_gMaj_+87RRPwLqm*veY*ZrXD+aLUPupg$8uh-T4$*(MJ z^E@YB-}1t>)#>1*m6%*|9XyEp47pMId~`H7i`W{;*TvqK-^>Z)dL3) zRn5Q7MApwlJip~Ff!mMTdPg2$xvsBXr;Q}upoF|`Gl{tF^R>kFI_-evrVKM~B|G1z z4j#F>Z(hdlWBp7HUd6!&5Fc%G7874+`5EGeEdNFPyyYot_}8iZujAl@9DFVDf_A+w z5HD%@N8$}Fk6F{d9_6_lyczMfHfJR90hVte{y)oK5Wi%3{ z&s*;jfBCPxU*Z*U&G}4Rb0XCB?^}V+@_aA`am`6cTyt_-Zu-^)`cr{?&8bOTa~cuX z=QW*(w^$<2Lq}U~#%&Dat|VXg;W6+Vfq#em!KvkS{*T}{1V45?>6;n10r29$>jST6 zx$e*2Nu~bg#I?@O#NT~N8aPtmYl+0wpG{o%`4Zw8a?0zXdx=-fD6hL7wcO0B9@HPI zzW+YaaU&DgabpwLadQy=?}q#yr-0>V+`2GsKk{|l;ly>^3B+~W)x;Bxk@v}LwA_qa z2gZFzzK;8yxQ-j5fq#E=+@!>l7fTs9lE;(Qax-pi7`GMqI&Np;I&L50I_@muH!`O4 zj=RWmGj1&y_ZIm&?qlLQ?i=DdZls3(eYkx`ejggsax-pC7`GbvI&OX9I&KT%I_@yy zCGN|4c%0>C+!`?Maq@NC^Tc)B8^m?oZ^XYfme)UnHIhDj->*dcabh_yH6VW7 ze$KHk0ILI;cx^CyY!NIQ*e{cOC#Os*C z0)LV#?_S@?<8S(E8PgXSKW!@#2>62VM%!pI0q6qvQ2e%yzzdN`jx>a`tl*kDE^3pHP~3 z4*UIFwTbVTBF`UN6W8zInnYaB!*eV*r>U}m|693D4!qsK?N?O={;9|F5O{Hz*AL3q z=iMROOCNN;KaPpkc|7sB@#Oc^(=9jsECTaAPrlZF zgSd|SkhqQ;yrchoAC!`P7~XO-ZebX=H2FGi72-N>UE(@!Z{l(7eFg?wZpJMH<8A_8 z5cq$<3jmMN$$ws2XC?>FOX`>TAMj(8qjjDmuJv3e zuJwE+9-@f6582aM`e5qG3*%-7o(Fgn%gx`y{oIzg*3+B#*pl*mb};1Ik5U9C2A-dQ z=YnzfQjYfFx`RI;uJybl9&kCScieB5n?B@(aT9j&-zR(R=XX+CZu}hJR{)+Jcyr3p z`ul*N4gARtf1QJGBd+t^Ph9uIMdCRI$a(vwSu89 zoWylrg^26CsuFMVQ;y%dmYaE{hkC{W4*LqIs7=?gYHjK z;@XFF#I+9vh!;L0?~g5Ex#>e1sHX?;)WFA6j`m>*_^H6(=J2mL_+8>UuV=({UO$QF z?k~q<$R57qE+y2H9e4`h3X9Ms= zz>iao?!(*QCj|eK!;jG`=>8-muJcMoT<4XC_^ikBd%z-=n|URGdb$9Q4}3J`=)4wy z9}oOZ4*!CK-z2W{dPH33^_BSF-Q;ybPjBDx6&LEs3_K3-QkL`aRUiD=;CFNQQyqLB zah=zn#C2Xboi&hj|%<+hyRv%3VR=au40hXKoM1lOG z*>! zVZjeRz<<8#C$QY)hXFsE!!HefXz&|3{O;g~0)L#tUkiRn@DDls$KZzm|38PHc%X0p z2M0f=<-GsPfgcR~rVhUs_#W^lI{c;J|CX&8xDp=E4u^k)eEpo-74nb1kl(}p2mUX} zk1;6d{gBpjvmgEgzp%q^*HV-;`%r^oOr()a{WBsa#PPwsAm`OAHXkAj_&iP z;C~1Ix5G~~IOzVQCa&|!OkC$xjCj=_^7vlPax<@QP)|SLUx81h9G%x1@V|h6(Ba<& z|1^rnKH1mfv+1_ZuNyGA-Wta1-=`ey^H1>KK~D6cLGP2q#N)@1=S`W2Pp=~H11xR1sq-!5HzHr_=}-Jw z8o3^u0Xc6VXASw9bJW4l64(CUCSI{riR=EELHzV#nb!i#O`R{Gp3}ge1Aj<4TIV{QKaqbog6{A2}kwr`rKJh7>3q_5a(?(@qOUU7))dZi-1D4E=sAfx4`pLbwh?a0^s?!+~JDDmWdl6&Vh z7V>XF{!#KZ|15FMzfHWAy-)9B$iD&k$wvjpc;O8S>>nToL>#0h7@icw%af<7kl!Ej zdy?PkwY>juAo%^jUq`<7XB%;?=Lqo(BjgPM|3ZEr$bV1%Tid+v;P(bUV4VMc(|WQK z*LsQ)A8Nn1v8?5$|J@+JBl-WVllRs41ivfzE6LY-{vodQ>?Izvq8wjGA-_4~zaanB zRXM&sfZq)K6yt;LPX^*zPd?)LH_7{Ridk;@QyubKksoETJm2gDel_qHldt_*MO^FI zO1xqXx$fNy`Q;)10r{hw%6h#3za02+Cj{M}ti%U4l0N6P-1MOg4W+n*WPzZM~Hv6-46&g5&(MB;^~$v&S6Irdf{fr%c^Me;T0J@H=lgOT4Mr!dr$drHvrs!9CJ(^TGZ z8(MDWRS0tCldtvcAYQ+Otos4TDF`{9sX^D1hlXmYaGCKu&Y=wVt8G=k7Ou z)8iQnIr$;y82Os>nD~IXQqOD1$p<;1q zzV1U#PROY^!@qvb=}Y|jF}V&OYPp$L4#%0{qP2 z2cH#m|6^Nj>dypzMu%Sv{EXn&b@-jY&j9{Nhd&Se^x$uB_(#DH0ROhb{{VhE@Wamz zx<836H~mQqeolv94*WFWH+A^Ez)ubSM2Ei={8ZrYaQJ7yPYM2GhyMfo6yQgj6Lfzv zICxIt&p*g{xES%F_vG=bGVxNM#BV^nyyb0)SFyZ5@f1bmdD(Ew%{nHBd7TBG4EPhu zxlmuOKfe-B7+W4U!_4*XPuOEJZY<*Z`|gyMn|jQ+IRpP`e#!w)3iULlob1Wu$^BsP zlYl?V;co{&G5BZMxA*P*0DdCyqtEl7uil3+8F4-D)wJBqHzDMV1)c!-UzAg+LQ-#k zb`U>PS?V}JJnb@h{qQ>RPVpt@ljWu!`&F0T{D|}Y=ap-eJdcP;TwdP;O{6$*YQ8% zdLOW83#4x*KMv%S10EarXyCDcFS6XE;q!~t#C6<#kP{Pfz5$N`Jm*4Roza0;u$;%O zLtMx02szOpX9e)6z#jsS0{jQ%YoEg`k~*~>9XEmHrq6#sP7UCZflmb<3HU0?*Ks!! z*KtokPDIEFvDnwo2*3*i4-dSSRX3gPebWhXwu`co^WJmiYHm$Bjx{ z$4zCq>2qkvX#_kJ@cF<)0^dmaI__TLI_^ct2?05g{^#p+aNuQu2Ls;Pa_;j8@IBx! zb@=au4w;lcu@PC0HcWKc5&uO{o^MBx1aQJP({|WwRhrbN`AK)K!_;@KajO^%n*I zBlwLS{s8blfIrXSZv+25_-7pcOYq-;AAY&ai|bEqx!G@T!7t(Pn}Yua{J{=?0r;=M z-|6tLg8vHq&kjHO3g3FY1V4-AJl~4ozW~3z!yg0wbMRL<{6pYB1OL9m{|)|A@MEnE zdcCq)Zr1Aw_*EQ!NAMqmKi=W50{;>CM;-n{@E?L7d{xl(C$`+w{{Z{~4!@UMYCz~RpX-+pzsx1N82UxA!Qj+~$1Uj{$> z8sBsbDscx%f8{`Rel{fb2IpOmqj*QF)+XQ2M! z4u1*w|AN2Q;ok)RH25DJe#CXY`cHvh68K5reSzDrSoY3$1MuVEpS7I(@Cy86;73?5 z^{}7Da{Jsu<0r-c(-$nl0v!T56JxN@z|6f7QA;<~&kN>20~0ugS^h9X0SfYl!Qg)E0i0gdM64%$~pFzHPh2+=&AHe?o)SRpvee<$kQSY5u8OyaEeci7vaedvdEpdH) zuP^ae)#UfcV~FePgexpJeKz&vwte2_@E;S`d3_{aYOIX=6Y}>$|5I%8^}+P5q3v5f z%e4=hUjh6*kkfHpuI%#~^<<zvoX-wys;@^wE4+u~nmtnc!Bu2`0vI=4Yi2J#~mlh^%n5r4Qq z&NGFH59uerw<<$iUx%ziTwh0RLtI}+?L+*~5&8V}M9WS6TcQ4CyHl#FrhC{vRZMc8a_Y@TTRaKbxRGAIR7JA7`8N+4vj5&t$nCU+Px} ze*^gK9DXn2di^#Ta{hsw73AxI0pV|@b5VMkKq3WeuNz|U!GTH%gugR1%4@q-yZyx z;16^7lZa~{Rzl7S$k|K2_TeJ<%fWx)@I5$?p6*p_o2@`L{;_|+VKZ}69bKgr?G zBd&ee1UdhMoRj2hAMS#`1pF@!KjJQ59~Oh3+;Z+iY48_;-^k$)2Y(^>^Bn#P;@XFO zkh1`Cu8^;NcnSV|@I&mD`SQHtSZ?Q+B6LcSvTWof6Y!zHCsK~)FCeb#z8`Xi zK+Z4VgMlYJ=wGMirzfuY6)iXQ4}zQy$XVm?kAvSA{3i}S=Gz_oG7i5b_}#!C?(mm@ z-xd754*w?jUBLg~@S`2|tyd@T8vySJd#O_lAaU*66Ub=~IVq0$>S+hO znB|;bmAK~jgq*gJvk`b3;O8kv>%T`_^Ftl?)!7fbNDO4ZwmeqhyMWlCg2A< z=|3;6Kau68{>I?vcldR{Zv=iXhd&+shTv~-_-DXx0RAh7AO4ig%hX>V{4|#9I_i8& zfL{;%`VPMz_;tWP0=zcxU%+btPk7qD|GGcZ6W2N`T5jf56LLC`uQ~mRYtCZGsR22+ zfL90plX5ga!oU9YYkmgHO`X*sr<}uY4SrSdM>_na;8y{Ezr()`er51~I{dh2eEX*o z__-|S{Zk$Mir{y3_>;h|0RD1^e;E97;72*@tFtWd(!k3AZ)LgGuj|;8xUS<|$SDmu zSAdrS{+V(#Kh!y?Q^(c(w3eHFQWA1n0xto41m$S{bmE%74RVS@&L`l-fX6uRU#I3L zC$9M=EI0KRg`8#%e=zt(z+dR_cYBdx4|y?#?1kK z9^l!5_W+&^_;kxPU-#Qm;<{c(Atx*3guLvlCkycSmTSJ|q$aMfOXQ^-xl6mp)6~I7 z5ziI44(8`C;F+PGC%`iS4}C@I*E+QiF^Oya1suF2ah+E!;?wQ-7B#Zm?4OKK&v@ec zK9kMF_1Dzbi0kj`e-PLAnIyUDTQ4)NzR#or@C;ZF@btis0}lZH)N=0U7vkE__}6^( zq=TH=z|#WnWjW`MB(C`@Atw#w9C7#$z)uZ+u8=>p$>9-dB*5_?`gy`?ZM=4JG&OX-)Yx1Ltji`jDR~jC@{i z68O=fp5^3gJ?n^TJ?DsPJ@+VI-;ea3e61(!9bbQ|JC#U7ypAy7% zUacS}BIJxDU;8tIxaMq!oCuKf9(Z`*;qLpdqxLNtaqU}X;@Y=DmYel8`MR$vldpYi z34S=JX8`$H&q(50&r0H2&o;`}dXAH?^*kf4{S5oSzdz+)%KPG?SZ?MU7V0k!JPh!9 zl%w-%PF&|TlDN)mCgtn?TtU9hYd`p*p`N?s>%MwJT>B9Bp>Lmuf}EPbLjvz$x$b|> z847*~@aK@P_3R?9IiVi;>M=R`zQ+LK#RJ#J{8S-cS+J)U@%ol8Ag zznJaMB;tB}?I7OK`d=LRp`QBpvr#s=|4$^#P2W6FXHnq4<)iZ65e5VQ1$-Ou|A2o6 z{u6k{XTHAKTLydc>j3`_erLO-a^T!P9;!B=pEwvJ@wx$H}m=ob!L3wo7X4cErFXl=NFJV2UyPY8bkgo z>+f{Y|b3uZ=nwdftx;T$}fGm zPWk#hfDedk|9?QvYsgRj%D;Yn-*qm_O`XQiWd0@arvdTm@-|h^aNw^Xe;(!Q{VsNc z{}TL*?A!ZSgnBJ?nlfI1pTKhNe|_+ugWrvOy^m0T;`%=45ybU4nnGOfBea;fzAt){ z<);2;P|r#7_5ICZ#xPh88*pBi7U=aUiF z<2Ms=z0R*fT(9$+Sgzv^3LMA$bR}P}^QRKmapw`&*QM7IZ~K>ApKP|=jQarQdk^@1 z;PKx3`g0F>E#P;7ceI@Q){D5-Kb*MsZ8>r6+ZM}B{btNZB{nvBR4tIW&PZ^j)K_)qf_%W@r8`<5Pj`xSRyzXkArq5l5?KMj04?3-_1W?VgvqFK)K%1T_v%|~4KLj~fuOUiX%4a-eGPr-bL06z(Q2k;ZXzW_fD zJo0y$m)5U+i%neXPeEM!R+6~(t)}IselxBfN3F=${V8cE`sQ^I@^e|v^%ow9`7MASfc)W9{4WcF@DLony>Rp zWVzXgWdr|r^>3M1dEh%Cryb?!ym}GW{2`EI@_VnA{B^*0K>oj!qxsi~YyLyXG5NVy zN`AE8W~LsGnb&s6&tkdxQ+$7Z0pglp3UW;T*FPn{5%6u0Kag^?{;|Y0e>&ut{B27l ze+}@hkbjbLH2(^5&A$gZCcpPW$^Qm?3*^V~$mg56KPiZ7en!i6|Ky%0`6YpGhWr+k zqxE+ouKE2S$J_r|l0OajCdl7FIhwzRxaJ>&9B==pOa5Kp8zKKUX0|AG7^l%x3%z+Vr3@ZkRQQa>_rttXD< zI^W!5rJlmT*FkZv_I>iHA+3dlc5IohAk;4cS1W~iX|b5i13 zPk`mRpOf^JdI|tv2Kn_VN9&mc{-5Cg?eKRH*Ln^?j_J?So>I?c;7cL@6Xj?sd@(^Z$YzlfSZ)1il&fYdabDH1PS5^OACOUSEl8e(qveR;L{<0Fy(0d z`A!8tiRC=s^u)EEoRDMc*;ifaX$X7@lfggZ@NW>;dLBWJsi${UspkvuNsu2aqW^KE^^~#P97hwuZ|(5A6W4kMLXN2? zb7iS#GVlqIzm9UWp8MdB2R~S(pzDuBTA=P~f!kRK{~(DTi0xtUil@M}5zX2i9g z4v=H&S)Wzv83ep1O$wZ&QxuKPRsFA0fx&hfOB=5o7z-r7h$KSgw86{9ME}zX;@* z{LG0ZzYg#=kl&kfG=Dg8&7TN4Cckz9$zKk0j>#V!SMomqZw2|$ z;sm{a5)s$@G?wfBxf)aQD+6x{`K>5N>t6+a3-FIQ{ENi3p4*UP<{LJ;)Dt?cZ$C7L z{FIi54|MUoR2%$e;P-O)!-#7=6ClUblRJvkvj})o$lpUb+MjpeHvvCNyr9=B0dcJ- zmF2o#ts_Z2xq&x^{92Tw^^66-5%{Yd{$}D@&tAwe{h1wJ>Ny9zA>_ZM9IYpLeBb&u z0Kcf^yuKBQYdy6g$JBEyjMUQ^czwv9L^)c|JmQ+a4023gSgK35#*Tpu6!f&jg-uH+?9j;^p=MYbn$V#5%^`n zAL#JM64!dBV@}{0vtLO)%Ym1H{KJ%^`{672rNNJ#Jm~dGPF(BBV7aNn^k?)7sizR| zQjp(}a7nE$`rnJEC#%p<-CqVi4Q6--+Mb2 za*9IE9^gfQds6!MS?dpPx%TtvW2rwk@WPN&i*mG|&4_D$2gvdE=b_}!16~O7cTkSz zA0e*!XCcSrr@1frzknBn{Di6e`=I%0iEDl~%e6mY??`?<-~}MRE9GeZCh+rvf5GA3 zA+Gg2gB&y8eK)0^*r|Q{Dj($Mw4C=ia$1{Ss_J1Mq#&P62a5?1Xg#63Ea{&KDIhvm$ zt*`&t!EXRO8}ROy>$+(DW5CY}{$hu}8T>5ZpK|z*z|RbR$aMbeqVvjPxmk@&;8!MJ zKX2ZMxaN#<@WsSyc+z;+ai!&E+>9{pZSpn$3-O5mlh3{WhMWwLlOrJL`Bo(UaG`uY zrKaVkp7fA2o_wum8S(D3G6LQVkW+yb5s_)p5w{IuzP`zku_zyDfiV&a-p%E22GFPL1uf2XzOX1!9vxC_bG z{H??*rAg=AhkGF>1>}4pUvna7^q<#=b@uy)EI0Kehn(`{YfcN|htkXYv^zjfGRRp< zzUJ&Ae!Fxs@4OB{PEyGEPQKq$a`h&yqXg)c}_kL(jM|-L;if=v4HQS z9L;|UeoXMgWc8ny_CFSJ&B<=LnQsip=>|MH@b$o>0YB=T2fk9HBi3GrT2rOr;oi*`ui%^yHK z^J&xB!0#@x-27Km=-XQIb-m6K*Y%2ZJPa|Hz z@-4(0S$>nazMd8?r?1bZo(0y=NPLy$Rf+#&d3WLmEuTaDyyYi={{eltXF2!(r-O&j z<=^LdALRTK&2rP{$dI2GcqHJpDM#162lx@epXTt_gC7C>e;xh{@WX>2F1P=@w4PL! zn|Xx;zqrG10)ANV2RZ!t;D-T!hr_=DerWJNIs9mOeETgF_?a!|b*}(^NbuV^{L$ct z0DqCg-vxef@UJ`kFW?6QKW5&b*DI^#X1zS%S9bUv!2kV+`AhOM&f%{F{}=d29R36F z{{uf*zM$(*WVxyTC;0gtejV_CfZxmEPY3@y_!}Jl8SuY>|JdP&$nV>4U%^jmIq$cE z;C}(XzQgYW{%7!KIs8rFe**u!!+!(*NAM#R2ztHJS#H+r1NfyKehcv5gWuob&jJ4( z_*)(RMeyH(|K8#MQP8(uZ@|xBIj>h)@Lz-9+To7?{}uR49sWM>UxI(z;r{^t1^96b z1-)K5EjR1+9Q+Cnzb*LBz#r}Kmx2Ej{DThvF8EKt|K;%G7xt~!WAL+D&g)eP{72w- zaQI`ve+d3chkqFS2jD+&_?{xZ`tO6E$a1bfANcpcujBB0f`1qM=??!N@b7?s%;Dby z|2Fs$i~8nu3wUzNbzXWusPx43K6^Qc>-~s|6W99@RU@wV&1+@3+2=Q*o`K}+xTA<` z&SJ>90XbXA*XyP84t|69(%`WID+~O+BJqKB<9PXZ;y+@`?_Yu!^Y!^U)RPzZHQ-e& z*Zymr4T$S~1A0QvRmhn@zUHiS@GX#Y1#(W4KO~1dAG}Qb^HzC2dx!X%BGJ7430YkF z!1IbtT<3c%A+pHprB#X7&n4gY-GaF04~G1ckUxujUB|QFp8)@f!*5W^e_oo?j`+FN zGT&~NoB1Ax{CU8S0pCD5ntuiSqu{?LU+?Gkg}8pdc|d8Y-_&yia*A56$BX8aBd+&_ zt4>_cw=Ib4{oi^L*ZUlfBz`JkfOkL7v)t5q80y?WzTVGpCvm->;UVJscyW=q*8h;W z-mmYs<))rPP*1ipzJ4AAUe&+K1J|b-i{I*Y!F>T>JCLa#PPUblUc05pFY6%K>d>`N7ri^_`AX15sB;X5fT#blQFG#UMVd%^W6#KHYH#4+Y#66fj-3l7fkL~HU#o_K>jM=+kx++ z93A%(_}jpL>F|S9@SU%=f*;3nJzr^lHV4m7T>Dvpc?Xf|{0H(kL;f}3n}ENi933}QMPGk5f}g;0?oSr*H-KN-;Wu;e_QbWH1BjQ7 zmBhP0M_6w9`45bHfP9_TN#feiYs7zNj_%FB2l?wEKU^hWf7Ss{VmbFG2l#)3U*6$2 z0e>y{y&e8E2cJ(|`?;3*p80Y<+-$k&=NcIIJ^9+tAH=nv;VS!|e_m{q<2|b7CVw^L z=K=l~@XC~<Kdh@X6u)VqHkS#IXL0>({N#lJr~ zuK?oO&pgBfqDy`e%T4}r$ZrFD8SufBqvOs1|4;DOIQ;$KF9rXq!~fvmKZ$EUBUSb9 zPtBcSz5R@7xtZ_(VBAu`mjG`_IXd5N;4cP$tixXf{vz9#yj3UcauwrHU{9X=!I{4GTU+wVs64&>U9*3N=fwd;N zWGeP}-T|Kq`C)4YJ>LYxH9wW*Ce`F$y&(A&flq<_7L=p?pA7zF@Ygx~Q{Ybm|AoU3 zTTA9;s+$OYV$1pZAsca>Zvn_L^WAn{=Gz4L1jt|N$XN@%$yt0>axMWM4>>O>NBj1b zxV~N*wYG1*;~*!ExuVzE9bp!kTV8y-T@yC{Es?8@52n1 zn{JH)znsHw4gN^*M>_na;Ew=*zr()`{&4VrI{dhGed{s|{9Kmvx>N^$DEM6+{v_~+ zfWOw^p8$U__)i^vsCvHo2Z5j5a<0EH_yfVOqr7`$K-z`a$o{{Fa-)-4FbF4!<|}eZimQ@Hc_q2mA{T|1J2v z!T+N{(DkRc+|=I-{BjPzHTXTjALa1>1iuIP2Oa)h@VkTm+u*k19(@+-|EQO55CE{dr)$o0q+7i;Trj`i@rZFGI8C{X)HIXogt@? z!!Ji%&-2Y8rxWD#BVWfIMO<^{K~6`=xlO+2glOzP->FmNb$|$#n|XDBoZ`US18+(> znm-u)cHl2`_&dRG3;s2S{~7!?;KyjrZI8slPe+`5b<2@SB0($>9$puE*m9$T7#!;Qgiok7p6^rjWmda~u$T9hg_e%a_;7uU^Kg!YkG|i-M=5IF!zm(pU*|!UUmxKJxl%wl=oVey+fE<%Qc&p_9Ko0rQ+62A61uQpzyA1gC9eyA1OM^e# z;co`N6!;e%{yXqXf*+-A(Di4q+|*wJ{NfJ3HgWA|Q^+y>jI%}hITCnr$Uo-DIS;~06!1-^Bn#*@NDkD{IU+e75G`fAL;Oyf}aKa0}lTV_?f}~gcJW^qJs(CQuIroHa+8`Katb>9vc&Z~)(mn2 zAg3?+I_^l~nll%2(m~EG@--)TSO59`&Mm*E4sW@cS6av^20RV$x|E~&Es1NLBZ#a2 z7xA*`pl8tg zGmqtF_Hn_l?C_ft*M4?{9MjLROQoMvfyaUTi;kSz;G3M@izO#?FW(4R6a0wa$M5UEj_T*O+>90h{7MeL8F8(@1LT>YIkE6Db z69#gIkgwxTAg(z}ASX2BJS1Op!u9u`Z~R&Ex>Hok&AdWEPFdg~fj6NX&7VO0(>nP) z%R=J%y{H=@KLq5z03IB8@B#ky>$tIr-yS7*Ud&>-nO88#DNVlSR41-|o=IH&ZNz8J zmCrZrv)sG;VB8b~gRZ|A@kz1e`mLPhCg-=jl{WC-0>6J4_%GmdDM#x(LR{D73UOVR zmyrJ->^)to)XtS zd?T)Xh&X#w@+TPTy zisfcr?_u0?M139~ZzXpDRa&*4GiR(UzGQz(e-6tt6H{-s7{06{Z0`E>aI_?p6^ji+s)bN&LSU^85Obqhwr@^9*uE zTF&QUjb=4}d=g{+)7kzNyCekE{D6 z7jfMu#s~I^w!djzP`?$hkwl=0q49bo~j4YyDX)H}&6#{5HVv z0Ut~`I_?bOTK_u8xeGZ*$k&|n#C4xPhMYT)lYE?SAKnI@*K*#6Es5*;_9CwR84vll zApa2Xo4{{Sj?U{XaqUm|@xJ$szcZ?-oRon*OL_v;1&K0KW+SF5nk{e+GUIc#%oI{IkIO0sj~H5#XnRd+h5F zrv8(_%K<+Dd<5|0z|R6d20Z)}U!6yQ*8qMP_*CGBfUmNg_wx?$4}$+1_yOR5OqF^x z=j2hjxl|nD7gEXlkn>w^`m-N$s*xXgfl2gu>J!)dapL^{=TtDABg1FwtW1;1y zo_$cyX7Y9ap8$U^`1c(CXYlucA8DF@-!wliaXp`8wcOOX8*<8!uQ?6D-vxdThd&bh zo!~DZ|H@vuo?K2`?_08!xZa2GF>$T)H}MM3<#V`Uru+8!4ydPq<+|TAryls*!Jh+s z8}Rj%qxT!xPF(XZ5g*?tso9>M@5E2v3h?p}GlE{<+?JblG*{H~?H8HUBwur;0N)h& z(!+W?hh>nn5$1Ib_y*unXZreS#tm)9O=G$CQ|~vDh4{}TA-wa-OI-WYgYxg%8%K^O zU-M@W-w{skZ?lm2)}!)%fEC1dS^f`kz5mHU;vx2@@z#IMa_#>STW8u?L9bU8;z@tW zd904*CTAV=c_;bWp9jR#|C!8N&kM-;8*;MD_N~`i;H53s_1c|YuD7cYA2e59ry4^% zn*AQwMU=nTTTS3SksLhO9RGUM4{-1*#5daffyDP)zMA-5%TGD@YX^@v*T2q}Hm3mb z-z!O=n`$zX@MetXE--7((W##q5j>I)*KJm86b{eR%6v|Rhvwv60AIV17x$>jBfnvk;uayqad zNxs*hFY$c0sLZfCdfGnd?D0- zpK^3vUJ%#&t9&G`>lJd5|31|9ia}iOqmsgMv)@cTa&`{ z7wUXMzMhAFftk z8e+Mb*KDYBHu+lT65?9t-^8`fJ;b%ne~D|IuZU}%q5tPQug-!x6I!n4Rjo4>aji2O zajml$ajml&ajml}ajkQl<))uAq0ax2uXU~_u61rBu5}(Gu614|u62GPu60IP>g(qW zs57wayB}wax~_wa)&;wayuqn|@A*I@ge|{oG7k>pVbQ>pV|f>%31~ z>kRg%|M9DJ#v|Kos{uw3`Q z&bJD2eO;&%arGw>AJIqrfld z@XHd{`L-dh`CW;tKa6;ac5>Y~&T>=FNT_Eg@DaezQ;v@N6#U`f|91GXSNhg{DELi* z4*@I9TxXyPF@&7K$^NvxLn|}6(arXl62mCVS zX#Pv^`+^_hub}r;9LvqPeZbG;@beMZ`PL<_^)w@{es|*6OUdhF{Vg~3^oDxY0Ph9- zAm!+|*TC-y{#%D1cC~NayMtd1csJk^fOi3Y6?kXhQP%kKI{{B^Iq$1%#IV z-V}JSb-tV?z~flXeNI7K`&N{=&bJBiM&;#op*EJAel~`2mjG`Bd>iFx{=eWi1pkr4 z{|E+^m+Py#mYaT7 zg>k0>uL67(y3^1h}@mYY6Q zgn9-6uK;`|<>Y=ayI+_2d$#)ekPqs~ z3_LIJQkHWc8i1b%{O%5aJovf6zXCiL@Tl8-b>;+KA9xPn(}8CPzS?rGe=BkA+a==K z=a0lQbd~S7{b{*5|73%4vv2p+pA~pH%ekH=;Aa89x5J+RerE9hbolFu>wM1;*ZE#0 zuKqLP`+k_21m0)Ta?^)QP*2hwzCL6Gp4)QnLq+g2fZxL54+B3v_;blmxJX_fUP}Dd zOZjla8shKfBr((S>?VGHo2>h5;(5!+_lN!_uJ1EUyi@wX^G!osUq{MIy!>Q¥$C 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�UBICH-%tA1(S!DSUrT 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

T~ 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

M7_$cIt?E~&@ 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

2ZcuJHj)5L<&L%aoq#N~cGkeKWsW+Sj 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%0Dp$U zUj+P175SF{|M3cc8SpPt_-6zEIE8;6@K-A8Uk&^xDEzg+ze17!3gFLF_*Vh{YYKl8 z@SmvgHv|9c3jccGAFuFl1pa!3zXSMBQuqUn_Wa@B|KCvfV~F>^{x(73j|2Xd3V$N- zpRDjF1OJ-}{|MlpsPK;l{#6QpCh(u4@Mi)4TMB`0IgxlEU8r{2wX&Yk~hvg}(*( zKUVnLfImm!ZwLOh3jcQCKTF|{dO!U9^NGSAOT7R5bB@9v5B$vve-iNLD*P$H|DD30 z2K{H?&hS>fLZ{P_z1 zR^b0d;Sa0{fBoxHg+H4382f8FKR?i}@W%mvfx@2v{J$#v$-sZP!k-HKTNM7$z<-6p zp8@>8Dg0T$f2G2o1N>VR{wcs;sPGp6|L+QaG4Nld@RtDp9}0gt@L#R)&j$WK75*yV zzeeG&2L29(e<|=^tMIP?{=XFd2H?L=;co)|Z3=%2@L#X+uLu6SQX>ESK|AmlEBqb6 zzg>}k)Cb|`pBohZ7~=ispMMnoc;LTL;ZFqq9SVO6@K00tM*x37!KVZNO$t5}@b@ZS zf6oH^1jYOJIe?$8h(8bTGZcJ2;7b&IA>eOT@Wp^XSn>D2O8|ekf-eL74T{g-Q~>@% z#lIg|3HX_c`c)C{-#;1@e}B6M_-|GCmjeDa1-}Bs|E?nb2H?L#;co)|)e3(L@Ruw6 z>w*70g})v6?^XCa0AHct15M$t-!&@Yk0IXw`rRyrKMwfcSNIcw|3QU68Ti*I{3C#W zj>110_&-qiGl74;!k-2FyDF|9asl5>!RG@$TEQ0qzDZHP62LE1)UO=yyDQ?a1blA= zzYy?yDfn8z_f_yK0KZ64zXrhXt?;iU-v95Pe5mMu8}KJ8{96G(K*0w-4BvkTD)<<{ z4^r^)fPYreza+pXDg3E`AFkj>1O8wIpGmy`{FSQUvw;6F1)mG}#ftjp1Ac_UUj+E0 z6nqKbpI5|R4*1aueZ9=2e@<5LF@T?_;Nyt*KmR*L!6yR#34p;41;2uizH~{!#^B3;30a^Un&vU#9Rk z0RAckzZUST6!EtL{%VDPBj9gP@Ew4ETM>WM$Klt%Qx*PLz~8Lk69C_!h(8(dGZp?3 zfWK40rvrYqBK`@0zf0lI0sQ?6ehT0l74a7WewM;N4e)and>P=^DB_z6$WqDfk+|*DClrz%Nnos{sFkf^P!+QU%`v_!kv?8{n5I z_^rhI*Z*<_za9AN6nyk2;rs7P3O)|-FDv*&z`vs4Qvkn0!KVTKRRx~`_}3JC7T{l3 z@VS7mSMd3Oe?!3+0sc(|Ujq2I6nr`0-%;?DfPYuPF9iI13ceQb?<@Effd4?jHvs-a z1-};XA1nA)z<;XXHv+y{!FK@ua|Ivusog*Q`}Y?LJ_hh#D)>0!{ja~aDELIcf2H74 z0KZmo{h0>%PZWFx;9C{(X950O1)mG}bqYQo@ZTx;BEYX#@Fjr%Ucr|GeuILq1pE&Q zej(t0RPeQc|4G5G0Q}Diz5(zX75rMjZ&L8B#QXQJUle>B;5RGycEGnQ_zsZ&uL?fu zv+(QR-xPc-;I}IH1i=5U;FF2>_wQ51{+SB=e=7W=0soo8p9%QC6#i_$|E=Kj0KZ+q z7Xbbr1z!yK9SXh_@c%0K3gZ3q)2!%UCGhWZeB^(BVj=Kd>-)cqu>jGzn_9H0{(p!dnwkQux;b|1%1IEAS^N z{B6L0s=~h&_y;Te+kyWQg+Kc9@bkwIg+G>f|M}xGg+BrKhbsI@z&}{wPX+!16#g{e zPgM9bfPa|6KLPlsD*QRXf1tvj2mB)riTwJv0Px!n4CfQgnC@c!-_4Hee}5Je@BN!Q z{-cZkj=8X1{yc$UtCQ??529AH~yyhnTOPzboAje))7i z@4Mj#|89zTi1#}=-x}oOLVrhM=-q$`%jVA#3-bH1$Fqk8OiOJ3><8&j+5Cffdin1F z`DY3K9TdM8pZ{Q*z%}NFo&94!7}4KW>JmR}ZnK94t}x&0>>v9<4v2r3y~6wVpx}R$ z{9b?j2h#-ZGe1QBLg4QT{4W_E<-eNz2l?gZHuo|69ih zd{*azhU8e@^hn1Aiaj|5ES|8(>X?LhRxF-_xA9yhrl+IhlCAet7(c z63@?ndBi6(e>A&)kO9&8JC*V;rpJ5z-v{KMA;e!s{==A$$A3)FZ|dkjs>$!)fA5&Ewa{LR3>AMh_0{F}+&&!3dJ&GY|C;G+lHM+UDSg1?6IyPD>C-{bR>Nc^xM zNMAXBq`;3Set!pFAn=zEAKX8JzlQrSHoW;?Af5F;`Wd_agS z{2dJZhngpDUQYb_?=$jyx%m&C|2@R>{iB6=*Yls{0>6!TK0iGFW`XaKWFzhC58mA7 z{C0s)BHldZ4LLY}#BO-~WD)O*|6GBe5@Gyz3H(&zi$bEevHHJto8i&?cF_Fz&);ca zerg5(X7Zb-tRV-l-$w!;7;O9J;(r(T-o$&aFZd7c?_(}#JQJVa;lz*e!{#>U4=0|l zp911t>*qYdUr2m{!+(dsmlMygA9(yP2>g@84{`X{349~*uIJCacE|PKM!akNW(s`l z5IaBo`h({`UEmKU{$Q5B{jEE{IalB(6F+w6LpyJA{|5qJMf@WUezoDz`Yo)qxCa;y}2+oM<4e1(BBpa>}Gfre-*{=KYxw~ z@t-32pN`N!Uhvle{{-N_kNjO(V)f*YW%={_<$}Kn_$LDY&w{@xLjMNA-v<0yz&~zJ z-2YbcyRN^EH9YEn;8{Dr{{Ei^{MQQpUnBGv3jTQD&j$V{1b>(B?fl=(`p@@|D#4#h z{ygHn>&HpJzgh5CkpBecXJZ=pQSeuQ{BwZ+5c38TTK~=DcU}LSFYp_QckTZV3w)Oi zw*F_Z{CWK!G(4KWYLI^}$o~_;pF{qenV;9cN$}T`-+%oz8Tk8k$Mye${D(UHeGHHC zPkSzW{pA7wIKdzDgRQ@7{*D#=S-^ij@RthyZ1TJ2uU6pmiFeK4dV!x#{4_`XzY+4! zr~Lir?~6eG`}Dy5-$wp@9R3poey_I3*3VeOqxq=>{ksIjKTYuGlRt&U&)4tug1;8{ zF9ZJP1^;Xzf4+We1b+kQ-{rvnm*D@2{MWGjxqqwR?*RTQfq#N|fe7v2lYg}R&vp1u zFg)si@?yJxWD@V4zpeuQxq|;?^8exRKOp!E$zSdBUjzIfke@$)tNqF5&#%|KT>VF^ zc>tt8Xb-`j$J z_|LX}s~!F~1b-&^{r#H?{N2q3rk4}%UkCZk@(4M2{udI@pZ|B;XyZRMgbsiHpJ#Yf zzjjc+n?U?a1b+_sUFYv-1%Kl6wtvC-^ZGXf`2Qe3uU{4UN3((@vU(hDUZC+F!t2+v z$<}W?dp!GffmFkz`lW&RXM*?(1pkoD)_hPZ{_>-4}&+nbU z|Fq!GCjS8r{}X~g9r(+E|2@G!o&3i*{J#;;*IzyHW_t`dqC@Tl0tcE4TvY!i;?437 zIr#b;VtCa59FTtn$Uj@~e@p)0{u2y>`|lI@ZN$6gr(B4?kmC2x?<^4iV!@x%Zs*sv zexDZnRiJ(k0RIPqe**bk@1Lv|{B^+p5b$>h{!7W99&8+a<@?8Pg1-sWe-7{;V_qOc z^ZPXU4|4d61-_a1OC9_*hDY-scp-fMnFr#3MevXP)y_|;!@o@M$CKYbzmEccS95{r z)sOEVZh~Ck|4GApdm4ZJG_b|ikMDo{{=W{M z@0a}k*?a&0`IkC)?&J4k%?9axPs%^o|6mUjgE#2w`G!a9Cs;q%{&lY5QUCHl|EfX% zMw%BGkUx?7=kgz7c;qhze(zi#tY7fFvPJNxN9f-q_$z?F2KetYZ?K{M<&i%aZ?J9r z{`ZDQ^$YT@`n4J!)vp@FzZk?{YTh74@t08i$-%(sYa+YJU@S|H9U&H z9>l){#Ghl{AVvPiDgMLkHu7H--edj8>4r!CX7ab0&sTVdcm2N<_~(C5O01Qa`5=an+Kry(_gdk z2lpwj{;z`g=L-IM@)v~I!(V@RK=9{~KaY6N|2ptD3;vY9ZT&8H_&*l>b>z?V`QHHk zear^u%@m)Xx#U0E;g2&ss(;{hTYta*P2fL4@HdnHV26LK;Ljv~@cix7|1IFZR`5q} zxAi}gEjT`Zg@V7B{Q1Ot{&#?X0r~m*&-lmsgYz3K1K$r?h4^!c9~K1ZE06z6A^z6- z@b$AA#J^4O-$s7(@@~k%{X-M+{5=>Uf0n>MN&LVNd-(dzHawc&4v_yEkpIJizk&R& z>-Pr*f9xCK^=|_HuLS=e2xKe@>`A?DJ#e1b+_s{rktKz&}&)zf6Aq`qwTjo|^=J8OXmG_*V)3@5s+zzv2GZ z1%DOre*yg6%mRxxIdwIk;!9R!Mzr*3L6Z~z!{|)fB3;uzfSp@>F^Y4T~xPE!WU(Dj~!tS47cvQdmm3IH} z&;L3Q|Luamf&7OvANPMF@H>cioj>+T!uj{u#mg!XFsG-GgZtwQkMhr?{DbFT@BF<1 zIff5S(a$E35tst5J|1^6!${9DLB#^Jw6@V5j1ufYGj z;7{%1WfcgRrxzgyOEa)Z;3r1N4>k(~?f(VDyFNcYS>Vfv&kl(`eEpndc+|hdH^cY8 zKS2F%7W^&bKg8jmCit^}zXSLe3jQ8lZU0=)pXLkx65!tk{O<_>z`WNJ zynYJSWhVLg^T!vXZ2!&6t04#Xe<{SjhIoGd<^14eoc~wEPYJQx$NImOXn2%=+bX;M z^60@{{!t+R%gE33Ki2z;mF9>gKKO;lzfy?*RN^lQg7lU1uL|*Jz7-z-ZXo_W%@YLg zck=jOjxhcUiRahPtBH5@FVFC({?!zJaL&B??*ZbUC-^@n{~3<@JtX++$)8EQ=Z^vY zZw3GGZnpm}e~aLce%toX@9zQpgUu6cuYbJ$rR49!^5^?sqTx~fv&iqSe=P7%5&Uz> zzZ(;>yMg}+{(SPc5#ZIoH}F@JpU>}O(YF3(dks1G`uT@={{CAN@qGRA`yE33S?}8Z z`RmsQ#Gh#{uu=b7$?y97#&L#6{mTdby@7wa;NL=i*ZkZd_)CC)AK1;IAZq1M%Mc>%sDF9yh0jla z5dX8}=j*?O{I2<_7W~D)p9uUL1pocycg@eYg1-Xz2LgYRd4UtHpK9{E_MiO?kLq7d ze*gZH1pF71pU=>gUnlr$$!|{2AqVeYpF?o{T8N*` z28Q#ohDY@)r1;y(?$z%o5dUrD=j*?)m#tsVkmzl!{%=K_H#kuLONgK0h`+1hQT%xy z+WE~Q-iv<>i2o|`^Z5VmZR6*!pRsTP8-)1#?`8SE{$$NamN|E1vngZ!@Z z=cj@{5BRfyzvtn&|3muN$?YAIefa)yfxxE`Z^8>Xc>N|D9?ee$#qaOm=^*|&g1;a_ z|7^kE4E$#R{~EzxMSk=0Lde0_|GR=e@e?~g{`Gq%@JE}u@_r}3{#s4`^bmV^|GF9; z)jtjR&jS9-1^*s>?fhKe@aGHuEb{x;&pE*Vy5PTp{9_&dmj!EiI{g0%d^Yj?{Rh5(oR)^` ze+BVV9R8CHkLuq_`R5Vu?cW!I{O=_{zkd0M{I2IuUkUMlO}uOWZx-TD|1|vklMmwW zcOYrgfBH%ASAqUr3H$@h0!RCQ9r?cusebtWpI~@Y|0eQB(+SI)|Eqw1BKi6J&)Ub% zkL&qYxe)&X;$5G=xm}3A`LpozXAy}1Rl&b9LVum$ZwLPCfPb^#Uq^oa{D-ff9|eDO zb9ny6z(4pXJU?5=pY52RM8l)`NhE)sZ~aaM{`1Mt=Vx3$J3p@dvsQ@zG~!+R=QBe5 zB^1B^`r{@L{}#bNll;dz>bFVo*Mk1d0RGEI#g5?fR|ut)JV0 z{{r&?FE9VF^^;)h-;4RZ$NG=&i098Aa){s0|6p^Q?>}D~9@Vdo;`g7w?*#ED9)tUL z9r<1BcVELJe-rur>(~3c=FyZ-Fs>ZI|0Ma%%j+Qr&wswauO;3!e{+TS17Fzw`{TbC z#Q%}t@4KJv|Jab|!~6HX;7=uguuk6j-}}4%ynfN?xPLk1f70RaYIxMYO!E8Ze-`jh z68t;Je-HEX`9D?gx064QcrX74fWOr6-tXk=ue`slzw7+gzRT6JL9vO1*`FqXq zsD4>r+WGPKuM)(+!|-1G-2WQ+&3J?y-2YcP_UDt|KfiN2jp z|4NYmJm4=B`~&y5{R`GF7zWS3R^Uew?~4B!A^utr|9lYtPlA6k`OU}ULk=GQfU&s# zGl`!RLWkEs!SJa54WR!EK>TwBe>3?F5_0hPe-`+S#P1(MhsVD`h`$5W{|OL(%5k`U zF^P75>URkZfX6@7@Th*VEq48u5by0ji-7+o!QVK*`d#@K3;v3)!uOA-fd3`(^ZoB< z^4lnO)_|2G&@BVkubp_;{Ow|R&%r-GUrq7*_n#UNe~#cEH_-d7=J$5~x$_U+ztaVO zGw?qL{8fVgHu9gdQ{8!s`yUqk(O=vC`SY&@e(%3&@AZW5|Le$aUY-a!xPQIiPXqoX z!0-JxIgvkMP*kVa_5MrnziG<*pAY;?f&WawKc4)q>(A2+??uY}mB7CY`0o(>Q_0^i zr266e*UJJwJHq?rC_Gf9yB;optl?pKk&GWrBYp`Hy7kN+9++b9S!U-@4x*l@Z*Vhz5ca9h`)G4c>TTr z@t2&8>sL&EdK&lN4DkBhXn0h=3i9U=;MK1M_}7!4&)-9bMK*tDnGcAf^;1JU-!OUn z*@j2)x3$^&`TzZwZ$SLZ$aC-YiI!6Un=liYKI8fP zED_=_+-&Pt;fuc=#NYcgTR$HE=j89s{5<~Y#Pin=HV|LLcpm=^hDY@e{1V=O@87D8 zrr#GlFMlcc&pz7L-{t>Q@aF;l@4$c5>3IIukl(fbM;adGUl06$0{=~dKWmK5-)wIo z2d{sz;7@F~^$*tB+kgH7{uP41iu|tqXSv`{1OC5(f46L0|4Wat`J1;VLk^yQ7sI3a zX952|z@I1htI0n-#2)_q=^VkIPyRgOz54$P{Lho0uiqi*Hh=r)&LH^u-OIc{;(d>= z-(kexyA$oa#p~C@@Th(b6u*D}>Cz{B{brM&$Nv)fUHi{t0^dM<42wUwe+0k1jd-5_ zTH-%t1>^buZg`Y`&aZa;lo0RruUnt+{Ewf6_ur?-+RnuW1EsGloWNtm^Y~vT{!+&C z_~#iO#oxTe#-B>O7ys@c{^T?9{3IP`T!n;ZgnTD1QI^#)J5a1phelAMEg7DfrufzaQ|g7W~(d-}V0G zTY^9SciX>sU;h$-fA_O-{U0R%Mm8|)ZXn9=sQ#(s_rLztANWruKcD}ZC))WL!0ONE ze}NGHLgHU%JdghoA^!9~ZT*^k^&0@irzngi3)60pUzw(K9&0iP8qx#2p z*!cbPp9JC`DflbNKa&-l*Y6O)pGkiI>$gLI|6=m<{`Z?;>(`U@kA)L>N{D|L@qaR& z$G=dBzw)o}{vQD1|5Av5z{xg#*Yn^0xw!wS#Jl>xkKs}O3%7;$KLx~pw&2f;(0_*D zuLAzz!2h7&uOR=Mtl_+W_X++c@>de?oxf6n|8w&5`8)j-+rNF-{IGBW2c3)iS3vyz zjOXzu8y@v9f4l8p1@T_|hk^LdCO?mV2l-vkA1a0TW3p`iM>yi2CB)xB@%zs|BSHMF zfOD6s{7BIUTC^S5ZzvN$A{{|9z>+e_)e~o;MNjsFgZe}LhUznJ`)#C!D{2mFr;{!huz z>&Nq-Blzpd@85qifxnUbeEt7Uep`i|Md1D0^8#GIZ4t)b&G4vx%^?1hK>WuE{(fiL z$gkQNdgm=(zcGTp9rW*H;J-oemy_Qt?~sH0iv)jK7dyYjM0@jd3h*x=Kkwhq$8h<9Co_L_qGzk~R`A=%r<`o9%pcrQl&`J-CO-#@>ngZxhu{Bb$nZw&-q?)-D- zAAEjK7W@t5_wV0l0Dq0(UqgP^_3I+RpVQU$-=F`Pz<i;znUe??gmB~-m4U^ zet@FBkkJldV6< z2Rp$1O9X%XZsGMm5BQ@m#`RDBUu6EShDZ5V1OElUf2QDXBL9Y9;`Ej0f12P=i?;du z_umVFzeeyc%d`0pXMVo_FB1H<~7#F!=w5)kiVVc z_vY^s;GZY>pE=*=zlr6~{SOKLy4}O;e;M$5Z&RTBD=)DAxlG9J2E4cVQU0lWSbwuG z|I2~@6!S3TKlDQD5AI*VGI0NR!y|w6p5ggl3HL{#&onbU(N*PBw3Fq540WADKVX@TmUr-NXIY z0{;TRzwQ$2zlJS%p8q3)znT30^Y8V*zfJI;QDFW1QoVv-xc_&--`d0GAFQ*t{%-*O zV=lw>FO1MX%J8WE9l(Dh@RthyyU5R#ff;rZVL{Hq23)8wxT7M{Lx|677T znf%qndG((G{M*RSum8tiZu{TM;XkSX_wO9y`TLhV{*i`9{c8gCn+f8dC-`q5zw7x& zyTF$b|31r~EyuvmLi}wMzkmJR3gS<^+}4lR|BNea{avr0oku)Bf9DgQ=7>Mn@TmUn zv37n_ee+WW;(t%@SCRifhyQKCpV%vW{oD!sTLgb2`3(|s@ct!Uf#-J}@$ZMw;h(?R z*YK!*X%xS||96A;DWP{*}bL>OWP8KfQN&{VPEHiv<5>^1JH)slZ1S z+WcMh|4@iOhvN6we-?;;2l;vZKP10v{T*>7o}cx^yXt?a;nDo$?G;}C2SNPj3I5ot zZ2esIzhB^o5Wg?mu=)DATZq4y;!h*qJO9i9@vkF4um7=E+xT7Q-{T5#{qu<5(-Hp| z!=w7w#@YJ&=Vu;>{}#btaE*=MeB>_V;PqQ3@Fm2L4WYyLpXY`6>-vPx&wLR7Cc(dw z{6ii7Ho@Nn{Eq?u81n+F*CKxY`icC*9R4E>kNVdJ{Eq|wy@Efk$oB6rhyPB&ALtui z|0jUIRq$t$-&OxF1%Ev7F9QDLt8x7c$v@SR{~*Jo`lpgVk9cqWKLz}!3;v(T-;?>- zat=%s{1qVoXMn#_@c%{r9EX3F;IAfssxSX%f&WLrf9kb%{tj^X*9-n;kpE)fKk6FX z|Ec76t-q0mNBwUH{^x=J8o|Gi{0BMmze4aw?;XDWUI6|U!T%BYk7j;H?uKdK1VKlVD?f0zGo!=w7=1OIa1uN3?f$$zjT|5<{+mi+$te+l^i5d720KhWX- zRq(fh{=WkJXI+cyUqyaCf4u+MhDY`90RC5jf3@IW6QTbt!5T zxTF3f4Uh6q2L3mI|6{>F?0P%@F8>?kpjnpDyMC z4_$wk5&!T`wDT6P-#Dyi-$KFP z0Q_G8f0f|>n*6T*KP>oLf&Uxee@F1|AiwMSXQklp0RC@*zn%Pi{U3Ovoj?2M&LQCI zXV^46zp2E#t{(;)9?fs;zTxX>i2o-L|A~UXnf$K#%@F)Kz`qgrrwaaV)9mE# z=g9wB!Cwgcn}Pos!JkBa*Yme01%Da&{nu~pz~3VHv&ny(Bmd6?e;xVF<4JugTBn zFOB>*ik(UD`RiAL>pzf7#_u+3F7}3#6Mo}Pba@?|2$ss=aJvPf9%pX z{PQ>03H}=Ln~*~ep8r*bNBLKP{JR4GV}|#7!q@*w^1J4zQQ+GnjQ=lz?>56ujtM#B z;Q1eTGmbxj_(MbJ@ayLxhDY_Q2K9>u^*c-OPbB{V4*$afUrfB~`TI(Nf0TGv|9%zt zM&idf;@@a^RKI#K|J_0ThR?+P+oQzx-!=bd349{)uKBq`;71bgnx9g`qx{>!{PY6( zFBAOvdfh zUnTJO1^$Z!|JUTta`?{^{K@@o{{Hbk zpEi)+_4%hL!=wDOfPWb9A1(NM-|GF=K)|FIa`61q1b;sH^N9BHPXYb{^7HHehsbZE z*ck$!zePg)^N5eziFV%N@qaDwwZxn4CFJ1z=R*Dsl)r!eQ$hZHZ^!)~ahuKD)&E|G zNBxf-5I%p00{@AEKac!LA=!st|K1|-rNr;=;HL}mCxiG82k|c!{0qs?_dnjheadkC zR}$~KevLCcs((6&KMlk`NAL%3xBWlTQNP)OKZpGO>yJ^u|BK)sMSj=$XVe|Ie&dNB z?1+D%z!wsKxr3i?cvSx~Q2#L?|Aaen{9luQwZp%+;gP?J{QmQQI`EeY{^~N@|8;D` zp!1(Z~q+&{9W#{`Sbnv&O5E2_n(CmxQ}>#{+dhtBnI&K%MFj}S2D=P zUrqJ*;y)h5zjrzAU;AA){*8|Q^)@{6Cns5drq6!@@INT{Tgt6}uET$y;4cLJ6M?_) z-MD|(-EIA>{GHbt{*W#qzN`P6=5{ghvF!WN?EVzPch--6zmDSf?>`ej{5O-I&rcco zFJ!-$&(E}=pZ%Z>_$LDYhk}1|g#JdsA3xa6k3at`;O~DA?%(P6MAp9o;(7hDiRbH= zl_@Y=;HN~$e=P7t5%T@+#p}0}c-Q&2kKsG#k0q8#_0OaE@%E2Np#B#M{*~n4%Q1h; z1pX7^UC&>i7vir5^K%x6{};i3(7kqkl3DzGKiIDV*MAc6Mj3LjG6&8T_yXcxufHu2 z_?wC6=MNtLBZf!)Z>IX0A9o&J|0jd`{Vw>MD{TFO`+u+qJpXpVpFbpg{pSIH&V9K4 ziT7E*IX#3N+<&^^QT~o71AMEhQ86MTYiu|d>d-InM{F4NKIr(34_)iu5P2_L( z`7Z_jxq?4)maYE?hyMY=U!5F2{{_JRh2Wn?{{7g%v%7&$1b;L6OK4!d`d&mg{cNc1*V|FNMY zkez>ngdBYS9~1Zm#2*wwho67u3Gt^M7{2~XK>TY3e^Z414+MW6@XrMP-5$jK+d+QU z`LB!NQU5A{zZCdK3I5@gwtsyb^*>zj*OTABes2T*0>Pg_{^1V)Y=OU;c-Q{bDDb7k zyY{bl43FyH4D!FTZ}{g&eiQr)$bY;e|ILEG9r(+E|DcEP{I!wa_4?;zfgd=>&X22p zXBi&VFFM7pU;q5v3*s*o{ORPM;;7#Y!JkNe|Mla2!2goqUr7FEn4j;zO9g)x`HPA7 z&VRFjziTD#U)Ee({{xtx@4q_)e*>uh1HeB(@ZUiG{g|KUe}=$6NWAO%=N5slCEoS? zWxC#9FcRhbyB=}F&J{GAc< z`#+4=-#p?Eb=0q);ZglMsDAOpd;NbF)bA9*zlQvKIsCT>{5s+j9Q;y&-$DE-4*q+A zpE%$4|3o&hynn5RNA-(Mwe#oy_g9vH`X!nLiq_9VRn~9++?fOmCs056~_I7k1_5%>n;_jT|k0>7Sk*Yn344Ugupp6c)4 ze_jXmUnTgrk^j}`&;i<@E!=wCD z)9w26|M#2U1padcf5J1?U*(sX+kE{kC!WvW3gXXTJg?spA^x(lHh%y5eH+Bzb3UH` z-ZeJH(1EQ#w2ir;Zgn5GQ#`67Wiif{_*5LD=9=D z;QpzCKa2d8$64OX|5M;!F8CLIZ1cZvm;bkaiQsR$-R56GxaV&M{$UGn{ri1l{ryAr zVfha>JgR?jS$O@w0RD>ve_Dk8^8|kd@V5Z}qlWiRk$nHjBY)1A&;lfe-VHQ6cy{{z z@87Qu{+fLr$Mp~T&BrT34mJ*fIK!j*S5yA}>+e>O|3vch=dZs|{;ubLvxWG#6Mt<; z^fp)jx9%0j~Vy>BNs<@p3-K@SZ81{h$rhZ!?HLTktO-f3CxSir^32W#`v_ z{m~BmD+GU!W?Mh=@_5L>(hMvY{F&tUpZ~W2|IjCK{huVi`FL)~!TkdbkLq6!^4|*l za|QpQpWFQT^$+(yAo!!pZT;IRdT;;w1NfT+|3zO|e_}}X;qU+UUWDs+8}aLR5B&vv z|LbmeRKE_2zjY7Gd-4AT;=hvo{QOa{-p0?bpY3;d#&oIRZ+^(uuY&^i{C@-gyMn)- z{CvYt+^O!owYxdNcnbdful{=*=^*xaUcV^Aqxv|b0^e_g&HrD<^ZZu``L|L2=Et3f*S{`%hrj>Z?VWT;!_;_0K=pF8y>d# z`{yqjjgfa_%GOo!`I(8Li}|Uf3vTCaUlMb8r;9Jn{51}9Ptk|JnCP?BjM|>FYwP7{L{$a z>hRAM{MF?5KmU&h{#~BM`R8u7`M<^dtQ~>vfc2lfBaL{@9%#i@V`%fe*IHGe*XRk&%Z(NSCc;&zgPc(z`y5nc>ZoD|E%a> z=JqT1cQZWde?9p#V=U+SlYsw3^7Hy9Y_pY)+bz`3>z^U`qnFwG7Z2O%9|HV$2>GY( zvTG-3-d+ef`2JTa_)A)?KaXfH|775QSMaYPKfnIv{#Am%mHhtx4+H*Di*f(Akl*$E z{cyvh{&xU>3h29QfZ9{CVVez5e=|;7=#Nzy7Jf z|F7VmPJY+*+cv?U1N?^q|A<=r`fC;WV_E(A{d@<{wvVvaUmrKTx2N#^FX+FB`T73y zsNvE46;l4q#Cz*+B*_0`A^zB|cK%|SpWol@d0f9B@5+Cy;ZgoU{tD*j>;GuOqx>`1 z+4=Y9KMLf3z2Kit`Mc)tYQdlQo%Q?Ye>CtvE%>X*@7jN!5d3MtKL+@}75w!P`db8l z7VxJ7|ClAX|E=Vo;pqR7hDZI+Cx5DM{>K7;wc!6>l%4-OyM_k9*WcrUzmoh_G_cFPx_uV{&bcvuV0bjQU6Ln{8=FWR|S6w`7d<%>jZx#n4i;t zKV})8-wos+?(pwsc$9ww$UhtS=Lr6|7+e2+*n;KtpDp-X$zM$Id;8BM;Qw3j5ASaM zM?3s~2>zTO?E3ZlbAbPf<+%O@J*>Zv!+(k4QT>~M|7_r2D)?viw0?7X3psfGcL@B5 zSj(T`i2pAk{>(O8zdW*g{W}-Le`y`A-<`dz|MZaP!=FFADDYnspWukU*6^tQ)f9h) zFaA6b|M-`1{M&ll`1f}Bk2gH>r~PQ_=l7ov{ND)v-f`A{5oyZlg#4`ne;x7rIs7GtNBvI+ z`Ckk2UqybterJ)Njel7Gd%ljJzgH2@*E_#|x`TJ^UymBzn<>731pTh-=Q)N)^~(YE zn+oc;S3UNxqWt%F^sk5Ek-rf5rvd*Zf`2{v(;faPg1-#-rvv{w!JjnH?jNr8`<39Y z0{#-Zd|3vbC<;edC!=w7wk-wHUEN}mw3H(#Z&)3g^gKYjin4f;#f8TwVcs{?Q zhL|LjGmU$NldM{@CAa|LckO z>VG%z$GvI$&*!%~DYEnb#l-XL&wAqde$VfpZ+J96O%%WX`m+MWUrT52x-w6J|*6{T^8~Bq~;q}`={uL}?K0kvDkLn*!e*fQJdJy=p zB0ry>r9Q@H* z4+H-s!QW-5t>5G90*LqTRKZ_G{x;&h_5TR)&mlkWU-iM(pU3*=E;`d?zuiqfUqy9DQa%rcznfzY-3qbrQlb_e` z;6rTuyk0Dvz;q%05yZRpj~4}gJn>Um{doShLjF}Ob1>DF4c+@cuss{A&e&!U*e6W&_XX?*qXfyKDIRs|EfU@8bM34!8cd z9QB`Sc$9w|`73<&UjqF92>v}rTL1SBe}~{N?iODErNDpeYMg&N`CYGH6bgLc2pfNj zqkoqf9@Vd%;!h;r+dr3s_-h1zN}BaQ;Hcjs!5!P|Fj7G z7YqJM;9mp$)q?*?@}Jv?l z@TU=ftb@PU@MwNoLHwV9_*V=5+2p^#;eSi;caYzI{{0O215LPof0BP3^YioX--5re zhh0CZ#Czw@&w>9)!9QlK?O#9U=le&Fz~>O3;^0p=JnCN+h`$BIUnKZT$$zH9f2H7W zBESFo<7?obCHTK2e6UuKt$`{5;}auV1|>@Jony z?Z00Nd?WD_S^ro$1Ahv9zf7C|a0egvG45X`@vispdKw<}uM*79R?xpOg8w4&AMA+# zNWouA{#4?<{qqmt&lmg;l0VJizd-P}0)Geaex1E%--}zdsZ5{kKf;7n9%r`e8KiZzn&${yFU=eEwqL1g3q8>sLtp z$&U4Nz2Q;)D)+Me%Ol>a-<}};<%0h;^1Ig062ad=e*ga69r(8h{!QdBVg+M&1DgbY za-7Y-ga9x9p1@!5nXNzX-+3q7{>8ESbN`>j^Y{O+BmQ{C^ZDCic+|h@KH=-HH;BKu z8PCrO^1H78uQ5FGH#mF} z|B|4e4Q3X|KLPmn{{qinm%_;W{S1%hFCX~(1OH2ce_(|ErGmc%_!EJD+?P22u@U-@ zH9X3{68HxK|Hp#=eDY6abH&;mSR?pr$)88OxBioW|K}E*e@le=)c17DF0O8PXqq4@38;6 z2>rJh9{DqYe-!XPvL5>%jL=_cc;wFm{?Wky#P`_0EJA;k;gP=>_{RYMvm3DgQ}Vmk zU$xY!^_LF(>227bbxmaJ?=#3EbwRki2a2T`j0a_@<;CzzWy_SKkFy# zuZYlplHrj*5%|Xe{}#bt8=-%b;7GtR#$LjM7VNBL&~|9IfPRPeV)=)X|# z=L7!);6G&}&Of#&viToxc$9w$@J|H(ft#>DmHZC`LJxlZyPx5azmoj*gnQS&S-^je z;D4L^hllFJ-oKh8_;dPQ+DRH^=i&KJ1O6uk|Mm#$|Crz}1paK`|5flOTpL;cHwykT z;GYEixtnqSM@Q&C)9|SORluJE{O<_&Wka{(m;`ANLE+zdS*t@J3qbtW3;rG@k*(jW1%L98 z@cvy0{Hq23LFB)i<G1>b4?;lv;8XwvD;Yv-?~xo7lZoU1N>hL{srWBoxeX9{1w1o0sPt9asA&Q{|JAw<~FbY zDTYV&uLkwMANXqp{~zS<$NYT%==KkeKlXOpKUe*BF+7UD8Px9q5dWEiKb8D_9P!r* z{5ax+^Bep%JpX5e_}fAJl_35-cHsJ5NB*fnF@5Fz-_`J_e$fZo{i~ih@BA|t_)iu5 z?c_h6`FZ{m1b+khgZ1G>+1Eqe&5$# z@5l4@eD9C%^1J;WpZoLqe7)YU_xrkbnMx{;`VW4>>2Co3a>ido{y@QB#P}KHCm8*I z0{r5IasRi;Um^HKR37yo9`5Xa6Yxhe{<2wm{#68jIOAuNugL$t{MYXm;Qzq*pOOEt z;Qz(sPf|Wi$XC4w&%f|&-G4VLZ{I&FsXUs09`$eDzqW(^XE1&b@~a8{6vhvIQm?-` z{~f?T$M|E&kFfgM64}$&=F#a;~rTOn;{Llu@`DX#YirOIlEoMJ|mYb{lZ!Yv- zQRPwpG2|=qzc2s(KMefW8NVm_+XO$J@#Dx(F#MyyKgRg?y|4S1_TMAL@%+LlFP*=k zDv#!u1Lk)e)IXT<`;#x7zXKURpL}!wo&f#|#$QXmbp7Qqc^~Da>+dpCe^f)g|IPYO zf%==4!0Xpxo?d_H{C`U2(fTEkpJ432)4)$*{43-~3j6`XzwBWAJm6md z{zb+QTcGDZlj;wAv+w_B89(SLz5eF>bAjLEKD_>$$(PQr7?nrspH9Ac{aph7cE-;o zziOay`fm6C7n3ixP|vTPkZ*85?!N)$rSmI+$+xBa&j9`B1OH9NkNH5) zKT7b&Fn-9>di~A*Zvp=h<0q3Zz5lwO@x#gYQT_h)zYYB2!Fc{97U}+_>%TRV52d{H z^XDj)N9&hI^_#Ds?t=P#j34XL|D5qd8#}MxLY17qKj5hc@cfcp`VlIR`i}wrJ-}bf z`1{BoDD2-=jGsupxqpfR|6lU$zkeOJSnr>5mhbP?{K{wis3v;;3H1Njzklxqe%CU1 z{!Pi3&i{@okLI62zS?sCefj<3z|Ug*LFC&%Kez8c-!Xm``R4OON#MV%enGYWo?yRz z%_84EKW+a7l}G)DG}ZI>>h?YU{_h8VsdC!4*FT#6;ibU!OaC+fS%Lrde=n}`$WI_Y zlx}!_zclc-lW+H*K)!T-Zf5+HXLSEQD%S5m0Q{orABaQqzfOJ(#p@?{@uf zsyv!sDyTmM)ZaG*`&lmM*Hh(@p8@>Jz<)sf1Bhv;BR641>{!~ z`k(k9u0MnF($C+syy<&z<&hzNsOQG(tnHb6M+9H z@b5By$a1}ZUlP{u7UL(AZ{B|&1Ado>@cbu|Fa7*?1Cw7w`A>!V(^MXiJ228I?zVVwArAO}`%SCo_Jr54Eq}-YM{9pFiUnKdia-vyJ}4 zfnVlf+<%!B+MirNcb0~pz>(`S)l*sz)xfRCFD!j&qs`(OTN$8zb%1(gYhe@ z*6S}lKV4(|wARl3*BbaOYT)@tkzY2j@buk2zdvB|-Cfo{Pvz12`9S^Ap#I+&e@6E^QeFG`t1bz z|CRCUtkL^dI{(izerOx#`5y!Ps7LVp%cN<)r`5l`{&Sdo9Oah^`L|Ub%`f;lUB5ZM zZlL~b#xMG@u78r{+xzDT<42KiKL7Rre)U>-epAWsE%*bN{7;mxFXa2EJepq;)oG~rTj#p{$H5-Q^EZEg8HXCis!$U{8+)Cpz>&bxnO?J1Aiam z7hb35mn!&s7~d1E_rLl2VF2)-48#4eAYVGa>#98JKaG5I{|p5FX2$oCFTMWX!1zAk zzX<#%AH)5hA-}IM|GFxV`p*XbOTb^l_>I@=^_TwrmKBVjN4|Ofy$t*lj6a$DhC=^G z89%hG^ZDl$;1{or=f92op_XsoKZ>Y4ntvqu36%Fg{|*KIYm8rggPwn7!QaH>n^JzN zkYCT#pAP0X0@VK({{Cwai06&-U zbI2bl^nZ@=gWKuz!+d@k2mIK_@%+1fqW52I!SAN>X#TO}oA z5B!P1zr*-D$(Q#3-;AFG{6ye4u7~G;%B9~><z5ich{0#Dw zY5n~7kEy`l!uaFU_5Q0Ptp7&Fk8ZE`zj^S}W`uN>a+sQ(n>hi=yUH&O79GkzlZ=JWRl zzz>gL){p#+f?r4F(fZ|+Z@&Ir4E&*t-)W1x>vu5Yr*?E+KTCnXkMT#4zfkCZ594Q$ z@1wl`{8|qDYW4B_i*42Y+wZ^F`>%@1qxmOv(*2wF&kupWnDM8QFYW&Yj33ll`(C5} zkAPpk0q*}W`P(QT_-6MXtn#S;IP%T?w+i^n89(V$J%8!UVHbpO=@gQxHI{2x_$w z{4Da#^LGRAn>~g5-%7r8{ynYosDDovz5eF@`vmx78UKatdj9tN$M*b3GkzNR=JVqw z;O}Mp1?1;h8_xE>WPBg_sTA;^e_Mc`5{c(OeTVK}dVYUj<CiQUjTnl6YRHh=?_qObi{2}B^^FPY?k>s26-wph0 zO>zG#T=suS zzw+mL|J$Fxu+QItDv$hp^3CV}Z-HN|Iris~FMa-^kjf)JzNhp0`wsXE7=IV}9|-sV z_ZUBkd>`fg_n-d(zj+JXf9y^@f2sebDv$b)?&a+N5b!&+#QqfWUlsa)PUVsBCEsiG z|2^=pGyZj#{r|@JKG6RW;E!#E`+x8YJ%8!-`yWic9_1s2`CV0cG{0=B-+X>J2I_C! z8rMIVeChrbrSixRigoUvY~ash{OKE|GXAhH_5AM<{Jo6t>7%cI_44AsFTbA) z{Ab(Z{^Nbxmp;GHSmjawk$tuArSsG8UjlwA<7bc`c>bXO8OPthSA1fz#q~c&%YV@rG@_6b-?*flwU98TdO=;zbr7nKSBMs82^^b`akK2 z>o4(@p1)71f33=+`oo{s`!AdF{`JoX_2)AFe)9jZeCs&!oMZf;{@Two{9C~Pr4#P| z>%F>v>G^F|XPnQbd~>UQ`}&=s@@RhfRKNNB_AjVESRL^Gnb?2-w$j(S{y_hMGVJ-4 zRC(lw4RFryF7Th~g8dTTxVwHMR37;uUhSLLZz1(zsvv44@c%xA@oSSW{rq|YQ*hlU~q%8s+`{mjwRzj9>d(?ROXa z1B@U3qV~=GcR%p&?}7WDNd9YrAEffAe=qsw{wodqUW}hdeyrekW&AYYKLGrLj32#U z&%d7K+vnf6jGsq-;GFTVe_7zy>WSw+hJ5M$muf1H<{vc3dH$6L{tm`ZC13jd^;X6Y z1%5@~x9o-c-$lOk@829^^5|kNgpCs)6S5zL=A4l~kQ{KP-9|rXo>Vy50@lgoFNT4Z!|L@+%1buS`CP@;?jt^D2+( z&!YOXjr!|@`qz1J{X-7w`NatSYL!QR+F6DC2dO;rvw{B<@DDNm zP4cDt=YGZydf9pYJ`Mb*UcmjAJLK;E9ij55|4`sJ0sdmfk04(<|86t+R+N|SUw<+6 zM^gRf>xX8b{wH6=^BYON^!mB3%A@(k0Y3`(0~vpXOMe}c|K8>Niw?s5pL3a?!{l#L zzNc{gy{+6HW>RuzSs3v7v`6!^2pDMbDkgVfdBBz*q=qdbbeJFbGJeP~ zXa6q(fBG;y|H8-I?f=OtkLI5S{Fi`#knyLGFU|j3#*ZAX`#0zRGVm)5$NguLKhfI% z_VxdO%A@|{$WNfW|M}+?;16T`Hplh+rOyw%%=kH=|DnMDf$?XPZ{NS|>pzR}gGcE3 zo9{ml2Y#Iqc>e3jm!AJ-GWlJUuV$^EUH>mk{tV^q*Y9?|<*T^z5bGa ze?0c9QC{kQ0F!S)dHeSd*!{0#^1WQ&+l)_OZ#UolMg*PzHfB1&+qsTY+-@CwXKL*c#>?yteQvYpK9?d@$^#304_cDGG z`BML1GJXc=|9#*`y@C5L{gb=?o2WeMKV+nH{pSP!Q^v1KzSMs@-WO`zLQQe#|I6fAjvc82CRh z{Z}DB+iKXp|7S6NCi%e>@b|wI_?5=u`QJFB`q?a5}Epg#_0OZ&o8Y2_3vf;738-S{4W_l2KXz1f6q9)ewSVP zcNjkf_^W~6g7F*ttk+MP|1&C&_D>G*(}4d5<4<(yk7WF?H=OIg7WiuzKa+g>{IIXz zb4>me<<+l;FYsmOPc!w$Q~h2_`>+2Ep#F((;{9{`tX{un1$5`{&;N_bH$JEHvxWS1 zl}GEJ1?Ha)>R&h>*T0W^>G^f8$|FA{L9f60{J0tTPfWmmzw^3(>G`#`$|FAw_*;QL zhVf64FP-17F@EG&-M@K$Zv%b?<5&Mh_b>ha$IDFq8Or}`?H}uM@LXW(kE8m{_1gjJ zpFI(;U)2k`e*5P)wx6W(X#MiZH}4;x13z{W_9v6y#Jb>ZznjVg~A#U)CQzAF4d+KZpE0O8d{h z-M|l;jQwXX>HZs9p!G-3Jt~jgRNDBD_rJddex0{)|98mmQegKv z?|-#a9`&CH`u`62A2WW;6}|p}^CM7(-Tz9)PbWW+_wT>|0sq0Nxc?OL9}U3t-S!tS zc^~B$3;Fj{9?dVG>i1IKUw;;;|0?76`%TZ!e*fIA|A}e1|B;lB6Y8(6@~HmUM1B34 z^&bKCuV(y{*punk4eJwTlTy5rN4jjn#!a8bEfG2&GYvR@PB9gO!8+5^S{FQ`Q)4D-_O9W^EU3k z`*q#F^!z%U$-hc@>GemP%A@&3y`|@8KEIv^_1|XvB7f-mrPm*SF@6^CF95&sOgz7- z=DeT|h89$AD zFXjE$?-k%jzk}!B>4skarv<;I%A@%QO>@rwD)8qs{tWUTv3z^})o0=Q*SpNGWAfiq zzJ}mGH5>akC_hffN2olSUnI@XM|uDH{Q>5;g7HWEsn^eb{B;VYBMS&m3`18oGDP4a|K7;bo=Vuzv#r^N6y!7**hANNx&!_$a>+C;&i-Z1W zF@CPg`VTVsTa=fcKfYz^4}RNue%=S_k9i-@FYK1yKhpC@dzDA?3nSk=KT83BHRDet zUwZ!dknxkqH}`Ka@c&}`?c_fq?BAq$cz&l`=F88=`9lBb`Ag3qtC)OE%8wK3PgQxe zei<}BALae~zdV>9_aa6x~e$@c=m;V6I zuRHnD{V!PM(fm@t{2l@R0>&RnzV!E}$}ht8&!oK9nxDP@f>j>XpAP012I^nT_?O5Z zX!(xyWBeTQ&FjB5@Gmlc`#bLTU(LmMe$Tti4`TB1l#j6bx92}V<XvWMKbi5PfFBP0ZyA5D%lQ>ug6DtCWxg4c&!xP5{o4IMt@3F8v7rA3p#M>f zU+k{lfA;S$ve$nEpFiKj3@D4uz2uww zuQl-Nq~iXMx$M7|%A@|%K>yDHe-Pu}blLv^#`l5sZwvgHjNht|-v9RbWncf9Oui@O z?eo_@|G#4D&ja)80P4TN_*2Q3uHS2nA2i2#{dNNWs1Nb}S?SUrq4H?|gaSVX_}3YK zuS@?o#*YMkSK!C3!2O?h>A$G*sQ(z?cL)Ay#xGLX-T8fj@#BEs6Zkbg!u{7I-`@ZB z^;1>lQU8g+j|KiT#_vjgnV^CPsO={*ehT^CdvxCa{z)I;A7lJMCG`B4TcGtv&-aX< z|DL}7aw*{V`vJe(N<9A}_q$vFPAZS)pEJ*S{r3m{#8ud@Nq#Boz_a@wtMbUtCqI(% z{{Fqdk6(lRh*G-$3I&(nW0m9igULrzzP**V>%Xem`s z@3jiH{a;ib`N`ym8uNPz_-of)^d0nsyymHI7Rnw{`+r3fnQ-g_PdtR^Ov4K-eU5DC~u#? zcKs7o9@U?|$a(#b0QK)={JG>$u|R8_p6!gENq(lWf8&Aw!UjCQa^>{=z7wwB=T#o{ zpS(o(?=!C7k-&d?BldI1Z!Y)^R37;;OP$xxXyC79{OaX(|I)voxQg);$v0m=i~;_y zjNg-d>GPM~Pw@PPQQkhk?fr9($xosDF=76vRUXYhllo6K)_)x6e^5HEKemFNzjS`B zW%5as|5~VjmCB>~W0&dulVH?80n|Tm6Ry8gMP2_O!SAQ?$j>6*+&`0m|2N}rCcm`c zPuYy?-{UeLyaneEQ+~MMmsEMwf5>t@f3yF$K>u~OVm~TG&tLlYcOO-GzF9s8b2?&d#U<&p0L^M42U zYcjAOLB2Hqbvtmrh0FZb&v3pg<)!(5qVlN!Y%u?KLI3S{V&CU-{%uqq`Jt)$`ZM?c zd%*wW3+!JfzmIVJUsZYJN0M(||L+5T!!GQ1sI2#2Y2o=PP34iFNPZsG?|=Oi!p7{oBuf!&M&nDIe+ny+;2Z0zc#%?5`j{kPnn$`(;%g`T69V*Z)Vr z_c4CqhuvNOpEG{cO5MLX|5d<0z7O|bCRF?O{#q&GwGQW<= z-*lOObU*fks_FeREO6n_cYFU-S9!F4d0_vfgZXV_{L$pUF8Ciae#~mUf6doVn}Of_ zJKX;z^7{&YQkXOi!wy#M~a75MuYzfE;L|46~#!}wuqbpPh{vkmx-4&eSLy7Zq^ zdDMR_@H2qFn(?=h|Ekr${rvDD<0p}C&i^yuzxqGi|06Z@{H4!t4pDj3e=hmS#`^CB ze$#{4PbFXa_cxwWdE_UgInVE1z<-PJOVrf;+vk^k{U2xYRVW{0t)HDg%+#Mv^_%;5 zH>khPAw0ifyMsVDv#zD^s$~_ECu}6?^nQ|%=kNN>G{1O^go{Qz3a4ZK7V`- z{2E!f|N4(=U;6w~Rh38mr;%^I{@MrpUX0(Bd};s8XYzw6A7RbUzW$S$`hB4O{hibpzfYLnKhoz9ZZLl6 zdcA+m{gVa!U59c1lweNx;7h{MIM%{5z2!czz3P1H1nyl}GbW1@r$6_3fBY2g|13$q%Ny{rmUq^?sDB^zZ$5wBQ^ooF2cA5O>t9L!Myr3@ zudDLN&m-SFe~JQs7~^+ssMo)m;J?iHDW5v`|GmH;cn#-+ujOpFiuE{4L7&7V2NE@~HmcZF+v@`FS6x|CwKK{oNbs^^^8bq{<^d zmV9&nlmdPN;}?F)-ToQH_@3>~{SyrQJjSm<{vm7r_WE6B{1Ec9jr~&w`0Xy>`Cli0 zisjqaZ)=rD^UovSe10zn{H=^XI#SQSnc#oI_;DG|`Bwn`gp0WU&E)s925$Y)lc4gb z|3vc5=f@D>Ps_!Asm8khy;lFWpQ!T4Pu-#WH=iFW1Ap_c*gs5u5dHmuKpFP>Z%}#U zhkvGhbN^KVe&tKpKlzOAe^~&g@3voF<&husx%R!3^Pitpfq#(k6Pjtig5X!YjO(96 z`Rzi!jLM_>yf@3!BU$-hAPW!8aX=XHTNFezw=|O_fJ}KKbVR z7m>j4_80aKFupy%yG;HP<)xor-BNi}fAE+3`Z4Qo0_q=}kLxeq+THvHsyy<;fd35e zuQPraL%#I;ANr^~>OYoz^ZaTK{9TMciG1nxUxQn?{<)NI zDO`U~s647a3Dn;T)W3r9kCC5YU2yjLEoJ-+@)IfVfBtI&{L%m5{^vZW_pg0^+kWBO zIKPte_W5Dwzh&}!C@-zwsDH74G~f&K8=>-O{UUce@89jg{AS+4{`F`*zvqPgGfm}@ zAH2tT|LzFbQJo2N+H_yM$z(3CTvE)}1`ajJ0vB2*F{3f2l@cqYe z$-&3f+lFB1LiF_~R{nvj_;E!ee z#_ja}k$&8AAo#^p z9?d_Td>`fg^X~`z@r+-$y`KMzg8v5N=aFyDzd!IVF@7@n(&txxVf@g&dj1JU|6bt7 z-h<~~tb^{qjnIELl}GbWA>Zu(1>k?f_#McXuK(SPpGm%XehvbD_aeCeag1-D-GK0q89(@I=lv@V_`4WCn|$f~+rjuz?%Hw5^hMe+RWb#!0CwJ%1pNyaUjdT6uf&Z#Hc+vSg zn0)E{8KUxN{yy@}^JgUR&ollh@}=|V4C6=cbM`+P_`UDN{a5d-=P#W<-Blj-A4k4< z{)_>BF5|~AzJ30jWBf$We**BU2I2lEk}sVpU#Y*L%w9B z@)$p8zn;H&{v-kasgij9Azk(SC(`{Z@XbDdB2*sDKa_m)`EMrh*E9YC@&^ZC`fmGc z7(a%5FXjBt|FeJ}dLQopHu=)u|9Md5QUB@Wn?FC91N^~^|57(S|EH|}?bp8p89yJ) zKN;oBMAb@ZV$n#@+S&`&sk1 z_unkWk0;;Ue+z(Lq7?3b0r{bVUsUB$|5;%EDZrn__}j>ro_}UAelGAA0l#Ev-2Zv< zrTc#|l}G)14(R=7&VLE;`!jyy9(w(y_g{K5zL$J+|1AUlCdMC4zI6Xz&-lsY`)L38 z&%adQR}RMWFV$1`FWvvkt2~;2%>SJC{}sSbWc-ojOZWdb89$MH^ZvgQ_!kℑ5%u z_4hO5=Y#%N1OK@P@cirb((|`}{%fEAYnl8rl(*N<&aYB=w0>a+o!8IDp#HEjxc*d^ z_192&{E6gC`~Lvrd&xKV&sN~KE{Eqo+ohk% zf?V$euG5&t?rTvr1_-Vl30sLEx|2O&4`ST~^XM_FoIq>7l z<{u(EXd&{~6#{c@Xzsc!2J|4b3m` z&F;UV%A@`h$T$1{8Tjupel+>g`-ig_KaG6z`Rg3;i&w$@$GP;2s66W52mD`vKZ)^^ zT+V+S<7b2UUj+UajDL`P>G!W@Fn-W6=l%Cr;NN8YFt6T!odO$=zT5Yo-x)s!_?LlS z{~^5oV_f?6R35E=3h;jeemvvvCI2m}fBX6y%J@0to7ev};IC!;=LYKe+n@im=l>Iv ze~I$a>+jK3@%%@-%vX9C=ciJ>npLnpzj7*%)-U$B-oK%g_uoHnfcb4;{M9e$`Lz_* zFOBgNvbArXpErR&HWc@NiF|wg?EXiqJnBD5o_-2Wi*BP`!uzg8-b`p*OV=PvLo z)WH4%m+N<)$**_0erHr3)gO97?|*au6;>Z64WfAj{@>GU;`;ZKA7{9c?9?0m3(Rc{KVv^QrrsR{~-DH-#@VHPk0p9pF?@+-|rcv@~HlFQ2+g){=XQ%)JuB*JZNoL zyZ-BppGkfi?SKFMt2FSthT;An8?60m!u7L?$+x3?FDq}?pQ`d`e(@)r_rEfr{%Vim z`d5%2EBIAZ9{H)jF9-a^jDL&#D9g8&)3bo_vw&X#_-$(A{u{rn*KfY%+w*U(@~D5$ zk9z(Il=ttS5a5T_!G4xY|3Q^Uegg0-1OHFPe<@D)Z(slR{A23k`rn{@1#5nG{u3rY zhw{y=ygmQ5Dv$clqy7W;Isg2tg8msTo3m@f_(e=(;A28c_#lB<)!yOV^to_FZd_t^UEWk{~={N`i)9P+(XxBve2H1Hpc!1KRIzWw}S zpWhW!9?d^0N3Vaf;Wq*PmyADtgkJwiln;Ef=l>bwX952i;NN8Y+vH38?{~)c{;c~q z_usR?537&o-{Mu>zx4dHg~@lPy!85aqspW8OQ!nG=bsj!{)P>3{iDd2etw_K96~z2r;x-|{Mt`i~+%!RWsW@Fy{THjTt{`&%dHRETKFI|5hGJec2&g<`a;8$vl z`)@j0&tJO!%Bei+KaqU%`WpcJS&YA#eChg|!T9;0|AD~2#rXN;OV1yXP4N6mysqar zmexP;&AxxvS9vtQunW%XXAr1Ahw-P99~pq@yM2EB$oNU*o7c}^;FoBM``<~v^!#2_ zd{&S2U z_J;0Xx_(+Peq^rm{1^fJ35-92{8HBbv-kfICO@9?VM4xaGd%y5l$WmG4NQI;G=`a6-|-SX}JC#pR1gMZcMkGcO6fnTW=_E(VK z*YfTD%c(r_W63wq|0%$~#rSu~uVDFh|7~02`pb>e`$yWptyCV>p9|(U4b*>}@q3Xk z?Z3!2xc*mM=I1c^H(lm`WAd{p|Ex8Cd;N1&9?dWOlHPyj`8^ZNujg~P{{!Sp|9(mr zl}COw`R4Q2EZ|RP{0H8|=a1e04klmAW&RqIZ%X;v*8J`Le~IZo0n9%c^xrEQ&wnua zg9X2<%A@%wgZa+|{w~Hp;IjW6jPC>e&jWr;TipLm^2-YS*KLRMCC2Oh(_Y9ws`6-l z*;K!IelG;|zry%E$$we!2Qhx=W#|3l1K?k0{9WWr?;roh_;KW$=f`5;muQdYf0}&h z{g0w5kLI5U{H4He#rPE`==Ha+U;F%cmhn@7za024Fn)9LrTfn|Cg06v{vReknDWy7 zyH^Lie&Z=WM%ceyRUWNhI?c~~{#Xg-H=FUVliyVElNdkhioSl#`_F3Nf5rIqChGkY zA^5u(KOXpLz^~I0&)-Y_P{FUI@@W34`ux;7#t-`4 zIsaY2|DEx>l7Cp5KjTM|@1wkb|M`I5x(A+r%~`tt8iF6C@@W3~4uXKNt81 zf!`(;_rH<+>cae+t32x8^M`Z)X8}K^H}K7*}z}n#r}Bm zrSpHO$|FAw%>N|tcMimUD*4j&zg^{#?*slR;MW_3eINO+3G;tU<&mFFegftF_utdN z&lrsTh(A9#N7@4sB& zPi6d-Gbi=<6{Q0lq{%bDOeklvI{^&_odDOr6w)XR=djI~r0sP>2>_?<%fAPHq z+W+sp3iJP^q{<^d=3ni{2I>Fbe*Y%$FEjqZ<=U72{i+L$pH@`Ab7#K)mJj@zui^eH zq-wv1*{u3+d;dMG@~Hnj^3DB!3-}ute;E1I1V4@OLyPJD&GYXz@Q*P59P*{#UvQA| zBY}Sh__r8;J^7(R|9>)m4Ddaw8QPAXz+Bpo#Or^6{3e3mM&;4^$B}QYe_`PN%=kec z>is8u{-gdVTz^%{j}+>!r}C)&6sq4#dH?lW6x9DI<0q3Z-M`ZrKOd~$y}&Ow8u$MR z`S$yF_W4;_nlC z@~2(;rGe~Oj0L{z^Xq>qkNiCH z1J8N>=a(?xmz{$B!Q?+vKzCmMrBojILB*ZdUv1#0GyY2QrT0JAF@7ZQ>jHnqTe$yh z^3~W1eA)A#qVlN!IP%T;*8~2Rsn{>I+THcLQRR`J2>fv1|1=Hz{mGY}pR!dR`6B&D5KbQQ#{^9mtBb7(}XMz3K4)|g3V*e!h_W5t` zzZxo!{E$-4>#qaw%O+$07WvZiM=6y@emME&{_h0*m)^sEy>kNntR=l#DA@Yj8S{j`nRSCIl= z_WG|@dE{q5;Jp6(0sq5A*#Cxn>G@-c$|FAy`2B$&wHW&s$(QbbNlgB4%D-1&dLGBm zuijF5RDVzzJwGqy{m%~rLH!Mv;QD8NqSsHVe<_n+LV4-$|EH)tsy`0YKM2%6WGSw{ ze7d{(pIV0Vk5XQ$KSJeE{h6TtmqGm_mSaDaeChu6ipnEDtgK#t^ZxY;@LQ&0|0wy= z{i~VEBR`gW^Z9)!@P~hh{Suq>`bqb%IF(0!Jn)ADfBp*W_aR@pe~;t0o0$z_|fD`zyIPg z<0k-r67a*)@cdq9=4Zct`jN>eQC@og`IyS1`6Yw;r-1tJF#c-trS~8IX8bhZPX+#{ zkMaDnT>2wa9`)}7{&e8qX8c>^zb@?GzZgH8{AA<$O9Fn%THJs9Hhuj`^M7CEQU7rv z&g*9;@PA|cMdYhUfiL^|&t?2X;Lien%XPT_43~a0l}G)j0Dlhf=QI8>@}=|p43p2L z{1XMH=RCiwtjGOV+)npTD{r446;&SfpHBUo=l@*L|Er9jM!vNFhA@6uCFlN|2mJGl zf0%sf`RxqjM*)8U@NYBzb@C@!^SAf^UyL71zB&IC;6Jhfum8vlz5X8xeyGZ$_0J+d zjq?8c|03Y8Wc;!_wEvCOu)Y75Gk!v4z5eFsUzY&?xsAC0lb>rp@cJV#2CE#;L?(Zo z^0fj%`flgPsyv!sR2Ap`T@LEs%J?BWb^Vns-=1F{laHXhRDY*W@cd#a|AFP({kK(l z)PF4XA9(-Szkfag{ok98{p;k<5&Xg`kNi~NuLAxq#!vi0&%d}eaJ&BDCPbA zuK|9sO}PJ=yR(Xz{_@TgG2mG0gzn^^R{xOa5BZ0pG z`2S=48{|v-Ka=re$T#=@C%`Yd8L$7GFZKGj5%#}F<HpC109G{_euKy+~kLI6Gew=arZwLO*jGwSu z`_kucPcgnXRA2w*{@VfkN?URNN6DAw--F3l+N0~Y|NetDPEU-=qxq##{pR}Z1oc1q zDX!m3zV!QFs;fNmvw^=0`0q3R67s#m^*e|0gR1HEGyC@ee?Q~jCjSx3x99KOhUZu9 zE4_YF{yLLyLwWn}AKLzm?bv^X^5ca1r>Hzyzet*&kMjQe@7G{{|1kb}@>Qh3mwo@g z$@ux?2cAFu{yyN(%)tE@-mB*?`O{P$^&ec_dH??w`1kL?ekl171-f^>{tQxi|4-F@EgV zdi|y6zw3HVW4Ouhr*t`#qx#dRe)Ii@ zlc4@8dvX0&$losb6;&Sjp*8jXO*ZCt3izLXjs2DT^!(Zhe!9vdKb?H@{5lQ%cKfh@ zmHhUC-&*C7p9B0H;IC!;#+kbR>Vm(D@q-_6o*!p{zbX^=-_NC=s`9A+FyNmDevNOj zKau=qLjP4&9{Ew^o9ll8_;VP4C;8Iz@7s(Y5ByxHTNU{|fL&9Kim>?{xn$!ur3W^2iSd{#D@D z`XBZWl0VyO*!rWVn#v>JOTPK@^E}{RX8e8!b^p@OpDr+d{-e(8|2ptD9>o10CV#Xr z|BqE3^&cGOT>l%uAA1P5+FR(8kK8*WcNWT5~348r3t32{E$T!d5!oVNS z_y@>uZ-Mst7svRyev!m;V0v zPL)UVPbc4;e-QAO9mW0v^1E66Tg&13K;@C21Ntuk{4qaZ{}TCiEZ<)L*Hj+)VRfA6 z|9!we#rWlq==GP*zvGM_OMc)w^FRNT0{#ofaR2pP`p>I8>OUU%!N9-D_+82GD$M^^ z#!n*O-2Y{O-|sl?ecPS5f7W@2TtDe-(g# zn(><+)%&k*0H*J@e}eJD$v5|32=GUr!2QROA0_y&syymH8u*og|2gBQkuRNp+Zf*q z=3fQ)%TD6{&yp{_f70hioWH^3?e+VY$rt;<-T9NR@@W3aVE&;9|rstj9>V;?ti1;FJ=6wdV2om`qu`2xzl+5>EuTW zerc6Q^UomPoPS;5cVzrr@}>P3&G@-kITzmf5ypV0F+*FPNi zmCoS#XOb_ie>s&$^Uo&VT>l8*&t?2ECv^YP`p;(km~iL)w*l}EF#bmJrS;#(_=)73 z>)#0Y^>gt2i=A}0{`FKI%|C~HbNwTMzlibYkT0$Oe8!J|(s}(i27bApasN$!)cs4J zpD(TQsQ+B@LyYUMDe%)6e~3%}BgXec=>AQ=8StB*#r>y{FYUjkDv$aPA>Z78QNT}O z{5GhZP-zCNms_)!?t$^SA9G-uzpR`|%_D|rO{ruKl z<{&VD)4#4!?e*Jxd$@iqZ^!bOQO#LZTzmHu1{i`jgztVX;zbu#a zCouUlF6$qq@@W3C4fOh(^>+aE?_m7ir}g?v^Dp-c?mv$5(*3Kn%A@*28tVGZ=aF@->e6q>_|d@c0{l^opH04WevM%K1oFMc{_6((6O12zMz5cAejR1}Z1T(W={`-($&6>Zxeh*&3 z`SFyu-#@hTpE3D4l$WlbPgNexKNa*J5Bh)aH(dWR@}=jOSt^hG4D!wMb0qM0GyXC1 zrRRs8jGs%sm)6gJevSry)vLJwdcWxXFFik0QhC&WbQ67kn9mPmfd3}rZzErNei*~} z+2kh}{U-qbGUE@r;I97*j33j~+5b4;H@}AGf0=yy^}l`oG*x*t{~Yqo^KU%xe`NfS zi@Ja5@9!LA{Cx6##{4G&zhxfoe>3?nQa z9rziHpGW=^LjPMBKMnMs1pGUUANs3af9d@FoAI;B_fq}-^LHljyI;rapFqBJ{&rS* zwEmILI?tb3z+cPwo=dua>HJ;A_?hIJ`+pAb%l?7;-$;I>u>Pe~9`zp;<=p?tz;D3# z7hL*JFn&Dn=K_By<5#`xZvPEt{8aMI{WlNzhZuhh`O^N|&-k9^&i%Il`1Nn#^}j>D zwEya;Q7 zrRR??89%9|^ZZ!>{Kx;o{m&v_dj1GgdDMRf`R4v#3H$|&U-+8tzm0JIzQ_3Ct(^U@ z2L4^fk0w9x{1zy~zW#18z8Cmuz+aP(=bu3SumDWoZGVNzqxmP3Z@z!B7WmEo#{PNo z?bpxt^|yq{-=w_#{-<64LX}7LXH)&={@DQPZ+r{a-zrb<=bin%`6=pWw3oNh*)(j|25@2K5Ku#`P~Ezq>U*d;LnPJn|FCH}~IG;D5;Y zS6$Ap=fAjq&+mHu_gVGZ>)%D?QT>@zewEew|pA7sj zfd8=Pp8tNQZZYeBxQ$D{vdW|W(}4da@cS@+yi30am;Db^c{KlQ(Erz<|92U`@E>~r**|}=wu#5bfEE|19|`QsB!zfB$6sO!Ce1 z=YPPDz6Z~*@D08G#|r4q^RK1KqyBTqH_zWgz(2?M+2q?lKd{%YOc7lFMaoB5_1p8i zU*%E#v2C5tFNZ<>?HRw)pL%}w^>6#rn0#HzOV?i_Q-3_D{|8Wi4&%2Yzr8g-d;TYi z;`z^~y#4pj?f#FdJeq$J)gO5M;J<#dLH)goVLyj_>H2@@UYx&9`Q29kcKsnLkLu3= z_5TR!U&8oXZtDG`cj5mPVfzaiKbL&-`SB;*oycvl;(0@_SjJ^+(SU#*Zf7NA>&n&(FZWSRD8Nz%AXsbbg#wdDMSUduRXWfd6y} z?6)FcI{(Ho`R*?BJ|;hi^3wJHxyqydL#cmr|6K(A2baYCr<32@+VJ-JmsEM=$C00A ztpBgTf1B~I|D)Gmnt#}RxcNd|xc&(8 zYZTC(_y2t=kNhy;UjzR6QrPe5a(+RcVD}(dnUG_gqns6V$d_Cx-4 zcl`}`2VNNH?9U|sZNZE}0Jsyy<2d!MI{yFvukCkIdDMSUFYS9N>GwlIo#)3<#xED7{Q>`9*8gGqhZsK&_?3a*vo`L3 z6#4e=FShqz7nMi-XOeGTe^r2gv<~(Ul3!2iU*(Y>9INMV`c;AdU0v+oCV#%*f1~oq zk0sxHehLMC+sCm#x42&aHw3?x$|FCQd@tqw`>#6iPceRp651ar_{SMPy0@OcdH&S| z{+N2W|MBEY>;Ia{qyBy5o9kZ-_=BIo{#x>5g#HJrJo1D3(D_4o|NO&%pU3#O$d`V8 zcA4=bfnOW=uc`yme@)vzKdV_%ufO#9lOZaP`i~>utNyqBU;h5{a+?uI{yZ%JnBEJuk-u~2mYl9?6@k*9iFI8e;z_`8_Eg_-5aK zUsrkLhmh~3px=)Sb?*Obj9>eHz5Y)JVES(Rml!{R{BXl>4EzC&aQ`ooFa7@b(of-h zlFR((Onw99?f38O^&b+6{Vd8$fB*U=l}GEBM)UI-^LrM|FQ4(-mD20C(*HkheA)H? z!T52{>-`%_dH?=t9_rjbZ$6FtzgkB74Gi1=pZ5M4qw=W#q=DKu_fJdU-(mdfWwkHe zzy4OX;eFXjFFuRZV!H^u&-a(exx_g`a}{5Z3dA|6Y|x^NSj!>klicqyGB4f%NGaQ$A&OY{GyC9Xecn6BSv)c-Q5e>3A(sOE0}uWF6!uSsfcn2^gZ(MwOY@%)jq~d%FU>zebo#)TY4%knv;qLmKrt-*7Cf}U@>%hOl`1{G1*8d{orvd*B;Lq%c z`_Coce*eV2|4vhR)V~k-V}burC+t_KspntDQ}E07KVtG>l&@Ye?s$G&rt+x%Y*7Dr zP=D3Vxc+u7>tDv?`%ymLs^9Ma1C>Yh=TZG$%KP{KBvAj@7+n7W^6mHUY=5-MBR}R< zef^vJe=_hxx?ukf`HQIjz&HE)FRSv%k0U>gg8u&B0)CUO*iU)H-TB){<&hs8@9cjX z@bBw}{cYq+_pf_Z9{FM9o9FKg;4fzU9P;h=uk88H?2hX%QcKUz{`s$+pQiGt{#a1| zOi=%k9@x((UphYzsyy$fd6oB-2Wi*qdWz_?EO<&XOa*@Ii`cJP+ui={rt-*7C*Rz^D}cXp5cZprFWoA2!PQ{PQvJ ze|-h}Y2-`yzw;`O{3zhB1OApF*#Cz7C{MvJ`~I_0<&hstzPW!k0DtyS>=&!6=buxM zciexHR37b(E#9ESZ1tVU%LNvQ+ecj zMmzW47U2KN_z~nw_n+Y-aQ&?)FWrCQR36nILiKw+`pdt5+d%zoUd8@a@}>JvbCpMa z0+?S0@Xs;+1@fi)&uPX_Cf~gOd$$uC+>OWmN51adKRbbcgz=Y=FP)zU zRUXYhhJ5q<*adv=NZfxW`O^KT<0zcJLV4-_6Rq;7{zOp!ZczWbqp_d!gkHZ2o`PTY z`TOAOIDeD!(&tYqs647a9jyOeP=6fbhllI>XB8~paelwZ_?hH;Deu4jzXARk#@|W) z%YuKB@x#XG{hw_3nZO?~2G76dle&ND=f8ba9?d_4d~^Tr2mThu_mXelzwGy~HZp!L z`R4QI0pLIW2JU|%)+4hb13gE zkam84=WC|_5b8gH^8WpQIMn(45}knO9~GhZ-yXqlsq$$4$>e7n{!!rn!1#shYrnMM z7a5D|e}MA#-#@eW?_H+;xCH0*dmPl?jPVzdKUk>$X_ZIw%O~GUdH?#I0R9NZ_cYM+ zYbE%vFn;V<-M@K${0RINjNgiU>F3Xl#^LqrL-`6q{l}PmJmqH#`R`R8%|D6ykEXnT z{%1h{iErZi?~va|@ZVH<aQ$m2f6>a@^*2*_RDU$ppGSHB`dt9^FJSyRjr8>+y?$vt5!au}Tfg|*WcZx|D?(zKLhwTfd3KWk0!r~r{I^pe#;m?i+uC^z6tys#!n~Te*U%n9~nPn zf^+`)z<*>4p8r9YeyGZ$`G*7l7Vw8N{uT12*WYoB9}WE5!2gl)D?F{QpL|cjFY9vl z9AkVh`R4l{cYy!pTX_DZ8q@i2f%f`;rt)b1K@;`*oA)12HRtnp^{LnoZKD0P1>1Lg ze*7VoM}9u}$&~k>Uxk5xi1GK6|Cr_5{qJY|q)E>GUj+D}({TSc$p287|AQ)z`cEa_ z{QaH9fIo!s7d)fquYSF9fiLTio|hOuJ5l!^OacG=gMh!8@n=QR{wtt6U;jl+$Mai8 z`RYRcag|5&%bns}zmlN-PZ{6m(jPnn*MFAs(*19s%A@){Z#nBPh3aSg!p-&k?f1{^ z^-pK~5a0&`zgiORKb(B&^()HJH9?q|IncvOiGbwMse{9dM$b9TyrhHp# z-gZ8U$v1dT&u^C%u;=$PlfNFV^A!aDfd#n!ptd?+S;)W1S?9_?Sxbm#f?L^bF0dx;e6_a|Ta`EgN|M}7$Kp9Fp;<0q3}-I~9>{$DYEIPmKO zzyAlg|IOsv`^WaLGx#<1XQT<*}|I?uUQj2l@p)UR6Dv$gG;5PyOJB;6qeEa&f`@hEIV<<1Ze}0LnKbh+H z(!Bl8Kg~e>gO=d=t#w&{)ulMU(`9}%lRrrL4%U9L_us23kJc}b`ZwSIY61H9F2nU# zZtrgY_EmZ0N6v7bpRItO%lMJx+t2TI|K}J#2Ka4&-(orL{}uA*Tk|hu{rMS{NBzf< zZ@&K$4gBvJ{|@<+EYI%$0ORKaza8*Pr{ex6cF^nJM(|6hJnBCwNw2^8`m+P@J2L)W z@}>DlGkym7=KMPW|0l-p(^2;?%|Dy*qul92rTKMMdE{q;`8^N(-Hcx)M$a$7YS^CNPR5UVNAKTs%KO)E0PtT}h5Mh^Mf>*t zwf)AcaefWurRT?nDv#!uH_Lhby#VUp#Q0yk^w%?f&}^qa2>5x7f1dnK*8a8UcbV}+ z$v3aR!N8xh2G2jDD?a}US@pcF@@W1kp#M1FmrcWdEcy2JZ~K#&{1D1lx9YX?<5V8i zpAPCD3hM9wF|L0W`Q0twUccTdkNiyX&F9zQz`w@$Ipo`)U$Fi6*5dl}nEBcHSt^g} z51!-P|M8&y$aUB+(@pO`d;M&`zRDv%4EQ5~e}?fJxb#mleiZOW1AqE@+<#A({$!O$ z{l@}-4DiD?U_XI;`~0=%-;2pFpuF__)K%qC{qdmwv7r9X8Gk4F&j{DwHpWjP-@O07 z3H(MI@%(O)|CiuDsq(1*4D!wQk0$^>i}9~_$LF`be%~>^?_KBhI|=wDKf(Qn^l;}F zQ+d>XHt;6{|5e6sM*hh{1sA~jqh|=?=aFx|et8S{=hB(}d+GkC7UUhDUpTGusQ<)d zJ%983p9cK6P1s*T{$tjH+x@?&^2pC5-+X?U0sL!>|8;NOzy0|o+aIzS*YD}0^T++g z8DDn(C6!0@N4=-#7f=2BpI>K!`U`Kt{_+0WUuD?-|Fr#o89)7f=k+rS_#ZR=_yO9t z@87mRX)CV(9hZ6Ur#Qce^3wU;SLMmAr{NWS#*n<$k> ze(*f!{!an^q|dM)N51|3mp#96Dv$gy@_n>_{MY{?;CKB3`*+Bfo_{*3Jo0lv|4V>B z?n~@f8K~F4y0HIWS9#=n<~!%V4ES&Ru;0w3KUL+C9|HVT;LqKSeJ}YHg#J_a;QScM zOV`h0l}Gi5gZe)L^=E&D{gvc56zV^s^2m=S-+caD1^kZRVE-ifF@hhh^2kpB{u2Q=U&yNGFyp5I|6|~f+lTufKz=jJv;Egq9`&CM{B^+pgYlP<-_P>x^Xn?( z2QARoulfA90r=xHasRi-A1?TBs66UFhI}vO{paT=z`w)zaWCriA0YUDGk!YoHvxb0 zx48d(fh(^p9na=-44G1{13n%@+J3QAij#6^vCZ1W|gP@i@{$F z{tAb`O?=<)|6bz3kNo_np2}1I899-!KMKL0@9{Z}-&SQRT^xo~zfd+?Zbp z_}@AF&Lj2w4q1K`>+fGX{0i{5fq(f{?*E*-wQs-wW&7P!p879)IdcDQ2mf=2-(UQ3 zR=@W7{lwwN&eMKK^5Oe)2l%gTPvxoq4DrqVR|@_|Kd^s5{L1rRIR?A` z4^*D~Z1F4e;r@4lf8h@8SGE3!<3{QAkFJE}qy6{CBwx+S#|O-3syx+S2=n^|>Ms@F z{`cz=#BXBNZ>R+;p{k7u1 zWUZI&=czpTDdOizK79Vl!9VwB_CxpR^LM4kZ>#d;S3v&W_ad^8Wi5>fg4X>(3GY3aftG|5oM6PZB>Q`SARzw2J)sqbmPn zf4lg;^K<+F`?=76HSoKZvtRQ*y?&MVZ{-;5{x4E_>c2qz%6z#08sHB-$bJ{`eb3)p zRG$1Y=)V^D=N)1{B))xr*!{OZ%=s~rx4(XC@Bgz^p6ZW!J#zonf%<=O_|J-OpC8*V zb@;L1*8~5xBiw&}!1=v;l=GiR-gke_Re73Ug4A#BzXnkMi+{3TCcf|aIa}q)4}pIw z_?sPm+!%fSR#zT8`B>HZ`FH*i|^~dp2}1Ii3=iMKQsY;aaeDr~&p+G$&Gq+?d>gBNdw#PV`C*c`??1c$r&XT%PloqR($q#{lDfrJi{4wHJu7Bk+RIU6E`8dPj z7m9DrzdQK*9sUR653xMk-|O(p#jmVCe10zjKe-0aKX#&?zy0~Qeg9vf@-+X%xAgj( z&%evT-|O&uir?4M|1O80BEH%G72rQrllz|`zWw~Q`_EE&>OTkizY_eP9R4El?XTZg zwdS$i;pc!b42f2#QA`M(bQ zQis1H;QW7Z`03zZ5B{+_-2bkC^FN~U)PEMt|3>he*JHoKB)$K9^KYv1zWx56eg2j>^1~!=-=9^j`zuf7ss1vk{}!l!LVd13Q+(g|uZ~rD@}rhSo}WSB z=QLoyRD9p(kF!*s{ABUX^D_keZ)4bR_>kT|zWHBxD(4dP;ztLK6d;N=5 zp85~L{D(pR_cY}Ca{~6Crt;+H!Tg7VzuVz27vFyUWcR<*;TM8G0{m``xc^e|H+bfM zvC32bCE}MzKK%MI4gAK9*LXr-DBQ{KsS2Uo5`w{-3Jy3yNmDJe-&qNK1K4r`}>f}Q~kv-zX?!(-4^UG6yJA$)l_-%WApX?HSezn!5?xK z`(vgAyT5K$dGgD}H=o}Rfj=&e{T%Uq_gA{glOO$#?%%w>9tOWdEA~&F8tnc$r#0tW zOWt??v{HGhKU3;A=QjoFZ`p?Zo({jN^*lWPY|e)Q=G&`0)t>|PKL+*x*CpO@K~_@_lG0jO7d4*>us<9ewC;C zW0vXjW6o~|)IYr)*FReP4IY1r%9EcezIlH=4gTQv?020O?EXqsdGZt9)%~02FB|+n z9ezlB-~Dya;U|l4-d{7pzn}y6|NZn}`{!*({x`|{?yp5EPxC8;`8@~qcQ}vhubmaF z{w0ol^MLuQIkmT zex~@HJpL$$p9}kMJ@|dPasSK2U+(dHsyy{yAinwe=SJ}Vb@*w|>iJ)79r&tNJ;xk= z!pg|=w+Z~R?%e;oGqvyg{NZPnr~Z?{F9!d*9_(*+_;&yORG$10_?y9h$>Eoa-^QA^ z?LY7EGr->he*4R~|HiZQ{C&TFdgSZZPAX6HPy0}xKlA?G z4*oX|f1~(QJpLw!pAY^H@K3#*`|qEl`_J+C^;DkvuMppS{aOnC0EfRveBaOiUgPky z)~d`rpO&ky_l zsCymz7fRml-_F-mdFnqO`mYK7k9YV3#P@yvf1krI65qUkqruc2qz%5xsR|6{-(as&IL#kber-v2kNJo&}oHw6D@hd)>Rfu8yAaQJ27 zha?~FzcKjTZsh(CitqdU|6-M={*yn8+<#5Mf6n1|cvY|ei=O_o9e#%Rg+~9e;8!2W z{jU@Mc8~wB!%tin*?%+e(;a^M`MUpc9{+BYr}?LeZ=Sz1z~AohbH(@l{(!9xzZCj! z0ew&fgafKVJNhvHx0uAD_zoj}+f` z{#vU%^`9ladH=Toe~QC@U3}m9d&uDzh+k;*-xmCX4!_6idj7XdzVd_p{QKSEXB0)A zzj*M6-puo#FTU^m-JMxS|&DZ}oK>bl8*uP;(u=<~JR)a_kI1~9hE0PrZ}?y+rTfooBh|tukY#qw^5v5CVBh$YtQe{J)B=F zdEfKnfXY+<@zDPr(0|)|+20k=Z>jR+CxL$__&21pAGKWX|Ibv64UP$q!a} zsy|igPm=>5KEETO{(WQFufIaq@7uq-Ri6C9ul4>l@BdNY?;g*7XYqaeuhRpZ?=AVJ zp7~96n5>Zr9kf=`~I=#SLGqjH;}yV_jeyxKGmNl^_%bijEDLQ9e!fK`tP00_4k&% z{rR`u|45am`ZJ;a2~dCZ!|bPvpJc7S?N?KI@^fJQ9|ZpyhoA50-`;;uIQ$~${~_?- z$>jdGiC=mDRF1*+7ppw=Ukd#{4F0)~vR`MV-v7S++g9btkNGC@`Tq#`|2q86;`{E; zYL9XKy(RCv|4&Ti`s1bk%K3+%KU1OpD%02>CBFUhgZBCzSM}5UQel46z<=D~&lA5> z<;IbZwm((n$xny=v%vq?;TMbF#N$6So#(el@~u4i43($)b76i@Lj8vve)I==|5v{M zQCWsPzXJ}xK>W&lc>g>F{`pzlf2R1p-~ZlT<*EOKE&BY0r0}r+4EQ+?f2;Vdk z?fZ9@!_O8!+wh+S|Juj7|52;-{2#V_d;Wb@p8AjbR`+lEv%r7R;qMpU_w}RM4nJRf z^Zs}a{GT0ur`5WD-@m`O!{L{L{{r}-CwTs&#ZR*4Z?FHYDo^u|`7ZMH*Nfn9arjHc zx9@-3fAC4JzfkhNpMMyy@>G8k)IS&M|Jvc#`%tf6chCGbI{b9-=YgL-gZu9*zVH6O zTji<$T=C74aQKtN?`SQU_1CC<4!=PB6bXdyuleBTKE?enTBGMb+?v0Af4rpf z)PHnI_y4eG*-wn|IR-D!&RRA=J|UM{5?7B_ZNS-)xW*|KdC(VA@R-qza0Dv z=CHp^{1nT#`|qIg{tCn&%dR|zj_|$+eqG5e{Yqi`U|1{RZxGuSJ@vc zerHepwN#$`a_~O{f7X2V^ThYP{^|G{=Rc6Vz5ng~*G}cB{JOEt{_}o}?0+rzOBS*}M*Pb2Te*$w z{_|9x{6g_7^Wpy2f&X=%Lm=v(YRzb@GMKf0LnDc8u4uD?)x-}`UFRi6AT@y-3enf!Oy-y^>7 z`7vDO$u9tZ3;5OFW4}vLu>C*zea@#!-uL{Opz>6I8PvZO>OW@%`!mG%eg9i4l_x*8 zH1hm?4}NX|`)`SF?|=LG)omr`Ka{-h{ss040e>>D)`vdm3i$A^c;5qJ(>MBou zviRAO3!lFo;7@V*w|}AcpYQX}ha7$h^j`}8WvjUVZ1H{H|K3&QssD2E3yuDFfxq72 zw_mUOzg_Z`AME?@Gl!r5lit7P{j(eV0js(Hr^WYu|NAv6PyLsQZ{ELqz~AQZt8dW# z``*9&*5Rk_itN7({J|e`|9!=8S~+<6XwQGlN1P8y-v0h4JAaSL)BLiee)IYJ8`S^r z$LyDg?|Xh7Q+e`>VSc}Z|JSGNpAg^v`kUSVwQD&azfrIMUeEmdsyx*n^|N08kmSSn zPdU{8jl+-mQu`A;{w9YX65l+32f@GeGw%Nh@n7)xT~wa>&lca@KZn6z=_&0vR^WP=D zZ~v#LJk38{HQ&V_g`c14>|n5#rN(10}ekH{HEYf`;zD1>}!4g zefQ5JDo^uI5Z}CiV!^+06Z?I|_su^=<;f30|INT3^%eW~i0_;Kohna$2KZ-y|3ESO zPm1q*|Dwc^e_8UCuRkgu;`aL=->5v*pDp#9uOH5W`g6bL`kQXn`^TQ2{rcf0l_$Rh z=GPMZmp8NDIiR1T^5mC;-x~aTzhQrX_(_$6mydS;BUPUK=)IAzU(W`A&=&S*ieLHq ztFjE+|J{*aBzb%P+4(!ZWq) z{mm33Ri5fkkoqhA@bj+^)L-E6hl%eyfA2c{Wbm&7fBRnUf0_8c^LJet=hsT!_xVph zm8bcIVEy|+{Tm(rp@8-G*~j(Q*c$BqS>wo`7BGM9uk5#ze5=X_hkUeOfAm#(nqM}o z|8+3G4GzDb_`dVI&f({YZ{DBRgWu&h?tg~(=UV;S&(8}~p879`{c|JuPdNM);@kJP zJ^#lXe%!B-uU~Egf3L&;HlV-D;U|KBGx#^{=lLHNf3j!(15}>opCW$c^EZ6|-3tC* zhd-u7pFb5l>5sksyBvNI^gkH|9J7u`|nQhA9wh>#kZfo5&PefKPq|O{^|NZ?!V49z5Zu-*8e3(zOCea z&#&iIp5~Vh>o*GK*RGuFPZqz4wPCEkMzv9S^0UN`mO%La8V&wIhd=lSJwM;i@BQxZ zv-j)$n=SJX`|036`v>=bP<-FlKQ=k?RkrK;?fqxZ@8W~(Hwu`a=*YK~{C>~=8>jNL zesRC+`I*n3aWKDOhq(TRKkEMV|H}UtVV}RjDo=h6_z!@8=3(|965sdyiB);>V-7^# ze-psZboeg?^dEHi@!&rQ{%(i=f%wh;fAs(Tmp%X1M|ghUN#6JQM+=pw`6WU9lcD~l z4!_0@y?^4Wo!o$Ze%^HWsp3aTK79T&!GGW=_kZ?J+TVLJA7%SvRG#`z{X_2`^Y;f% z0e_FfueVG4_Ve3bzn>g_#=*$_I~Dxae{%m_#oui8Z~HA&p8C%g-|T-H__sRzdOz#_ zecwO!h9iHG? z{4X4Smp$6I?>}oCQEMH3Huz70f8k%;{|(~%zJA_O<*ENX@Sg$ySBHPU_`c`w_P@FQ z$0Q$bZ5ZpXQCn4>>MxY~D~$a!6Y4+hg!Zdi3zNTBub+yX^e1eYe;TSh`2~OK{Zk_U zpTqliHu%>@)%fpDt7-XL#P`i_URBQTk-TqyFRDD%pIxErH|O^P)PG40?c4K<*%xen zomHOvtYeY$dlCGvYjOXb#qVS;miljN%u%6e&X16MdEJx$2kiW>N7v^0r5@4qTWxLlux0+aOXbN=t1;@o|8HrAKM(xLb-4e=e`^1gXj6-v-vpH> zzqo<+vt)kZ{&T^vUYGlS?_cfDJ(>T{GXGEiI{eh;+Rrmyf6NDey7>0_OOb!6$hUv0 z*W>w3k-TsJ{HuJLU({K;esljUfcm3O;rWfNrx(t*fBshWlONqKa{s&m{$JwT^Gm3& zef$2h&reQ$o?lN#-Z~CZ)f;gB2Fd%rf9PN3Q~&7~>i*5If4l|#k9PPEh;M)W#jgJ@ zl_x(7{3YO@9mD-E5a0LvL*i7P{9N(PuOGh+{%;O{SHShV@>H(>u;dfX!><0@-akny zPxTi={mY>KaSp#j1HFHJzdtG6;YW4W`zIvzhws1lz~Aifr;0z_)Bl$aKP0~S{>yUk z&u_@{uNR~HU+VGOt31uWTzvEWivsXparhsI?_2*l4nOT8J%4lmegJ;4!#^m#Z~Zqo z{2cMk^`_{jm%G3NS#5dP}4fr!0{$}xg>p$J$XLZr@H`l)q{0fJk z*--cId;jn8#yr35fcdkUaQ+p^`|j^%Do^u|x;XOru@?IO+~IE$zpLl`ed6$A!Cwdd z$fn$XbR#`~`}L3g{JKNsss9AA)BK_nBkzxI!7p(5 zBbo%eKif9v`o~K?)~es${{zn8{L_;6t>0~Daz0n`EiK<(zdG?PH__wv@d@ITO>K~-?RDV9y{|nUL zzYY7n0@iur=hG$M#G0Rde&2TFr%C=ID{r5_MJiA8D}w#|E6nfn_FVsJ z@z3@6?a$?WndI&J%dY>a4xFzOtM^}LkDsOT)PDu^e*pSFt0Vh8#J8XS_WCtbdGg~g ziF|&QgMZNB4-$Wpr~ls_ev z?fcI@|L-{Rg_5uQ`gP?p+UIAn%G3Okr2b3^g!gX^sDE^4u0QKcJ?R@OVfkp+f0xRW zU)Vizezm}lx`_Q9;-`50za4%#__e{$a`+uv=>E_2_>ZbQ^&i(G^8Ts|{xXN(PyBm4 z{t}0uD86}qPXYhxEpP!Auf8%1Fe~UQX|HGDV&wswk)BIzTbpNK` z1pGf7{yg!0_xFB>pC`Wg{5lQ%E{WX#Zt;EJf4kk0k7}vs=X?KktIE^-N~HeE=S+D2 zG=uspF5&t+ieKB>FxFqA4yio(<>Hqc`}Yj+ue+4}#8$e0-}C3@Zk!(|dHekrd;cx( z&iORS`<}n=s66#wdU@pYBM$oC)Pwzn;`@I8$QLS4eoW6uzZLkyFJphB_`dglhpIgJ zN#dLPuMPMwC$WDtpr51iwE`Fi0|Kq_wa0UDE zZM46|bARqrdGZsk(Ccsd?ZK~iCHu$4?_l}XIHGE)Jo)9~o9o{J{HuGjAAh#)zm?_N z^Y5+lKA^oDWIz&k?01dV{%IaR`x?$)Cwcq+vBnwoq{>tMaeX4! zzYEk~;qWJkpW>uHx-2X=LeeZwGRC($@4g9X)zchgTsCIh&uCV&I z=l{IQlb<7gvgE`2ryKZPZe+ig_`di5FHm{%OQHWB;Ex~3{tWSb&)@r0p8S|(z5mSh zPXfQjP3$idf4DV&`~HYhdGeFQ4;l0C3I0Hb-?Y76f8XoZ{x@^|oh0vj{dWH?oKKOw z@Acd7w{m`%{x#sAJ&gS;I%q%D^6mR8PUXpu z?yLP|!@m~%J8oxxuJ}zY-#)*$-NE@~lE2U@*v=19d8$7J>K_309~;5`FX!p`J!;i& zl@oPD<;l+x-(0^N!0&e_`)M7upJ@4Z|9w=R{G_WR@85yoZ+7^(;#Z!($}(*K$~3OO zK=Qut-^zF7ivs53?qYwd;FfUr~Wg=H~YUA{D0EfZ`Mis*Gm1BAME*8s66?p{Ucw0+z0*} z_pv`xeBb)dS9$Vt#W(xEAN(uFu%C3H?%)3W-R?h0<;l;uR`+i{f5w5IHX z^3nEZIs9z#Ly`-hzX!m-|9rU2{MR`A6!FdXKc<2|W<2-b?;?GE26_B@RG#|J6h9>S@cEqv ze)9*|&lA7${;4d(zCQ*y@`aN3ef{nlm8bekVf`M5`k!?82gJ9ZzxM0zX%4>v*6&I1 ze{%ReyXf`v-Cx@se*ATj_s3J@`R(YCVF8H&- z|JdP=7T@>!;mnC#e~#qs&!6o5AFJ|Ie;KU*3sCYp@;{dJe<_1_x)A2 z9CY~E;Lij9iif!WRRR4TDo_3Afu9ThD-M5~_`dzK(~&`>%2f+5Y(|PxC9fUhm&z35NI20`Suveu4OPEztUF)ZGq0<_7Io ze*a?Fe*^rV9RAv#y8j0%8<&r^zun=N+!*=%dlUTT5A*!HU7`J^9)FM{KS1)n@87#g z5z<`(p$84OE`|c;J-hM{XGHKKVRj^4~cKSe)t;vk+a#a*+-v0-~PWt<;l+g z{~PdsarkWm`lSv(8~ksexdm0{{J5Q z8P9Y7bHw-U|LH1E{g;FP1Nismu)j9o`j1q3^5brfeEsnw_?NuM{_cQ&XO$;E5&WIt zf9UY5CkH!!1r9$2{GY(@J%{^mBff9{U#{}he;W8dgTK?^CyVdf|JxjXrugRm{{{Sq zU*i5V#P{w0i7HS1=Yzi&{9!M%KR@972dg~!r7-_};J-MJ{dMB|?*G{;PksgXzkxsP z754W8^dC`q@?&m^JpaFge@`y^HLnWx`ZG=C$&UyBf8Y;)mHqbO`}Y4(l_x(*d~^T* z0sgT0?B623Z~qTgdGgc2KLq}jud_cj;QW(Rp8Q;x{}J#%aQJxv{r4Sy0r-D{-(>;! zzcJwaFHm{vzZmBK7x<0eVE?H24Lq;EuYHsAHTwp8e)Uy(sz2)1$n$#~>OW@@`)$Sd zJ-=G1Jo&NUp8)@qx7hD1zVG}*t33G$;+y9;O1+t=r~3at|9{OA_QwXCf3nJxAApwzxQMIX9k@ANR=l)UVQWU z9|!*1pR&I?;QSYU#8!{0v;5B_q8pD+H+9{(MOA3G%S`PUx&_#*DVPJccB z&pm!?m8brb#m_VP?*RUb4u7-wEj|8hhhHwf`TXq&{!WK~Sp4fe{x*jnJyg#>+vq<5 z{5D_k{Ci)k=kN2+QhAzx4)~qGzu)2Kh<~f6|IrRVKBW6kHu~=j{&I(ZSp3Bv{~d>) zF21?{x`3a*f#;u_qUZ1P-%@#+f9!3M>z@ezx{d7jyH5KI^5o}@ODI*Z(Y)CqH^vWdGg3U-}jMsRM%b|E9{5pCi86e-H2<`I`L#@qPVIQhD+#p#LQB zU)aq4@auK|b3EU_H&f-w&$?aDKSlE4=TA@Y7j9wy#0}bS>+xSzdGbq!Yv26-!Cv4m z`i}iJ1GVp){{odKzw{36oA+;T@C&!HUo8Fup8i*N-yCfIMJi8zPFm#r`+@)ScJ>bj^mnK{`SEu}`u)NG z>qqwI-4d+-!zxdH0r)B4H`vL3-&?i6&a?h?RG$3gk-GmvWBmt!f67nn#|#SAf3(Vz zAANV^{BHoi^)B{Ti|>2=(?aFR&k;Yv=zk#i|NP8;{lU6_-}`?RDo=jQDBZvL`ZE>$ zKX$X9Abx+({j*=?$&VM`JpZ?Vzu_15ClAs6`#%3)r}E^--V@pXAn>2q%lpxWG$aQLa>CmZ+QNbu_%;QrU%rq|y${~9V!{pX5r&VLm6yZ^_2(_z8(-%gb$ zKQ=vb{-eSFtDOBr@qOp-u*#F4AijD2(!oFc2m8}*4>te*sXX~{_v!x4`HumA(;@at z#P{8QU#L9!IpW6}`)@4xGY+$VOnl$`r>i{q`Qn@N9}j-JBkU*N5p4c#RG$3YF_H7n z0KeLw?9UP3H~$ky*^j+Ha{o^Rf4#$BE&eT@*B_s$JoTR-zB&I%;HOt`|4r`I|CK6FevbI&{?7zI?Qiy<5Z^cd+f|{)eZbwp{E;e8em?j!z`wr=`zHeW zqg9^#q>RY^p9cSds_ZWs6|Da;Do=hf_}Sn;SdIOG_XP9Dt33Ik36cHJ1pndc>{lHf z%%7<8*sqzc z{pz0Q?=+PsKUe$`NoG7#Zdne zhyUCCdjIXTeCs-l%5(SyllA^L_s>%B&#TYRki7l-yY2ZORe73!)WebY-*V{xzJ}a? zw)pn_XZu?kaelt!?caZ8=f6^Usy|ukH}~&KsK09y_6q~n|BfTSS@McJ>5pCiVwI=* zLs0)}s6W0b*IyyN@AsFtR(bL>#LtlG!(abi1ODp{f8lt&|0{q0OXU#k`{xyhUoO7+ z@7EQAfBR|N|L6?uCso4o(e{U^JoTUch~EDRk_-3$3HS#b{!;NTwS0U1_c{Dr@y-3a z7W|2^-2XoDFS2~Q|8Xi${TD$0>%c$bboN_J(DNT;fp-6=sXX~*;^#;{JpUr_mpS~2 z6SeRA_fMBN{LDx7`kSBsuLr+*Gw#32gW9*Bf7UpnnyNhYA2T)b`MVMPB8T5meBZx+ z`l-W@7r)Th|C_+SqB-|pB>wHz{___JEGA3a&`KcD}! z%9Ec0{x0zAv}M1O_%l88uc7kfr->gj&fjkE`<%o6TJe4FfAmy&@{7bb@1H&3PmE_j z=3zbmrk?(nx8r$eZ;FK^F&iuk^tKmSeT$&Z;Hd4K!{e)A6O zKP-NlH9x!mrYcW-lHZ(Bbgwr@z7PaWVU+PSgGS?!QY^p8V{mBk!Moz<=K1cN4#z z(R=tm?f##2_<7*}3;qk2aQ}CR|D5I9`){VoQ~!nH7fL=n|0?H1{{Ddd4*!_=_Ul*M z?{+EIUvIizKRa*F?}KifZz1`XR{wVXeU+#FGoOiE{~FN$uI}t7iGPpB|54@1&k^4| zf3?6*>cRdJ@rPT!J^xEpp8S|>-M@MN)CPafW$ZW1((9LO`S$#uQ+e`}z^@DbswDPr z5x>6W+x?%?lk;hkzt)qFR(YyF9qO+S_1}F3`vu}Rs&;Y#_WVYuJo&leha?_8zcJu1 z>&5=YC-wUIzW%#J<;jnFR_`CvZwP*DZ}y94X#bUy+yC!-Ma=(GBb6sVB)++S8-t(e z@b7&}`}X}~@1H7txc)~1<})4nSpoBtlG$G%dHeHcyZ$|n{0EY^-#@VPKdC&;KU?N+ zzW>?`=0EBxuD?Wl`}a@V{+%jMejfN|fWOA!*L*tI`d91A^~Xxy_wx%U9Q7AM{bxb_ z3mkr;_(#mcqW;^yKXO%`=2s$qp5(*#M@#U}yPEs&_>7*PeSg}1yvmcGIaA*s$%fw= z{0R>KA@L7b{oDIztivx7KViPHa`0Z4l`p=skd4F{Q|4xVhkof&A-@dJHN$_{0_;lvFf+a?*>QxiO=czWgF{vDbzpWI$poIv-JAe??2i8XO8^$l7Gkw z*z>=10Q&=<*S>Fl_c`)ulDD59wm(YcY5x_;{LI&1Nie@v4u6{X>Me(p{@DIzJ34MeovLB{wu)m1^yC;zpZlqwN7e3;`LLW!;g7h&VNmv55Iou4Stgw zbpKVYeOB?Z-aj2q=Kt%}{6EF0JoTSCSNrDgKS&1uVexBPew%sPFEugs-`4-PsEH0g z^s@HN_b>W_|Bu73{)+Y!E#F@M3Wr}JzWMy?2mb6EdH;12Kh^T>{-05Knt$}X$o~6- zzs2Ft7T-Sqw*QsG&lKN0|0&=%9?1PK6Muo#zdip3Do_3Ah;QEC1Heyr_&4S1^*`O? z7d!Ill5gpG|73%s{`glS*Y8HCzfmgBZ{};de*6Bn*Y6QW{&mUQ?_b;bNh(kC&z1Vk z^-qQRuezD*-z2`h|LpberSjw#fPV}4DYvk{Uwq%^&sVEF`NiN50zdjz_Upc`*U#5~ zHI*m74E!PBHy^})d+~k!H&uD^qjL5BHP=4`e!ao$Ulp+bS}IR|EcnB~pX2cF4Cp`S z@Dsov4*sSg-2b$I^Z!ESssCi~M}WU!DEkWn_Pz-DDo=hQ_!GfjkjDPjfPSvZlb-_qB=C>k#r~0i{vRq& zej50b!7m@lexrrKUVr?i^5kcNp9%h7e?R2NPnEpy>mL~^PxTi;{nMfTv+w2lw*>U#RG$1&@y*Yl9|u3p z;kS81?_b~izH#KcO1_o#`JX-iO^*6w=11PYGob!1={&zd0qcLukslMV{)H+}^N)x6 zpMm<%ypQXjEx!Hw!Jc2N%9Ec2{__G4{qOT@kL7$r$-nH`|J7BV)-PS=mm&G^`;R#=zxw0Y&li8Z$FHsO z*8vs9k^=+`6fzg+N7nZ$n0MSA|}9zR;;$&VA??0-J^-#h&A;`{dB z7KfiAzPbNj2fyya-2Z;@efzJb%2WSE;4cKf=OgTQeM`^ZxBt4UJo!lr^!l6g&jY{T z6!xcy@7sTURG$2F@y++27J*;*DEk}5AK=-4t5lx+EbteDUvDb=Ef(wf`}SWgl_x(} zd~^RT1;58M_GgRl+kcm+Jo&K;_4=EBKKSFZ*xx0-Z~vvMJo(w;oBMAW_>CTCzvB`; zf8YMAukz#}Zr z0sQk{Vt<$TzWv``<;hPM-`xLSg8%Sb_T!faTmOkFPkxs8=JWe2@R!YFf1dch`)`TL zlOOeV<{<~G4{0#BU_5TL^ov*T=xI)k0xBlByp8P!V&Gr8l{KVJT zpDe!b_2Y#qPktfHe=GQRFJS+$_`dZYq4MM>=0~po_u$WegZ-Wbdj7uke_7?p&llfZ z{~y5LmB;?m;``SBN0lePRD5&&e+2)rMeJ`8e?{dBFZpP{{>oH&@+-v8mR$Jxw-fv> zZ?QjPWw86_0+lB}>z&B;{|WrNmat#-1MU0v-*A;DKWbT||1d~^Tp1%K%~?EfVGVr&0dFNdPuRC)3X#W(lgKJasv zvH#R6z5e#^@3j3{Do=jOyL$c2`Tqv~{&(5mC4L91fBXFHReAC=#W&~wJNRqfW551t z-T&<#f2GQkpCf*4-oc*uGx8J|E*Z&5UCqMo@ zz5eF@I|Tm4E7%|XVX*zzN#)5e5Z~N?N5Fr$fc;Oz_wB!lDo=iy_~!om6a4lo**_q@ zZ~vXG^5jRoujg;>zrVmQcK8F<==uBh-v)=D4F2EXKkxzfzf64J_wS5RdFnq~d~^Q) zfWP12e=UBNb^h%Azt`dCi61h~|G(h(UB&(1{E?pjVvm2N%2WT*%OlTUmH5b?|G#lH z`!x!+@4Np~RG$1C@y+vB4gB>VvVV*CzVr8)%9CFtzPbK2!2fU!`_shtoxcK=C%;tu zWMlnnf&bP=?Efmh@BA%PdGg~|M4tcJ;Lj;!|EiCp|Et`0|39blzT3>mB|&@qPR6 zGl!qHGIIZ&2L7*~asSQM>i&KE?-!M){&U1P_uuK@XRTwuxA?yO_o&K~pD%v0vHzNb z-|})8T7rMgdiLwA)BXGQU$V-RAGJ#NZ|=X=;9s?Y{VwAB_FpfRCqGvF zWMluG4Sv!__UDN2+kcm;JozE;&jCO2OZNW~-}n5zQ02+b6W`o_?Z98}@Sp!Y`oGEf z_TOg?KXJ8Qe{=tx3;w!I-2boQ`}W_*Do_2ViEr+|^T1E~iv6ZVx_{sPyHw@L&lEq| z*nj7P|GvYYD86t1zuKxi^&j(LwOo zx3OPmgPwoo{VCU2#Lti4^#kWy17ooDj zXsw^!{|hQl{TD+21EK#tzp(#=_^mD9?*Av1C%**zRPdYaWq+yoXL#R{`z0pZ!Ny>{C%eK z7pYy{d@B9AQs=sr7yyQn(>u>M>f0R%Cr%3-H$%pUH;n4px z4!>0VaUTB(l_x)6eDnSu0sdD9xc?i!ivCab%|EuB^CKm1fBt8mpGGQA^~V?L^OJ1M z?=Gl6{SWrni+`hMes`-p`RU-_4gRcy?8g=B`3?8@Ppdrn1>oNU{@aJxpDMon{IK`$ z*2A1%CV91OPx@o8-)5Dk`lCM9>u0`xN{9NJ9c8~veEa^j>u;j+|6)HB(4VUE(o8J;C`!lK;Y!e^}+I{&J~5WXx|O)E`@=)_=d#+5Y`w_iv8=FL}4++w0#* z<;hR^MDKs|{mV(<-|O&qi2uC>T7Qii>F`U%FO@)e|4asdYgO)l#TMPa@A=uFI_JNT zyzlix9hIl~6|U9utNi>uT>m3b{{=PJKPrAJYyIr|ql3zmUjqK4;9pvk{n&5y{90QJ zX8T=Kp8Rt0la2lR82HUq93KW1Iz{(BPq4;}tw@qPb(Q-Q;e7eCqP|0(br*Wv!x zi|_mWxeZjF`cH@cp8`(_xgR2!_S5J&jP=B zU7r8b;?MG&zosfr^Dh(Me11F!ezwDJwN3YL?|=LLdD7u$d>*;~UjY9rho2;VqNo4$ z4nJFb^Y!D4;6G50=bs_IZ~u)^d76JA^#2n06%K#C_`c`oS*LLQ?@7LwH9!0QYNqm3 ze+kq-59*)p@GHb`>ha%o+s{a&=;pzX@23-GINB#EtFLdO;lY9@$xAU*6Jnf&vqR8j}8!-R9 z4!`ksz5Z1#(4OBeho2^X$XNe3!M`eo=hyT{?f0}m>#tG0RG#`T65ss!%eTN^=kQZ^ zYQLE^f7`EmD%U?o@()-6>#tG&IO;F^La$%BF~6ly|8R%@>rc9V-_OquRe73U$$IUZ z??2^(zun=_`C0p`JoDS?@KZKuztHG^8ThRm^85$x*8Tv`{%xW1)PKxI?VHc<_rQO| z;phFL{hA(slEcpx-<+2AHP}qA!GeN0RJtApZ~k|&pf&Ph_4@)Ir3jheu0&@+KKwvQGb@yZ| zo%qu|{z``*65l*OpMqbd3D1A<|MdLr*B|!!4|L?yCEwCh|8*))>sJEnw+`x`>+qiy zzm?_N^}pco%fT-K|FWh$zjwv={reAHRi65f-V(Wg*Mnc`@V^)TPEY?oIQ%&AlO-R% z|2Beu^=aIHQhD@$N#FZNy;Yw2PXm7w`0ZoaAO45-eV;#`t@7mO!~BcE|Ha`?5#M)y zK0KZ4pDp>2wSM;fQ=sxxf05L0-ap?!{SP!_|8T(iZ*9)`ng{j#ef1Aid8)qx>i-Vv z?|%mSBgFU3|5ZmmBVc~vne0Ciu>V(8p8AjfR-b=!{kB2>3tF)MYry*ZoyGa%0qgIh z@>G8+)c+&Y|Bb^RbV%TlPY{iMUe=AZA#4+xllwGI2j1Lo(Q&H3>G^9|Z^J}Y4UymL4|Ct&_} zM}Bd@{PKAA*96S}*pBnX0rS(_bAFfPZ}mLCrl>sa-!j<02Vnp9KbQR;NA&)^&f{OD z^5n;Ur|)0$^Oth)k2?Hk#J696*snkTaQJ!Ro1ecN1V6U}_rFAZ-_MV{r1I2%q4?(W z=P>vc=doY)sGff(&-@RmJo)9&|55N)c4WV^_`c`Iit{<&Tk^iI|K}xeeu(4;c=~@$ z<*EPZt&y+)k3s+AE?_@j{DB@nUFFG-1OGVqH+N#c=AU}~*LeIJRG$1q@k5difBta- z{QeiRzgB$T`v+I4Jo!1$f0X)WXx&v*ROPw6x-m1B5;?zJ@;yEC&sBNqKfWaL z{6$0mS6{+@i@)^zdwcxeDo=hA_;tW<-Ie{};`_dTzJR5xvmE}Azk{v+qYgh={K|VaeE&BE|CGzP|19x+>mRN1 z)PG2PbNyq%KjHBI6yJCM{^juVVE)a(&%B)b?{{3!f3Ro$A5?kjzXaxg2KbNnWdBX^ zef>{WdGgDl{}$j^?Zy5H@l!qh|8oWVaoZyA|2XguJN$wF==uB3f1}=9f12cd&%c;H zoX?cJ@AYS0m8bb9LjP@`{}T>>v-rN(pMN?06!6=EU+pUHKmLTCzwh!j`cf5!FDdK-%`S#cE+o?SHDdOizKK%L9 zec<;Q&3?DKdj2VvZ_mG{%9CFqzPbPI2fx<6?9UM2_x@>Bl_x*`r^xjm2mTg^zgzsK zp8nUTbN$C8Z-4)Zy?&pmJk_5h^=BLFmjU%hjbZ=BdV2kCvFf+|zwcwe82pLgUvfYD zZ;S7{zdEZt^&i@$*U#KPlfa)lmi=m{=>C2F3o1{3Hu#gl|9c$!sp2PD^S95>QI#jZ zP<-?Gl?ncW@$Bb_@4LTpRi6A3@TY+P$OG(e7Tz@UFnZxhaK=<#vzZOj7`UgnfcYo!o zJk?(+^_%zClTiPA53;{ReBb@`w#t*AzFV)K=|2Vjx=HNUiqZ3HWu0I9{`^?w$&dOa z^7-=&_-{VMe!GDF>ncxvEcnlYpEa5NtHodG>HkrcCqF^_49SOIf6M~E$;0edKUL4) zcYnvIJo$Ozo6nEuz+aHberiBJSLMks0sjT?FPOsqV)2u#`Pg%!M|ua`&r`qo*x%yaekiUeb0|hDo^#t zOa12k{W{eD^yBRRCBE@XMZH|N6#y{e1V=&ni!Ta+zK~bN}Rl-)si^ zZv^z4s66=@;4cC{`6>2~i|@O?u26aM^Tan_|11W7yTk9+M9<%Me{FU6h2Sp*fBMth z|9J6z_tzAar~XUCH}9`}@Na*H{e-5vf8YHzMCHlP+!wk3mVv*=;b)5PyT5*N`1#_S z>;E438;J3?RzgT?V{nbY0$xjjAT>sC(|HR?PH`D$5?ynnP5$9kwgbq@P4itoF>u2*^TE5P3f{*ag0KP0~I{<>M^$r<7d`lJ7+_m6ph{Rs89Tgd)C z@qPDK8KmG@fa3ij)?3D*CEDo=hP_*KE5Rlxpm@qNF){b`jaKl(`I z{HudsvXcFL@f*mzDnHoguhA;b7Y58tYs~@s| zRQzd`jmt;->(_Htp8PbJe;x3nKVpB<*?Rxk-#=;l)l{DReDLdme_tW{zlm@E{F&{K zQhD+#z^@Pf;*Z%+Y^(df$TR;pRG$2#qk8|FpZ~>xU-wh??-75a$FHgKE&E;1(fv2KeEa;>`i%1fB|pfMud4D?e=*Fj3Dm#lbM}{s-^b&xRC)5F{)}9| z)4>0^i2c9Bf8X-${kucu$xjg9eEokq_{r<;l;ih@AhK;1_IUKQEyFuF8|25B^!;ul-jgUb8-Q;zf$GNPx?#GKPCMCsQ!fg*5G$9W`9g)?fZUyqs`Zx zpDOvv=cleB^4|}e;KQ%Z!+tyPA9DC(#qaO&GgO}Xk3OOMH~n+L-|g@}5&u1pztiEzi64@Dxc~FO zZ@7i$Kfa5ee|?WX)sden`3|1^FOGb^^8O6>e*yIW*tgt&)r)oi zKY4zCeWuEjUnG8ZH9df^eLoZrb;~ez?JrkLs@TiahC$eSWW1d8$9P>OKGE z%>CO1>VMGTw->+LNqXe{Gv48+i*LUEO9X$l!yh5O{ri{f`(uT}&lNu;^@rE5EBM2= z^7`!*zlGJm?GIIXntxQa$oY2z|0{?8Pe6aY!;b~O2l(|%xc}xo^#1cbzm7Tb9VPGk z{PBp&)BF;k{>!2MHQ#gnH;C_h|8k|ulbs+SS&)0u~%9Ec~UGHDB|32Wq_#^vw1oUUCJoy#iUj_bSJJ>G_=x3@t`B^n0`@b6e z2Y0gHJSo`x$E!T~u{9(8Yrx-E%KmuqS6TO${ruRi^5o}{z&nC_umOeev;%GAK)NejN zZ-n{_cXRz4#rM7bTBY*jr-^UA{<;bL=YCiL=HcR2Vt``M3A4t9QKsXX~v;+wDkM}Xh+clJk#?>j%;Ri6A@@YBF= zae)0r;_t8)+}aLNr>i{q1>&3MXC(O1|6@O^Z?N-IP36f?IVJM^i~|23hrdvK-}$Ni z2iO0(1*Wtc-HSBl_x);fu3KXoWJn>cOUrs9sZvF z!PalD!!H-#JU{n?f7>DMzxuV>_pM)*!<;``^1k&uu6&wbbc~*#xqjoJ{`yDQ-+!I1 z-}m~Zw#t)Vd}`$VmjV7Uhu>|0_J8x-zegN?R735T8~bM>_znN${^PF?*8eFgPyLsR zZ{Gitz(3;fGsO43e_QVG;~MGy^Nju{ga3C0_n&Zs?%((N?WoFA|3%`P=O+{V-~VEN ztoXj?PnpV-Ukd&d@Q)s2f0_8c*KdEQJoy#ko6n!A;Gc4w{T~ME`TJhKMXNmd*-iBN zoAaLr{`vo~e@uMe{grlt^UZJ4_51Fx+f|%g%u>LPsdFnqSHnRU` zz~A8Thlro+>3^NW&l5k}=>J*pZ?DSzpAg@7e+^N2>Ob-H$n!G`{68Ij&p~?rzWeK- z!%qSKIq>IK>t!H1W;*>jm&{tG}Eg?;R>ne(4#J^P2~Lt!Vc1 z#P{vrswz)@1^Bt(uX6Z%#P{vr-+=nxsK@^L5yAHFe3d6ZD=u<=Z-PJn z6!z=iseRx6eOcwn&jtT2@ZYP?en0VjuOHu5dGZUyH}~%n@V7Kzf81Tc_U~6JPku_P z$oanw{=YHo&lcaee=l#y`S&I7+rQmZp6XAN`px~j4C*g$#QvmF|Hs(9$7enNe;l7A zLo`Vy$|N-vCduTqk}Q-7nTjr?hRSlBggKNUne(JFau||HYAUQ8S|O9uq{DR3q%uk7 zkbbYL_v`w3e?Onc`}Y1^f4H{qw`cd)>-Bzr4%f9qW`4fczduW!{ET*He)j9XS>O+B z!TzV}o9{yT?49pM4FS#z-@jF`b)PGU?@bUW|_=g?D z{%rMq$M0{B{I{C-9lu*7PxY5-{r2&@0P630FxNlzdNaQ%{~bEJJ_;T`$4Z|3ymT`^ z`}M~M;P2at{Zno*{#1{jEP3)9!2byRbq;^I`dvK!YKLERRCxa^0{^>1xc{a%n*M{| zKNg(-SCXgxOTk|Z{=!4q&s4vyr~h{)PksgXpMd{TYxW1KKiT6yCwcO#)i2O|{Qd9G zz<;m}``gtY=JCf%p8V{N;p^Au;6HE}`-5*X>mU62jo|u^lRWta;C})BZE5VUQQ!CV z`@+LHzfJSLpI^OA@>GAB)^ERm^A*&;xGnoLZZ`At)&Ip2oUhcpul`RZPxTjeGV5>G z|1H$t_DJ@}++ynYoqtqmFmOS~z;I9V%f^_y<+-m0UJOB2RJo#nd{{VicquB4J zzVH0oUh?Eus&Aiv*Mi@;Bm2wL_x=8Zoen?qnDF^`J^06UV!!rwh+-oBFx-V#&vWWlriPdGb?_43E+13iaFh`gi1$?lAM4=*drzJgr|D ztlwWy|3-&DQGMV2%X)`jqrUz6@gMNlb>;bWzSH#YyMI|NdFnr}tJ%M{|1bC}PGEnD z`o8;@ZzWHDgZlRVN$M2-`Tv#O*#Ad;-~O$UJozannEq|Q5%?QUWIw&o%-?tavPSac zr-R=Z{7sqcpRK;{{$+#Y$1z&d`LfWP%5_J360_xf?OXUvw!Yg zX8ykWmxCowejWIGgMY{=>@QQ_cm3^gD(BZ}zFFXgCGcs|DUzr9Q@fe{Yrp@sFVtUt zn(>48FWc@m^GgeyKjW7Dcun%;*PdwnO8vv}^M5n&56$BKTij!O-~7((!TI)@_suU$ z@>G9mW_W(hq5e5Nd48GqMw{PE$&;VgJv_e_;CJf9{pYIhyZ*G7Jo)J-8{aOb%t;|H%lj~AKw`CdP#NS^$p)6M$Xub+Mzs!?fG?v`WyA*`BmH>ZGJoYvY*u>JilYWPd$(4_m%p->(2p_r~d2I zx351L;J@qeuYbVw@4Nm~IQ)X1rhnV-0)8xq`%ih$_`d7UAjwnz$!8efzW#Itf2+gq zslM;}v)SRNf!_`Mm(S<^N2%|-{yZ;v>OT|wOzm2kakiU&8ZS@o=>HT`PI=^ZJD6 zmkoaLK<>YBvGIM^p9dvRe(u@Ex6l7)ga6nd_McbZcm0_tdGhPPKNtMR2eY5`i0R*V z{drjOU*rR6seeb_5mOS~H=bH7muRjC8Z+Zp$AFJ=X{_H7v z^7FvY1;6=~?4SFX>ECz$>@k${muY@r;DQtQH0cz{Q~f1cf39`?83gsuy~_B(>rchw zrvBjZ6SwTg+ma{0zMomYWGy&;{katUjN#n>I`w_?yCRSC+coc--=&hL`WwzO_1p7{ zLH(<)=K1A38Et;wNuKsD1OP>7P3yg1He};m;(&4vy%JlEM z{?s`9I`D^qKX?T9U!lJ5`g5`5ssDltP5<`wClCD94u7Ni!Q)2{$NxWsmOK1n@UH zx#X$-60JYeI{)7U^$#9x{NVLx!Xz`l76CtQ*^i4QPk!|Pvwrsexdr_5uIK(s)%VSB zrz1a8^S=50C3&hp`(jhSJ-;zff5Qztzp>9mo8NDeC%-T^JioEv|8^tKuUP$`?ZuLh zgXh03k|)1Gef#=z2l%-+vp;sS>EGvHAbIkOE;0Svej)g8-ok#~lxY5R$&;TtFznwA z{?r2YFPs|9e_Hb7mw|sT`0d89pY?1s|1imupE)Sp|9J3Yx3S;3G@3t1^5j>6Uj+V( zW7$uAE}CB|dGd1yhx>m3{H}Mf-}w1x{;`rLzYhEf;OE@Qe!cpM`=`WdAb9+j6mmX! znyJ4}BB;;7>(677r}_&nHS@Et-w#9m|J=p?`Rd>4@&AxK`DNfg0{)15*soT9tH&QM zdGc%2x4-`M82F#x%l^U_%=~+J{3Vhnzwol~{GR~-#PP-tUjLI{G=6fx4}N~X=64Tl ztdxlPdnHfxmuvlr{TqM%S_1X2Q@=^TPgURd`y*F7{3`G#fuDXKuiw!QKY09%F5-Mo z&HH};cFp~q&(VCF!1@LK+y^;7O7o|A@)t;+)~^=!-?K2ki4)kLuKv*hKd{V6_e-Aq zlp$vS+xzD^@PBxS{pIR63JfghuaG?X>FU>O{qg-f4g7D5*x0-c}z z{@JTAzur%=Kex=xZ+QR=e45ll^5hp@Zr0Df|D6v0+!FR5f7STep8fN-y$2j~l z^;>%U4UYVKnm1Ma_m>3E|7#rer)m9(^LKpzehBryGnMDpX-2g3-z>?~{PJLaAA^7J zv+Va#-*^5Sh-y-#3% zr$2A};Onn~H_ZC^zW=ei^Weq4`^5n;^Hh!i4;rRN04gS%ua{n)=@0(wpBR^a7zT;=D^>ig!`OWt7`}(sH`~zpR-)FAr-?x9aJMx1yAN=>1g7f=T@>GBJO=f;E?LWSL zo1y+c=WzWM>id3w=r+ldU#7nO{=qNcZ>eDaAN9w1*6%0DlV78Lfz|)7;D7cG`*+QY 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 H@&5k@#YmBd literal 0 HcmV?d00001 diff --git a/source/common/database.cpp b/source/common/database.cpp new file mode 100644 index 0000000..9a88a5e --- /dev/null +++ b/source/common/database.cpp @@ -0,0 +1,567 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" + +#include +using namespace std; +#include +#include +#include +#include +//#include +#include +#include +#include +#include + +// Disgrace: for windows compile +#ifdef WIN32 +#include +#include +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include "unix.h" +#include +#endif + +#include "database.h" +#include "EQStream.h" +#include "packet_functions.h" +#include "emu_opcodes.h" +#ifdef WORLD + #include "../WorldServer/WorldDatabase.h" + extern WorldDatabase database; +#endif +#ifdef LOGIN + #include "../LoginServer/LoginDatabase.h" + extern LoginDatabase database; +#endif +#ifdef PARSER + #include "../PacketParser/ParserDatabase.h" + extern ParserDatabase database; +#endif + +#ifdef PATCHER + #include "../PatchServer/PatcherDatabase.h" + extern PatcherDatabase database; +#endif +#include "../common/EQEMuError.h" +#include "../common/packet_dump.h" +#include "../common/Log.h" + +#ifdef WORLD +ThreadReturnType DBAsyncQueries(void* str) +{ + // allow some buffer for multiple queries to collect + Sleep(10); + DBStruct* data = (DBStruct*)str; + database.RunAsyncQueries(data->queryid); + delete data; + THREAD_RETURN(NULL); +} +#endif + +Database::Database() +{ + InitVars(); +} + +bool Database::Init(bool silentLoad) { + char host[200], user[200], passwd[200], database[200]; + unsigned int port=0; + bool compression = false; + bool items[6] = {false, false, false, false, false, false}; + const char* exampleIni[] = { "[Database]", "host = localhost", "user = root", "password = pass", "database = dbname", "### --- Assure each parameter is on a new line!" }; + + if(!ReadDBINI(host, user, passwd, database, &port, &compression, items)) { + //exit(1); + return false; + } + + if (!items[0] || !items[1] || !items[2] || !items[3]) + { + LogWrite(DATABASE__ERROR, 0, "DB", "Database file %s is incomplete.", DB_INI_FILE); + int i; + for (i = 0; i < 4; i++) + { + if ( !items[i] ) + LogWrite(DATABASE__ERROR, 0, "DB", "Could not find parameter %s", exampleIni[i+1]); // offset by 1 because the [Database] entry + } + LogWrite(DATABASE__ERROR, 0, "DB", "Example File:"); + int length = sizeof exampleIni / sizeof exampleIni[0]; + for(i=0;i Database::GetVersions(){ + map opcodes; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select distinct version_range1, version_range2 from opcodes"); + while(result && (row = mysql_fetch_row(result))){ + if(row[0] && row[1]) + opcodes[atoi(row[0])] = atoi(row[1]); + } + return opcodes; +} + +map Database::GetOpcodes(int16 version){ + map opcodes; + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select name, opcode from opcodes where %i between version_range1 and version_range2 order by version_range1, id", version); + while(result && (row = mysql_fetch_row(result))){ + opcodes[row[0]] = atoi(row[1]); + } + return opcodes; +} + +int32 Database::AuthenticateWebUser(char* userName, char* passwd, int32* status){ + if(status) { + *status = 0; + } + Query query; + MYSQL_ROW row; + int32 id = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select id, status from web_users where username='%s' and passwd = sha2('%s', 512)", getSafeEscapeString(userName).c_str(), getSafeEscapeString(passwd).c_str()); + if(result && (row = mysql_fetch_row(result))){ + id = atoul(row[0]); + if(status) { + *status = atoul(row[1]); + } + } + return id; +} + +int32 Database::NoAuthRoute(char* route){ + Query query; + MYSQL_ROW row; + int32 status = 0xFFFFFFFF; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select status from web_routes where route='%s'", getSafeEscapeString(route).c_str()); + if(result && (row = mysql_fetch_row(result))){ + status = atoul(row[0]); + } + return status; +} + +void Database::HandleMysqlError(int32 errnum) { + switch(errnum) { + case 0: + break; + case 1045: // Access Denied + case 2001: { + AddEQEMuError(EQEMuError_Mysql_1405, true); + break; + } + case 2003: { // Unable to connect + AddEQEMuError(EQEMuError_Mysql_2003, true); + break; + } + case 2005: { // Unable to connect + AddEQEMuError(EQEMuError_Mysql_2005, true); + break; + } + case 2007: { // Unable to connect + AddEQEMuError(EQEMuError_Mysql_2007, true); + break; + } + } +} + +void Database::InitVars() { + +} + +Database::~Database() +{ +#ifdef WORLD + DBQueryMutex.writelock(__FUNCTION__, __LINE__); + activeQuerySessions.clear(); + DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); + + DBAsyncMutex.writelock(); + continueAsync = false; + map>::iterator itr; + for (itr = asyncQueries.begin(); itr != asyncQueries.end(); itr++) + { + asyncQueriesMutex[itr->first]->writelock(); + deque queries = itr->second; + while (queries.size() > 0) + { + Query* cur = queries.front(); + queries.pop_front(); + safe_delete(cur); + } + asyncQueriesMutex[itr->first]->releasewritelock(); + Mutex* mutex = asyncQueriesMutex[itr->first]; + asyncQueriesMutex.erase(itr->first); + safe_delete(mutex); + } + asyncQueries.clear(); + + asyncQueriesMutex.clear(); + DBAsyncMutex.releasewritelock(); + + PurgeDBInstances(); +#endif +} + +#ifdef WORLD +void Query::AddQueryAsync(int32 queryID, Database* db, QUERY_TYPE type, const char* format, ...) { + in_type = type; + va_list args; + va_start(args, format); +#ifdef WIN32 + char* buffer; + int buf_len = _vscprintf(format, args) + 1; + buffer = new char[buf_len]; + vsprintf(buffer, format, args); +#else + char* buffer; + int buf_len; + va_list argcopy; + va_copy(argcopy, args); + buf_len = vsnprintf(NULL, 0, format, argcopy) + 1; + va_end(argcopy); + + buffer = new char[buf_len]; + vsnprintf(buffer, buf_len, format, args); +#endif + va_end(args); + query = string(buffer); + + Query* asyncQuery = new Query(this, queryID); + + safe_delete_array(buffer); + + db->AddAsyncQuery(asyncQuery); +} + +void Query::RunQueryAsync(Database* db) { + db->RunQuery(query.c_str(), query.length(), errbuf, &result, affected_rows, last_insert_id, &errnum, retry); +} +#endif + +MYSQL_RES* Query::RunQuery2(QUERY_TYPE type, const char* format, ...){ + va_list args; + va_start( args, format ); + #ifdef WIN32 + char * buffer; + int buf_len = _vscprintf( format, args ) + 1; + buffer = new char[buf_len]; + vsprintf( buffer, format, args ); + #else + char* buffer; + int buf_len; + va_list argcopy; + va_copy(argcopy, args); + buf_len = vsnprintf(NULL, 0, format, argcopy) + 1; + va_end(argcopy); + + buffer = new char[buf_len]; + vsnprintf(buffer, buf_len, format, args); + #endif + va_end(args); + query = string(buffer); + + + safe_delete_array( buffer ); + + + return RunQuery2(query.c_str(), type); +} +MYSQL_RES* Query::RunQuery2(string in_query, QUERY_TYPE type){ + switch(type){ + case Q_SELECT: + break; + case Q_DBMS: + case Q_REPLACE: + case Q_DELETE: + case Q_UPDATE: + safe_delete(affected_rows); + affected_rows = new int32; + break; + case Q_INSERT: + safe_delete(last_insert_id); + last_insert_id = new int32; + } + if(result){ + if(!multiple_results) + multiple_results = new vector(); + multiple_results->push_back(result); + } + query = in_query; + +#if defined WORLD && defined _DEBUG + if (type == Q_UPDATE || type == Q_INSERT || type == Q_DELETE || type == Q_REPLACE) + { + char* filteredTables[] = { " characters", " character_", " `character_", " statistics", " variables", " char_colors", " `guild", " bugs" }; + + bool match = false; + for (int i = 0; i < sizeof(filteredTables) / sizeof(filteredTables[0]); i++) + { + if (query.find(filteredTables[i]) != std::string::npos) { + match = true; + break; + } + } + try + { + if (!match) + { + FILE* pFile; + pFile = fopen("sql_updates.sql", "a+"); + fwrite(query.c_str(), 1, query.length(), pFile); + fwrite(";", sizeof(char), 1, pFile); + fwrite("\n", sizeof(char), 1, pFile); + fclose(pFile); + } + } + catch (...) {} + } +#endif + + + database.RunQuery(query.c_str(), query.length(), errbuf, &result, affected_rows, last_insert_id, &errnum, retry); + return result; +} + +#ifdef WORLD +void Database::RunAsyncQueries(int32 queryid) +{ + Database* asyncdb = FindFreeInstance(); + DBAsyncMutex.writelock(); + map>::iterator itr = asyncQueries.find(queryid); + if (itr == asyncQueries.end()) + { + DBAsyncMutex.releasewritelock(); + return; + } + + asyncQueriesMutex[queryid]->writelock(); + deque queries; + while (itr->second.size()) + { + Query* cur = itr->second.front(); + queries.push_back(cur); + itr->second.pop_front(); + } + itr->second.clear(); + asyncQueries.erase(itr); + DBAsyncMutex.releasewritelock(); + asyncQueriesMutex[queryid]->releasewritelock(); + + int32 count = 0; + while (queries.size() > 0) + { + Query* cur = queries.front(); + cur->RunQueryAsync(asyncdb); + this->RemoveActiveQuery(cur); + queries.pop_front(); + safe_delete(cur); + } + FreeDBInstance(asyncdb); + + bool isActive = IsActiveQuery(queryid); + if (isActive) + { + continueAsync = true; + DBStruct* tmp = new DBStruct; + tmp->queryid = queryid; +#ifdef WIN32 + _beginthread(DBAsyncQueries, 0, (void*)tmp); +#else + pthread_t t1; + pthread_create(&t1, NULL, DBAsyncQueries, (void*)tmp); + pthread_detach(t1); +#endif + } +} + +void Database::AddAsyncQuery(Query* query) +{ + DBAsyncMutex.writelock(); + map::iterator mutexItr = asyncQueriesMutex.find(query->GetQueryID()); + if (mutexItr == asyncQueriesMutex.end()) + { + Mutex* queryMutex = new Mutex(); + queryMutex->SetName("AsyncQuery" + query->GetQueryID()); + asyncQueriesMutex.insert(make_pair(query->GetQueryID(), queryMutex)); + } + map>::iterator itr = asyncQueries.find(query->GetQueryID()); + asyncQueriesMutex[query->GetQueryID()]->writelock(); + + if ( itr != asyncQueries.end()) + itr->second.push_back(query); + else + { + deque queue; + queue.push_back(query); + asyncQueries.insert(make_pair(query->GetQueryID(), queue)); + } + + AddActiveQuery(query); + + asyncQueriesMutex[query->GetQueryID()]->releasewritelock(); + DBAsyncMutex.releasewritelock(); + + bool isActive = IsActiveQuery(query->GetQueryID(), query); + if (!isActive) + { + continueAsync = true; + DBStruct* tmp = new DBStruct; + tmp->queryid = query->GetQueryID(); +#ifdef WIN32 + _beginthread(DBAsyncQueries, 0, (void*)tmp); +#else + pthread_t t1; + pthread_create(&t1, NULL, DBAsyncQueries, (void*)tmp); + pthread_detach(t1); +#endif + } +} + +Database* Database::FindFreeInstance() +{ + Database* db_inst = 0; + map::iterator itr; + DBInstanceMutex.writelock(__FUNCTION__, __LINE__); + for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { + if (!itr->second) + { + db_inst = itr->first; + itr->second = true; + break; + } + } + + if (!db_inst) + { + WorldDatabase* tmp = new WorldDatabase(); + db_inst = (Database*)tmp; + tmp->Init(); + tmp->ConnectNewDatabase(); + dbInstances.insert(make_pair(db_inst, true)); + } + DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); + + return db_inst; +} + +void Database::PurgeDBInstances() +{ + map::iterator itr; + DBInstanceMutex.writelock(__FUNCTION__, __LINE__); + for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { + WorldDatabase* tmpInst = (WorldDatabase*)itr->first; + safe_delete(tmpInst); + } + dbInstances.clear(); + DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); +} + + +void Database::PingAsyncDatabase() +{ + map::iterator itr; + DBInstanceMutex.readlock(__FUNCTION__, __LINE__); + for (itr = dbInstances.begin(); itr != dbInstances.end(); itr++) { + Database* tmpInst = itr->first; + tmpInst->ping(); + } + DBInstanceMutex.releasereadlock(__FUNCTION__, __LINE__); +} + +void Database::FreeDBInstance(Database* cur) +{ + DBInstanceMutex.writelock(__FUNCTION__, __LINE__); + dbInstances[cur] = false; + DBInstanceMutex.releasewritelock(__FUNCTION__, __LINE__); +} + +void Database::RemoveActiveQuery(Query* query) +{ + DBQueryMutex.writelock(__FUNCTION__, __LINE__); + + vector::iterator itr; + for (itr = activeQuerySessions.begin(); itr != activeQuerySessions.end(); itr++) + { + Query* curQuery = *itr; + if (query == curQuery) + { + activeQuerySessions.erase(itr); + break; + } + } + DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); +} + +void Database::AddActiveQuery(Query* query) +{ + DBQueryMutex.writelock(__FUNCTION__, __LINE__); + activeQuerySessions.push_back(query); + DBQueryMutex.releasewritelock(__FUNCTION__, __LINE__); +} + +bool Database::IsActiveQuery(int32 id, Query* skip) +{ + bool isActive = false; + + DBQueryMutex.readlock(__FUNCTION__, __LINE__); + vector::iterator itr; + for (itr = activeQuerySessions.begin(); itr != activeQuerySessions.end(); itr++) + { + Query* query = *itr; + if (query == skip) + continue; + + if (query->GetQueryID() == id) + { + isActive = true; + break; + } + } + DBQueryMutex.releasereadlock(__FUNCTION__, __LINE__); + + return isActive; +} +#endif \ No newline at end of file diff --git a/source/common/database.h b/source/common/database.h new file mode 100644 index 0000000..4a5fbd2 --- /dev/null +++ b/source/common/database.h @@ -0,0 +1,183 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQ2EMU_DATABASE_H +#define EQ2EMU_DATABASE_H + +#ifdef WIN32 + #include + #include +#endif +#include + +#include "dbcore.h" +#include "types.h" +#include "linked_list.h" +#include "EQStream.h" +#include "MiscFunctions.h" +#include "Mutex.h" +#include +#include +#include + +using namespace std; +class Query; + +class Database : public DBcore +{ +public: + Database(); + ~Database(); + bool Init(bool silentLoad=false); + bool LoadVariables(); + void HandleMysqlError(int32 errnum); + map GetOpcodes(int16 version); + int32 AuthenticateWebUser(char* userName, char* passwd,int32* status = 0); + int32 NoAuthRoute(char* route); + map GetVersions(); + +#ifdef WORLD + void AddAsyncQuery(Query* query); + void RunAsyncQueries(int32 queryid); + Database* FindFreeInstance(); + void RemoveActiveQuery(Query* query); + void AddActiveQuery(Query* query); + bool IsActiveQuery(int32 id, Query* skip=0); + void PingAsyncDatabase(); +#endif +protected: + +private: + void InitVars(); + +#ifdef WORLD + void PurgeDBInstances(); + void FreeDBInstance(Database* cur); + bool continueAsync; + map> asyncQueries; + map asyncQueriesMutex; + map dbInstances; + vector activeQuerySessions; + Mutex DBAsyncMutex; + Mutex DBInstanceMutex; + Mutex DBQueryMutex; +#endif +}; + +typedef struct { + int32 queryid; +}DBStruct; + +class Query{ +public: + Query() { + result = 0; + affected_rows = 0; + last_insert_id = 0; + errnum = 0; + row = 0; + retry = true; + escaped_name = 0; + escaped_pass = 0; + escaped_data1 = 0; + multiple_results = 0; + memset(errbuf, 0, sizeof(errbuf)); + queryID = 0; + } + Query(Query* queryPtr, int32 in_id) { + result = 0; + affected_rows = 0; + last_insert_id = 0; + errnum = 0; + row = 0; + retry = true; + escaped_name = 0; + escaped_pass = 0; + escaped_data1 = 0; + multiple_results = 0; + memset(errbuf, 0, sizeof(errbuf)); + query = string(queryPtr->GetQuery()); + in_type = queryPtr->GetQueryType(); + queryID = in_id; + } + + ~Query(){ + if(result) + mysql_free_result(result); + result = 0; + safe_delete(affected_rows); + safe_delete(last_insert_id); + safe_delete_array(escaped_name); + safe_delete_array(escaped_pass); + safe_delete_array(escaped_data1); + if(multiple_results){ + vector::iterator itr; + for(itr = multiple_results->begin(); itr != multiple_results->end(); itr++){ + mysql_free_result(*itr); + } + safe_delete(multiple_results); + } + } + int32 GetLastInsertedID() { return *last_insert_id; } + int32 GetAffectedRows() { return *affected_rows; } + MYSQL_RES* GetResult() { return result; } + MYSQL_RES* RunQuery2(string in_query, QUERY_TYPE type); + char* GetError() { return errbuf; } + int32 GetErrorNumber(){ return errnum; } + const char* GetQuery() { return query.c_str(); } + char* GetField(int8 field_num) { + if(!row && result) + *row = mysql_fetch_row(result); + if(row && result && field_num < mysql_num_fields(result)) + return *row[field_num]; + else + return NULL; + } + void NextRow(){ + if(result) + *row = mysql_fetch_row(result); + } + void AddQueryAsync(int32 queryID, Database* db, QUERY_TYPE type, const char* format, ...); + void RunQueryAsync(Database* db); + MYSQL_RES* RunQuery2(QUERY_TYPE type, const char* format, ...); + + QUERY_TYPE GetQueryType() { + return in_type; + } + + int32 GetQueryID() { return queryID; } + + char* escaped_name; + char* escaped_pass; + char* escaped_data1; +private: + string query; + char errbuf[MYSQL_ERRMSG_SIZE]; + MYSQL_RES *result; + vector* multiple_results; + int32* affected_rows; + int32* last_insert_id; + int32 errnum; + QUERY_TYPE in_type; + bool retry; + MYSQL_ROW* row; + MYSQL mysql; + int32 queryID; +}; +#endif diff --git a/source/common/dbcore.cpp b/source/common/dbcore.cpp new file mode 100644 index 0000000..e200e9d --- /dev/null +++ b/source/common/dbcore.cpp @@ -0,0 +1,368 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "debug.h" + +#include +using namespace std; +#include +//#include +#include +#include "dbcore.h" +#include +#include +#include +#include "types.h" +#include "MiscFunctions.h" +#include "Log.h" + +#ifdef WIN32 + #define snprintf _snprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include "unix.h" + #include +#endif + +#ifdef _EQDEBUG + #define DEBUG_MYSQL_QUERIES 0 +#else + #define DEBUG_MYSQL_QUERIES 0 +#endif + +DBcore::DBcore() { + mysql_init(&mysql); + pHost = 0; + pPort = 0; + pUser = 0; + pPassword = 0; + pDatabase = 0; + pCompress = false; +pSSL = false; +pStatus = Closed; +} + +DBcore::~DBcore() { + pStatus = Closed; + mysql_close(&mysql); +#if MYSQL_VERSION_ID >= 50003 + mysql_library_end(); +#else + mysql_server_end(); +#endif + safe_delete_array(pHost); + safe_delete_array(pUser); + safe_delete_array(pPassword); + safe_delete_array(pDatabase); +} + + +bool DBcore::ReadDBINI(char* host, char* user, char* passwd, char* database, unsigned int* port, bool* compress, bool* items) { + char line[256], * key, * val; + bool on_database_section = false; + FILE* f; + + if ((f = fopen(DB_INI_FILE, "r")) == NULL) { + LogWrite(DATABASE__ERROR, 0, "DBCore", "Unable to open '%s' for reading", DB_INI_FILE); + return false; + } + + //read each line + while (fgets(line, sizeof(line), f) != NULL) { + + //remove any new line or carriage return + while ((key = strstr(line, "\n")) != NULL) + *key = '\0'; + while ((key = strstr(line, "\r")) != NULL) + *key = '\0'; + + //ignore blank lines and commented lines + if (strlen(line) == 0 || line[0] == '#') + continue; + + key = strtok(line, "="); + + if (key == NULL) + continue; + + //don't do anything until we find the [Database] section + if (!on_database_section && strncasecmp(key, "[Database]", 10) == 0) + on_database_section = true; + else { + val = strtok(NULL, "="); + + if (val == NULL) + { + if (strcasecmp(key, "password") == 0) { + strcpy(passwd, ""); + items[2] = true; + } + continue; + } + if (strcasecmp(key, "host") == 0) { + strcpy(host, val); + items[0] = true; + } + else if (strcasecmp(key, "user") == 0) { + strcpy(user, val); + items[1] = true; + } + else if (strcasecmp(key, "password") == 0) { + strcpy(passwd, val); + items[2] = true; + } + else if (strcasecmp(key, "database") == 0) { + strcpy(database, val); + items[3] = true; + } + else if (strcasecmp(key, "port") == 0 && port) { + *port = atoul(val); + items[4] = true; + } + else if (strcasecmp(key, "compression") == 0) { + if (strcasecmp(val, "on") == 0) { + if(compress) { + *compress = true; + items[5] = true; + LogWrite(DATABASE__INFO, 0, "DBCore", "DB Compression on."); + } + } + } + } + } + + fclose(f); + + if (!on_database_section) { + LogWrite(DATABASE__ERROR, 0, "DBCore", "[Database] section not found in '%s'", DB_INI_FILE); + return false; + } + + return true; +} + + +// Sends the MySQL server a keepalive +void DBcore::ping() { + if (!MDatabase.trylock()) { + // well, if's it's locked, someone's using it. If someone's using it, it doesnt need a keepalive + return; + } + mysql_ping(&mysql); + + int32* errnum = new int32; + *errnum = mysql_errno(&mysql); + + switch (*errnum) + { + case CR_COMMANDS_OUT_OF_SYNC: + case CR_SERVER_GONE_ERROR: + case CR_UNKNOWN_ERROR: + { + LogWrite(DATABASE__ERROR, 0, "DBCore", "[Database] We lost connection to the database., errno: %i", errno); + break; + } + } + + safe_delete(errnum); + MDatabase.unlock(); +} + +bool DBcore::RunQuery(const char* query, int32 querylen, char* errbuf, MYSQL_RES** result, int32* affected_rows, int32* last_insert_id, int32* errnum, bool retry) { + if (errnum) + *errnum = 0; + if (errbuf) + errbuf[0] = 0; + bool ret = false; + LockMutex lock(&MDatabase); + if (pStatus != Connected) + Open(); + + LogWrite(DATABASE__QUERY, 0, "DBCore", query); + if (mysql_real_query(&mysql, query, querylen)) { + if (mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) + pStatus = Error; + if (mysql_errno(&mysql) == CR_SERVER_LOST || mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) { + if (retry) { + LogWrite(DATABASE__ERROR, 0, "DBCore", "Lost connection, attempting to recover..."); + ret = RunQuery(query, querylen, errbuf, result, affected_rows, last_insert_id, errnum, false); + } + else { + pStatus = Error; + if (errnum) + *errnum = mysql_errno(&mysql); + if (errbuf) + snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql)); + LogWrite(DATABASE__ERROR, 0, "DBCore", "#%i: %s\nQuery:\n%s", mysql_errno(&mysql), mysql_error(&mysql), query); + ret = false; + } + } + else { + if (errnum) + *errnum = mysql_errno(&mysql); + if (errbuf) + snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql)); + LogWrite(DATABASE__ERROR, 0, "DBCore", "#%i: %s\nQuery:\n%s", mysql_errno(&mysql), mysql_error(&mysql), query); + ret = false; + } + } + else { + if (result && mysql_field_count(&mysql)) { + *result = mysql_store_result(&mysql); + } + else if (result) + *result = 0; + if (affected_rows) + *affected_rows = mysql_affected_rows(&mysql); + if (last_insert_id) + *last_insert_id = mysql_insert_id(&mysql); + if (result) { + if (*result) { + ret = true; + } + else { + if (errnum) + *errnum = UINT_MAX; + if (errbuf){ + if((!affected_rows || (affected_rows && *affected_rows == 0)) && (!last_insert_id || (last_insert_id && *last_insert_id == 0))) + LogWrite(DATABASE__RESULT, 1, "DBCore", "No Result."); + } + ret = false; + } + } + else { + ret = true; + } + } + + if (ret) + { + char tmp1[200] = {0}; + char tmp2[200] = {0}; + if (result && (*result)) + snprintf(tmp1, sizeof(tmp1), ", %i rows returned", (int) mysql_num_rows(*result)); + if (affected_rows) + snprintf(tmp2, sizeof(tmp2), ", %i rows affected", (*affected_rows)); + + LogWrite(DATABASE__DEBUG, 0, "DBCore", "Query Successful%s%s", tmp1, tmp2); + } + else + LogWrite(DATABASE__DEBUG, 0, "DBCore", "Query returned no results in %s!\n%s", __FUNCTION__, query); + + return ret; +} + +int32 DBcore::DoEscapeString(char* tobuf, const char* frombuf, int32 fromlen) { + LockMutex lock(&MDatabase); + return mysql_real_escape_string(&mysql, tobuf, frombuf, fromlen); +} + +bool DBcore::Open(const char* iHost, const char* iUser, const char* iPassword, const char* iDatabase,int32 iPort, int32* errnum, char* errbuf, bool iCompress, bool iSSL) { + LockMutex lock(&MDatabase); + safe_delete_array(pHost); + safe_delete_array(pUser); + safe_delete_array(pPassword); + safe_delete_array(pDatabase); + pHost = new char[strlen(iHost) + 1]; + strcpy(pHost, iHost); + pUser = new char[strlen(iUser) + 1]; + strcpy(pUser, iUser); + pPassword = new char[strlen(iPassword) + 1]; + strcpy(pPassword, iPassword); + pDatabase = new char[strlen(iDatabase) + 1]; + strcpy(pDatabase, iDatabase); + pCompress = iCompress; + pPort = iPort; + pSSL = iSSL; + return Open(errnum, errbuf); +} + +bool DBcore::Open(int32* errnum, char* errbuf) { + if (errbuf) + errbuf[0] = 0; + LockMutex lock(&MDatabase); + if (GetStatus() == Connected) + return true; + if (GetStatus() == Error) + mysql_close(&mysql); + if (!pHost) + return false; + /* + Quagmire - added CLIENT_FOUND_ROWS flag to the connect + otherwise DB update calls would say 0 rows affected when the value already equalled + what the function was tring to set it to, therefore the function would think it failed + */ + int32 flags = CLIENT_FOUND_ROWS; + if (pCompress) + flags |= CLIENT_COMPRESS; + if (pSSL) + flags |= CLIENT_SSL; + if (mysql_real_connect(&mysql, pHost, pUser, pPassword, pDatabase, pPort, 0, flags)) { + pStatus = Connected; + return true; + } + else { + if (errnum) + *errnum = mysql_errno(&mysql); + if (errbuf) + snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql)); + pStatus = Error; + return false; + } +} + +char* DBcore::getEscapeString(const char* from_string){ + if(!from_string) + from_string =""; + int orig_size = strlen(from_string); + int escape_size = (orig_size * 2) + 1; + char* escaped = new char[escape_size]; + memset(escaped, 0, escape_size); + DoEscapeString(escaped, from_string, orig_size); + return escaped; +} + +string DBcore::getSafeEscapeString(const char* from_string){ + if(!from_string) + from_string =""; + int orig_size = strlen(from_string); + int escape_size = (orig_size * 2) + 1; + char* escaped = new char[escape_size]; + memset(escaped, 0, escape_size); + DoEscapeString(escaped, from_string, orig_size); + string ret = string(escaped); + safe_delete_array(escaped); + return ret; +} + +string DBcore::getSafeEscapeString(string* from_string){ + if(!from_string) + return ""; + int orig_size = from_string->length(); + int escape_size = (orig_size * 2) + 1; + char* escaped = new char[escape_size]; + memset(escaped, 0, escape_size); + DoEscapeString(escaped, from_string->c_str(), orig_size); + string ret = string(escaped); + safe_delete_array(escaped); + return ret; +} + diff --git a/source/common/dbcore.h b/source/common/dbcore.h new file mode 100644 index 0000000..b6cbfd2 --- /dev/null +++ b/source/common/dbcore.h @@ -0,0 +1,80 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef DBCORE_H +#define DBCORE_H + +#ifdef WIN32 + #include + #include + //#include +#endif +#include +#include "../common/types.h" +#include "../common/Mutex.h" +#include "../common/linked_list.h" +#include "../common/queue.h" +#include "../common/timer.h" +#include "../common/Condition.h" +#ifdef LOGIN + #define DB_INI_FILE "login_db.ini" +#endif +#ifdef WORLD + #define DB_INI_FILE "world_db.ini" +#endif +#ifdef PARSER + #define DB_INI_FILE "parser_db.ini" +#endif +#ifdef PATCHER + #define DB_INI_FILE "patcher_db.ini" +#endif +class DBcore{ +public: + enum eStatus { Closed, Connected, Error }; + DBcore(); + ~DBcore(); + eStatus GetStatus() { return pStatus; } + bool RunQuery(const char* query, int32 querylen, char* errbuf = 0, MYSQL_RES** result = 0, int32* affected_rows = 0, int32* last_insert_id = 0, int32* errnum = 0, bool retry = true); + int32 DoEscapeString(char* tobuf, const char* frombuf, int32 fromlen); + void ping(); + char* getEscapeString(const char* from_string); + string getSafeEscapeString(const char* from_string); + string getSafeEscapeString(string* from_string); + +protected: + bool Open(const char* iHost, const char* iUser, const char* iPassword, const char* iDatabase, int32 iPort, int32* errnum = 0, char* errbuf = 0, bool iCompress = false, bool iSSL = false); + bool ReadDBINI(char *host, char *user, char *pass, char *db, unsigned int* port, bool* compress, bool *items); +private: + bool Open(int32* errnum = 0, char* errbuf = 0); + + MYSQL mysql; + Mutex MDatabase; + eStatus pStatus; + + char* pHost; + char* pUser; + char* pPassword; + char* pDatabase; + bool pCompress; + int32 pPort; + bool pSSL; +}; +#endif + + diff --git a/source/common/debug.cpp b/source/common/debug.cpp new file mode 100644 index 0000000..3f2f98b --- /dev/null +++ b/source/common/debug.cpp @@ -0,0 +1,336 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +/* + JA: File rendered obsolete (2011-08-12) + +#include "debug.h" + +#include +using namespace std; +#include +#include +#ifdef WIN32 + #include + + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp +#else + #include + #include + #include +#endif +#include "../common/MiscFunctions.h" + +EQEMuLog* LogFile = new EQEMuLog; +AutoDelete adlf(&LogFile); + +static const char* FileNames[EQEMuLog::MaxLogID] = { "logs/eq2emu", "logs/eq2emu", "logs/eq2emu_error", "logs/eq2emu_debug", "logs/eq2emu_quest", "logs/eq2emu_commands" }; +static const char* LogNames[EQEMuLog::MaxLogID] = { "Status", "Normal", "Error", "Debug", "Quest", "Command" }; + +EQEMuLog::EQEMuLog() { + for (int i=0; i= 2 + pLogStatus[i] = 1 | 2; +#else + pLogStatus[i] = 0; +#endif + logCallbackFmt[i] = NULL; + logCallbackBuf[i] = NULL; + } +#if EQDEBUG < 2 + pLogStatus[Status] = 3; + pLogStatus[Error] = 3; + pLogStatus[Debug] = 3; + pLogStatus[Quest] = 2; + pLogStatus[Commands] = 2; +#endif +} + +EQEMuLog::~EQEMuLog() { + for (int i=0; i= MaxLogID) { + return false; + } + LockMutex lock(&MOpen); + if (pLogStatus[id] & 4) { + return false; + } + if (fp[id]) { + return true; + } + + char exename[200] = ""; +#if defined(WORLD) + snprintf(exename, sizeof(exename), "_world"); +#elif defined(ZONE) + snprintf(exename, sizeof(exename), "_zone"); +#endif + char filename[200]; +#ifndef NO_PIDLOG + snprintf(filename, sizeof(filename), "%s%s_%04i.log", FileNames[id], exename, getpid()); +#else + snprintf(filename, sizeof(filename), "%s%s.log", FileNames[id], exename); +#endif + fp[id] = fopen(filename, "a"); + if (!fp[id]) { + cerr << "Failed to open log file: " << filename << endl; + pLogStatus[id] |= 4; // set file state to error + return false; + } + fputs("---------------------------------------------\n",fp[id]); + return true; +} + +bool EQEMuLog::write(LogIDs id, const char *fmt, ...) { + char buffer[4096]; + + if (!this) { + return false; + } + if (id >= MaxLogID) { + return false; + } + bool dofile = false; + if (pLogStatus[id] & 1) { + dofile = open(id); + } + if (!(dofile || pLogStatus[id] & 2)) + return false; + LockMutex lock(&MLog[id]); + + time_t aclock; + struct tm *newtime; + + time( &aclock ); //Get time in seconds + newtime = localtime( &aclock ); //Convert time to struct + + if (dofile){ +#ifndef NO_PIDLOG + fprintf(fp[id], "[%04d%02d%02d %02d:%02d:%02d] ", newtime->tm_year+1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec); +#else + fprintf(fp[id], "%04i [%04d%02d%02d %02d:%02d:%02d] ", getpid(), newtime->tm_year+1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec); +#endif + } + + va_list argptr; + va_start(argptr, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, argptr); + va_end(argptr); + if (dofile) + fprintf(fp[id], "%s\n", buffer); + if(logCallbackFmt[id]) { + msgCallbackFmt p = logCallbackFmt[id]; + p(id, fmt, argptr ); + } + + if (pLogStatus[id] & 2) { + if (pLogStatus[id] & 8) { + fprintf(stderr, "[%04d%02d%02d %02d:%02d:%02d] [%s] ", newtime->tm_year+1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec, LogNames[id]); + fprintf(stderr, "%s\n", buffer); + } + else { + fprintf(stdout, "[%04d%02d%02d %02d:%02d:%02d] [%s] ", newtime->tm_year+1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec, LogNames[id]); + fprintf(stdout, "%s\n", buffer); + } + } + if (dofile) + fprintf(fp[id], "\n"); + if (pLogStatus[id] & 2) { + if (pLogStatus[id] & 8) + fprintf(stderr, "\n"); + else + fprintf(stdout, "\n"); + } + if(dofile) + fflush(fp[id]); + return true; +} + +bool EQEMuLog::writebuf(LogIDs id, const char *buf, int8 size, int32 count) { + if (!this) { + return false; + } + if (id >= MaxLogID) { + return false; + } + bool dofile = false; + if (pLogStatus[id] & 1) { + dofile = open(id); + } + if (!(dofile || pLogStatus[id] & 2)) + return false; + LockMutex lock(&MLog[id]); + + time_t aclock; + struct tm *newtime; + + time( &aclock ); // Get time in seconds + newtime = localtime( &aclock ); // Convert time to struct + + if (dofile){ +#ifndef NO_PIDLOG + fprintf(fp[id], "[%02d.%02d. - %02d:%02d:%02d] ", newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec); +#else + fprintf(fp[id], "%04i [%02d.%02d. - %02d:%02d:%02d] ", getpid(), newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec); +#endif + } + + if (dofile) { + fwrite(buf, size, count, fp[id]); + fprintf(fp[id], "\n"); + } + if(logCallbackBuf[id]) { + msgCallbackBuf p = logCallbackBuf[id]; + p(id, buf, size, count); + } + if (pLogStatus[id] & 2) { + if (pLogStatus[id] & 8) { + fprintf(stderr, "[%s] ", LogNames[id]); + fwrite(buf, size, count, stderr); + fprintf(stderr, "\n"); + } else { + fprintf(stdout, "[%s] ", LogNames[id]); + fwrite(buf, size, count, stdout); + fprintf(stdout, "\n"); + } + } + if(dofile) + fflush(fp[id]); + return true; +} + +bool EQEMuLog::writeNTS(LogIDs id, bool dofile, const char *fmt, ...) { + char buffer[4096]; + va_list argptr; + va_start(argptr, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, argptr); + va_end(argptr); + if (dofile) + fprintf(fp[id], "%s\n", buffer); + if (pLogStatus[id] & 2) { + if (pLogStatus[id] & 8) + fprintf(stderr, "%s\n", buffer); + else + fprintf(stdout, "%s\n", buffer); + } + return true; +}; + +bool EQEMuLog::Dump(LogIDs id, int8* data, int32 size, int32 cols, int32 skip) { + if (!this) { +#if EQDEBUG >= 10 + cerr << "Error: Dump() from null pointer"<= MaxLogID) + return false; + bool dofile = false; + if (pLogStatus[id] & 1) { + dofile = open(id); + } + if (!(dofile || pLogStatus[id] & 2)) + return false; + LockMutex lock(&MLog[id]); + write(id, "Dumping Packet: %i", size); + // Output as HEX + int j = 0; char* ascii = new char[cols+1]; memset(ascii, 0, cols+1); + int32 i; + for(i=skip; i= 32 && data[i] < 127) + ascii[j++] = data[i]; + else + ascii[j++] = '.'; + } + int32 k = ((i-skip)-1)%cols; + if (k < 8) + writeNTS(id, dofile, " "); + for (int32 h = k+1; h < cols; h++) { + writeNTS(id, dofile, " "); + } + writeNTS(id, dofile, " | %s\n", ascii); + if (dofile) + fflush(fp[id]); + safe_delete_array(ascii); + return true; +} + +void EQEMuLog::SetCallback(LogIDs id, msgCallbackFmt proc) { + if (!this) + return; + if (id >= MaxLogID) { + return; + } + logCallbackFmt[id] = proc; +} + +void EQEMuLog::SetCallback(LogIDs id, msgCallbackBuf proc) { + if (!this) + return; + if (id >= MaxLogID) { + return; + } + logCallbackBuf[id] = proc; +} + +void EQEMuLog::SetAllCallbacks(msgCallbackFmt proc) { + if (!this) + return; + int r; + for(r = Status; r < MaxLogID; r++) { + SetCallback((LogIDs)r, proc); + } +} + +void EQEMuLog::SetAllCallbacks(msgCallbackBuf proc) { + if (!this) + return; + int r; + for(r = Status; r < MaxLogID; r++) { + SetCallback((LogIDs)r, proc); + } +} +*/ \ No newline at end of file diff --git a/source/common/debug.h b/source/common/debug.h new file mode 100644 index 0000000..7422d5e --- /dev/null +++ b/source/common/debug.h @@ -0,0 +1,143 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQDEBUG_H +#define EQDEBUG_H + +// Debug Levels +/* + 1 = Normal + 3 = Some extended debug info + 5 = Light DETAIL info + 7 = Heavy DETAIL info + 9 = DumpPacket/PrintPacket + You should use even numbers too, to define any subset of the above basic template +*/ +#ifndef EQDEBUG + #define EQDEBUG 1 +#endif + + +#if defined(DEBUG) && defined(WIN32) + //#ifndef _CRTDBG_MAP_ALLOC + #include + #include + #if (_MSC_VER < 1300) + #include + #include + #define _CRTDBG_MAP_ALLOC + #define new new(_NORMAL_BLOCK, __FILE__, __LINE__) + #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) + #endif + //#endif +#endif + +#ifndef ThrowError + void CatchSignal(int); + #if defined(CATCH_CRASH) || defined(_EQDEBUG) + #define ThrowError(errstr) { cout << "Fatal error: " << errstr << " (" << __FILE__ << ", line " << __LINE__ << ")" << endl; LogWrite(WORLD__ERROR, 0, "Debug", "Thrown Error: %s (%s:%i)", errstr, __FILE__, __LINE__); throw errstr; } + #else + #define ThrowError(errstr) { cout << "Fatal error: " << errstr << " (" << __FILE__ << ", line " << __LINE__ << ")" << endl; LogWrite(WORLD__ERROR, 0, "Debug", "Thrown Error: %s (%s:%i)", errstr, __FILE__, __LINE__); CatchSignal(0); } + #endif +#endif + +#ifdef WIN32 + // VS6 doesn't like the length of STL generated names: disabling + #pragma warning(disable:4786) +#endif + +#ifndef WIN32 + #define DebugBreak() if(0) {} +#endif + +#ifdef WIN32 + #include + #include +#endif + +#include "../common/Mutex.h" +#include +#include + + +class EQEMuLog { +public: + EQEMuLog(); + ~EQEMuLog(); + + enum LogIDs { + Status = 0, //this must stay the first entry in this list + Normal, + Error, + Debug, + Quest, + Commands, + MaxLogID + }; + + //these are callbacks called for each + typedef void (* msgCallbackBuf)(LogIDs id, const char *buf, int8 size, int32 count); + typedef void (* msgCallbackFmt)(LogIDs id, const char *fmt, va_list ap); + + void SetAllCallbacks(msgCallbackFmt proc); + void SetAllCallbacks(msgCallbackBuf proc); + void SetCallback(LogIDs id, msgCallbackFmt proc); + void SetCallback(LogIDs id, msgCallbackBuf proc); + + bool writebuf(LogIDs id, const char *buf, int8 size, int32 count); + bool write(LogIDs id, const char *fmt, ...); + bool Dump(LogIDs id, int8* data, int32 size, int32 cols=16, int32 skip=0); +private: + bool open(LogIDs id); + bool writeNTS(LogIDs id, bool dofile, const char *fmt, ...); // no error checking, assumes is open, no locking, no timestamp, no newline + + Mutex MOpen; + Mutex MLog[MaxLogID]; + FILE* fp[MaxLogID]; +/* LogStatus: bitwise variable + 1 = output to file + 2 = output to stdout + 4 = fopen error, dont retry + 8 = use stderr instead (2 must be set) +*/ + int8 pLogStatus[MaxLogID]; + + msgCallbackFmt logCallbackFmt[MaxLogID]; + msgCallbackBuf logCallbackBuf[MaxLogID]; +}; + +//extern EQEMuLog* LogFile; + +#ifdef _EQDEBUG +class PerformanceMonitor { +public: + PerformanceMonitor(sint64* ip) { + p = ip; + QueryPerformanceCounter(&tmp); + } + ~PerformanceMonitor() { + LARGE_INTEGER tmp2; + QueryPerformanceCounter(&tmp2); + *p += tmp2.QuadPart - tmp.QuadPart; + } + LARGE_INTEGER tmp; + sint64* p; +}; +#endif +#endif diff --git a/source/common/emu_opcodes.cpp b/source/common/emu_opcodes.cpp new file mode 100644 index 0000000..a07135e --- /dev/null +++ b/source/common/emu_opcodes.cpp @@ -0,0 +1,39 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "emu_opcodes.h" + +const char *OpcodeNames[_maxEmuOpcode+1] = { + "OP_Unknown", + +//a preprocessor hack so we dont have to maintain two lists +#define N(x) #x +#if !defined(LOGIN) + #include "emu_oplist.h" +#endif +#ifdef LOGIN + #include "login_oplist.h" +#endif +#undef N + + "" +}; + + diff --git a/source/common/emu_opcodes.h b/source/common/emu_opcodes.h new file mode 100644 index 0000000..9011de1 --- /dev/null +++ b/source/common/emu_opcodes.h @@ -0,0 +1,56 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EMU_OPCODES_H +#define EMU_OPCODES_H + +//this is the highest opcode possibly used in the regular EQ protocol +#define MAX_EQ_OPCODE 0xFFFF +/* + + +the list of opcodes is in emu_oplist.h + +we somewhat rely on the fact that we have more than 255 opcodes, +so we know the enum type for the opcode defines must be at least +16 bits, so we can use the protocol flags on them. + +*/ + +typedef enum { //EQEmu internal opcodes list + OP_Unknown=0, + +//a preprocessor hack so we dont have to maintain two lists +#define N(x) x +#if !defined(LOGIN) + #include "emu_oplist.h" +#endif +#ifdef LOGIN + #include "login_oplist.h" +#endif +#undef N + + _maxEmuOpcode +} EmuOpcode; + +extern const char *OpcodeNames[_maxEmuOpcode+1]; + +#endif + + diff --git a/source/common/emu_oplist.h b/source/common/emu_oplist.h new file mode 100644 index 0000000..1784af6 --- /dev/null +++ b/source/common/emu_oplist.h @@ -0,0 +1,505 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +N(OP_LoginReplyMsg), +N(OP_LoginByNumRequestMsg), +N(OP_WSLoginRequestMsg), +N(OP_ESInitMsg), +N(OP_ESReadyForClientsMsg), +N(OP_CreateZoneInstanceMsg), +N(OP_ZoneInstanceCreateReplyMsg), +N(OP_ZoneInstanceDestroyedMsg), +N(OP_ExpectClientAsCharacterRequest), +N(OP_ExpectClientAsCharacterReplyMs), +N(OP_ZoneInfoMsg), +N(OP_CreateCharacterRequestMsg), +N(OP_DoneLoadingZoneResourcesMsg), +N(OP_DoneSendingInitialEntitiesMsg), +N(OP_DoneLoadingEntityResourcesMsg), +N(OP_DoneLoadingUIResourcesMsg), +N(OP_PredictionUpdateMsg), +N(OP_RemoteCmdMsg), +N(OP_SetRemoteCmdsMsg), +N(OP_GameWorldTimeMsg), +N(OP_MOTDMsg), +N(OP_ZoneMOTDMsg), +N(OP_GuildRecruitingMemberInfo), +N(OP_GuildRecruiting), +N(OP_GuildRecruitingDetails), +N(OP_GuildRecruitingImage), +N(OP_AvatarCreatedMsg), +N(OP_AvatarDestroyedMsg), +N(OP_RequestCampMsg), +N(OP_MapRequest), +N(OP_CampStartedMsg), +N(OP_CampAbortedMsg), +N(OP_WhoQueryRequestMsg), +N(OP_WhoQueryReplyMsg), +N(OP_MonitorReplyMsg), +N(OP_MonitorCharacterListMsg), +N(OP_MonitorCharacterListRequestMsg), +N(OP_ClientCmdMsg), +N(OP_Lottery), +N(OP_DispatchClientCmdMsg), +N(OP_DispatchESMsg), +N(OP_UpdateTargetMsg), +N(OP_UpdateOpportunityMsg), +N(OP_UpdateTargetLocMsg), +N(OP_UpdateCharacterSheetMsg), +N(OP_UpdateSpellBookMsg), +N(OP_UpdateInventoryMsg), +N(OP_UpdateRecipeBookMsg), +N(OP_RequestRecipeDetailsMsg), +N(OP_RecipeDetailsMsg), +N(OP_UpdateSkillBookMsg), +N(OP_UpdateSkillsMsg), +N(OP_ChangeZoneMsg), +N(OP_ClientTeleportRequestMsg), +N(OP_TeleportWithinZoneMsg), +N(OP_TeleportWithinZoneNoReloadMsg), +N(OP_MigrateClientToZoneRequestMsg), +N(OP_MigrateClientToZoneReplyMsg), +N(OP_ReadyToZoneMsg), +//N(OP_AddClientToGroupMsg), +//N(OP_AddGroupToGroupMsg), +N(OP_RemoveClientFromGroupMsg), +N(OP_RemoveGroupFromGroupMsg), +N(OP_MakeGroupLeaderMsg), +N(OP_GroupCreatedMsg), +N(OP_GroupDestroyedMsg), +N(OP_GroupMemberAddedMsg), +N(OP_GroupMemberRemovedMsg), +N(OP_GroupRemovedFromGroupMsg), +N(OP_GroupLeaderChangedMsg), +N(OP_GroupSettingsChangedMsg), +N(OP_SendLatestRequestMsg), +N(OP_ClearDataMsg), +N(OP_SetSocialMsg), +N(OP_ESStatusMsg), +N(OP_ESZoneInstanceStatusMsg), +N(OP_ZonesStatusRequestMsg), +N(OP_ZonesStatusMsg), +N(OP_ESWeatherRequestMsg), +N(OP_ESWeatherRequestEndMsg), +//N(OP_WSWeatherUpdateMsg), +N(OP_DialogSelectMsg), +N(OP_DialogCloseMsg), +N(OP_RemoveSpellEffectMsg), +N(OP_RemoveConcentrationMsg), +N(OP_QuestJournalOpenMsg), +N(OP_QuestJournalInspectMsg), +//N(OP_SkillSlotMapping), +N(OP_QuestJournalSetVisibleMsg), +N(OP_QuestJournalWaypointMsg), +N(OP_CreateGuildRequestMsg), +N(OP_CreateGuildReplyMsg), +N(OP_GuildsayMsg), +//N(OP_GuildKickMsg), +N(OP_GuildUpdateMsg), +N(OP_FellowshipExpMsg), +N(OP_ConsignmentCloseStoreMsg), +N(OP_ConsignItemRequestMsg), +N(OP_ConsignItemResponseMsg), +//N(OP_PurchaseConsignmentRequestMsg), +N(OP_PurchaseConsignmentLoreCheckRe), +N(OP_QuestReward), +//N(OP_PurchaseConsignmentResponseMsg), +//N(OP_ProcessScriptMsg), +//N(OP_ProcessWorkspaceMsg), +N(OP_HouseDeletedRemotelyMsg), +N(OP_UpdateHouseDataMsg), +N(OP_UpdateHouseAccessDataMsg), +N(OP_PlayerHouseBaseScreenMsg), +N(OP_PlayerHousePurchaseScreenMsg), +N(OP_PlayerHouseAccessUpdateMsg), +N(OP_PlayerHouseDisplayStatusMsg), +N(OP_PlayerHouseCloseUIMsg), +N(OP_BuyPlayerHouseMsg), +N(OP_BuyPlayerHouseTintMsg), +N(OP_CollectAllHouseItemsMsg), +N(OP_RelinquishHouseMsg), +N(OP_EnterHouseMsg), +N(OP_ExitHouseMsg), +N(OP_ExamineConsignmentRequestMsg), +N(OP_MoveableObjectPlacementCriteri), +N(OP_EnterMoveObjectModeMsg), +N(OP_PositionMoveableObject), +N(OP_CancelMoveObjectModeMsg), +N(OP_ShaderCustomizationMsg), +N(OP_ReplaceableSubMeshesMsg), +N(OP_ExamineConsignmentResponseMsg), +N(OP_HouseDefaultAccessSetMsg), +N(OP_HouseAccessSetMsg), +N(OP_HouseAccessRemoveMsg), +N(OP_PayHouseUpkeepMsg), +N(OP_TintWidgetsMsg), +N(OP_UISettingsResponseMsg), +N(OP_UIResetMsg), +N(OP_KeymapLoadMsg), +N(OP_KeymapNoneMsg), +N(OP_KeymapDataMsg), +N(OP_KeymapSaveMsg), +N(OP_DispatchSpellCmdMsg), +N(OP_HouseCustomizationScreenMsg), +N(OP_CustomizationPurchaseRequestMs), +N(OP_CustomizationSetRequestMsg), +N(OP_CustomizationReplyMsg), +N(OP_EntityVerbsRequestMsg), +N(OP_EntityVerbsReplyMsg), +N(OP_EntityVerbsVerbMsg), +N(OP_ChatRelationshipUpdateMsg), +N(OP_ChatCreateChannelMsg), +N(OP_ChatJoinChannelMsg), +N(OP_ChatWhoChannelMsg), +N(OP_ChatLeaveChannelMsg), +N(OP_ChatTellChannelMsg), +N(OP_ChatTellUserMsg), +N(OP_ChatToggleFriendMsg), +N(OP_ChatToggleIgnoreMsg), +N(OP_ChatSendFriendsMsg), +N(OP_ChatSendIgnoresMsg), +N(OP_ChatFiltersMsg), +N(OP_LootItemsRequestMsg), +N(OP_StoppedLootingMsg), +N(OP_SitMsg), +N(OP_StandMsg), +N(OP_SatMsg), +N(OP_StoodMsg), +//N(OP_QuickbarAddMsg), +N(OP_DefaultGroupOptionsRequestMsg), +N(OP_DefaultGroupOptionsMsg), +N(OP_GroupOptionsMsg), +N(OP_DisplayGroupOptionsScreenMsg), +N(OP_DisplayInnVisitScreenMsg), +N(OP_DumpSchedulerMsg), +//N(OP_LSRequestPlayerDescMsg), +N(OP_LSCheckAcctLockMsg), +N(OP_WSAcctLockStatusMsg), +N(OP_RequestHelpRepathMsg), +N(OP_UpdateMotdMsg), +N(OP_RequestTargetLocMsg), +N(OP_PerformPlayerKnockbackMsg), +N(OP_PerformCameraShakeMsg), +N(OP_PopulateSkillMapsMsg), +N(OP_CancelledFeignMsg), +N(OP_SignalMsg), +N(OP_SkillInfoRequest), +N(OP_SkillInfoResponse), +N(OP_ShowCreateFromRecipeUIMsg), +N(OP_CancelCreateFromRecipeMsg), +N(OP_BeginItemCreationMsg), +N(OP_StopItemCreationMsg), +N(OP_ShowItemCreationProcessUIMsg), +N(OP_UpdateItemCreationProcessUIMsg), +N(OP_DisplayTSEventReactionMsg), +N(OP_ShowRecipeBookMsg), +N(OP_KnowledgebaseRequestMsg), +N(OP_KnowledgebaseResponseMsg), +N(OP_CSTicketHeaderRequestMsg), +N(OP_CSTicketInfoMsg), +N(OP_CSTicketCommentRequestMsg), +N(OP_CSTicketCommentResponseMsg), +N(OP_CSTicketCreateMsg), +N(OP_CSTicketAddCommentMsg), +N(OP_CSTicketDeleteMsg), +N(OP_CSTicketChangeNotificationMsg), +N(OP_WorldDataUpdateMsg), +N(OP_WorldDataChangeMsg), +N(OP_KnownLanguagesMsg), +N(OP_LsRequestClientCrashLogMsg), +N(OP_LsClientBaselogReplyMsg), +N(OP_LsClientCrashlogReplyMsg), +N(OP_LsClientAlertlogReplyMsg), +N(OP_LsClientVerifylogReplyMsg), +N(OP_ClientTeleportToLocationMsg), +N(OP_UpdateClientPredFlagsMsg), +N(OP_ChangeServerControlFlagMsg), +N(OP_CSToolsRequestMsg), +N(OP_CSToolsResponseMsg), +N(OP_CreateBoatTransportsMsg), +N(OP_PositionBoatTransportMsg), +N(OP_MigrateBoatTransportMsg), +N(OP_MigrateBoatTransportReplyMsg), +N(OP_DisplayDebugNLLPointsMsg), +N(OP_ExamineInfoRequestMsg), +N(OP_QuickbarInitMsg), +N(OP_QuickbarUpdateMsg), +N(OP_MacroInitMsg), +N(OP_MacroUpdateMsg), +N(OP_QuestionnaireMsg), +N(OP_LevelChangedMsg), +N(OP_SpellGainedMsg), +N(OP_EncounterBrokenMsg), +N(OP_OnscreenMsgMsg), +N(OP_DisplayWarningMsg), +N(OP_ModifyGuildMsg), +N(OP_GuildEventMsg), +N(OP_GuildEventAddMsg), +N(OP_GuildEventActionMsg), +N(OP_GuildEventListMsg), +N(OP_RequestGuildEventDetailsMsg), +N(OP_GuildEventDetailsMsg), +N(OP_RequestGuildBankEventDetailsMs), +N(OP_GuildBankUpdateMsg), +N(OP_RewardPackMsg), +N(OP_RenameGuildMsg), +N(OP_ZoneToFriendRequestMsg), +N(OP_ZoneToFriendReplyMsg), +N(OP_WaypointRequestMsg), +N(OP_WaypointReplyMsg), +N(OP_WaypointSelectMsg), +N(OP_WaypointUpdateMsg), +N(OP_CharNameChangedMsg), +N(OP_ShowZoneTeleporterDestinations), +N(OP_SelectZoneTeleporterDestinatio), +N(OP_ReloadLocalizedTxtMsg), +N(OP_RequestGuildMembershipMsg), +N(OP_GuildMembershipResponseMsg), +N(OP_LeaveGuildNotifyMsg), +N(OP_JoinGuildNotifyMsg), +N(OP_RequestGuildInfoMsg), +N(OP_GuildBankEventListMsg), +N(OP_AvatarUpdateMsg), +N(OP_BioUpdateMsg), +N(OP_InspectPlayerMsg), +N(OP_WSServerLockMsg), +N(OP_WSServerHideMsg), +N(OP_LSServerLockMsg), +N(OP_CsCategoryRequestMsg), +N(OP_CsCategoryResponseMsg), +N(OP_KnowledgeWindowSlotMappingMsg), +N(OP_LFGUpdateMsg), +N(OP_AFKUpdateMsg), +N(OP_AnonUpdateMsg), +N(OP_UpdateActivePublicZonesMsg), +N(OP_UnknownNpcMsg), +N(OP_PromoFlagsDetailsMsg), +N(OP_ConsignViewCreateMsg), +N(OP_ConsignViewGetPageMsg), +N(OP_ConsignViewReleaseMsg), +N(OP_UpdateDebugRadiiMsg), +N(OP_ConsignRemoveItemsMsg), +//N(OP_SnoopMsg), +N(OP_ReportMsg), +N(OP_UpdateRaidMsg), +N(OP_ConsignViewSortMsg), +N(OP_TitleUpdateMsg), +N(OP_FlightPathsMsg), +N(OP_ClientFellMsg), +N(OP_ClientInDeathRegionMsg), +N(OP_CampClientMsg), +N(OP_GetAvatarAccessRequestForCSToo), +N(OP_CSToolAccessResponseMsg), +N(OP_DeleteGuildMsg), +N(OP_TrackingUpdateMsg), +N(OP_BeginTrackingMsg), +N(OP_StopTrackingMsg), +N(OP_AdvancementRequestMsg), +N(OP_MapFogDataInitMsg), +N(OP_MapFogDataUpdateMsg), +//N(OP_UpdateAvgFrameTimeMsg), +N(OP_CloseGroupInviteWindowMsg), +N(OP_UpdateGroupMemberDataMsg), +N(OP_WorldPingMsg), +N(OP_MoveLogUpdateMsg), +N(OP_OfferQuestMsg), +//N(OP_MailGetHeadersMsg), +N(OP_MailGetMessageMsg), +N(OP_MailSendMessageMsg), +N(OP_MailDeleteMessageMsg), +N(OP_MailGetHeadersReplyMsg), +N(OP_MailGetMessageReplyMsg), +N(OP_MailSendMessageReplyMsg), +N(OP_MailCommitSendMessageMsg), +N(OP_MailSendSystemMessageMsg), +N(OP_MailRemoveAttachFromMailMsg), +N(OP_WorldShutdownUpdateMsg), +N(OP_ClientIdleBeginMsg), +N(OP_ClientIdleEndMsg), +N(OP_DisplayMailScreenMsg), +N(OP_NotifyApprenticeStoppedMentori), +N(OP_CorruptedClientMsg), +N(OP_MailEventNotificationMsg), +N(OP_RestartZoneMsg), +N(OP_CharTransferStartRequestMsg), +N(OP_CharTransferStartReplyMsg), +N(OP_CharTransferRequestMsg), +N(OP_CharTransferReplyMsg), +N(OP_CharTransferRollbackRequestMsg), +N(OP_CharTransferCommitRequestMsg), +N(OP_CharTransferRollbackReplyMsg), +N(OP_CharTransferCommitReplyMsg), +N(OP_GetCharacterSerializedRequestM), +N(OP_GetCharacterSerializedReplyMsg), +N(OP_CreateCharFromCBBRequestMsg), +N(OP_CreateCharFromCBBReplyMsg), +N(OP_HousingDataChangedMsg), +N(OP_HousingRestoreMsg), +N(OP_AuctionItem), +N(OP_AuctionItemReply), +N(OP_AuctionCoin), +N(OP_AuctionCoinReply), +N(OP_AuctionCharacter), +N(OP_AuctionCharacterReply), +N(OP_AuctionCommitMsg), +N(OP_AuctionAbortMsg), +N(OP_CharTransferValidateRequestMsg), +N(OP_CharTransferValidateReplyMsg), +N(OP_CharacterLinkdeadMsg), +N(OP_RaceRestrictionMsg), +N(OP_SetInstanceDisplayNameMsg), +N(OP_EqHearChatCmd), +N(OP_EqDisplayTextCmd), +N(OP_EqCreateGhostCmd), +N(OP_EqCreateWidgetCmd), +N(OP_EqCreateSignWidgetCmd), +N(OP_EqDestroyGhostCmd), +N(OP_EqUpdateGhostCmd), +N(OP_EqSetControlGhostCmd), +N(OP_EqSetPOVGhostCmd), +N(OP_EqHearCombatCmd), +N(OP_EqHearSpellCastCmd), +N(OP_EqHearSpellInterruptCmd), +N(OP_EqHearSpellFizzleCmd), +N(OP_EqHearConsiderCmd), +N(OP_EqUpdateSubClassesCmd), +N(OP_EqCreateListBoxCmd), +N(OP_EqSetDebugPathPointsCmd), +N(OP_EqCannedEmoteCmd), +N(OP_EqStateCmd), +N(OP_EqPlaySoundCmd), +N(OP_EqPlaySound3DCmd), +N(OP_EqPlayVoiceCmd), +N(OP_EqHearDrowningCmd), +N(OP_EqHearDeathCmd), +N(OP_EqGroupMemberRemovedCmd), +N(OP_EqHearChainEffectCmd), +N(OP_EqReceiveOfferCmd), +N(OP_EqInspectPCResultsCmd), +N(OP_EqDrawablePathGraphCmd), +N(OP_EqDialogOpenCmd), +N(OP_EqDialogCloseCmd), +N(OP_EqCollectionUpdateCmd), +N(OP_EqCollectionFilterCmd), +N(OP_EqCollectionItemCmd), +N(OP_EqQuestJournalUpdateCmd), +N(OP_EqQuestJournalReplyCmd), +N(OP_EqQuestGroupCmd), +N(OP_EqUpdateMerchantCmd), +N(OP_EqUpdateStoreCmd), +N(OP_EqUpdatePlayerTradeCmd), +N(OP_EqHelpPathCmd), +N(OP_EqHelpPathClearCmd), +N(OP_EqUpdateBankCmd), +N(OP_EqExamineInfoCmd), +N(OP_EqCloseWindowCmd), +N(OP_EqUpdateLootCmd), +N(OP_EqJunctionListCmd), +N(OP_EqShowDeathWindowCmd), +N(OP_EqDisplaySpellFailCmd), +N(OP_EqSpellCastStartCmd), +N(OP_EqSpellCastEndCmd), +N(OP_EqResurrectedCmd), +N(OP_EqChoiceWinCmd), +N(OP_EqSetDefaultVerbCmd), +N(OP_EqInstructionWindowCmd), +N(OP_EqInstructionWindowCloseCmd), +N(OP_EqInstructionWindowGoalCmd), +N(OP_EqInstructionWindowTaskCmd), +N(OP_EqEnableGameEventCmd), +N(OP_EqShowWindowCmd), +N(OP_EqEnableWindowCmd), +N(OP_EqFlashWindowCmd), +N(OP_EqHearPlayFlavorCmd), +N(OP_EqUpdateSignWidgetCmd), +N(OP_EqDebugPVDCmd), +N(OP_EqShowBookCmd), +N(OP_EqQuestionnaireCmd), +N(OP_EqGetProbsCmd), +N(OP_EqHearHealCmd), +N(OP_EqChatChannelUpdateCmd), +N(OP_EqWhoChannelQueryReplyCmd), +N(OP_EqAvailWorldChannelsCmd), +N(OP_ArenaGameTypesMsg), +N(OP_EqUpdateTargetCmd), +N(OP_EqConsignmentItemsCmd), +N(OP_EqStartBrokerCmd), +N(OP_EqMapExplorationCmd), +N(OP_EqStoreLogCmd), +N(OP_EqSpellMoveToRangeAndRetryCmd), +N(OP_EqUpdatePlayerMailCmd), +N(OP_EqFactionUpdateCmd), +N(OP_UpdateTitleCmd), +N(OP_UpdatePositionMsg), +N(OP_AttackNotAllowed), +N(OP_AttackAllowed), +N(OP_CancelSpellCast), +N(OP_BadLanguageFilter), +N(OP_DressingRoom), +N(OP_TraitsList), +N(OP_PointOfInterest), +N(OP_AdventureList), +N(OP_CharacterAchievements), +N(OP_RecipeList), +N(OP_BagOptions), +N(OP_AchievementUpdateMsg), +N(OP_PetOptions), +N(OP_BrokerAddBag), +N(OP_CharacterPet), +N(OP_ClearForTakeOffMsg), +N(OP_CharacterCurrency), +N(OP_TradeskillList), +N(OP_RecipeBook), +N(OP_CharacterMerc), +N(OP_AfterInvSpellUpdate), +N(OP_CharacterCreatedDungeons), +N(OP_CharacterHousingList), +N(OP_HouseItemsList), +N(OP_CharacterMounts), +N(OP_LoadCalendarEvents), +N(OP_LoadWelcomeWindow), +N(OP_DungeonMakerItemRequest), +N(OP_SysClient), +N(OP_LFGGroupSearch), +N(OP_MarketPlacePrices), +N(OP_MarketFundsUpdate), +N(OP_MarketAddFundsRequest), +N(OP_ZoneBgInstanceList), +N(OP_UIEvent), +N(OP_Launchpad), +N(OP_EQHearThreatCmd), +N(OP_EqHearSpellNoLandCmd), +N(OP_Weakness), +N(OP_SavageBarInitMsg), +N(OP_PetOptionsResponse), +N(OP_CurrentPet), +N(OP_JournalQuestStoryline), +N(OP_DailyObjectives), +N(OP_RecipeListUnknown), +N(OP_EQHearDispellCmd), +N(OP_ClearForLandingMsg), +N(OP_LikeOption), +N(OP_HeritageMsg), +N(OP_OpenCharCust), +N(OP_PaperdollImage), +N(OP_ReadyForTakeOffMsg), +N(OP_EarlyLandingRequestMsg), +N(OP_SubmitCharCust), +N(OP_DietyAbilityWindow), +N(OP_EqTargetItemCmd), \ No newline at end of file diff --git a/source/common/linked_list.h b/source/common/linked_list.h new file mode 100644 index 0000000..023a9d2 --- /dev/null +++ b/source/common/linked_list.h @@ -0,0 +1,445 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef LINKEDLIST_H +#define LINKEDLIST_H + +#include "types.h" + +enum direction{FORWARD,BACKWARD}; + +template class LinkedListIterator; + +template +class ListElement +{ +private: + + TYPE data; + ListElement* next; + ListElement* prev; +public: + ListElement (); + ListElement (const TYPE&); + ListElement (const ListElement&); + + ~ListElement (); + + ListElement& operator= (const ListElement&); + + ListElement* GetLast () + { + ListElement* tmp = this; + while (tmp->GetNext()) { + tmp = tmp->GetNext(); + } + return tmp; + } + ListElement* GetNext () const { return next ; } + ListElement* GetPrev () const { return prev ; } + + inline TYPE& GetData () { return data ; } + inline const TYPE& GetData () const { return data ; } + + void SetData ( const TYPE& d ) { data = d ; } // Quagmire - this may look like a mem leak, but dont change it, this behavior is expected where it's called + void SetLastNext ( ListElement* p ) + { + GetLast()->SetNext(p); + } + void SetNext (ListElement* n) { next = n ; } + void SetPrev (ListElement* p) { prev = p ; } + + void ReplaceData(const TYPE&); +}; + +template +class LinkedList +{ +private: + int32 count; + ListElement* first; + bool list_destructor_invoked; + +public: + + LinkedList(); + ~LinkedList(); + bool dont_delete; + LinkedList& operator= (const LinkedList&); + + void Append (const TYPE&); + void Insert (const TYPE&); + TYPE Pop(); + TYPE PeekTop(); + void Clear(); + void LCount() { count--; } + void ResetCount() { count=0; } + int32 Count() { return count; } + friend class LinkedListIterator; +}; + +template +class LinkedListIterator +{ +private: + LinkedList& list; + ListElement* current_element; + direction dir; + +public: + LinkedListIterator(LinkedList& l,direction d = FORWARD) : list(l), dir(d) {}; + + void Advance(); + const TYPE& GetData(); + bool IsFirst() + { + if (current_element->GetPrev() == 0) + return true; + else + return false; + } + bool IsLast() + { + if (current_element->GetNext() == 0) + return true; + else + return false; + } + bool MoreElements(); + void MoveFirst(); + void MoveLast(); + void RemoveCurrent(bool DeleteData = true); + void Replace(const TYPE& new_data); + void Reset(); + void SetDir(direction); +}; + +template +void LinkedListIterator::Advance() +{ + if (current_element == 0) + { + return; + } + if (dir == FORWARD) + { + current_element = current_element->GetNext(); + } + else + { + current_element = current_element->GetPrev(); + } + + if (list.list_destructor_invoked) + { + while(current_element && current_element->GetData() == 0) + { +// if (current_element == 0) +// { +// return; +// } + if (dir == FORWARD) + { + current_element = current_element->GetNext(); + } + else + { + current_element = current_element->GetPrev(); + } + } + } +} + +template +bool LinkedListIterator::MoreElements() +{ + if (current_element == 0) + return false; + return true; +} + +template +const TYPE& LinkedListIterator::GetData() +{ + return current_element->GetData(); +} + +template +void LinkedListIterator::MoveFirst() +{ + ListElement* prev = current_element->GetPrev(); + ListElement* next = current_element->GetNext(); + + if (prev == 0) + { + return; + } + +// if (prev != 0) +// { + prev->SetNext(next); +// } + if (next != 0) + { + next->SetPrev(prev); + } + current_element->SetPrev(0); + current_element->SetNext(list.first); + list.first->SetPrev(current_element); + list.first = current_element; +} + + +template +void LinkedListIterator::MoveLast() +{ + ListElement* prev = current_element->GetPrev(); + ListElement* next = current_element->GetNext(); + + if (next == 0) + { + return; + } + + if (prev != 0) + { + prev->SetNext(next); + } + else + { + list.first = next; + } +// if (next != 0) +// { + next->SetPrev(prev); +// } + current_element->SetNext(0); + current_element->SetPrev(next->GetLast()); + next->GetLast()->SetNext(current_element); +} + +template +void LinkedListIterator::RemoveCurrent(bool DeleteData) +{ + ListElement* save; + + if (list.first == current_element) + { + list.first = current_element->GetNext(); + } + + if (current_element->GetPrev() != 0) + { + current_element->GetPrev()->SetNext(current_element->GetNext()); + } + if (current_element->GetNext() != 0) + { + current_element->GetNext()->SetPrev(current_element->GetPrev()); + } + if (dir == FORWARD) + { + save = current_element->GetNext(); + } + else + { + save = current_element->GetPrev(); + } + current_element->SetNext(0); + current_element->SetPrev(0); + if (!DeleteData) + current_element->SetData(0); + safe_delete(current_element); + current_element = save; + list.LCount(); +} + +template +void LinkedListIterator::Replace(const TYPE& new_data) +{ + current_element->ReplaceData(new_data); +} + +template +void LinkedListIterator::Reset() +{ + if (!(&list)) + { + current_element=0; + return; + } + + if (dir == FORWARD) + { + current_element = list.first; + } + else + { + if (list.first == 0) + { + current_element = 0; + } + else + { + current_element = list.first->GetLast(); + } + } + + if (list.list_destructor_invoked) + { + while(current_element && current_element->GetData() == 0) + { +// if (current_element == 0) +// { +// return; +// } + if (dir == FORWARD) + { + current_element = current_element->GetNext(); + } + else + { + current_element = current_element->GetPrev(); + } + } + } +} + +template +void LinkedListIterator::SetDir(direction d) +{ + dir = d; +} + +template +ListElement::ListElement(const TYPE& d) +{ + data = d; + next = 0; + prev = 0; +} + +template +ListElement::~ListElement() +{ +// cout << "ListElement::~ListElement()" << endl; + + if (data != 0) + safe_delete(data); + data = 0; + if (next != 0) + { + safe_delete(next); + next = 0; + } +} + +template +void ListElement::ReplaceData(const TYPE& new_data) +{ + if (data != 0) + safe_delete(data); + data = new_data; +} + +template +LinkedList::LinkedList() +{ + list_destructor_invoked = false; + first = 0; + count = 0; + dont_delete = false; +} + +template +LinkedList::~LinkedList() +{ + list_destructor_invoked = true; + if(!dont_delete) + Clear(); +} + +template +void LinkedList::Clear() { + while (first) { + ListElement* tmp = first; + first = tmp->GetNext(); + tmp->SetNext(0); + safe_delete(tmp); + } + ResetCount(); +} + +template +void LinkedList::Append(const TYPE& data) +{ + ListElement* new_element = new ListElement(data); + + if (first == 0) + { + first = new_element; + } + else + { + new_element->SetPrev(first->GetLast()); + first->SetLastNext(new_element); + } + count++; +} + +template +void LinkedList::Insert(const TYPE& data) +{ + ListElement* new_element = new ListElement(data); + + new_element->SetNext(first); + if (first != 0) + { + first->SetPrev(new_element); + } + first = new_element; + count++; +} + +template +TYPE LinkedList::Pop() { + TYPE ret = 0; + if (first) { + ListElement* tmpdel = first; + first = tmpdel->GetNext(); + if (first) + first->SetPrev(0); + ret = tmpdel->GetData(); + tmpdel->SetData(0); + tmpdel->SetNext(0); + safe_delete(tmpdel); + count--; + } + return ret; +} + +template +TYPE LinkedList::PeekTop() { + if (first) + return first->GetData(); + return 0; +} + +#endif + + diff --git a/source/common/login_oplist.h b/source/common/login_oplist.h new file mode 100644 index 0000000..c8bd536 --- /dev/null +++ b/source/common/login_oplist.h @@ -0,0 +1,61 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#if defined(LOGIN) || defined(MINILOGIN) +N(OP_LoginRequestMsg), +N(OP_LoginByNumRequestMsg), +N(OP_WSLoginRequestMsg), +N(OP_ESLoginRequestMsg), +N(OP_LoginReplyMsg), +N(OP_WorldListMsg), +N(OP_WorldStatusChangeMsg), +N(OP_AllWSDescRequestMsg), +N(OP_WSStatusReplyMsg), +N(OP_AllCharactersDescRequestMsg), +N(OP_AllCharactersDescReplyMsg), +N(OP_CreateCharacterRequestMsg), +N(OP_ReskinCharacterRequestMsg), +N(OP_CreateCharacterReplyMsg), +N(OP_WSCreateCharacterRequestMsg), +N(OP_WSCreateCharacterReplyMsg), +N(OP_DeleteCharacterRequestMsg), +N(OP_DeleteCharacterReplyMsg), +N(OP_PlayCharacterRequestMsg), +N(OP_PlayCharacterReplyMsg), +N(OP_ServerPlayCharacterRequestMsg), +N(OP_ServerPlayCharacterReplyMsg), +N(OP_KeymapLoadMsg), +N(OP_KeymapNoneMsg), +N(OP_KeymapDataMsg), +N(OP_KeymapSaveMsg), +//N(OP_LSRequestPlayerDescMsg), +N(OP_LSCheckAcctLockMsg), +N(OP_WSAcctLockStatusMsg), +N(OP_LsRequestClientCrashLogMsg), +N(OP_LsClientBaselogReplyMsg), +N(OP_LsClientCrashlogReplyMsg), +N(OP_LsClientAlertlogReplyMsg), +N(OP_LsClientVerifylogReplyMsg), +N(OP_BadLanguageFilter), +N(OP_WSServerLockMsg), +N(OP_WSServerHideMsg), +N(OP_LSServerLockMsg), +N(OP_UpdateCharacterSheetMsg), +N(OP_UpdateInventoryMsg), +#endif diff --git a/source/common/md5.cpp b/source/common/md5.cpp new file mode 100644 index 0000000..1244c8c --- /dev/null +++ b/source/common/md5.cpp @@ -0,0 +1,281 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include /* for memcpy() */ +#include "../common/md5.h" +#include "../common/MiscFunctions.h" +#include "../common/seperator.h" + +MD5::MD5() { + memset(pMD5, 0, 16); +} + +MD5::MD5(const uchar* buf, uint32 len) { + Generate(buf, len, pMD5); +} + +MD5::MD5(const char* buf, uint32 len) { + Generate((const uchar*) buf, len, pMD5); +} + +MD5::MD5(const int8 buf[16]) { + Set(buf); +} + +MD5::MD5(const char* iMD5String) { + Set(iMD5String); +} + +void MD5::Generate(const char* iString) { + Generate((const uchar*) iString, strlen(iString)); +} + +void MD5::Generate(const int8* buf, uint32 len) { + Generate(buf, len, pMD5); +} + +bool MD5::Set(const int8 buf[16]) { + memcpy(pMD5, buf, 16); + return true; +} + +bool MD5::Set(const char* iMD5String) { + char tmp[5] = { '0', 'x', 0, 0, 0 }; + for (int i=0; i<16; i++) { + tmp[2] = iMD5String[i*2]; + tmp[3] = iMD5String[(i*2) + 1]; + if (!Seperator::IsHexNumber(tmp)) + return false; + pMD5[i] = hextoi(tmp); + } + return true; +} + +MD5::operator const char* () { + snprintf(pMD5String, sizeof(pMD5String), "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", pMD5[0], pMD5[1], pMD5[2], pMD5[3], pMD5[4], pMD5[5], pMD5[6], pMD5[7], pMD5[8], pMD5[9], pMD5[10], pMD5[11], pMD5[12], pMD5[13], pMD5[14], pMD5[15]); + return pMD5String; +} + +bool MD5::operator== (const MD5& iMD5) { + if (memcmp(pMD5, iMD5.pMD5, 16) == 0) + return true; + else + return false; +} + +bool MD5::operator== (const int8* iMD5) { + if (memcmp(pMD5, iMD5, 16) == 0) + return true; + else + return false; +} + +bool MD5::operator== (const char* iMD5String) { + char tmp[5] = { '0', 'x', 0, 0, 0 }; + for (int i=0; i<16; i++) { + tmp[2] = iMD5String[i*2]; + tmp[3] = iMD5String[(i*2) + 1]; + if (pMD5[i] != hextoi(tmp)) + return false; + } + return true; +} + +MD5& MD5::operator= (const MD5& iMD5) { + memcpy(pMD5, iMD5.pMD5, 16); + return *this; +} + +MD5* MD5::operator= (const MD5* iMD5) { + memcpy(pMD5, iMD5->pMD5, 16); + return this; +} + +/* Byte-swap an array of words to little-endian. (Byte-sex independent) */ +void MD5::byteSwap(uint32 *buf, uint32 words) { + int8 *p = (int8 *)buf; + do { + *buf++ = (uint32)((uint32)p[3]<<8 | p[2]) << 16 | + ((uint32)p[1]<<8 | p[0]); + p += 4; + } while (--words); +} + +void MD5::Generate(const int8* buf, uint32 len, int8 digest[16]) { + MD5Context ctx; + Init(&ctx); + Update(&ctx, buf, len); + Final(digest, &ctx); +} + +/* Start MD5 accumulation. */ +void MD5::Init(struct MD5Context *ctx) { + ctx->hash[0] = 0x67452301; + ctx->hash[1] = 0xefcdab89; + ctx->hash[2] = 0x98badcfe; + ctx->hash[3] = 0x10325476; + ctx->bytes[1] = ctx->bytes[0] = 0; +} + +/* Update ctx to reflect the addition of another buffer full of bytes. */ +void MD5::Update(struct MD5Context *ctx, int8 const *buf, uint32 len) { + uint32 t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) /* Update 64-bit byte count */ + ctx->bytes[1]++; /* Carry from low to high */ + + + + t = 64 - (t & 0x3f); /* Bytes available in ctx->input (>= 1) */ + if (t > len) { + memcpy((int8*)ctx->input+64-t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((int8*)ctx->input+64-t, buf, t); + byteSwap(ctx->input, 16); + Transform(ctx->hash, ctx->input); + buf += t; + len -= t; + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->input, buf, 64); + byteSwap(ctx->input, 16); + Transform(ctx->hash, ctx->input); + buf += 64; + len -= 64; + } + /* Buffer any remaining bytes of data */ + memcpy(ctx->input, buf, len); +} + +/* Final wrapup - pad to 64-byte boundary with the bit pattern +* 1 0* (64-bit count of bits processed, LSB-first) */ +void MD5::Final(int8 digest[16], MD5Context *ctx) { + int count = ctx->bytes[0] & 0x3F; /* Bytes mod 64 */ + int8 *p = (int8*)ctx->input + count; + /* Set the first byte of padding to 0x80. There is always room. */ + *p++ = 0x80; + /* Bytes of zero padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count+8); + byteSwap(ctx->input, 16); + Transform(ctx->hash, ctx->input); + p = (int8*)ctx->input; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->input, 14); + /* Append 8 bytes of length in *bits* and transform */ + ctx->input[14] = ctx->bytes[0] << 3; + + ctx->input[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + Transform(ctx->hash, ctx->input); + byteSwap(ctx->hash, 4); + memcpy(digest, ctx->hash, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +/* The four core functions */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) (w += f(x,y,z)+in, w = (w<>(32-s)) + x) + + + +/* The heart of the MD5 algorithm. */ +void MD5::Transform(uint32 hash[4], const uint32 input[16]) { + uint32 a = hash[0], b = hash[1], c = hash[2], d = hash[3]; + + MD5STEP(F1, a, b, c, d, input[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, input[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, input[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, input[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, input[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, input[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, input[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, input[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, input[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, input[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, input[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, input[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, input[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, input[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, input[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, input[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, input[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, input[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, input[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, input[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, input[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, input[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, input[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, input[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, input[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, input[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, input[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, input[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, input[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, input[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, input[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, input[12]+0x8d2a4c8a, 20); + + + + + MD5STEP(F3, a, b, c, d, input[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, input[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, input[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, input[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, input[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, input[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, input[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, input[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, input[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, input[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, input[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, input[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, input[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, input[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, input[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, input[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, input[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, input[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, input[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, input[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, input[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, input[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, input[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, input[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, input[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, input[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, input[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, input[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, input[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, input[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, input[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, input[ 9]+0xeb86d391, 21); + + hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d; +} diff --git a/source/common/md5.h b/source/common/md5.h new file mode 100644 index 0000000..6c54f94 --- /dev/null +++ b/source/common/md5.h @@ -0,0 +1,64 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef MD5_H +#define MD5_H +#include "../common/types.h" + + +class MD5 { +public: + struct MD5Context { + uint32 hash[4]; + uint32 bytes[2]; + uint32 input[16]; + }; + static void Generate(const int8* buf, uint32 len, int8 digest[16]); + + static void Init(struct MD5Context *context); + static void Update(struct MD5Context *context, const int8 *buf, uint32 len); + static void Final(int8 digest[16], struct MD5Context *context); + + MD5(); + MD5(const uchar* buf, uint32 len); + MD5(const char* buf, uint32 len); + MD5(const int8 buf[16]); + MD5(const char* iMD5String); + + void Generate(const char* iString); + void Generate(const int8* buf, uint32 len); + bool Set(const int8 buf[16]); + bool Set(const char* iMD5String); + + bool operator== (const MD5& iMD5); + bool operator== (const int8 iMD5[16]); + bool operator== (const char* iMD5String); + + MD5& operator= (const MD5& iMD5); + MD5* operator= (const MD5* iMD5); + MD5* operator= (const int8* iMD5); + operator const char* (); +protected: + int8 pMD5[16]; +private: + static void byteSwap(uint32 *buf, uint32 words); + static void Transform(uint32 hash[4], const int32 input[16]); + char pMD5String[33]; +}; +#endif diff --git a/source/common/misc.cpp b/source/common/misc.cpp new file mode 100644 index 0000000..6f5daa0 --- /dev/null +++ b/source/common/misc.cpp @@ -0,0 +1,305 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifdef WIN32 + // VS6 doesn't like the length of STL generated names: disabling + #pragma warning(disable:4786) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include "misc.h" +#include "types.h" +using namespace std; + +#define ENC(c) (((c) & 0x3f) + ' ') +#define DEC(c) (((c) - ' ') & 0x3f) + +map DBFieldNames; + +#ifndef WIN32 +#ifdef FREEBSD +int print_stacktrace() +{ + printf("Insert stack trace here...\n"); + return(0); +} +#else //!WIN32 && !FREEBSD == linux +#include +int print_stacktrace() +{ + void *ba[20]; + int n = backtrace (ba, 20); + if (n != 0) + { + char **names = backtrace_symbols (ba, n); + if (names != NULL) + { + int i; + cerr << "called from " << (char*)names[0] << endl; + for (i = 1; i < n; ++i) + cerr << " " << (char*)names[i] << endl; + free (names); + } + } + return(0); +} +#endif //!FREEBSD +#endif //!WIN32 + +int Deflate(unsigned char* in_data, int in_length, unsigned char* out_data, int max_out_length) +{ +z_stream zstream; +int zerror; + + zstream.next_in = in_data; + zstream.avail_in = in_length; + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + deflateInit(&zstream, Z_FINISH); + zstream.next_out = out_data; + zstream.avail_out = max_out_length; + zerror = deflate(&zstream, Z_FINISH); + + if (zerror == Z_STREAM_END) + { + deflateEnd(&zstream); + return zstream.total_out; + } + else + { + cout << "Error: Deflate: deflate() returned " << zerror << " '"; + if (zstream.msg) + cout << zstream.msg; + cout << "'" << endl; + zerror = deflateEnd(&zstream); + return 0; + } +} + +int Inflate(unsigned char* indata, int indatalen, unsigned char* outdata, int outdatalen, bool iQuiet) +{ +z_stream zstream; +int zerror = 0; +int i; + + zstream.next_in = indata; + zstream.avail_in = indatalen; + zstream.next_out = outdata; + zstream.avail_out = outdatalen; + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + + i = inflateInit2( &zstream, 15 ); + if (i != Z_OK) { + return 0; + } + + zerror = inflate( &zstream, Z_FINISH ); + + if(zerror == Z_STREAM_END) { + inflateEnd( &zstream ); + return zstream.total_out; + } + else { + if (!iQuiet) { + cout << "Error: Inflate: inflate() returned " << zerror << " '"; + if (zstream.msg) + cout << zstream.msg; + cout << "'" << endl; + } + + if (zerror == Z_DATA_ERROR || zerror == Z_ERRNO) + return -1; + + if (zerror == Z_MEM_ERROR && zstream.msg == 0) + { + return 0; + } + + zerror = inflateEnd( &zstream ); + return 0; + } +} + +void dump_message_column(unsigned char *buffer, unsigned long length, string leader, FILE *to) +{ +unsigned long i,j; +unsigned long rows,offset=0; + rows=(length/16)+1; + for(i=0;i= 0x41 && val <=0x5A) || (val >= 0x61 && val <=0x7A)) + return true; + else + return false; +} + +unsigned int GetSpellNameCrc(const char* src) { + if (!src) + return 0; + uLong crc = crc32(0L, Z_NULL, 0); + return crc32(crc, (unsigned const char*)src, strlen(src)); +} + +int GetItemNameCrc(string item_name){ + const char *src = item_name.c_str(); + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (unsigned const char *)src,strlen(src)) + 1; + return sint32(crc) * -1; +} + +unsigned int GetNameCrc(string name) { + const char* src = name.c_str(); + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (unsigned const char*)src, strlen(src)) + 1; + return int32(crc)-1; +} diff --git a/source/common/misc.h b/source/common/misc.h new file mode 100644 index 0000000..4107eb9 --- /dev/null +++ b/source/common/misc.h @@ -0,0 +1,65 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _MISC_H + +#define _MISC_H +#include +#include +#include + +using namespace std; + +#define ITEMFIELDCOUNT 116 + +void Unprotect(string &s, char what); + +void Protect(string &s, char what); + +bool ItemParse(const char *data, int length, map > &items, int id_pos, int name_pos, int max_field, int level=0); + +int Tokenize(string s, map & tokens, char delim='|'); + +void LoadItemDBFieldNames(); + +void encode_length(unsigned long length, char *out); +unsigned long decode_length(char *in); +unsigned long encode(char *in, unsigned long length, char *out); +void decode(char *in, char *out); +void encode_chunk(char *in, int len, char *out); +void decode_chunk(char *in, char *out); + +int Deflate(unsigned char* in_data, int in_length, unsigned char* out_data, int max_out_length); +int Inflate(unsigned char* indata, int indatalen, unsigned char* outdata, int outdatalen, bool iQuiet=true); +#ifndef WIN32 +int print_stacktrace(); +#endif + +bool alpha_check(unsigned char val); + +void dump_message_column(unsigned char *buffer, unsigned long length, string leader="", FILE *to = stdout); +string string_from_time(string pattern, time_t now=0); +string timestamp(time_t now=0); +string long2ip(unsigned long ip); +string pop_arg(string &s, string seps, bool obey_quotes); +int EQsprintf(char *buffer, const char *pattern, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *arg6, const char *arg7, const char *arg8, const char *arg9); +unsigned int GetSpellNameCrc(const char* src); +int GetItemNameCrc(string item_name); +unsigned int GetNameCrc(string name); +#endif diff --git a/source/common/op_codes.h b/source/common/op_codes.h new file mode 100644 index 0000000..c25e9a0 --- /dev/null +++ b/source/common/op_codes.h @@ -0,0 +1,44 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _OP_CODES_H + +#define _OP_CODES_H + +static const char OP_SessionRequest = 0x01; +static const char OP_SessionResponse = 0x02; +static const char OP_Combined = 0x03; +static const char OP_SessionDisconnect = 0x05; +static const char OP_KeepAlive = 0x06; +static const char OP_ServerKeyRequest = 0x07; +static const char OP_SessionStatResponse= 0x08; +static const char OP_Packet = 0x09; +static const char OP_Fragment = 0x0d; +static const char OP_OutOfOrderAck = 0x11; +static const char OP_Ack = 0x15; +static const char OP_AppCombined = 0x19; +static const char OP_OutOfSession = 0x1d; + +#if defined(LOGIN) || defined(CHAT) + #define APP_OPCODE_SIZE 1 +#else + #define APP_OPCODE_SIZE 2 +#endif + +#endif diff --git a/source/common/opcodemgr.cpp b/source/common/opcodemgr.cpp new file mode 100644 index 0000000..6d57921 --- /dev/null +++ b/source/common/opcodemgr.cpp @@ -0,0 +1,350 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "debug.h" +#include +#include +#include "opcodemgr.h" +//#include "debug.h" +#include "emu_opcodes.h" +#include "../common/Log.h" + +#if defined(SHARED_OPCODES) && !defined(EQ2) + #include "EMuShareMem.h" + extern LoadEMuShareMemDLL EMuShareMemDLL; +#endif + +#include +#include +using namespace std; + + +//#define DEBUG_TRANSLATE + + +OpcodeManager::OpcodeManager() { + loaded = false; +} +bool OpcodeManager::LoadOpcodesMap(map* eq, OpcodeSetStrategy *s, std::string* missingOpcodes){ + //do the mapping and store them in the shared memory array + bool ret = true; + EmuOpcode emu_op; + map::iterator res; + //stupid enum wont let me ++ on it... + + + for(emu_op = (EmuOpcode)(0); emu_op < _maxEmuOpcode; emu_op=(EmuOpcode)(emu_op+1)) { + //get the name of this emu opcode + const char *op_name = OpcodeNames[emu_op]; + if(op_name[0] == '\0') { + break; + } + + //find the opcode in the file + res = eq->find(op_name); + if(res == eq->end()) { + if(missingOpcodes) { + if(missingOpcodes->size() < 1) { + missingOpcodes->append(op_name); + } + else { + missingOpcodes->append(", " + std::string(op_name)); + } + } + else { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcode %s is missing from the opcodes table.", op_name); + } + s->Set(emu_op, 0xFFFF); + continue; //continue to give them a list of all missing opcodes + } + + //ship the mapping off to shared mem. + s->Set(emu_op, res->second); + } + return ret; +} +bool OpcodeManager::LoadOpcodesFile(const char *filename, OpcodeSetStrategy *s) { + FILE *opf = fopen(filename, "r"); + if(opf == NULL) { + LogWrite(OPCODE__ERROR, 0, "Opcode", "Unable to open opcodes file '%s'. Thats bad.", filename); + return(false); + } + + map eq; + + //load the opcode file into eq, could swap in a nice XML parser here + char line[2048]; + int lineno = 0; + uint16 curop; + while(!feof(opf)) { + lineno++; + line[0] = '\0'; //for blank line at end of file + if(fgets(line, sizeof(line), opf) == NULL) + break; + + //ignore any line that dosent start with OP_ + if(line[0] != 'O' || line[1] != 'P' || line[2] != '_') + continue; + + char *num = line+3; //skip OP_ + //look for the = sign + while(*num != '=' && *num != '\0') { + num++; + } + //make sure we found = + if(*num != '=') { + LogWrite(OPCODE__ERROR, 0, "Opcode", "Malformed opcode line at %s:%d\n", filename, lineno); + continue; + } + *num = '\0'; //null terminate the name + num++; //num should point to the opcode + + //read the opcode + if(sscanf(num, "0x%hx", &curop) != 1) { + LogWrite(OPCODE__ERROR, 0, "Opcode", "Malformed opcode at %s:%d\n", filename, lineno); + continue; + } + + //we have a name and our opcode... stick it in the map + eq[line] = curop; + } + fclose(opf); + return LoadOpcodesMap(&eq, s); +} + +//convenience routines +const char *OpcodeManager::EmuToName(const EmuOpcode emu_op) { + if(emu_op > _maxEmuOpcode) + return "OP_Unknown"; + + return(OpcodeNames[emu_op]); +} + +const char *OpcodeManager::EQToName(const uint16 eq_op) { + //first must resolve the eq op to an emu op + EmuOpcode emu_op = EQToEmu(eq_op); + if(emu_op > _maxEmuOpcode) + return "OP_Unknown"; + + return(OpcodeNames[emu_op]); +} + +EmuOpcode OpcodeManager::NameSearch(const char *name) { + EmuOpcode emu_op; + //stupid enum wont let me ++ on it... + for(emu_op = (EmuOpcode)(0); emu_op < _maxEmuOpcode; emu_op=(EmuOpcode)(emu_op+1)) { + //get the name of this emu opcode + const char *op_name = OpcodeNames[emu_op]; + if(!strcasecmp(op_name, name)) { + return(emu_op); + } + } + return(OP_Unknown); +} + +RegularOpcodeManager::RegularOpcodeManager() +: MutableOpcodeManager() +{ + emu_to_eq = NULL; + eq_to_emu = NULL; + EQOpcodeCount = 0; + EmuOpcodeCount = 0; +} + +RegularOpcodeManager::~RegularOpcodeManager() { + safe_delete_array(emu_to_eq); + safe_delete_array(eq_to_emu); +} + +bool RegularOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { + NormalMemStrategy s; + s.it = this; + MOpcodes.lock(); + + loaded = true; + eq_to_emu = new EmuOpcode[MAX_EQ_OPCODE]; + emu_to_eq = new uint16[_maxEmuOpcode]; + EQOpcodeCount = MAX_EQ_OPCODE; + EmuOpcodeCount = _maxEmuOpcode; + + //dont need to set eq_to_emu cause every element should get a value + memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); + memset(emu_to_eq, 0xCD, sizeof(uint16)*_maxEmuOpcode); + + bool ret = LoadOpcodesMap(eq, &s, missingOpcodes); + MOpcodes.unlock(); + return ret; +} + +bool RegularOpcodeManager::LoadOpcodes(const char *filename) { + NormalMemStrategy s; + s.it = this; + MOpcodes.lock(); + + loaded = true; + eq_to_emu = new EmuOpcode[MAX_EQ_OPCODE]; + emu_to_eq = new uint16[_maxEmuOpcode]; + EQOpcodeCount = MAX_EQ_OPCODE; + EmuOpcodeCount = _maxEmuOpcode; + + //dont need to set eq_to_emu cause every element should get a value + memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); + memset(emu_to_eq, 0xCD, sizeof(uint16)*_maxEmuOpcode); + + bool ret = LoadOpcodesFile(filename, &s); + MOpcodes.unlock(); + return ret; +} + +bool RegularOpcodeManager::ReloadOpcodes(const char *filename) { + if(!loaded) + return(LoadOpcodes(filename)); + + NormalMemStrategy s; + s.it = this; + MOpcodes.lock(); + + memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); + + bool ret = LoadOpcodesFile(filename, &s); + + MOpcodes.unlock(); + return(ret); +} + + +uint16 RegularOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { + //opcode is checked for validity in GetEQOpcode + uint16 res; + MOpcodes.lock(); + + if(emu_op > _maxEmuOpcode) + res = 0; + else + res = emu_to_eq[emu_op]; + + MOpcodes.unlock(); +#ifdef _DEBUG_TRANSLATE + fprintf(stderr, "M Translate Emu %s (%d) to EQ 0x%.4x\n", OpcodeNames[emu_op], emu_op, res); +#endif + return(res); +} + +EmuOpcode RegularOpcodeManager::EQToEmu(const uint16 eq_op) { + //opcode is checked for validity in GetEmuOpcode +//Disabled since current live EQ uses the entire uint16 bitspace for opcodes +// if(eq_op > MAX_EQ_OPCODE) +// return(OP_Unknown); + EmuOpcode res; + MOpcodes.lock(); + if(eq_op >= MAX_EQ_OPCODE) + res = OP_Unknown; + else + res = eq_to_emu[eq_op]; + MOpcodes.unlock(); +#ifdef _DEBUG_TRANSLATE + fprintf(stderr, "M Translate EQ 0x%.4x to Emu %s (%d)\n", eq_op, OpcodeNames[res], res); +#endif + return(res); +} + +void RegularOpcodeManager::SetOpcode(EmuOpcode emu_op, uint16 eq_op) { + + //clear out old mapping + uint16 oldop = 0; + + if(emu_op <= _maxEmuOpcode) + oldop = emu_to_eq[emu_op]; + + if(oldop != 0 && oldop < MAX_EQ_OPCODE) + eq_to_emu[oldop] = OP_Unknown; + + //use our strategy, since we have it + NormalMemStrategy s; + s.it = this; + s.Set(emu_op, eq_op); +} + + +void RegularOpcodeManager::NormalMemStrategy::Set(EmuOpcode emu_op, uint16 eq_op) { + if(uint32(emu_op) >= it->EmuOpcodeCount || eq_op >= it->EQOpcodeCount) + return; + it->emu_to_eq[emu_op] = eq_op; + it->eq_to_emu[eq_op] = emu_op; +} + +NullOpcodeManager::NullOpcodeManager() +: MutableOpcodeManager() { +} + +bool NullOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { + return(true); +} + +bool NullOpcodeManager::LoadOpcodes(const char *filename) { + return(true); +} + +bool NullOpcodeManager::ReloadOpcodes(const char *filename) { + return(true); +} + +uint16 NullOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { + return(0); +} + +EmuOpcode NullOpcodeManager::EQToEmu(const uint16 eq_op) { + return(OP_Unknown); +} + +EmptyOpcodeManager::EmptyOpcodeManager() +: MutableOpcodeManager() { +} + + +bool EmptyOpcodeManager::LoadOpcodes(const char *filename) { + return(true); +} + +bool EmptyOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { + return(true); +} + +bool EmptyOpcodeManager::ReloadOpcodes(const char *filename) { + return(true); +} + +uint16 EmptyOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { + map::iterator f; + f = emu_to_eq.find(emu_op); + return(f == emu_to_eq.end()? 0 : f->second); +} + +EmuOpcode EmptyOpcodeManager::EQToEmu(const uint16 eq_op) { + map::iterator f; + f = eq_to_emu.find(eq_op); + return(f == eq_to_emu.end()?OP_Unknown:f->second); +} + +void EmptyOpcodeManager::SetOpcode(EmuOpcode emu_op, uint16 eq_op) { + emu_to_eq[emu_op] = eq_op; + eq_to_emu[eq_op] = emu_op; +} + + diff --git a/source/common/opcodemgr.h b/source/common/opcodemgr.h new file mode 100644 index 0000000..fe487f0 --- /dev/null +++ b/source/common/opcodemgr.h @@ -0,0 +1,162 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef OPCODE_MANAGER_H +#define OPCODE_MANAGER_H + +#include "types.h" +#include "Mutex.h" +#include "emu_opcodes.h" + +#include +using namespace std; + +class OpcodeManager { +public: + OpcodeManager(); + virtual ~OpcodeManager() {} + + virtual bool Mutable() { return(false); } + virtual bool LoadOpcodes(const char *filename) = 0; + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr) = 0; + virtual bool ReloadOpcodes(const char *filename) = 0; + + virtual uint16 EmuToEQ(const EmuOpcode emu_op) = 0; + virtual EmuOpcode EQToEmu(const uint16 eq_op) = 0; + + static const char *EmuToName(const EmuOpcode emu_op); + const char *EQToName(const uint16 emu_op); + EmuOpcode NameSearch(const char *name); + + //This has to be public for stupid visual studio + class OpcodeSetStrategy { + public: + virtual void Set(EmuOpcode emu_op, uint16 eq_op) = 0; + virtual ~OpcodeSetStrategy(){} + }; + +protected: + bool loaded; //true if all opcodes loaded + Mutex MOpcodes; //this only protects the local machine + //in a shared manager, this dosent protect others + + static bool LoadOpcodesFile(const char *filename, OpcodeSetStrategy *s); + static bool LoadOpcodesMap(map* eq, OpcodeSetStrategy *s, std::string* missingOpcodes = nullptr); +}; + +class MutableOpcodeManager : public OpcodeManager { +public: + MutableOpcodeManager() : OpcodeManager() {} + virtual bool Mutable() { return(true); } + virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op) = 0; +}; + +#ifdef SHARED_OPCODES //quick toggle since only world and zone should possibly use this +//keeps opcodes in shared memory +class SharedOpcodeManager : public OpcodeManager { +public: + virtual ~SharedOpcodeManager() {} + + virtual bool LoadOpcodes(const char *filename); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); + virtual bool ReloadOpcodes(const char *filename); + + virtual uint16 EmuToEQ(const EmuOpcode emu_op); + virtual EmuOpcode EQToEmu(const uint16 eq_op); + +protected: + class SharedMemStrategy : public OpcodeManager::OpcodeSetStrategy { + public: + void Set(EmuOpcode emu_op, uint16 eq_op); + }; + static bool DLLLoadOpcodesCallback(const char *filename); +}; +#endif //SHARED_OPCODES + +//keeps opcodes in regular heap memory +class RegularOpcodeManager : public MutableOpcodeManager { +public: + RegularOpcodeManager(); + virtual ~RegularOpcodeManager(); + + virtual bool Editable() { return(true); } + virtual bool LoadOpcodes(const char *filename); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); + virtual bool ReloadOpcodes(const char *filename); + + virtual uint16 EmuToEQ(const EmuOpcode emu_op); + virtual EmuOpcode EQToEmu(const uint16 eq_op); + + //implement our editing interface + virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op); + +protected: + class NormalMemStrategy : public OpcodeManager::OpcodeSetStrategy { + public: + RegularOpcodeManager *it; + void Set(EmuOpcode emu_op, uint16 eq_op); + }; + friend class NormalMemStrategy; + + uint16 *emu_to_eq; + EmuOpcode *eq_to_emu; + uint32 EQOpcodeCount; + uint32 EmuOpcodeCount; +}; + +//always resolves everything to 0 or OP_Unknown +class NullOpcodeManager : public MutableOpcodeManager { +public: + NullOpcodeManager(); + + virtual bool LoadOpcodes(const char *filename); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); + virtual bool ReloadOpcodes(const char *filename); + + virtual uint16 EmuToEQ(const EmuOpcode emu_op); + virtual EmuOpcode EQToEmu(const uint16 eq_op); + + //fake it, just used for testing anyways + virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op) {} +}; + +//starts as NullOpcodeManager, but remembers any mappings set +//could prolly have been implemented with an extension to regular, +//by overriding its load methods to be empty. +class EmptyOpcodeManager : public MutableOpcodeManager { +public: + EmptyOpcodeManager(); + + virtual bool LoadOpcodes(const char *filename); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); + virtual bool ReloadOpcodes(const char *filename); + + virtual uint16 EmuToEQ(const EmuOpcode emu_op); + virtual EmuOpcode EQToEmu(const uint16 eq_op); + + //fake it, just used for testing anyways + virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op); +protected: + map emu_to_eq; + map eq_to_emu; +}; + +#endif + + diff --git a/source/common/packet_dump.cpp b/source/common/packet_dump.cpp new file mode 100644 index 0000000..27cb0a9 --- /dev/null +++ b/source/common/packet_dump.cpp @@ -0,0 +1,195 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +#include +#include +#include + +using namespace std; + +#include "packet_dump.h" +#include "EQStream.h" +#include "../common/servertalk.h" + +void DumpPacketAscii(const uchar* buf, int32 size, int32 cols, int32 skip) { + // Output as ASCII + for(int32 i=skip; i 32 && buf[i] < 127) + { + cout << buf[i]; + } + else + { + cout << '.'; + } + } + cout << endl << endl; +} + +void DumpPacketHex(const uchar* buf, int32 size, int32 cols, int32 skip) { + if (size == 0 || size > 39565) + return; + // Output as HEX + char output[4]; + int j = 0; char* ascii = new char[cols+1]; memset(ascii, 0, cols+1); + int32 i; + for(i=skip; i= 32 && buf[i] < 127) { + ascii[j++] = buf[i]; + } + else { + ascii[j++] = '.'; + } +// cout << setfill(0) << setw(2) << hex << (int)buf[i] << " "; + } + int32 k = ((i-skip)-1)%cols; + if (k < 8) + cout << " "; + for (int32 h = k+1; h < cols; h++) { + cout << " "; + } + cout << " | " << ascii << endl; + safe_delete_array(ascii); +} + +void DumpPacket(const uchar* buf, int32 size) +{ + DumpPacketHex(buf, size); +// DumpPacketAscii(buf,size); +} + +void DumpPacket(const ServerPacket* pack, bool iShowInfo) { + if (iShowInfo) { + cout << "Dumping ServerPacket: 0x" << hex << setfill('0') << setw(4) << pack->opcode << dec; + cout << " size:" << pack->size << endl; + } + DumpPacketHex(pack->pBuffer, pack->size); +} + +void DumpPacketBin(const ServerPacket* pack) { + DumpPacketBin(pack->pBuffer, pack->size); +} + +void DumpPacketBin(int32 data) { + DumpPacketBin((uchar*)&data, sizeof(int32)); +} + +void DumpPacketBin(int16 data) { + DumpPacketBin((uchar*)&data, sizeof(int16)); +} + +void DumpPacketBin(int8 data) { + DumpPacketBin((uchar*)&data, sizeof(int8)); +} + + +void DumpPacketBin(const void* iData, int32 len) { + if (!len) + return; + const int8* data = (const int8*) iData; + int32 k=0; + for (k=0; k 1) + cout << " " << hex << setw(2) << setfill('0') << (int) data[k-3] << dec; + if (tmp > 2) + cout << " " << hex << setw(2) << setfill('0') << (int) data[k-2] << dec; + if (tmp > 3) + cout << " " << hex << setw(2) << setfill('0') << (int) data[k-1] << dec; + cout << endl; +} diff --git a/source/common/packet_dump.h b/source/common/packet_dump.h new file mode 100644 index 0000000..42fe2ef --- /dev/null +++ b/source/common/packet_dump.h @@ -0,0 +1,41 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef PACKET_DUMP_H +#define PACKET_DUMP_H + +#include +using namespace std; + +#include "../common/types.h" +#include "EQPacket.h" + +class ServerPacket; + +void DumpPacketAscii(const uchar* buf, int32 size, int32 cols=16, int32 skip=0); +void DumpPacketHex(const uchar* buf, int32 size, int32 cols=16, int32 skip=0); +void DumpPacketBin(const void* data, int32 len); +void DumpPacket(const uchar* buf, int32 size); +void DumpPacket(const ServerPacket* pack, bool iShowInfo = false); +void DumpPacketBin(const ServerPacket* pack); +void DumpPacketBin(int32 data); +void DumpPacketBin(int16 data); +void DumpPacketBin(int8 data); + +#endif diff --git a/source/common/packet_functions.cpp b/source/common/packet_functions.cpp new file mode 100644 index 0000000..a7e6a6c --- /dev/null +++ b/source/common/packet_functions.cpp @@ -0,0 +1,537 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +#include +#include +#include +#include +#include "packet_dump.h" +#include "EQStream.h" +#include "packet_functions.h" + +#ifndef WIN32 + #include +#endif + +using namespace std; + +#define eqemu_alloc_func Z_NULL +#define eqemu_free_func Z_NULL + + +int DeflatePacket(unsigned char* in_data, int in_length, unsigned char* out_data, int max_out_length) { +#ifdef REUSE_ZLIB + static bool inited = false; + static z_stream zstream; + int zerror; + + if(in_data == NULL && out_data == NULL && in_length == 0 && max_out_length == 0) { + //special delete state + deflateEnd(&zstream); + return(0); + } + if(!inited) { + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + deflateInit(&zstream, Z_FINISH); + } + + zstream.next_in = in_data; + zstream.avail_in = in_length; +/* zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + deflateInit(&zstream, Z_FINISH);*/ + zstream.next_out = out_data; + zstream.avail_out = max_out_length; + zerror = deflate(&zstream, Z_FINISH); + + deflateReset(&zstream); + + if (zerror == Z_STREAM_END) + { +// deflateEnd(&zstream); + return zstream.total_out; + } + else + { +// zerror = deflateEnd(&zstream); + return 0; + } +#else + if(in_data == NULL) { + return(0); + } + + z_stream zstream; + int zerror; + + zstream.next_in = in_data; + zstream.avail_in = in_length; + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + deflateInit(&zstream, Z_FINISH); + zstream.next_out = out_data; + zstream.avail_out = max_out_length; + zerror = deflate(&zstream, Z_FINISH); + + if (zerror == Z_STREAM_END) + { + deflateEnd(&zstream); + return zstream.total_out; + } + else + { + zerror = deflateEnd(&zstream); + return 0; + } +#endif +} + +uint32 InflatePacket(uchar* indata, uint32 indatalen, uchar* outdata, uint32 outdatalen, bool iQuiet) { +#ifdef REUSE_ZLIB + static bool inited = false; + static z_stream zstream; + int zerror; + + if(indata == NULL && outdata == NULL && indatalen == 0 && outdatalen == 0) { + //special delete state + inflateEnd(&zstream); + return(0); + } + if(!inited) { + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + inflateInit2(&zstream, 15); + } + + zstream.next_in = indata; + zstream.avail_in = indatalen; + zstream.next_out = outdata; + zstream.avail_out = outdatalen; + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + + i = inflateInit2( &zstream, 15 ); + if (i != Z_OK) { + return 0; + } + + zerror = inflate( &zstream, Z_FINISH ); + + inflateReset(&zstream); + + if(zerror == Z_STREAM_END) { + return zstream.total_out; + } + else { + if (!iQuiet) { + cout << "Error: InflatePacket: inflate() returned " << zerror << " '"; + if (zstream.msg) + cout << zstream.msg; + cout << "'" << endl; + //DumpPacket(indata-16, indatalen+16); + } + + if (zerror == -4 && zstream.msg == 0) + { + return 0; + } + + return 0; + } +#else + if(indata == NULL) + return(0); + + z_stream zstream; + int zerror = 0; + int i; + + zstream.next_in = indata; + zstream.avail_in = indatalen; + zstream.next_out = outdata; + zstream.avail_out = outdatalen; + zstream.zalloc = eqemu_alloc_func; + zstream.zfree = eqemu_free_func; + zstream.opaque = Z_NULL; + + i = inflateInit2( &zstream, 15 ); + if (i != Z_OK) { + return 0; + } + + zerror = inflate( &zstream, Z_FINISH ); + + if(zerror == Z_STREAM_END) { + inflateEnd( &zstream ); + return zstream.total_out; + } + else { + if (!iQuiet) { + cout << "Error: InflatePacket: inflate() returned " << zerror << " '"; + if (zstream.msg) + cout << zstream.msg; + cout << "'" << endl; + //DumpPacket(indata-16, indatalen+16); + } + + if (zerror == -4 && zstream.msg == 0) + { + return 0; + } + + zerror = inflateEnd( &zstream ); + return 0; + } +#endif +} + +int32 roll(int32 in, int8 bits) { + return ((in << bits) | (in >> (32-bits))); +} + +int64 roll(int64 in, int8 bits) { + return ((in << bits) | (in >> (64-bits))); +} + +int32 rorl(int32 in, int8 bits) { + return ((in >> bits) | (in << (32-bits))); +} + +int64 rorl(int64 in, int8 bits) { + return ((in >> bits) | (in << (64-bits))); +} + +int32 CRCLookup(uchar idx) { + if (idx == 0) + return 0x00000000; + + if (idx == 1) + return 0x77073096; + + if (idx == 2) + return roll(CRCLookup(1), 1); + + if (idx == 4) + return 0x076DC419; + + for (uchar b=7; b>0; b--) { + uchar bv = 1 << b; + + if (!(idx ^ bv)) { + // bit is only one set + return ( roll(CRCLookup (4), b - 2) ); + } + + if (idx&bv) { + // bit is set + return( CRCLookup(bv) ^ CRCLookup(idx&(bv - 1)) ); + } + } + + //Failure + return false; +} + +uint32 GenerateCRC(int32 b, int32 bufsize, uchar *buf) { + int32 CRC = (b ^ 0xFFFFFFFF); + int32 bufremain = bufsize; + uchar* bufptr = buf; + + while (bufremain--) { + CRC = CRCLookup((uchar)(*(bufptr++)^ (CRC&0xFF))) ^ (CRC >> 8); + } + + return (htonl (CRC ^ 0xFFFFFFFF)); +} + +long int CRCArray[] = { + 0, +1996959894, +3993919788, +2567524794, +124634137, +1886057615, +3915621685, +2657392035, +249268274, +2044508324, +3772115230, +2547177864, +162941995, +2125561021, +3887607047, +2428444049, +498536548, +1789927666, +4089016648, +2227061214, +450548861, +1843258603, +4107580753, +2211677639, +325883990, +1684777152, +4251122042, +2321926636, +335633487, +1661365465, +4195302755, +2366115317, +997073096, +1281953886, +3579855332, +2724688242, +1006888145, +1258607687, +3524101629, +2768942443, +901097722, +1119000684, +3686517206, +2898065728, +853044451, +1172266101, +3705015759, +2882616665, +651767980, +1373503546, +3369554304, +3218104598, +565507253, +1454621731, +3485111705, +3099436303, +671266974, +1594198024, +3322730930, +2970347812, +795835527, +1483230225, +3244367275, +3060149565, +1994146192, +31158534, +2563907772, +4023717930, +1907459465, +112637215, +2680153253, +3904427059, +2013776290, +251722036, +2517215374, +3775830040, +2137656763, +141376813, +2439277719, +3865271297, +1802195444, +476864866, +2238001368, +4066508878, +1812370925, +453092731, +2181625025, +4111451223, +1706088902, +314042704, +2344532202, +4240017532, +1658658271, +366619977, +2362670323, +4224994405, +1303535960, +984961486, +2747007092, +3569037538, +1256170817, +1037604311, +2765210733, +3554079995, +1131014506, +879679996, +2909243462, +3663771856, +1141124467, +855842277, +2852801631, +3708648649, +1342533948, +654459306, +3188396048, +3373015174, +1466479909, +544179635, +3110523913, +3462522015, +1591671054, +702138776, +2966460450, +3352799412, +1504918807, +783551873, +3082640443, +3233442989, +3988292384, +2596254646, +62317068, +1957810842, +3939845945, +2647816111, +81470997, +1943803523, +3814918930, +2489596804, +225274430, +2053790376, +3826175755, +2466906013, +167816743, +2097651377, +4027552580, +2265490386, +503444072, +1762050814, +4150417245, +2154129355, +426522225, +1852507879, +4275313526, +2312317920, +282753626, +1742555852, +4189708143, +2394877945, +397917763, +1622183637, +3604390888, +2714866558, +953729732, +1340076626, +3518719985, +2797360999, +1068828381, +1219638859, +3624741850, +2936675148, +906185462, +1090812512, +3747672003, +2825379669, +829329135, +1181335161, +3412177804, +3160834842, +628085408, +1382605366, +3423369109, +3138078467, +570562233, +1426400815, +3317316542, +2998733608, +733239954, +1555261956, +3268935591, +3050360625, +752459403, +1541320221, +2607071920, +3965973030, +1969922972, +40735498, +2617837225, +3943577151, +1913087877, +83908371, +2512341634, +3803740692, +2075208622, +213261112, +2463272603, +3855990285, +2094854071, +198958881, +2262029012, +4057260610, +1759359992, +534414190, +2176718541, +4139329115, +1873836001, +414664567, +2282248934, +4279200368, +1711684554, +285281116, +2405801727, +4167216745, +1634467795, +376229701, +2685067896, +3608007406, +1308918612, +956543938, +2808555105, +3495958263, +1231636301, +1047427035, +2932959818, +3654703836, +1088359270, +936918000, +2847714899, +3736837829, +1202900863, +817233897, +3183342108, +3401237130, +1404277552, +615818150, +3134207493, +3453421203, +1423857449, +601450431, +3009837614, +3294710456, +1567103746, +711928724, +3020668471, +3272380065, +1510334235, +755167117}; + +uint32 GenerateCRCRecipe(uint32 initial, void* buf, uint32 len) +{ + uint32 c = 0xFFFFFFFF; + sint8* u = static_cast(buf); + for (size_t i = 0; i < len; ++i) + { + c = CRCArray[(c ^ u[i]) & 0xFF] ^ (c >> 8); + } + return c; +} \ No newline at end of file diff --git a/source/common/packet_functions.h b/source/common/packet_functions.h new file mode 100644 index 0000000..318ff2d --- /dev/null +++ b/source/common/packet_functions.h @@ -0,0 +1,45 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef PACKET_FUNCTIONS_H +#define PACKET_FUNCTIONS_H +#include "types.h" +#include "EQPacket.h" + +int32 roll(int32 in, int8 bits); +int64 roll(int64 in, int8 bits); +int32 rorl(int32 in, int8 bits); +int64 rorl(int64 in, int8 bits); + +void EncryptProfilePacket(EQApplicationPacket* app); +void EncryptProfilePacket(uchar* pBuffer, int32 size); + +#define EncryptSpawnPacket EncryptZoneSpawnPacket +//void EncryptSpawnPacket(EQApplicationPacket* app); +//void EncryptSpawnPacket(uchar* pBuffer, int32 size); + +void EncryptZoneSpawnPacket(EQApplicationPacket* app); +void EncryptZoneSpawnPacket(uchar* pBuffer, int32 size); + +int DeflatePacket(unsigned char* in_data, int in_length, unsigned char* out_data, int max_out_length); +uint32 InflatePacket(uchar* indata, uint32 indatalen, uchar* outdata, uint32 outdatalen, bool iQuiet = false); +uint32 GenerateCRC(int32 b, int32 bufsize, uchar *buf); +uint32 GenerateCRCRecipe(uint32 b, void* buf, uint32 bufsize); + +#endif diff --git a/source/common/queue.h b/source/common/queue.h new file mode 100644 index 0000000..e29ea05 --- /dev/null +++ b/source/common/queue.h @@ -0,0 +1,128 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef QUEUE_H +#define QUEUE_H + +template +class MyQueue; + +template +class MyQueueNode +{ +public: + MyQueueNode(T* data) + { + next = 0; + this->data = data; + } + + friend class MyQueue; + +private: + T* data; + MyQueueNode* next; +}; + +template +class MyQueue +{ +public: + MyQueue() + { + head = tail = 0; + } + ~MyQueue() { + clear(); + } + + void push(T* data) + { + if (head == 0) + { + tail = head = new MyQueueNode(data); + } + else + { + tail->next = new MyQueueNode(data); + tail = tail->next; + } + } + + T* pop() + { + if (head == 0) + { + return 0; + } + + T* data = head->data; + MyQueueNode* next_node = head->next; + delete head; + head = next_node; + + return data; + } + + T* top() + { + if (head == 0) + { + return 0; + } + + return head->data; + } + + bool empty() + { + if (head == 0) + { + return true; + } + + return false; + } + + void clear() + { + T* d = 0; + while((d = pop())) { + delete d; + } + return; + } + + int count() + { + int count = 0; + MyQueueNode* d = head; + while(d != 0) { + count++; + d = d->next; + } + return(count); + } + +private: + MyQueueNode* head; + MyQueueNode* tail; +}; + +#endif diff --git a/source/common/seperator.h b/source/common/seperator.h new file mode 100644 index 0000000..b033aa8 --- /dev/null +++ b/source/common/seperator.h @@ -0,0 +1,165 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +// This class will split up a string smartly at the div character (default is space and tab) +// Seperator.arg[i] is a copy of the string chopped at the divs +// Seperator.argplus[i] is a pointer to the original string so it doesnt end at the div + +// Written by Quagmire +#ifndef SEPERATOR_H +#define SEPERATOR_H + +#include +#include + +class Seperator +{ +public: + Seperator(const char* message, char div = ' ', int16 in_maxargnum = 10, int16 arglen = 100, bool iObeyQuotes = false, char div2 = '\t', char div3 = 0, bool iSkipEmpty = true) { + int i; + argnum = 0; + msg = strdup(message); + this->maxargnum = in_maxargnum; + argplus = new const char *[maxargnum+1]; + arg = new char *[maxargnum+1]; + for (i=0; i<=maxargnum; i++) { + argplus[i]=arg[i] = new char[arglen+1]; + memset(arg[i], 0, arglen+1); + } + + int len = strlen(message); + int s = 0, l = 0; + bool inarg = (!iSkipEmpty || !(message[0] == div || message[0] == div2 || message[0] == div3)); + bool inquote = (iObeyQuotes && (message[0] == '\"' || message[0] == '\'')); + argplus[0] = message; + if (len == 0) + return; + + for (i=0; i= arglen) + l = arglen; + if (l){ + if(l > 1 && (argplus[argnum][0] == '\'' || argplus[argnum][0] == '\"')){ + l--; + memcpy(arg[argnum], argplus[argnum]+1, l); + } + else + memcpy(arg[argnum], argplus[argnum], l); + } + arg[argnum][l] = 0; + argnum++; + if (iSkipEmpty) + inarg = false; + else { + s=i+1; + argplus[argnum] = &message[s]; + } + } + } + else if (iObeyQuotes && (message[i] == '\"' || message[i] == '\'')) { + inquote = true; + } + else { + s = i; + argplus[argnum] = &message[s]; + if (!(message[i] == div || message[i] == div2 || message[i] == div3)) { + inarg = true; + } + } + if (argnum > maxargnum) + break; + } + if (inarg && argnum <= maxargnum) { + l = i-s; + if (l >= arglen) + l = arglen; + if (l) + memcpy(arg[argnum], argplus[argnum], l); + } + } + ~Seperator() { + for (int i=0; i<=maxargnum; i++) + safe_delete_array(arg[i]); + safe_delete_array(arg); + safe_delete_array(argplus); + if (msg) + free(msg); + } + int16 argnum; + char** arg; + const char** argplus; + char * msg; + bool IsSet(int num) const { + return IsSet(arg[num]); + } + bool IsNumber(int num) const { + return IsNumber(arg[num]); + } + bool IsHexNumber(int num) const { + return IsHexNumber(arg[num]); + } + static bool IsSet(const char *check) { + return check[0] != '\0'; + } + static bool IsNumber(const char* check) { + bool SeenDec = false; + int len = strlen(check); + if (len == 0) { + return false; + } + int i; + for (i = 0; i < len; i++) { + if (check[i] < '0' || check[i] > '9') { + if (check[i] == '.' && !SeenDec) { + SeenDec = true; + } + else if (i == 0 && (check[i] == '-' || check[i] == '+') && !check[i+1] == 0) { + // this is ok, do nothin + } + else { + return false; + } + } + } + return true; + } + static bool IsHexNumber(char* check) { + int len = strlen(check); + if (len < 3) + return false; + if (check[0] != '0' || (check[1] != 'x' && check[1] != 'X')) + return false; + for (int i=2; i '9') && (check[i] < 'A' || check[i] > 'F') && (check[i] < 'a' || check[i] > 'f')) + return false; + } + return true; + } + inline int16 GetMaxArgNum() const { return maxargnum; } + inline int16 GetArgNumber() const { return argnum; } +private: + int16 maxargnum; +}; + +#endif diff --git a/source/common/servertalk.h b/source/common/servertalk.h new file mode 100644 index 0000000..fa2c1c1 --- /dev/null +++ b/source/common/servertalk.h @@ -0,0 +1,754 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef EQ_SOPCODES_H +#define EQ_SOPCODES_H + +#define EQEMU_PROTOCOL_VERSION "0.5.0" + +#include "types.h" +#include "packet_functions.h" +#include + +#define SERVER_TIMEOUT 45000 // how often keepalive gets sent +#define INTERSERVER_TIMER 10000 +#define LoginServer_StatusUpdateInterval 15000 +#define LoginServer_AuthStale 60000 +#define AUTHCHANGE_TIMEOUT 900 // in seconds + +#define ServerOP_KeepAlive 0x0001 // packet to test if port is still open +#define ServerOP_ChannelMessage 0x0002 // broadcast/guildsay +#define ServerOP_SetZone 0x0003 // client -> server zoneinfo +#define ServerOP_ShutdownAll 0x0004 // exit(0); +#define ServerOP_ZoneShutdown 0x0005 // unload all data, goto sleep mode +#define ServerOP_ZoneBootup 0x0006 // come out of sleep mode and load zone specified +#define ServerOP_ZoneStatus 0x0007 // Shows status of all zones +#define ServerOP_SetConnectInfo 0x0008 // Tells server address and port # +#define ServerOP_EmoteMessage 0x0009 // Worldfarts +#define ServerOP_ClientList 0x000A // Update worldserver's client list, for #whos +#define ServerOP_Who 0x000B // #who +#define ServerOP_ZonePlayer 0x000C // #zone, or #summon +#define ServerOP_KickPlayer 0x000D // #kick +#define ServerOP_RefreshGuild 0x000E // Notice to all zoneservers to refresh their guild cache for ID# in packet +#define ServerOP_GuildKickAll 0x000F // Remove all clients from this guild +#define ServerOP_GuildInvite 0x0010 +#define ServerOP_GuildRemove 0x0011 +#define ServerOP_GuildPromote 0x0012 +#define ServerOP_GuildDemote 0x0013 +#define ServerOP_GuildLeader 0x0014 +#define ServerOP_GuildGMSet 0x0015 +#define ServerOP_GuildGMSetRank 0x0016 +#define ServerOP_FlagUpdate 0x0018 // GM Flag updated for character, refresh the memory cache +#define ServerOP_GMGoto 0x0019 +#define ServerOP_MultiLineMsg 0x001A +#define ServerOP_Lock 0x001B // For #lock/#unlock inside server +#define ServerOP_Motd 0x001C // For changing MoTD inside server. +#define ServerOP_Uptime 0x001D +#define ServerOP_Petition 0x001E +#define ServerOP_KillPlayer 0x001F +#define ServerOP_UpdateGM 0x0020 +#define ServerOP_RezzPlayer 0x0021 +#define ServerOP_ZoneReboot 0x0022 +#define ServerOP_ZoneToZoneRequest 0x0023 +#define ServerOP_AcceptWorldEntrance 0x0024 +#define ServerOP_ZAAuth 0x0025 +#define ServerOP_ZAAuthFailed 0x0026 +#define ServerOP_ZoneIncClient 0x0027 // Incomming client +#define ServerOP_ClientListKA 0x0028 +#define ServerOP_ChangeWID 0x0029 +#define ServerOP_IPLookup 0x002A +#define ServerOP_LockZone 0x002B +#define ServerOP_ItemStatus 0x002C +#define ServerOP_OOCMute 0x002D +#define ServerOP_Revoke 0x002E +#define ServerOP_GuildJoin 0x002F +#define ServerOP_GroupIDReq 0x0030 +#define ServerOP_GroupIDReply 0x0031 +#define ServerOP_GroupLeave 0x0032 // for disbanding out of zone folks +#define ServerOP_RezzPlayerAccept 0x0033 +#define ServerOP_SpawnCondition 0x0034 +#define ServerOP_SpawnEvent 0x0035 + +#define UpdateServerOP_Verified 0x5090 +#define UpdateServerOP_DisplayMsg 0x5091 +#define UpdateServerOP_Completed 0x5092 + +#define ServerOP_LSInfo 0x1000 +#define ServerOP_LSStatus 0x1001 +#define ServerOP_LSClientAuth 0x1002 +#define ServerOP_LSFatalError 0x1003 +#define ServerOP_SystemwideMessage 0x1005 +#define ServerOP_ListWorlds 0x1006 +#define ServerOP_PeerConnect 0x1007 + +#define ServerOP_LSZoneInfo 0x3001 +#define ServerOP_LSZoneStart 0x3002 +#define ServerOP_LSZoneBoot 0x3003 +#define ServerOP_LSZoneShutdown 0x3004 +#define ServerOP_LSZoneSleep 0x3005 +#define ServerOP_LSPlayerLeftWorld 0x3006 +#define ServerOP_LSPlayerJoinWorld 0x3007 +#define ServerOP_LSPlayerZoneChange 0x3008 + +#define ServerOP_UsertoWorldReq 0xAB00 +#define ServerOP_UsertoWorldResp 0xAB01 + +#define ServerOP_EncapPacket 0x2007 // Packet within a packet +#define ServerOP_WorldListUpdate 0x2008 +#define ServerOP_WorldListRemove 0x2009 +#define ServerOP_TriggerWorldListRefresh 0x200A + +#define ServerOP_WhoAll 0x0210 + +#define ServerOP_SetWorldTime 0x200B +#define ServerOP_GetWorldTime 0x200C +#define ServerOP_SyncWorldTime 0x200E + +//EQ2 Opcodes +#define ServerOP_CharTimeStamp 0x200F + +#define ServerOP_NameFilterCheck 0x2011 +#define ServerOP_BasicCharUpdate 0x2012 +#define ServerOP_CharacterCreate 0x2013 +#define ServerOP_NameCharUpdate 0x2014 +#define ServerOP_GetLatestTables 0x2015 +#define ServerOP_GetTableQuery 0x2016 +#define ServerOP_GetTableData 0x2017 +#define ServerOP_RaceUpdate 0x2018 +#define ServerOP_ZoneUpdate 0x2019 +#define ServerOP_BugReport 0x201A +#define ServerOP_ResetDatabase 0x201B +#define ServerOP_ZoneUpdates 0x201C +#define ServerOP_LoginEquipment 0x201D // updates charater select screen item appearances (gear appear) +#define ServerOP_CharacterPicture 0x201E + + +/************ PACKET RELATED STRUCT ************/ +class ServerPacket +{ +public: + ~ServerPacket() { safe_delete_array(pBuffer); } + ServerPacket(int16 in_opcode = 0, int32 in_size = 0) { + this->compressed = false; + size = in_size; + opcode = in_opcode; + if (size == 0) { + pBuffer = 0; + } + else { + pBuffer = new uchar[size]; + memset(pBuffer, 0, size); + } + destination = 0; + InflatedSize = 0; + } + ServerPacket* Copy() { + if (this == 0) { + return 0; + } + ServerPacket* ret = new ServerPacket(this->opcode, this->size); + if (this->size) + memcpy(ret->pBuffer, this->pBuffer, this->size); + ret->compressed = this->compressed; + ret->InflatedSize = this->InflatedSize; + return ret; + } + bool Deflate() { + if (compressed) + return false; + if ((!this->pBuffer) || (!this->size)) + return false; + uchar* tmp = new uchar[this->size + 128]; + int32 tmpsize = DeflatePacket(this->pBuffer, this->size, tmp, this->size + 128); + if (!tmpsize) { + safe_delete_array(tmp); + return false; + } + this->compressed = true; + this->InflatedSize = this->size; + this->size = tmpsize; + uchar* new_buffer = new uchar[this->size]; + memcpy(new_buffer, tmp, this->size); + safe_delete_array(tmp); + uchar* tmpdel = this->pBuffer; + this->pBuffer = new_buffer; + safe_delete_array(tmpdel); + return true; + } + bool Inflate() { + if (!compressed) + return false; + if ((!this->pBuffer) || (!this->size)) + return false; + uchar* tmp = new uchar[InflatedSize]; + int32 tmpsize = InflatePacket(this->pBuffer, this->size, tmp, InflatedSize); + if (!tmpsize) { + safe_delete_array(tmp); + return false; + } + compressed = false; + this->size = tmpsize; + uchar* tmpdel = this->pBuffer; + this->pBuffer = tmp; + safe_delete_array(tmpdel); + return true; + } + int32 size; + int16 opcode; + uchar* pBuffer; + bool compressed; + int32 InflatedSize; + int32 destination; +}; + +#pragma pack(1) + +struct GetLatestTables_Struct{ + float table_version; + float data_version; +}; + +struct ServerLSInfo_Struct { + char name[201]; // name the worldserver wants + char address[250]; // DNS address of the server + char account[31]; // account name for the worldserver + char password[256]; // password for the name + char protocolversion[25]; // Major protocol version number + char serverversion[64]; // minor server software version number + int8 servertype; // 0=world, 1=chat, 2=login, 3=MeshLogin, 4=World Debug + int32 dbversion; // database major+minor version from version.h (for PatchServer) +}; + +struct ServerLSStatus_Struct { + sint32 status; + sint32 num_players; + sint32 num_zones; + int8 world_max_level; +}; + +struct ServerSystemwideMessage { + int32 lsaccount_id; + char key[30]; // sessionID key for verification + int32 type; + char message[0]; +}; + +struct ServerSyncWorldList_Struct { + int32 RemoteID; + int32 ip; + sint32 status; + char name[201]; + char address[250]; + char account[31]; + int32 accountid; + int8 authlevel; + int8 servertype; // 0=world, 1=chat, 2=login + int32 adminid; + int8 showdown; + sint32 num_players; + sint32 num_zones; + bool placeholder; +}; + +struct UsertoWorldRequest_Struct { + int32 lsaccountid; + int32 char_id; + int32 worldid; + int32 FromID; + int32 ToID; + char ip_address[21]; +}; + +struct UsertoWorldResponse_Struct { + int32 lsaccountid; + int32 char_id; + int32 worldid; + int32 access_key; + int8 response; + char ip_address[80]; + int32 port; + int32 FromID; + int32 ToID; +}; + +struct ServerEncapPacket_Struct { + int32 ToID; // ID number of the LWorld on the other server + int16 opcode; + int16 size; + uchar data[0]; +}; + +struct ServerEmoteMessage_Struct { + char to[64]; + int32 guilddbid; + sint16 minstatus; + int32 type; + char message[0]; +}; + +/*struct TableVersion{ + char name[64]; + int32 version; + int32 max_table_version; + int32 max_data_version; + sint32 data_version; + int8 last; + char column_names[1000]; +};*/ + +typedef struct { + char name[256]; + unsigned int name_len; + unsigned int version; + unsigned int data_version; +} TableVersion; + +template void AddPtrData(string* buffer, Type& data){ + buffer->append((char*)&data, sizeof(Type)); +} +template void AddPtrData(string* buffer, Type* data, int16 size){ + buffer->append(data, size); +} +class LatestTableVersions { +public: + LatestTableVersions(){ + tables = 0; + current_index = 0; + total_tables = 0; + data_version = 0; + } + ~LatestTableVersions(){ + safe_delete_array(tables); + } + void SetTableSize(int16 size){ + total_tables = size; + tables = new TableVersion[total_tables]; + } + void AddTable(char* name, int32 version, int32 data_version){ + strcpy(tables[current_index].name, name); + tables[current_index].version = version; + tables[current_index].data_version = data_version; + current_index++; + } + int16 GetTotalSize(){ + return total_tables * sizeof(TableVersion) + sizeof(int16); + } + int16 GetTotalTables(){ + return total_tables; + } + TableVersion* GetTables(){ + return tables; + } + TableVersion GetTable(int16 index){ + return tables[index]; + } + string Serialize(){ + AddPtrData(&buffer, total_tables); + for(int16 i=0;i tmp_queries; +}; +class TableDataQuery{ +public: + TableDataQuery(char* table_name){ + if( strlen(table_name) >= sizeof(tablename) ) + return; + strcpy(tablename, table_name); + num_queries = 0; + columns_size = 0; + columns = 0; + version = 0; + table_size = 0; + } + TableDataQuery(){ + num_queries = 0; + columns_size = 0; + columns = 0; + version = 0; + table_size = 0; + } + ~TableDataQuery(){ + safe_delete_array(columns); + for(int32 i=0;iquery); + safe_delete(queries[i]); + } + } + int32 GetTotalQueries(){ + return num_queries; + } + string* Serialize(){ + buffer = ""; + num_queries = queries.size(); + if(GetTotalQueries() == 0) + return 0; + table_size = strlen(tablename); + AddPtrData(&buffer, table_size); + AddPtrData(&buffer, tablename, table_size + 1); + AddPtrData(&buffer, version); + if(num_queries > 200){ + int32 max_queries = 200; + AddPtrData(&buffer, max_queries); + } + else + AddPtrData(&buffer, num_queries); + AddPtrData(&buffer, columns_size); + AddPtrData(&buffer, columns, columns_size); + vector::iterator query_iterator; + int16 count = 0; + for(int i=GetTotalQueries() - 1;i >=0 && count < 200;i--){ + AddPtrData(&buffer, queries[i]->size); + AddPtrData(&buffer, queries[i]->query, queries[i]->size); + safe_delete_array(queries[i]->query); + safe_delete(queries[i]); + queries.pop_back(); + count++; + } + return &buffer; + } + void DeSerialize(uchar* data){ + uchar* ptr = data; + + memcpy(&table_size, ptr, sizeof(table_size)); + ptr+= sizeof(table_size); + memcpy(&tablename, ptr, table_size + 1); + ptr+= table_size + 1; + + memcpy(&version, ptr, sizeof(version)); + ptr+= sizeof(version); + + memcpy(&num_queries, ptr, sizeof(num_queries)); + ptr+= sizeof(num_queries); + + memcpy(&columns_size, ptr, sizeof(columns_size)); + ptr+= sizeof(columns_size); + columns = new char[columns_size + 1]; + memcpy(columns, ptr, columns_size + 1); + ptr+= columns_size; + + for(int32 i=0;isize, ptr, sizeof(new_query->size)); + ptr+= sizeof(new_query->size); + new_query->query = new char[new_query->size + 1]; + memcpy(new_query->query, ptr, new_query->size); + ptr+= new_query->size; + queries.push_back(new_query); + } + catch( bad_alloc &ba ) + { + cout << ba.what() << endl; + if( NULL != new_query ) + delete new_query; + } + } + } + string buffer; + int32 num_queries; + int32 version; + int16 table_size; + char tablename[64]; + int16 columns_size; + char* columns; + vector queries; +}; + +// Max number of equipment updates to send at once +struct EquipmentUpdateRequest_Struct +{ + int16 max_per_batch; +}; + +// Login's structure of equipment data +struct LoginEquipmentUpdate +{ + int32 world_char_id; + int16 equip_type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; + int32 slot; +}; + +// World's structure of equipment data +struct EquipmentUpdate_Struct +{ + int32 id; // unique record identifier per world + int32 world_char_id; + int16 equip_type; + int8 red; + int8 green; + int8 blue; + int8 highlight_red; + int8 highlight_green; + int8 highlight_blue; + int32 slot; +}; + +// How many equipmment updates are there to send? +struct EquipmentUpdateList_Struct +{ + sint16 total_updates; +}; + +struct ZoneUpdateRequest_Struct{ + int16 max_per_batch; +}; + +struct LoginZoneUpdate{ + string name; + string description; +}; + +struct ZoneUpdate_Struct{ + int32 zone_id; + int8 zone_name_length; + int8 zone_desc_length; + char data[0]; +}; + +struct ZoneUpdateList_Struct{ + uint16 total_updates; + char data[0]; +}; + +//EQ2 Specific Structures Login -> World (Image) +struct CharacterTimeStamp_Struct { +int32 char_id; +int32 account_id; +int32 unix_timestamp; +}; + +//EQ2 Specific Structures World -> Login (Image) + +/**UPDATE_FIELD TYPES** +These will be stored beside the timestamp on the world server to determine what has changed on between the timestamp, when the update is sent, it will remove the flag. + +8 bits in a byte: +Example: 01001100 +0 Level Flag +1 Race Flag +0 Class Flag +0 Gender Flag +1 Zone Flag +1 Armor Flag +0 Name Flag +0 Delete Flag +**/ +#define LEVEL_UPDATE_FLAG 1 +#define RACE_UPDATE_FLAG 2 +#define CLASS_UPDATE_FLAG 4 +#define GENDER_UPDATE_FLAG 8 +#define ZONE_UPDATE_FLAG 16 +#define ARMOR_UPDATE_FLAG 32 +#define NAME_UPDATE_FLAG 64 +#define DELETE_UPDATE_FLAG 128 +//This structure used for basic changes such as level,class,gender, and deletes that are not able to be backed up +struct CharDataUpdate_Struct { +int32 account_id; +int32 char_id; +int8 update_field; +int32 update_data; +}; +struct BugReport{ +char category[64]; +char subcategory[64]; +char causes_crash[64]; +char reproducible[64]; +char summary[128]; +char description[2000]; +char version[32]; +char player[64]; +int32 account_id; +char spawn_name[64]; +int32 spawn_id; +int32 zone_id; +}; + +struct RaceUpdate_Struct { +int32 account_id; +int32 char_id; +int16 model_type; +int8 race; +}; + +//If this structure comes in with more than 74 bytes, should probably discard (leaves 65 bytes for new_name) +#define CHARNAMEUPDATESTRUCT_MAXSIZE 74 +struct CharNameUpdate_Struct { +int32 account_id; +int32 char_id; +int8 name_length; // If its longer than 64, something is wrong :-/ +char new_name[0]; +}; + +//If this structure comes in with more than 78 bytes, should probably discard (leaves 65 bytes for new_zone) +#define CHARZONESTRUCT_MAXSIZE 78 +struct CharZoneUpdate_Struct { +int32 account_id; +int32 char_id; +int32 zone_id; +int8 zone_length; // If its longer than 64, something is wrong :-/ +char new_zone[0]; +}; + +struct WorldCharCreate_Struct { +int32 account_id; +int32 char_id; +int16 model_type; +int16 char_size; +uchar character[0]; +}; + +struct WorldCharNameFilter_Struct { +int32 account_id; +int16 name_length; +uchar name[0]; +}; + +struct WorldCharNameFilterResponse_Struct { +int32 account_id; +int32 char_id; +int8 response; +}; + +#define CHARPICSTRUCT_MINSIZE 10 +// Should only be used for the headshot picture +struct CharPictureUpdate_Struct { + int32 account_id; + int32 char_id; + int16 pic_size; + char pic[0]; +}; + +#pragma pack() + +#endif diff --git a/source/common/sha512.cpp b/source/common/sha512.cpp new file mode 100644 index 0000000..10b9592 --- /dev/null +++ b/source/common/sha512.cpp @@ -0,0 +1,155 @@ +#include +#include +#include "sha512.h" + +const unsigned long long SHA512::sha512_k[80] = //ULL = uint64 + {0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, + 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, + 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, + 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, + 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, + 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, + 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, + 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, + 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, + 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, + 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, + 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, + 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, + 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, + 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, + 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, + 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, + 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, + 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, + 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, + 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL}; + +void SHA512::transform(const unsigned char *message, unsigned int block_nb) +{ + uint64 w[80]; + uint64 wv[8]; + uint64 t1, t2; + const unsigned char *sub_block; + int i, j; + for (i = 0; i < (int) block_nb; i++) { + sub_block = message + (i << 7); + for (j = 0; j < 16; j++) { + SHA2_PACK64(&sub_block[j << 3], &w[j]); + } + for (j = 16; j < 80; j++) { + w[j] = SHA512_F4(w[j - 2]) + w[j - 7] + SHA512_F3(w[j - 15]) + w[j - 16]; + } + for (j = 0; j < 8; j++) { + wv[j] = m_h[j]; + } + for (j = 0; j < 80; j++) { + t1 = wv[7] + SHA512_F2(wv[4]) + SHA2_CH(wv[4], wv[5], wv[6]) + + sha512_k[j] + w[j]; + t2 = SHA512_F1(wv[0]) + SHA2_MAJ(wv[0], wv[1], wv[2]); + wv[7] = wv[6]; + wv[6] = wv[5]; + wv[5] = wv[4]; + wv[4] = wv[3] + t1; + wv[3] = wv[2]; + wv[2] = wv[1]; + wv[1] = wv[0]; + wv[0] = t1 + t2; + } + for (j = 0; j < 8; j++) { + m_h[j] += wv[j]; + } + + } +} + +void SHA512::init() +{ + m_h[0] = 0x6a09e667f3bcc908ULL; + m_h[1] = 0xbb67ae8584caa73bULL; + m_h[2] = 0x3c6ef372fe94f82bULL; + m_h[3] = 0xa54ff53a5f1d36f1ULL; + m_h[4] = 0x510e527fade682d1ULL; + m_h[5] = 0x9b05688c2b3e6c1fULL; + m_h[6] = 0x1f83d9abfb41bd6bULL; + m_h[7] = 0x5be0cd19137e2179ULL; + m_len = 0; + m_tot_len = 0; +} + +void SHA512::update(const unsigned char *message, unsigned int len) +{ + unsigned int block_nb; + unsigned int new_len, rem_len, tmp_len; + const unsigned char *shifted_message; + tmp_len = SHA384_512_BLOCK_SIZE - m_len; + rem_len = len < tmp_len ? len : tmp_len; + memcpy(&m_block[m_len], message, rem_len); + if (m_len + len < SHA384_512_BLOCK_SIZE) { + m_len += len; + return; + } + new_len = len - rem_len; + block_nb = new_len / SHA384_512_BLOCK_SIZE; + shifted_message = message + rem_len; + transform(m_block, 1); + transform(shifted_message, block_nb); + rem_len = new_len % SHA384_512_BLOCK_SIZE; + memcpy(m_block, &shifted_message[block_nb << 7], rem_len); + m_len = rem_len; + m_tot_len += (block_nb + 1) << 7; +} + +void SHA512::final(unsigned char *digest) +{ + unsigned int block_nb; + unsigned int pm_len; + unsigned int len_b; + int i; + block_nb = 1 + ((SHA384_512_BLOCK_SIZE - 17) + < (m_len % SHA384_512_BLOCK_SIZE)); + len_b = (m_tot_len + m_len) << 3; + pm_len = block_nb << 7; + memset(m_block + m_len, 0, pm_len - m_len); + m_block[m_len] = 0x80; + SHA2_UNPACK32(len_b, m_block + pm_len - 4); + transform(m_block, block_nb); + for (i = 0 ; i < 8; i++) { + SHA2_UNPACK64(m_h[i], &digest[i << 3]); + } +} + +std::string sha512(std::string input) +{ + unsigned char digest[SHA512::DIGEST_SIZE]; + memset(digest,0,SHA512::DIGEST_SIZE); + SHA512 ctx = SHA512(); + ctx.init(); + ctx.update((unsigned char*)input.c_str(), input.length()); + ctx.final(digest); + + char buf[2*SHA512::DIGEST_SIZE+1]; + buf[2*SHA512::DIGEST_SIZE] = 0; + for (int i = 0; i < SHA512::DIGEST_SIZE; i++) + sprintf(buf+i*2, "%02x", digest[i]); + return std::string(buf); +} \ No newline at end of file diff --git a/source/common/sha512.h b/source/common/sha512.h new file mode 100644 index 0000000..72ce5a8 --- /dev/null +++ b/source/common/sha512.h @@ -0,0 +1,71 @@ +#ifndef SHA512_H +#define SHA512_H +#include + +class SHA512 +{ +protected: + typedef unsigned char uint8; + typedef unsigned int uint32; + typedef unsigned long long uint64; + + const static uint64 sha512_k[]; + static const unsigned int SHA384_512_BLOCK_SIZE = (1024/8); + +public: + void init(); + void update(const unsigned char *message, unsigned int len); + void final(unsigned char *digest); + static const unsigned int DIGEST_SIZE = ( 512 / 8); + +protected: + void transform(const unsigned char *message, unsigned int block_nb); + unsigned int m_tot_len; + unsigned int m_len; + unsigned char m_block[2 * SHA384_512_BLOCK_SIZE]; + uint64 m_h[8]; +}; + + +std::string sha512(std::string input); + +#define SHA2_SHFR(x, n) (x >> n) +#define SHA2_ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) +#define SHA2_ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) +#define SHA2_CH(x, y, z) ((x & y) ^ (~x & z)) +#define SHA2_MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) +#define SHA512_F1(x) (SHA2_ROTR(x, 28) ^ SHA2_ROTR(x, 34) ^ SHA2_ROTR(x, 39)) +#define SHA512_F2(x) (SHA2_ROTR(x, 14) ^ SHA2_ROTR(x, 18) ^ SHA2_ROTR(x, 41)) +#define SHA512_F3(x) (SHA2_ROTR(x, 1) ^ SHA2_ROTR(x, 8) ^ SHA2_SHFR(x, 7)) +#define SHA512_F4(x) (SHA2_ROTR(x, 19) ^ SHA2_ROTR(x, 61) ^ SHA2_SHFR(x, 6)) +#define SHA2_UNPACK32(x, str) \ +{ \ + *((str) + 3) = (uint8) ((x) ); \ + *((str) + 2) = (uint8) ((x) >> 8); \ + *((str) + 1) = (uint8) ((x) >> 16); \ + *((str) + 0) = (uint8) ((x) >> 24); \ +} +#define SHA2_UNPACK64(x, str) \ +{ \ + *((str) + 7) = (uint8) ((x) ); \ + *((str) + 6) = (uint8) ((x) >> 8); \ + *((str) + 5) = (uint8) ((x) >> 16); \ + *((str) + 4) = (uint8) ((x) >> 24); \ + *((str) + 3) = (uint8) ((x) >> 32); \ + *((str) + 2) = (uint8) ((x) >> 40); \ + *((str) + 1) = (uint8) ((x) >> 48); \ + *((str) + 0) = (uint8) ((x) >> 56); \ +} +#define SHA2_PACK64(str, x) \ +{ \ + *(x) = ((uint64) *((str) + 7) ) \ + | ((uint64) *((str) + 6) << 8) \ + | ((uint64) *((str) + 5) << 16) \ + | ((uint64) *((str) + 4) << 24) \ + | ((uint64) *((str) + 3) << 32) \ + | ((uint64) *((str) + 2) << 40) \ + | ((uint64) *((str) + 1) << 48) \ + | ((uint64) *((str) + 0) << 56); \ +} + +#endif \ No newline at end of file diff --git a/source/common/string_util.cpp b/source/common/string_util.cpp new file mode 100644 index 0000000..df3790d --- /dev/null +++ b/source/common/string_util.cpp @@ -0,0 +1,529 @@ +/* + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "string_util.h" +#include + +#ifdef _WINDOWS + #include + + #define snprintf _snprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + +#else + #include + #include +#include + +#endif + +#ifndef va_copy + #define va_copy(d,s) ((d) = (s)) +#endif + +// original source: +// https://github.com/facebook/folly/blob/master/folly/String.cpp +// +const std::string vStringFormat(const char* format, va_list args) +{ + std::string output; + va_list tmpargs; + + va_copy(tmpargs,args); + int characters_used = vsnprintf(nullptr, 0, format, tmpargs); + va_end(tmpargs); + + // Looks like we have a valid format string. + if (characters_used > 0) { + output.resize(characters_used + 1); + + va_copy(tmpargs,args); + characters_used = vsnprintf(&output[0], output.capacity(), format, tmpargs); + va_end(tmpargs); + + output.resize(characters_used); + + // We shouldn't have a format error by this point, but I can't imagine what error we + // could have by this point. Still, return empty string; + if (characters_used < 0) + output.clear(); + } + return output; +} + +const std::string str_tolower(std::string s) +{ + std::transform( + s.begin(), s.end(), s.begin(), + [](unsigned char c) { return ::tolower(c); } + ); + return s; +} + +std::vector split(std::string str_to_split, char delimiter) +{ + std::stringstream ss(str_to_split); + std::string item; + std::vector exploded_values; + while (std::getline(ss, item, delimiter)) { + exploded_values.push_back(item); + } + + return exploded_values; +} + +const std::string str_toupper(std::string s) +{ + std::transform( + s.begin(), s.end(), s.begin(), + [](unsigned char c) { return ::toupper(c); } + ); + return s; +} + +const std::string ucfirst(std::string s) +{ + std::string output = s; + if (!s.empty()) + output[0] = static_cast(::toupper(s[0])); + + return output; +} + +const std::string StringFormat(const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string output = vStringFormat(format, args); + va_end(args); + return output; +} + +std::vector SplitString(const std::string &str, char delim) { + std::vector ret; + std::stringstream ss(str); + std::string item; + + while(std::getline(ss, item, delim)) { + ret.push_back(item); + } + + return ret; +} + +std::string implode(std::string glue, std::vector src) +{ + if (src.empty()) { + return {}; + } + + std::ostringstream output; + std::vector::iterator src_iter; + + for (src_iter = src.begin(); src_iter != src.end(); src_iter++) { + output << *src_iter << glue; + } + + std::string final_output = output.str(); + final_output.resize (output.str().size () - glue.size()); + + return final_output; +} + +std::string EscapeString(const std::string &s) { + std::string ret; + + size_t sz = s.length(); + for(size_t i = 0; i < sz; ++i) { + char c = s[i]; + switch(c) { + case '\x00': + ret += "\\x00"; + break; + case '\n': + ret += "\\n"; + break; + case '\r': + ret += "\\r"; + break; + case '\\': + ret += "\\\\"; + break; + case '\'': + ret += "\\'"; + break; + case '\"': + ret += "\\\""; + break; + case '\x1a': + ret += "\\x1a"; + break; + default: + ret.push_back(c); + break; + } + } + + return ret; +} + +std::string EscapeString(const char *src, size_t sz) { + std::string ret; + + for(size_t i = 0; i < sz; ++i) { + char c = src[i]; + switch(c) { + case '\x00': + ret += "\\x00"; + break; + case '\n': + ret += "\\n"; + break; + case '\r': + ret += "\\r"; + break; + case '\\': + ret += "\\\\"; + break; + case '\'': + ret += "\\'"; + break; + case '\"': + ret += "\\\""; + break; + case '\x1a': + ret += "\\x1a"; + break; + default: + ret.push_back(c); + break; + } + } + + return ret; +} + +bool StringIsNumber(const std::string &s) { + try { + auto r = stod(s); + return true; + } + catch (std::exception &) { + return false; + } +} + +void ToLowerString(std::string &s) { + std::transform(s.begin(), s.end(), s.begin(), ::tolower); +} + +void ToUpperString(std::string &s) { + std::transform(s.begin(), s.end(), s.begin(), ::toupper); +} + +std::string JoinString(const std::vector& ar, const std::string &delim) { + std::string ret; + for (size_t i = 0; i < ar.size(); ++i) { + if (i != 0) { + ret += delim; + } + + ret += ar[i]; + } + + return ret; +} + +void find_replace(std::string &string_subject, const std::string &search_string, const std::string &replace_string) +{ + if (string_subject.find(search_string) == std::string::npos) { + return; + } + + size_t start_pos = 0; + while((start_pos = string_subject.find(search_string, start_pos)) != std::string::npos) { + string_subject.replace(start_pos, search_string.length(), replace_string); + start_pos += replace_string.length(); + } + +} + +void ParseAccountString(const std::string &s, std::string &account, std::string &loginserver) +{ + auto split = SplitString(s, ':'); + if (split.size() == 2) { + loginserver = split[0]; + account = split[1]; + } + else if(split.size() == 1) { + account = split[0]; + } +} + +//Const char based + +// normal strncpy doesnt put a null term on copied strings, this one does +// ref: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcecrt/htm/_wcecrt_strncpy_wcsncpy.asp +char* strn0cpy(char* dest, const char* source, uint32 size) { + if (!dest) + return 0; + if (size == 0 || source == 0) { + dest[0] = 0; + return dest; + } + strncpy(dest, source, size); + dest[size - 1] = 0; + return dest; +} + +// String N w/null Copy Truncated? +// return value =true if entire string(source) fit, false if it was truncated +bool strn0cpyt(char* dest, const char* source, uint32 size) { + if (!dest) + return 0; + if (size == 0 || source == 0) { + dest[0] = 0; + return false; + } + strncpy(dest, source, size); + dest[size - 1] = 0; + return (bool)(source[strlen(dest)] == 0); +} + +const char *MakeLowerString(const char *source) { + static char str[128]; + if (!source) + return nullptr; + MakeLowerString(source, str); + return str; +} + +void MakeLowerString(const char *source, char *target) { + if (!source || !target) { + *target = 0; + return; + } + while (*source) + { + *target = tolower(*source); + target++; source++; + } + *target = 0; +} + +int MakeAnyLenString(char** ret, const char* format, ...) { + int buf_len = 128; + int chars = -1; + va_list argptr, tmpargptr; + va_start(argptr, format); + while (chars == -1 || chars >= buf_len) { + safe_delete_array(*ret); + if (chars == -1) + buf_len *= 2; + else + buf_len = chars + 1; + *ret = new char[buf_len]; + va_copy(tmpargptr, argptr); + chars = vsnprintf(*ret, buf_len, format, tmpargptr); + } + va_end(argptr); + return chars; +} + +uint32 AppendAnyLenString(char** ret, uint32* bufsize, uint32* strlen, const char* format, ...) { + if (*bufsize == 0) + *bufsize = 256; + if (*ret == 0) + *strlen = 0; + int chars = -1; + char* oldret = 0; + va_list argptr, tmpargptr; + va_start(argptr, format); + while (chars == -1 || chars >= (int32)(*bufsize - *strlen)) { + if (chars == -1) + *bufsize += 256; + else + *bufsize += chars + 25; + oldret = *ret; + *ret = new char[*bufsize]; + if (oldret) { + if (*strlen) + memcpy(*ret, oldret, *strlen); + safe_delete_array(oldret); + } + va_copy(tmpargptr, argptr); + chars = vsnprintf(&(*ret)[*strlen], (*bufsize - *strlen), format, tmpargptr); + } + va_end(argptr); + *strlen += chars; + return *strlen; +} + +uint32 hextoi(const char* num) { + if (num == nullptr) + return 0; + + int len = strlen(num); + if (len < 3) + return 0; + + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + return 0; + + uint32 ret = 0; + int mul = 1; + for (int i = len - 1; i >= 2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') + ret += ((num[i] - 'A') + 10) * mul; + else if (num[i] >= 'a' && num[i] <= 'f') + ret += ((num[i] - 'a') + 10) * mul; + else if (num[i] >= '0' && num[i] <= '9') + ret += (num[i] - '0') * mul; + else + return 0; + mul *= 16; + } + return ret; +} + +uint64 hextoi64(const char* num) { + if (num == nullptr) + return 0; + + int len = strlen(num); + if (len < 3) + return 0; + + if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X')) + return 0; + + uint64 ret = 0; + int mul = 1; + for (int i = len - 1; i >= 2; i--) { + if (num[i] >= 'A' && num[i] <= 'F') + ret += ((num[i] - 'A') + 10) * mul; + else if (num[i] >= 'a' && num[i] <= 'f') + ret += ((num[i] - 'a') + 10) * mul; + else if (num[i] >= '0' && num[i] <= '9') + ret += (num[i] - '0') * mul; + else + return 0; + mul *= 16; + } + return ret; +} + +bool atobool(const char* iBool) { + + if (iBool == nullptr) + return false; + if (!strcasecmp(iBool, "true")) + return true; + if (!strcasecmp(iBool, "false")) + return false; + if (!strcasecmp(iBool, "yes")) + return true; + if (!strcasecmp(iBool, "no")) + return false; + if (!strcasecmp(iBool, "on")) + return true; + if (!strcasecmp(iBool, "off")) + return false; + if (!strcasecmp(iBool, "enable")) + return true; + if (!strcasecmp(iBool, "disable")) + return false; + if (!strcasecmp(iBool, "enabled")) + return true; + if (!strcasecmp(iBool, "disabled")) + return false; + if (!strcasecmp(iBool, "y")) + return true; + if (!strcasecmp(iBool, "n")) + return false; + if (atoi(iBool)) + return true; + return false; +} + +// removes the crap and turns the underscores into spaces. +char *CleanMobName(const char *in, char *out) +{ + unsigned i, j; + + for (i = j = 0; i < strlen(in); i++) + { + // convert _ to space.. any other conversions like this? I *think* this + // is the only non alpha char that's not stripped but converted. + if (in[i] == '_') + { + out[j++] = ' '; + } + else + { + if (isalpha(in[i]) || (in[i] == '`')) // numbers, #, or any other crap just gets skipped + out[j++] = in[i]; + } + } + out[j] = 0; // terimnate the string before returning it + return out; +} + + +void RemoveApostrophes(std::string &s) +{ + for (unsigned int i = 0; i < s.length(); ++i) + if (s[i] == '\'') + s[i] = '_'; +} + +char *RemoveApostrophes(const char *s) +{ + auto NewString = new char[strlen(s) + 1]; + + strcpy(NewString, s); + + for (unsigned int i = 0; i < strlen(NewString); ++i) + if (NewString[i] == '\'') + NewString[i] = '_'; + + return NewString; +} + +const char *ConvertArray(int input, char *returnchar) +{ + sprintf(returnchar, "%i", input); + return returnchar; +} + +const char *ConvertArrayF(float input, char *returnchar) +{ + sprintf(returnchar, "%0.2f", input); + return returnchar; +} + +bool isAlphaNumeric(const char *text) +{ + for (unsigned int charIndex = 0; charIndex 'z') && + (text[charIndex] < 'A' || text[charIndex] > 'Z') && + (text[charIndex] < '0' || text[charIndex] > '9')) + return false; + } + + return true; +} diff --git a/source/common/string_util.h b/source/common/string_util.h new file mode 100644 index 0000000..037d6a2 --- /dev/null +++ b/source/common/string_util.h @@ -0,0 +1,193 @@ +/* + * Copyright 2013 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _STRINGUTIL_H_ +#define _STRINGUTIL_H_ + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +// this doesn't appear to affect linux-based systems..need feedback for _WIN64 +#include +#endif + +#ifdef _WINDOWS +#include +#include +#include +#endif + +#include "types.h" + +//std::string based +const std::string str_tolower(std::string s); +const std::string str_toupper(std::string s); +const std::string ucfirst(std::string s); +std::vector split(std::string str_to_split, char delimiter); +const std::string StringFormat(const char* format, ...); +const std::string vStringFormat(const char* format, va_list args); +std::string implode(std::string glue, std::vector src); + +/** + * @param str + * @param chars + * @return + */ +inline std::string <rim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + str.erase(0, str.find_first_not_of(chars)); + return str; +} + +/** + * @param str + * @param chars + * @return + */ +inline std::string &rtrim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + str.erase(str.find_last_not_of(chars) + 1); + return str; +} + +/** + * @param str + * @param chars + * @return + */ +inline std::string &trim(std::string &str, const std::string &chars = "\t\n\v\f\r ") +{ + return ltrim(rtrim(str, chars), chars); +} + +template +std::string implode(const std::string &glue, const std::pair &encapsulation, const std::vector &src) +{ + if (src.empty()) { + return {}; + } + + std::ostringstream oss; + + for (const T &src_iter : src) { + oss << encapsulation.first << src_iter << encapsulation.second << glue; + } + + std::string output(oss.str()); + output.resize(output.size() - glue.size()); + + return output; +} + +// _WIN32 builds require that #include be included in whatever code file the invocation is made from (no header files) +template +std::vector join_pair(const std::string &glue, const std::pair &encapsulation, const std::vector> &src) +{ + if (src.empty()) { + return {}; + } + + std::vector output; + + for (const std::pair &src_iter : src) { + output.push_back( + + fmt::format( + "{}{}{}{}{}{}{}", + encapsulation.first, + src_iter.first, + encapsulation.second, + glue, + encapsulation.first, + src_iter.second, + encapsulation.second + ) + ); + } + + return output; +} + +// _WIN32 builds require that #include be included in whatever code file the invocation is made from (no header files) +template +std::vector join_tuple(const std::string &glue, const std::pair &encapsulation, const std::vector> &src) +{ + if (src.empty()) { + return {}; + } + + std::vector output; + + for (const std::tuple &src_iter : src) { + + output.push_back( + + fmt::format( + "{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", + encapsulation.first, + std::get<0>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<1>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<2>(src_iter), + encapsulation.second, + glue, + encapsulation.first, + std::get<3>(src_iter), + encapsulation.second + ) + ); + } + + return output; +} + +std::vector SplitString(const std::string &s, char delim); +std::string EscapeString(const char *src, size_t sz); +std::string EscapeString(const std::string &s); +bool StringIsNumber(const std::string &s); +void ToLowerString(std::string &s); +void ToUpperString(std::string &s); +std::string JoinString(const std::vector& ar, const std::string &delim); +void find_replace(std::string& string_subject, const std::string& search_string, const std::string& replace_string); +void ParseAccountString(const std::string &s, std::string &account, std::string &loginserver); + +//const char based + +bool atobool(const char* iBool); +bool isAlphaNumeric(const char *text); +bool strn0cpyt(char* dest, const char* source, uint32 size); +char *CleanMobName(const char *in, char *out); +char *RemoveApostrophes(const char *s); +char* strn0cpy(char* dest, const char* source, uint32 size); +const char *ConvertArray(int input, char *returnchar); +const char *ConvertArrayF(float input, char *returnchar); +const char *MakeLowerString(const char *source); +int MakeAnyLenString(char** ret, const char* format, ...); +uint32 AppendAnyLenString(char** ret, uint32* bufsize, uint32* strlen, const char* format, ...); +uint32 hextoi(const char* num); +uint64 hextoi64(const char* num); +void MakeLowerString(const char *source, char *target); +void RemoveApostrophes(std::string &s); + +#endif diff --git a/source/common/timer.cpp b/source/common/timer.cpp new file mode 100644 index 0000000..e9c08ef --- /dev/null +++ b/source/common/timer.cpp @@ -0,0 +1,207 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "../common/debug.h" +// Disgrace: for windows compile +#ifndef WIN32 + #include +#else + #include +#endif + +#include +using namespace std; + +#include "timer.h" + +int32 started_unix_timestamp = 0; +int32 current_time = 0; +int32 last_time = 0; + +Timer::Timer(){ + timer_time = 30000; //default to 30 seconds + start_time = current_time; + set_at_trigger = timer_time; + pUseAcurateTiming = false; + enabled = false; +} +Timer::Timer(int32 in_timer_time, bool iUseAcurateTiming) { + timer_time = in_timer_time; + start_time = current_time; + set_at_trigger = timer_time; + pUseAcurateTiming = iUseAcurateTiming; + if (timer_time == 0) { + enabled = false; + } + else { + enabled = true; + } +} + +Timer::Timer(int32 start, int32 timer, bool iUseAcurateTiming = false) { + timer_time = timer; + start_time = start; + set_at_trigger = timer_time; + pUseAcurateTiming = iUseAcurateTiming; + if (timer_time == 0) { + enabled = false; + } + else { + enabled = true; + } +} + +/* Reimplemented for MSVC - Bounce */ +#ifdef WIN32 +int gettimeofday (timeval *tp, ...) +{ + timeb tb; + + ftime (&tb); + + tp->tv_sec = tb.time; + tp->tv_usec = tb.millitm * 1000; + + return 0; +} +#endif + +/* This function checks if the timer triggered */ +bool Timer::Check(bool iReset) +{ + if (enabled && current_time-start_time > timer_time) { + if (iReset) { + if (pUseAcurateTiming) + start_time += timer_time; + else + start_time = current_time; // Reset timer + timer_time = set_at_trigger; + } + return true; + } + + return false; +} + +/* This function disables the timer */ +void Timer::Disable() { + enabled = false; +} + +void Timer::Enable() { + enabled = true; +} + +/* This function set the timer and restart it */ +void Timer::Start(int32 set_timer_time, bool ChangeResetTimer) { + start_time = current_time; + enabled = true; + if (set_timer_time != 0) + { + timer_time = set_timer_time; + if (ChangeResetTimer) + set_at_trigger = set_timer_time; + } +} + +/* This timer updates the timer without restarting it */ +void Timer::SetTimer(int32 set_timer_time) { + /* If we were disabled before => restart the timer */ + if (!enabled) { + start_time = current_time; + enabled = true; + } + if (set_timer_time != 0) { + timer_time = set_timer_time; + set_at_trigger = set_timer_time; + } +} + +int32 Timer::GetElapsedTime(){ + if (enabled) { + return current_time - start_time; + } + else { + return 0xFFFFFFFF; + } +} + +int32 Timer::GetRemainingTime() { + if (enabled) { + if (current_time-start_time > timer_time) + return 0; + else + return (start_time + timer_time) - current_time; + } + else { + return 0xFFFFFFFF; + } +} + +void Timer::SetAtTrigger(int32 in_set_at_trigger, bool iEnableIfDisabled) { + set_at_trigger = in_set_at_trigger; + if (!Enabled() && iEnableIfDisabled) { + Enable(); + } +} + +void Timer::Trigger() +{ + enabled = true; + + timer_time = set_at_trigger; + start_time = current_time-timer_time-1; +} + +const int32& Timer::GetCurrentTime2() +{ + return current_time; +} + +const int32& Timer::SetCurrentTime() +{ + struct timeval read_time; + int32 this_time; + + gettimeofday(&read_time,0); + if(started_unix_timestamp == 0) + started_unix_timestamp = read_time.tv_sec; + + this_time = (read_time.tv_sec - started_unix_timestamp) * 1000 + read_time.tv_usec / 1000; + + if (last_time == 0) + { + current_time = 0; + } + else + { + current_time += this_time - last_time; + } + + last_time = this_time; + +// cerr << "Current time:" << current_time << endl; + return current_time; +} + +int32 Timer::GetUnixTimeStamp(){ + struct timeval read_time; + gettimeofday(&read_time,0); + return read_time.tv_sec; +} diff --git a/source/common/timer.h b/source/common/timer.h new file mode 100644 index 0000000..a70a2d2 --- /dev/null +++ b/source/common/timer.h @@ -0,0 +1,88 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef TIMER_H +#define TIMER_H + +#include "types.h" +#include + +// Disgrace: for windows compile +#ifdef WIN32 + #include + #include + int gettimeofday (timeval *tp, ...); +#endif + +class Timer +{ +public: + Timer(); + Timer(int32 timer_time, bool iUseAcurateTiming = false); + Timer(int32 start, int32 timer, bool iUseAcurateTiming); + ~Timer() { } + + bool Check(bool iReset = true); + void Enable(); + void Disable(); + void Start(int32 set_timer_time=0, bool ChangeResetTimer = true); + void SetTimer(int32 set_timer_time=0); + int32 GetRemainingTime(); + int32 GetElapsedTime(); + inline const int32& GetTimerTime() { return timer_time; } + inline const int32& GetSetAtTrigger() { return set_at_trigger; } + void Trigger(); + void SetAtTrigger(int32 set_at_trigger, bool iEnableIfDisabled = false); + + inline bool Enabled() { return enabled; } + inline int32 GetStartTime() { return(start_time); } + inline int32 GetDuration() { return(timer_time); } + + static const int32& SetCurrentTime(); + static const int32& GetCurrentTime2(); + static int32 GetUnixTimeStamp(); + +private: + int32 start_time; + int32 timer_time; + bool enabled; + int32 set_at_trigger; + + // Tells the timer to be more acurate about happening every X ms. + // Instead of Check() setting the start_time = now, + // it it sets it to start_time += timer_time + bool pUseAcurateTiming; + +// static int32 current_time; +// static int32 last_time; +}; + +struct BenchTimer +{ + typedef std::chrono::high_resolution_clock clock; + + BenchTimer() : start_time(clock::now()) {} + void reset() { start_time = clock::now(); } + // this is seconds + double elapsed() { return std::chrono::duration(clock::now() - start_time).count(); } +private: + std::chrono::time_point start_time; +}; + +#endif diff --git a/source/common/types.h b/source/common/types.h new file mode 100644 index 0000000..c65ccf1 --- /dev/null +++ b/source/common/types.h @@ -0,0 +1,191 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef TYPES_H +#define TYPES_H + +#include + +using namespace std; + +//atoi is not int32 or uint32 safe!!!! +#define atoul(str) strtoul(str, NULL, 10) +#ifdef WIN32 + #define atoi64(str) _atoi64(str) +#else + #define atoi64(str) strtoll(str, NULL, 10) +#endif +typedef unsigned char int8; +typedef unsigned short int16; +typedef unsigned int int32; + +typedef unsigned char uint8; +typedef signed char sint8; +typedef unsigned short uint16; +typedef signed short sint16; +typedef unsigned int uint32; +typedef signed int sint32; + +#ifdef WIN32 + #if defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 64 + typedef unsigned __int64 int64; + typedef unsigned __int64 uint64; + typedef signed __int64 sint64; + #else + #error __int64 not supported + #endif +#else +typedef unsigned long long int64; +typedef unsigned long long uint64; +typedef signed long long sint64; +//typedef __u64 int64; +//typedef __u64 uint64; +//typedef __s64 sint64; +#endif + +typedef unsigned long ulong; +typedef unsigned short ushort; +typedef unsigned char uchar; + +#ifdef WIN32 + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + typedef void ThreadReturnType; +// #define THREAD_RETURN(x) return; + #define THREAD_RETURN(x) _endthread(); return; +#else + typedef void* ThreadReturnType; + typedef int SOCKET; + #define THREAD_RETURN(x) return(x); +#endif + +#define safe_delete(d) if(d) { delete d; d=0; } +#define safe_delete_array(d) if(d) { delete[] d; d=0; } +#define L32(i) ((int32) i) +#define H32(i) ((int32) (i >> 32)) +#define L16(i) ((int16) i) + +#ifndef WIN32 +// More WIN32 compatability + typedef unsigned long DWORD; + typedef unsigned char BYTE; + typedef char CHAR; + typedef unsigned short WORD; + typedef float FLOAT; + typedef FLOAT *PFLOAT; + typedef BYTE *PBYTE,*LPBYTE; + typedef int *PINT,*LPINT; + typedef WORD *PWORD,*LPWORD; + typedef long *LPLONG, LONG; + typedef DWORD *PDWORD,*LPDWORD; + typedef int INT; + typedef unsigned int UINT,*PUINT,*LPUINT; +#endif + + +#ifdef WIN32 +#define DLLFUNC extern "C" __declspec(dllexport) +#else +#define DLLFUNC extern "C" +#endif + + +#pragma pack(1) +struct uint16_breakdown { + union { + uint16 all; + struct { + uint8 b1; + uint8 b2; + } bytes; + }; + inline uint16& operator=(const uint16& val) { return (all=val); } + inline uint16* operator&() { return &all; } + inline operator uint16&() { return all; } + inline uint8& b1() { return bytes.b1; } + inline uint8& b2() { return bytes.b2; } +}; + +struct uint32_breakdown { + union { + uint32 all; + struct { + uint16 w1; + uint16 w2; + } words; + struct { + uint8 b1; + union { + struct { + uint8 b2; + uint8 b3; + } middle; + uint16 w2_3; // word bytes 2 to 3 + }; + uint8 b4; + } bytes; + }; + inline uint32& operator=(const uint32& val) { return (all=val); } + inline uint32* operator&() { return &all; } + inline operator uint32&() { return all; } + + inline uint16& w1() { return words.w1; } + inline uint16& w2() { return words.w2; } + inline uint16& w2_3() { return bytes.w2_3; } + inline uint8& b1() { return bytes.b1; } + inline uint8& b2() { return bytes.middle.b2; } + inline uint8& b3() { return bytes.middle.b3; } + inline uint8& b4() { return bytes.b4; } +}; + +struct EQ2_32BitString{ + int32 size; + string data; +}; +struct EQ2_16BitString{ + int16 size; + string data; +}; +struct EQ2_8BitString{ + int8 size; + string data; +}; + +struct EQ2_Color{ + int8 red; + int8 green; + int8 blue; +}; + +struct WorldTime{ + int16 year; + int month; + int day; + int hour; + int minute; +}; + +typedef enum QUERY_TYPE{ Q_SELECT, Q_UPDATE, Q_REPLACE, Q_INSERT, Q_DELETE, Q_DBMS} QUERY_TYPE; + +#pragma pack() + + +#endif diff --git a/source/common/unix.cpp b/source/common/unix.cpp new file mode 100644 index 0000000..6476c3a --- /dev/null +++ b/source/common/unix.cpp @@ -0,0 +1,45 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "unix.h" +#include +#include + +void Sleep(unsigned int x) { + if (x > 0) + usleep(x*1000); +} + +char* strupr(char* tmp) { + int l = strlen(tmp); + for (int x = 0; x < l; x++) { + tmp[x] = toupper(tmp[x]); + } + return tmp; +} + +char* strlwr(char* tmp) { + int l = strlen(tmp); + for (int x = 0; x < l; x++) { + tmp[x] = tolower(tmp[x]); + } + return tmp; +} + + diff --git a/source/common/unix.h b/source/common/unix.h new file mode 100644 index 0000000..82560a1 --- /dev/null +++ b/source/common/unix.h @@ -0,0 +1,34 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef WIN32 +#ifndef __UNIX_H__ +#define __UNIX_H__ + #ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP + #define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP {0, 0, 0, PTHREAD_MUTEX_RECURSIVE_NP, __LOCK_INITIALIZER} + #endif +#include + +typedef int SOCKET; + +void Sleep(unsigned int x); +char* strupr(char* tmp); +char* strlwr(char* tmp); +#endif +#endif diff --git a/source/common/version.h b/source/common/version.h new file mode 100644 index 0000000..db48edb --- /dev/null +++ b/source/common/version.h @@ -0,0 +1,56 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ + +#include "LogTypes.h" + +#ifndef VERSION_H +#define VERSION_H + +#define CURRENT_DATABASE_MINORVERSION 43 +#define CURRENT_DATABASE_MAJORVERSION 730 +#if defined(LOGIN) + #define EQ2EMU_MODULE "EQ2EMu LoginServer" +#elif defined(PATCHER) + #define EQ2EMU_MODULE "EQ2EMu PatchServer" +#elif defined(CHAT) + #define EQ2EMU_MODULE "EQ2EMu ChatServer" +#elif defined(ZONE) + #define EQ2EMU_MODULE "EQ2EMu ZoneServer" +#else + #define EQ2EMU_MODULE "EQ2EMu WorldServer" +#endif + +#if defined(LOGIN) +#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#elif defined(WORLD) +#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#else +#define CURRENT_VERSION "0.9.7-thetascorpii-DR2" +#endif + +#define COMPILE_DATE __DATE__ +#define COMPILE_TIME __TIME__ +#ifndef WIN32 + #define LAST_MODIFIED __TIME__ +#else + #define LAST_MODIFIED __TIMESTAMP__ +#endif + +#endif \ No newline at end of file diff --git a/source/common/xmlParser.cpp b/source/common/xmlParser.cpp new file mode 100644 index 0000000..e0f7f33 --- /dev/null +++ b/source/common/xmlParser.cpp @@ -0,0 +1,2974 @@ +/** + **************************************************************************** + *

XML.c - implementation file for basic XML parser written in ANSI C++ + * for portability. It works by using recursion and a node tree for breaking + * down the elements of an XML document.

+ * + * @version V2.44 + * @author Frank Vanden Berghen + * + * NOTE: + * + * If you add "#define STRICT_PARSING", on the first line of this file + * the parser will see the following XML-stream: + * some textother text + * as an error. Otherwise, this tring will be equivalent to: + * some textother text + * + * NOTE: + * + * If you add "#define APPROXIMATE_PARSING" on the first line of this file + * the parser will see the following XML-stream: + * + * + * + * as equivalent to the following XML-stream: + * + * + * + * This can be useful for badly-formed XML-streams but prevent the use + * of the following XML-stream (problem is: tags at contiguous levels + * have the same names): + * + * + * + * + * + * + * NOTE: + * + * If you add "#define _XMLPARSER_NO_MESSAGEBOX_" on the first line of this file + * the "openFileHelper" function will always display error messages inside the + * console instead of inside a message-box-window. Message-box-windows are + * available on windows 9x/NT/2000/XP/Vista only. + * + * Copyright (c) 2002, Frank Vanden Berghen - All rights reserved. + * Commercialized by Business-Insight + * See the file "AFPL-license.txt about the licensing terms + * + **************************************************************************** + */ +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE +#endif +#include "xmlParser.h" +#ifdef _XMLWINDOWS +//#ifdef _DEBUG +//#define _CRTDBG_MAP_ALLOC +//#include +//#endif +#define WIN32_LEAN_AND_MEAN +#include // to have IsTextUnicode, MultiByteToWideChar, WideCharToMultiByte to handle unicode files + // to have "MessageBoxA" to display error messages for openFilHelper +#endif + +#include +#include +#include +#include +#include + +XMLCSTR XMLNode::getVersion() { return _CXML("v2.44"); } +void freeXMLString(XMLSTR t){if(t)free(t);} + +static XMLNode::XMLCharEncoding characterEncoding=XMLNode::char_encoding_UTF8; +static char guessWideCharChars=1, dropWhiteSpace=1, removeCommentsInMiddleOfText=1; + +inline int mmin( const int t1, const int t2 ) { return t1 < t2 ? t1 : t2; } + +// You can modify the initialization of the variable "XMLClearTags" below +// to change the clearTags that are currently recognized by the library. +// The number on the second columns is the length of the string inside the +// first column. +// The "") }, + { _CXML("") }, + { _CXML("") }, + { _CXML("
")    ,5,  _CXML("
") }, +// { _CXML("")}, + { NULL ,0, NULL } +}; + +// You can modify the initialization of the variable "XMLEntities" below +// to change the character entities that are currently recognized by the library. +// The number on the second columns is the length of the string inside the +// first column. Additionally, the syntaxes " " and " " are recognized. +typedef struct { XMLCSTR s; int l; XMLCHAR c;} XMLCharacterEntity; +static XMLCharacterEntity XMLEntities[] = +{ + { _CXML("&" ), 5, _CXML('&' )}, + { _CXML("<" ), 4, _CXML('<' )}, + { _CXML(">" ), 4, _CXML('>' )}, + { _CXML("""), 6, _CXML('\"')}, + { _CXML("'"), 6, _CXML('\'')}, + { NULL , 0, '\0' } +}; + +// When rendering the XMLNode to a string (using the "createXMLString" function), +// you can ask for a beautiful formatting. This formatting is using the +// following indentation character: +#define INDENTCHAR _CXML('\t') + +// The following function parses the XML errors into a user friendly string. +// You can edit this to change the output language of the library to something else. +XMLCSTR XMLNode::getError(XMLError xerror) +{ + switch (xerror) + { + case eXMLErrorNone: return _CXML("No error"); + case eXMLErrorMissingEndTag: return _CXML("Warning: Unmatched end tag"); + case eXMLErrorNoXMLTagFound: return _CXML("Warning: No XML tag found"); + case eXMLErrorEmpty: return _CXML("Error: No XML data"); + case eXMLErrorMissingTagName: return _CXML("Error: Missing start tag name"); + case eXMLErrorMissingEndTagName: return _CXML("Error: Missing end tag name"); + case eXMLErrorUnmatchedEndTag: return _CXML("Error: Unmatched end tag"); + case eXMLErrorUnmatchedEndClearTag: return _CXML("Error: Unmatched clear tag end"); + case eXMLErrorUnexpectedToken: return _CXML("Error: Unexpected token found"); + case eXMLErrorNoElements: return _CXML("Error: No elements found"); + case eXMLErrorFileNotFound: return _CXML("Error: File not found"); + case eXMLErrorFirstTagNotFound: return _CXML("Error: First Tag not found"); + case eXMLErrorUnknownCharacterEntity:return _CXML("Error: Unknown character entity"); + case eXMLErrorCharacterCodeAbove255: return _CXML("Error: Character code above 255 is forbidden in MultiByte char mode."); + case eXMLErrorCharConversionError: return _CXML("Error: unable to convert between WideChar and MultiByte chars"); + case eXMLErrorCannotOpenWriteFile: return _CXML("Error: unable to open file for writing"); + case eXMLErrorCannotWriteFile: return _CXML("Error: cannot write into file"); + + case eXMLErrorBase64DataSizeIsNotMultipleOf4: return _CXML("Warning: Base64-string length is not a multiple of 4"); + case eXMLErrorBase64DecodeTruncatedData: return _CXML("Warning: Base64-string is truncated"); + case eXMLErrorBase64DecodeIllegalCharacter: return _CXML("Error: Base64-string contains an illegal character"); + case eXMLErrorBase64DecodeBufferTooSmall: return _CXML("Error: Base64 decode output buffer is too small"); + }; + return _CXML("Unknown"); +} + +///////////////////////////////////////////////////////////////////////// +// Here start the abstraction layer to be OS-independent // +///////////////////////////////////////////////////////////////////////// + +// Here is an abstraction layer to access some common string manipulation functions. +// The abstraction layer is currently working for gcc, Microsoft Visual Studio 6.0, +// Microsoft Visual Studio .NET, CC (sun compiler) and Borland C++. +// If you plan to "port" the library to a new system/compiler, all you have to do is +// to edit the following lines. +#ifdef XML_NO_WIDE_CHAR +char myIsTextWideChar(const void *b, int len) { return FALSE; } +#else + #if defined (UNDER_CE) || !defined(_XMLWINDOWS) + char myIsTextWideChar(const void *b, int len) // inspired by the Wine API: RtlIsTextUnicode + { +#ifdef sun + // for SPARC processors: wchar_t* buffers must always be alligned, otherwise it's a char* buffer. + if ((((unsigned long)b)%sizeof(wchar_t))!=0) return FALSE; +#endif + const wchar_t *s=(const wchar_t*)b; + + // buffer too small: + if (len<(int)sizeof(wchar_t)) return FALSE; + + // odd length test + if (len&1) return FALSE; + + /* only checks the first 256 characters */ + len=mmin(256,len/sizeof(wchar_t)); + + // Check for the special byte order: + if (*((unsigned short*)s) == 0xFFFE) return TRUE; // IS_TEXT_UNICODE_REVERSE_SIGNATURE; + if (*((unsigned short*)s) == 0xFEFF) return TRUE; // IS_TEXT_UNICODE_SIGNATURE + + // checks for ASCII characters in the UNICODE stream + int i,stats=0; + for (i=0; ilen/2) return TRUE; + + // Check for UNICODE NULL chars + for (i=0; i + static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return wsncasecmp(c1,c2,l);} + static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return wsncmp(c1,c2,l);} + static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return wscasecmp(c1,c2); } + #else + static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return wcsncmp(c1,c2,l);} + #ifdef __linux__ + // for gcc/linux + static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return wcsncasecmp(c1,c2,l);} + static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return wcscasecmp(c1,c2); } + #else + #include + // for gcc/non-linux (MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 4.3.2, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin, mingw) + static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) + { + wchar_t left,right; + do + { + left=towlower(*c1++); right=towlower(*c2++); + } while (left&&(left==right)); + return (int)left-(int)right; + } + static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) + { + wchar_t left,right; + while(l--) + { + left=towlower(*c1++); right=towlower(*c2++); + if ((!left)||(left!=right)) return (int)left-(int)right; + } + return 0; + } + #endif + #endif + static inline XMLSTR xstrstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)wcsstr(c1,c2); } + static inline XMLSTR xstrcpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)wcscpy(c1,c2); } + static inline FILE *xfopen(XMLCSTR filename,XMLCSTR mode) + { + char *filenameAscii=myWideCharToMultiByte(filename); + FILE *f; + if (mode[0]==_CXML('r')) f=fopen(filenameAscii,"rb"); + else f=fopen(filenameAscii,"wb"); + free(filenameAscii); + return f; + } + #else + static inline FILE *xfopen(XMLCSTR filename,XMLCSTR mode) { return fopen(filename,mode); } + static inline int xstrlen(XMLCSTR c) { return strlen(c); } + static inline int xstrnicmp(XMLCSTR c1, XMLCSTR c2, int l) { return strncasecmp(c1,c2,l);} + static inline int xstrncmp(XMLCSTR c1, XMLCSTR c2, int l) { return strncmp(c1,c2,l);} + static inline int xstricmp(XMLCSTR c1, XMLCSTR c2) { return strcasecmp(c1,c2); } + static inline XMLSTR xstrstr(XMLCSTR c1, XMLCSTR c2) { return (XMLSTR)strstr(c1,c2); } + static inline XMLSTR xstrcpy(XMLSTR c1, XMLCSTR c2) { return (XMLSTR)strcpy(c1,c2); } + #endif + static inline int _strnicmp(const char *c1,const char *c2, int l) { return strncasecmp(c1,c2,l);} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// the "xmltoc,xmltob,xmltoi,xmltol,xmltof,xmltoa" functions // +/////////////////////////////////////////////////////////////////////////////// +// These 6 functions are not used inside the XMLparser. +// There are only here as "convenience" functions for the user. +// If you don't need them, you can delete them without any trouble. +#ifdef _XMLWIDECHAR + #ifdef _XMLWINDOWS + // for Microsoft Visual Studio 6.0 and Microsoft Visual Studio .NET and Borland C++ Builder 6.0 + char xmltob(XMLCSTR t,char v){ if (t&&(*t)) return (char)_wtoi(t); return v; } + int xmltoi(XMLCSTR t,int v){ if (t&&(*t)) return _wtoi(t); return v; } + long long xmltol(XMLCSTR t,long long v){ if (t&&(*t)) return _wtoi64(t); return v; } + double xmltof(XMLCSTR t,double v){ if (t&&(*t)) swscanf(t, L"%lf", &v); /*v=_wtof(t);*/ return v; } + #else + #ifdef sun + // for CC + #include + char xmltob(XMLCSTR t,char v){ if (t) return (char)wstol(t,NULL,10); return v; } + int xmltoi(XMLCSTR t,int v){ if (t) return (int)wstol(t,NULL,10); return v; } + long long xmltol(XMLCSTR t,long long v){ if (t) return wstol(t,NULL,10); return v; } + #else + // for gcc + char xmltob(XMLCSTR t,char v){ if (t) return (char)wcstol(t,NULL,10); return v; } + int xmltoi(XMLCSTR t,int v){ if (t) return (int)wcstol(t,NULL,10); return v; } + long long xmltol(XMLCSTR t,long long v){ if (t) return wcstol(t,NULL,10); return v; } + #endif + double xmltof(XMLCSTR t,double v){ if (t&&(*t)) swscanf(t, L"%lf", &v); /*v=_wtof(t);*/ return v; } + #endif +#else + #ifdef _XMLWINDOWS + long long xmltol(XMLCSTR t,long long v){ if (t&&(*t)) return _atoi64(t); return v; } + #else + long long xmltol(XMLCSTR t,long long v){ if (t&&(*t)) return atoll(t); return v; } + #endif + char xmltob(XMLCSTR t,char v){ if (t&&(*t)) return (char)atoi(t); return v; } + int xmltoi(XMLCSTR t,int v){ if (t&&(*t)) return atoi(t); return v; } + double xmltof(XMLCSTR t,double v){ if (t&&(*t)) return atof(t); return v; } +#endif +XMLCSTR xmltoa(XMLCSTR t, XMLCSTR v){ if (t) return t; return v; } +XMLCHAR xmltoc(XMLCSTR t,const XMLCHAR v){ if (t&&(*t)) return *t; return v; } + +///////////////////////////////////////////////////////////////////////// +// the "openFileHelper" function // +///////////////////////////////////////////////////////////////////////// + +// Since each application has its own way to report and deal with errors, you should modify & rewrite +// the following "openFileHelper" function to get an "error reporting mechanism" tailored to your needs. +XMLNode XMLNode::openFileHelper(XMLCSTR filename, XMLCSTR tag) +{ + // guess the value of the global parameter "characterEncoding" + // (the guess is based on the first 200 bytes of the file). + FILE *f=xfopen(filename,_CXML("rb")); + if (f) + { + char bb[205]; + int l=(int)fread(bb,1,200,f); + setGlobalOptions(guessCharEncoding(bb,l),guessWideCharChars,dropWhiteSpace,removeCommentsInMiddleOfText); + fclose(f); + } + + // parse the file + XMLResults pResults; + XMLNode xnode=XMLNode::parseFile(filename,tag,&pResults); + + // display error message (if any) + if (pResults.error != eXMLErrorNone) + { + // create message + char message[2000],*s1=(char*)"",*s3=(char*)""; XMLCSTR s2=_CXML(""); + if (pResults.error==eXMLErrorFirstTagNotFound) { s1=(char*)"First Tag should be '"; s2=tag; s3=(char*)"'.\n"; } +#ifdef _XMLWINDOWS + _snprintf(message,2000, +#else + snprintf(message,2000, +#endif +#ifdef _XMLWIDECHAR + "XML Parsing error inside file '%S'.\n%S\nAt line %i, column %i.\n%s%S%s" +#else + "XML Parsing error inside file '%s'.\n%s\nAt line %i, column %i.\n%s%s%s" +#endif + ,filename,XMLNode::getError(pResults.error),pResults.nLine,pResults.nColumn,s1,s2,s3); + + // display message +#if defined(_XMLWINDOWS) && !defined(UNDER_CE) && !defined(_XMLPARSER_NO_MESSAGEBOX_) + MessageBoxA(NULL,message,"XML Parsing error",MB_OK|MB_ICONERROR|MB_TOPMOST); +#else + printf("%s",message); +#endif + exit(255); + } + return xnode; +} + +///////////////////////////////////////////////////////////////////////// +// Here start the core implementation of the XMLParser library // +///////////////////////////////////////////////////////////////////////// + +// You should normally not change anything below this point. + +#ifndef _XMLWIDECHAR +// If "characterEncoding=ascii" then we assume that all characters have the same length of 1 byte. +// If "characterEncoding=UTF8" then the characters have different lengths (from 1 byte to 4 bytes). +// If "characterEncoding=ShiftJIS" then the characters have different lengths (from 1 byte to 2 bytes). +// This table is used as lookup-table to know the length of a character (in byte) based on the +// content of the first byte of the character. +// (note: if you modify this, you must always have XML_utf8ByteTable[0]=0 ). +static const char XML_utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 End of ASCII range + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x80 0x80 to 0xc1 invalid + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x90 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xa0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xb0 + 1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 0xc2 to 0xdf 2 byte + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,// 0xe0 0xe0 to 0xef 3 byte + 4,4,4,4,4,1,1,1,1,1,1,1,1,1,1,1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; +static const char XML_legacyByteTable[256] = +{ + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +}; +static const char XML_sjisByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x80 0x81 to 0x9F 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x90 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xa0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xb0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xc0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 0xe0 to 0xef 2 bytes + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // 0xf0 +}; +static const char XML_gb2312ByteTable[256] = +{ +// 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x80 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x90 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xa0 0xa1 to 0xf7 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xb0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 + 2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1 // 0xf0 +}; +static const char XML_gbk_big5_ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x00 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x10 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x20 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x30 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x40 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x50 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x60 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,// 0x70 + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x80 0x81 to 0xfe 2 bytes + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0x90 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xa0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xb0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xc0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xd0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,// 0xe0 + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1 // 0xf0 +}; +static const char *XML_ByteTable=(const char *)XML_utf8ByteTable; // the default is "characterEncoding=XMLNode::encoding_UTF8" +#endif + + +XMLNode XMLNode::emptyXMLNode; +XMLClear XMLNode::emptyXMLClear={ NULL, NULL, NULL}; +XMLAttribute XMLNode::emptyXMLAttribute={ NULL, NULL}; + +// Enumeration used to decipher what type a token is +typedef enum XMLTokenTypeTag +{ + eTokenText = 0, + eTokenQuotedText, + eTokenTagStart, /* "<" */ + eTokenTagEnd, /* "" */ + eTokenEquals, /* "=" */ + eTokenDeclaration, /* "" */ + eTokenClear, + eTokenError +} XMLTokenType; + +// Main structure used for parsing XML +typedef struct XML +{ + XMLCSTR lpXML; + XMLCSTR lpszText; + int nIndex,nIndexMissigEndTag; + enum XMLError error; + XMLCSTR lpEndTag; + int cbEndTag; + XMLCSTR lpNewElement; + int cbNewElement; + int nFirst; +} XML; + +typedef struct +{ + ALLXMLClearTag *pClr; + XMLCSTR pStr; +} NextToken; + +// Enumeration used when parsing attributes +typedef enum Attrib +{ + eAttribName = 0, + eAttribEquals, + eAttribValue +} Attrib; + +// Enumeration used when parsing elements to dictate whether we are currently +// inside a tag +typedef enum XMLStatus +{ + eInsideTag = 0, + eOutsideTag +} XMLStatus; + +XMLError XMLNode::writeToFile(XMLCSTR filename, const char *encoding, char nFormat) const +{ + if (!d) return eXMLErrorNone; + FILE *f=xfopen(filename,_CXML("wb")); + if (!f) return eXMLErrorCannotOpenWriteFile; +#ifdef _XMLWIDECHAR + unsigned char h[2]={ 0xFF, 0xFE }; + if (!fwrite(h,2,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + if ((!isDeclaration())&&((d->lpszName)||(!getChildNode().isDeclaration()))) + { + if (!fwrite(L"\n",sizeof(wchar_t)*40,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } +#else + if ((!isDeclaration())&&((d->lpszName)||(!getChildNode().isDeclaration()))) + { + if (characterEncoding==char_encoding_UTF8) + { + // header so that windows recognize the file as UTF-8: + unsigned char h[3]={0xEF,0xBB,0xBF}; + if (!fwrite(h,3,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + encoding="utf-8"; + } else if (characterEncoding==char_encoding_ShiftJIS) encoding="SHIFT-JIS"; + + if (!encoding) encoding="ISO-8859-1"; + if (fprintf(f,"\n",encoding)<0) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } else + { + if (characterEncoding==char_encoding_UTF8) + { + unsigned char h[3]={0xEF,0xBB,0xBF}; + if (!fwrite(h,3,1,f)) + { + fclose(f); + return eXMLErrorCannotWriteFile; + } + } + } +#endif + int i; + XMLSTR t=createXMLString(nFormat,&i); + if (!fwrite(t,sizeof(XMLCHAR)*i,1,f)) + { + free(t); + fclose(f); + return eXMLErrorCannotWriteFile; + } + if (fclose(f)!=0) + { + free(t); + return eXMLErrorCannotWriteFile; + } + free(t); + return eXMLErrorNone; +} + +// Duplicate a given string. +XMLSTR stringDup(XMLCSTR lpszData, int cbData) +{ + if (lpszData==NULL) return NULL; + + XMLSTR lpszNew; + if (cbData==-1) cbData=(int)xstrlen(lpszData); + lpszNew = (XMLSTR)malloc((cbData+1) * sizeof(XMLCHAR)); + if (lpszNew) + { + memcpy(lpszNew, lpszData, (cbData) * sizeof(XMLCHAR)); + lpszNew[cbData] = (XMLCHAR)NULL; + } + return lpszNew; +} + +XMLSTR ToXMLStringTool::toXMLUnSafe(XMLSTR dest,XMLCSTR source) +{ + XMLSTR dd=dest; + XMLCHAR ch; + XMLCharacterEntity *entity; + while ((ch=*source)) + { + entity=XMLEntities; + do + { + if (ch==entity->c) {xstrcpy(dest,entity->s); dest+=entity->l; source++; goto out_of_loop1; } + entity++; + } while(entity->s); +#ifdef _XMLWIDECHAR + *(dest++)=*(source++); +#else + switch(XML_ByteTable[(unsigned char)ch]) + { + case 4: + if ((!(source[1]))||(!(source[2]))||(!(source[3]))) { *(dest++)='_'; source++; } + else + { + *dest=*source; + dest[1]=source[1]; + dest[2]=source[2]; + dest[3]=source[3]; + dest+=4; source+=4; + } + break; + case 3: + if ((!(source[1]))||(!(source[2]))) { *(dest++)='_'; source++; } + else + { + *dest=*source; + dest[1]=source[1]; + dest[2]=source[2]; + dest+=3; source+=3; + } + break; + case 2: + if (!(source[1])) { *(dest++)='_'; source++; } + else + { + *dest=*source; + dest[1]=source[1]; + dest+=2; source+=2; + } + break; + case 1: *(dest++)=*(source++); + } +#endif +out_of_loop1: + ; + } + *dest=0; + return dd; +} + +// private (used while rendering): +int ToXMLStringTool::lengthXMLString(XMLCSTR source) +{ + int r=0; + XMLCharacterEntity *entity; + XMLCHAR ch; + while ((ch=*source)) + { + entity=XMLEntities; + do + { + if (ch==entity->c) { r+=entity->l; source++; goto out_of_loop1; } + entity++; + } while(entity->s); +#ifdef _XMLWIDECHAR + r++; source++; +#else + ch=XML_ByteTable[(unsigned char)ch]; r+=ch; source+=ch; +#endif +out_of_loop1: + ; + } + return r; +} + +ToXMLStringTool::~ToXMLStringTool(){ freeBuffer(); } +void ToXMLStringTool::freeBuffer(){ if (buf) free(buf); buf=NULL; buflen=0; } +XMLSTR ToXMLStringTool::toXML(XMLCSTR source) +{ + if (!source) + { + if (buflen<1) { buflen=1; buf=(XMLSTR)malloc(sizeof(XMLCHAR)); } + *buf=0; + return buf; + } + int l=lengthXMLString(source)+1; + if (l>buflen) { freeBuffer(); buflen=l; buf=(XMLSTR)malloc(l*sizeof(XMLCHAR)); } + return toXMLUnSafe(buf,source); +} + +// private: +XMLSTR fromXMLString(XMLCSTR s, int lo, XML *pXML) +{ + // This function is the opposite of the function "toXMLString". It decodes the escape + // sequences &, ", ', <, > and replace them by the characters + // &,",',<,>. This function is used internally by the XML Parser. All the calls to + // the XML library will always gives you back "decoded" strings. + // + // in: string (s) and length (lo) of string + // out: new allocated string converted from xml + if (!s) return NULL; + + int ll=0,j; + XMLSTR d; + XMLCSTR ss=s; + XMLCharacterEntity *entity; + while ((lo>0)&&(*s)) + { + if (*s==_CXML('&')) + { + if ((lo>2)&&(s[1]==_CXML('#'))) + { + s+=2; lo-=2; + if ((*s==_CXML('X'))||(*s==_CXML('x'))) { s++; lo--; } + while ((*s)&&(*s!=_CXML(';'))&&((lo--)>0)) s++; + if (*s!=_CXML(';')) + { + pXML->error=eXMLErrorUnknownCharacterEntity; + return NULL; + } + s++; lo--; + } else + { + entity=XMLEntities; + do + { + if ((lo>=entity->l)&&(xstrnicmp(s,entity->s,entity->l)==0)) { s+=entity->l; lo-=entity->l; break; } + entity++; + } while(entity->s); + if (!entity->s) + { + pXML->error=eXMLErrorUnknownCharacterEntity; + return NULL; + } + } + } else + { +#ifdef _XMLWIDECHAR + s++; lo--; +#else + j=XML_ByteTable[(unsigned char)*s]; s+=j; lo-=j; ll+=j-1; +#endif + } + ll++; + } + + d=(XMLSTR)malloc((ll+1)*sizeof(XMLCHAR)); + s=d; + while (ll-->0) + { + if (*ss==_CXML('&')) + { + if (ss[1]==_CXML('#')) + { + ss+=2; j=0; + if ((*ss==_CXML('X'))||(*ss==_CXML('x'))) + { + ss++; + while (*ss!=_CXML(';')) + { + if ((*ss>=_CXML('0'))&&(*ss<=_CXML('9'))) j=(j<<4)+*ss-_CXML('0'); + else if ((*ss>=_CXML('A'))&&(*ss<=_CXML('F'))) j=(j<<4)+*ss-_CXML('A')+10; + else if ((*ss>=_CXML('a'))&&(*ss<=_CXML('f'))) j=(j<<4)+*ss-_CXML('a')+10; + else { free((void*)s); pXML->error=eXMLErrorUnknownCharacterEntity;return NULL;} + ss++; + } + } else + { + while (*ss!=_CXML(';')) + { + if ((*ss>=_CXML('0'))&&(*ss<=_CXML('9'))) j=(j*10)+*ss-_CXML('0'); + else { free((void*)s); pXML->error=eXMLErrorUnknownCharacterEntity;return NULL;} + ss++; + } + } +#ifndef _XMLWIDECHAR + if (j>255) { free((void*)s); pXML->error=eXMLErrorCharacterCodeAbove255;return NULL;} +#endif + (*d++)=(XMLCHAR)j; ss++; + } else + { + entity=XMLEntities; + do + { + if (xstrnicmp(ss,entity->s,entity->l)==0) { *(d++)=entity->c; ss+=entity->l; break; } + entity++; + } while(entity->s); + } + } else + { +#ifdef _XMLWIDECHAR + *(d++)=*(ss++); +#else + switch(XML_ByteTable[(unsigned char)*ss]) + { + case 4: *(d++)=*(ss++); ll--; + case 3: *(d++)=*(ss++); ll--; + case 2: *(d++)=*(ss++); ll--; + case 1: *(d++)=*(ss++); + } +#endif + } + } + *d=0; + return (XMLSTR)s; +} + +#define XML_isSPACECHAR(ch) ((ch==_CXML('\n'))||(ch==_CXML(' '))||(ch== _CXML('\t'))||(ch==_CXML('\r'))) + +// private: +char myTagCompare(XMLCSTR cclose, XMLCSTR copen) +// !!!! WARNING strange convention&: +// return 0 if equals +// return 1 if different +{ + if (!cclose) return 1; + int l=(int)xstrlen(cclose); + if (xstrnicmp(cclose, copen, l)!=0) return 1; + const XMLCHAR c=copen[l]; + if (XML_isSPACECHAR(c)|| + (c==_CXML('/' ))|| + (c==_CXML('<' ))|| + (c==_CXML('>' ))|| + (c==_CXML('=' ))) return 0; + return 1; +} + +// Obtain the next character from the string. +static inline XMLCHAR getNextChar(XML *pXML) +{ + XMLCHAR ch = pXML->lpXML[pXML->nIndex]; +#ifdef _XMLWIDECHAR + if (ch!=0) pXML->nIndex++; +#else + pXML->nIndex+=XML_ByteTable[(unsigned char)ch]; +#endif + return ch; +} + +// Find the next token in a string. +// pcbToken contains the number of characters that have been read. +static NextToken GetNextToken(XML *pXML, int *pcbToken, enum XMLTokenTypeTag *pType) +{ + NextToken result; + XMLCHAR ch; + XMLCHAR chTemp; + int indexStart,nFoundMatch,nIsText=FALSE; + result.pClr=NULL; // prevent warning + + // Find next non-white space character + do { indexStart=pXML->nIndex; ch=getNextChar(pXML); } while XML_isSPACECHAR(ch); + + if (ch) + { + // Cache the current string pointer + result.pStr = &pXML->lpXML[indexStart]; + + // check for standard tokens + switch(ch) + { + // Check for quotes + case _CXML('\''): + case _CXML('\"'): + // Type of token + *pType = eTokenQuotedText; + chTemp = ch; + + // Set the size + nFoundMatch = FALSE; + + // Search through the string to find a matching quote + while((ch = getNextChar(pXML))) + { + if (ch==chTemp) { nFoundMatch = TRUE; break; } + if (ch==_CXML('<')) break; + } + + // If we failed to find a matching quote + if (nFoundMatch == FALSE) + { + pXML->nIndex=indexStart+1; + nIsText=TRUE; + break; + } + +// 4.02.2002 +// if (FindNonWhiteSpace(pXML)) pXML->nIndex--; + + break; + + // Equals (used with attribute values) + case _CXML('='): + *pType = eTokenEquals; + break; + + // Close tag + case _CXML('>'): + *pType = eTokenCloseTag; + break; + + // Check for tag start and tag end + case _CXML('<'): + + { + // First check whether the token is in the clear tag list (meaning it + // does not need formatting). + ALLXMLClearTag *ctag=XMLClearTags; + do + { + if (!xstrncmp(ctag->lpszOpen, result.pStr, ctag->openTagLen)) + { + result.pClr=ctag; + pXML->nIndex+=ctag->openTagLen-1; + *pType=eTokenClear; + return result; + } + ctag++; + } while(ctag->lpszOpen); + + // Peek at the next character to see if we have an end tag 'lpXML[pXML->nIndex]; + + // If we have a tag end... + if (chTemp == _CXML('/')) + { + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenTagEnd; + } + + // If we have an XML declaration tag + else if (chTemp == _CXML('?')) + { + + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenDeclaration; + } + + // Otherwise we must have a start tag + else + { + *pType = eTokenTagStart; + } + break; + } + + // Check to see if we have a short hand type end tag ('/>'). + case _CXML('/'): + + // Peek at the next character to see if we have a short end tag '/>' + chTemp = pXML->lpXML[pXML->nIndex]; + + // If we have a short hand end tag... + if (chTemp == _CXML('>')) + { + // Set the type and ensure we point at the next character + getNextChar(pXML); + *pType = eTokenShortHandClose; + break; + } + + // If we haven't found a short hand closing tag then drop into the + // text process + + // Other characters + default: + nIsText = TRUE; + } + + // If this is a TEXT node + if (nIsText) + { + // Indicate we are dealing with text + *pType = eTokenText; + while((ch = getNextChar(pXML))) + { + if XML_isSPACECHAR(ch) + { + indexStart++; break; + + } else if (ch==_CXML('/')) + { + // If we find a slash then this maybe text or a short hand end tag + // Peek at the next character to see it we have short hand end tag + ch=pXML->lpXML[pXML->nIndex]; + // If we found a short hand end tag then we need to exit the loop + if (ch==_CXML('>')) { pXML->nIndex--; break; } + + } else if ((ch==_CXML('<'))||(ch==_CXML('>'))||(ch==_CXML('='))) + { + pXML->nIndex--; break; + } + } + } + *pcbToken = pXML->nIndex-indexStart; + } else + { + // If we failed to obtain a valid character + *pcbToken = 0; + *pType = eTokenError; + result.pStr=NULL; + } + + return result; +} + +XMLCSTR XMLNode::updateName_WOSD(XMLSTR lpszName) +{ + if (!d) { free(lpszName); return NULL; } + if (d->lpszName&&(lpszName!=d->lpszName)) free((void*)d->lpszName); + d->lpszName=lpszName; + return lpszName; +} + +// private: +XMLNode::XMLNode(struct XMLNodeDataTag *p){ d=p; (p->ref_count)++; } +XMLNode::XMLNode(XMLNodeData *pParent, XMLSTR lpszName, char isDeclaration) +{ + d=(XMLNodeData*)malloc(sizeof(XMLNodeData)); + d->ref_count=1; + + d->lpszName=NULL; + d->nChild= 0; + d->nText = 0; + d->nClear = 0; + d->nAttribute = 0; + + d->isDeclaration = isDeclaration; + + d->pParent = pParent; + d->pChild= NULL; + d->pText= NULL; + d->pClear= NULL; + d->pAttribute= NULL; + d->pOrder= NULL; + + updateName_WOSD(lpszName); +} + +XMLNode XMLNode::createXMLTopNode_WOSD(XMLSTR lpszName, char isDeclaration) { return XMLNode(NULL,lpszName,isDeclaration); } +XMLNode XMLNode::createXMLTopNode(XMLCSTR lpszName, char isDeclaration) { return XMLNode(NULL,stringDup(lpszName),isDeclaration); } + +#define MEMORYINCREASE 50 + +static inline void myFree(void *p) { if (p) free(p); } +static inline void *myRealloc(void *p, int newsize, int memInc, int sizeofElem) +{ + if (p==NULL) { if (memInc) return malloc(memInc*sizeofElem); return malloc(sizeofElem); } + if ((memInc==0)||((newsize%memInc)==0)) p=realloc(p,(newsize+memInc)*sizeofElem); +// if (!p) +// { +// printf("XMLParser Error: Not enough memory! Aborting...\n"); exit(220); +// } + return p; +} + +// private: +XMLElementPosition XMLNode::findPosition(XMLNodeData *d, int index, XMLElementType xxtype) +{ + if (index<0) return -1; + int i=0,j=(int)((index<<2)+xxtype),*o=d->pOrder; while (o[i]!=j) i++; return i; +} + +// private: +// update "order" information when deleting a content of a XMLNode +int XMLNode::removeOrderElement(XMLNodeData *d, XMLElementType t, int index) +{ + int n=d->nChild+d->nText+d->nClear, *o=d->pOrder,i=findPosition(d,index,t); + memmove(o+i, o+i+1, (n-i)*sizeof(int)); + for (;ipOrder=(int)realloc(d->pOrder,n*sizeof(int)); + // but we skip reallocation because it's too time consuming. + // Anyway, at the end, it will be free'd completely at once. + return i; +} + +void *XMLNode::addToOrder(int memoryIncrease,int *_pos, int nc, void *p, int size, XMLElementType xtype) +{ + // in: *_pos is the position inside d->pOrder ("-1" means "EndOf") + // out: *_pos is the index inside p + p=myRealloc(p,(nc+1),memoryIncrease,size); + int n=d->nChild+d->nText+d->nClear; + d->pOrder=(int*)myRealloc(d->pOrder,n+1,memoryIncrease*3,sizeof(int)); + int pos=*_pos,*o=d->pOrder; + + if ((pos<0)||(pos>=n)) { *_pos=nc; o[n]=(int)((nc<<2)+xtype); return p; } + + int i=pos; + memmove(o+i+1, o+i, (n-i)*sizeof(int)); + + while ((pos>2; + memmove(((char*)p)+(pos+1)*size,((char*)p)+pos*size,(nc-pos)*size); + + return p; +} + +// Add a child node to the given element. +XMLNode XMLNode::addChild_priv(int memoryIncrease, XMLSTR lpszName, char isDeclaration, int pos) +{ + if (!lpszName) return emptyXMLNode; + d->pChild=(XMLNode*)addToOrder(memoryIncrease,&pos,d->nChild,d->pChild,sizeof(XMLNode),eNodeChild); + d->pChild[pos].d=NULL; + d->pChild[pos]=XMLNode(d,lpszName,isDeclaration); + d->nChild++; + return d->pChild[pos]; +} + +// Add an attribute to an element. +XMLAttribute *XMLNode::addAttribute_priv(int memoryIncrease,XMLSTR lpszName, XMLSTR lpszValuev) +{ + if (!lpszName) return &emptyXMLAttribute; + if (!d) { myFree(lpszName); myFree(lpszValuev); return &emptyXMLAttribute; } + int nc=d->nAttribute; + d->pAttribute=(XMLAttribute*)myRealloc(d->pAttribute,(nc+1),memoryIncrease,sizeof(XMLAttribute)); + XMLAttribute *pAttr=d->pAttribute+nc; + pAttr->lpszName = lpszName; + pAttr->lpszValue = lpszValuev; + d->nAttribute++; + return pAttr; +} + +// Add text to the element. +XMLCSTR XMLNode::addText_priv(int memoryIncrease, XMLSTR lpszValue, int pos) +{ + if (!lpszValue) return NULL; + if (!d) { myFree(lpszValue); return NULL; } + d->pText=(XMLCSTR*)addToOrder(memoryIncrease,&pos,d->nText,d->pText,sizeof(XMLSTR),eNodeText); + d->pText[pos]=lpszValue; + d->nText++; + return lpszValue; +} + +// Add clear (unformatted) text to the element. +XMLClear *XMLNode::addClear_priv(int memoryIncrease, XMLSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, int pos) +{ + if (!lpszValue) return &emptyXMLClear; + if (!d) { myFree(lpszValue); return &emptyXMLClear; } + d->pClear=(XMLClear *)addToOrder(memoryIncrease,&pos,d->nClear,d->pClear,sizeof(XMLClear),eNodeClear); + XMLClear *pNewClear=d->pClear+pos; + pNewClear->lpszValue = lpszValue; + if (!lpszOpen) lpszOpen=XMLClearTags->lpszOpen; + if (!lpszClose) lpszClose=XMLClearTags->lpszClose; + pNewClear->lpszOpenTag = lpszOpen; + pNewClear->lpszCloseTag = lpszClose; + d->nClear++; + return pNewClear; +} + +// private: +// Parse a clear (unformatted) type node. +char XMLNode::parseClearTag(void *px, void *_pClear) +{ + XML *pXML=(XML *)px; + ALLXMLClearTag pClear=*((ALLXMLClearTag*)_pClear); + int cbTemp=0; + XMLCSTR lpszTemp=NULL; + XMLCSTR lpXML=&pXML->lpXML[pXML->nIndex]; + static XMLCSTR docTypeEnd=_CXML("]>"); + + // Find the closing tag + // Seems the ')) { lpszTemp=pCh; break; } +#ifdef _XMLWIDECHAR + pCh++; +#else + pCh+=XML_ByteTable[(unsigned char)(*pCh)]; +#endif + } + } else lpszTemp=xstrstr(lpXML, pClear.lpszClose); + + if (lpszTemp) + { + // Cache the size and increment the index + cbTemp = (int)(lpszTemp - lpXML); + + pXML->nIndex += cbTemp+(int)xstrlen(pClear.lpszClose); + + // Add the clear node to the current element + addClear_priv(MEMORYINCREASE,cbTemp?stringDup(lpXML,cbTemp):NULL, pClear.lpszOpen, pClear.lpszClose,-1); + return 0; + } + + // If we failed to find the end tag + pXML->error = eXMLErrorUnmatchedEndClearTag; + return 1; +} + +void XMLNode::exactMemory(XMLNodeData *d) +{ + if (d->pOrder) d->pOrder=(int*)realloc(d->pOrder,(d->nChild+d->nText+d->nClear)*sizeof(int)); + if (d->pChild) d->pChild=(XMLNode*)realloc(d->pChild,d->nChild*sizeof(XMLNode)); + if (d->pAttribute) d->pAttribute=(XMLAttribute*)realloc(d->pAttribute,d->nAttribute*sizeof(XMLAttribute)); + if (d->pText) d->pText=(XMLCSTR*)realloc(d->pText,d->nText*sizeof(XMLSTR)); + if (d->pClear) d->pClear=(XMLClear *)realloc(d->pClear,d->nClear*sizeof(XMLClear)); +} + +char XMLNode::maybeAddTxT(void *pa, XMLCSTR tokenPStr) +{ + XML *pXML=(XML *)pa; + XMLCSTR lpszText=pXML->lpszText; + if (!lpszText) return 0; + if (dropWhiteSpace) while (XML_isSPACECHAR(*lpszText)&&(lpszText!=tokenPStr)) lpszText++; + int cbText = (int)(tokenPStr - lpszText); + if (!cbText) { pXML->lpszText=NULL; return 0; } + if (dropWhiteSpace) { cbText--; while ((cbText)&&XML_isSPACECHAR(lpszText[cbText])) cbText--; cbText++; } + if (!cbText) { pXML->lpszText=NULL; return 0; } + XMLSTR lpt=fromXMLString(lpszText,cbText,pXML); + if (!lpt) return 1; + pXML->lpszText=NULL; + if (removeCommentsInMiddleOfText && d->nText && d->nClear) + { + // if the previous insertion was a comment () AND + // if the previous previous insertion was a text then, delete the comment and append the text + int n=d->nChild+d->nText+d->nClear-1,*o=d->pOrder; + if (((o[n]&3)==eNodeClear)&&((o[n-1]&3)==eNodeText)) + { + int i=o[n]>>2; + if (d->pClear[i].lpszOpenTag==XMLClearTags[2].lpszOpen) + { + deleteClear(i); + i=o[n-1]>>2; + n=xstrlen(d->pText[i]); + int n2=xstrlen(lpt)+1; + d->pText[i]=(XMLSTR)realloc((void*)d->pText[i],(n+n2)*sizeof(XMLCHAR)); + if (!d->pText[i]) return 1; + memcpy((void*)(d->pText[i]+n),lpt,n2*sizeof(XMLCHAR)); + free(lpt); + return 0; + } + } + } + addText_priv(MEMORYINCREASE,lpt,-1); + return 0; +} +// private: +// Recursively parse an XML element. +int XMLNode::ParseXMLElement(void *pa) +{ + XML *pXML=(XML *)pa; + int cbToken; + enum XMLTokenTypeTag xtype; + NextToken token; + XMLCSTR lpszTemp=NULL; + int cbTemp=0; + char nDeclaration; + XMLNode pNew; + enum XMLStatus status; // inside or outside a tag + enum Attrib attrib = eAttribName; + + assert(pXML); + + // If this is the first call to the function + if (pXML->nFirst) + { + // Assume we are outside of a tag definition + pXML->nFirst = FALSE; + status = eOutsideTag; + } else + { + // If this is not the first call then we should only be called when inside a tag. + status = eInsideTag; + } + + // Iterate through the tokens in the document + for(;;) + { + // Obtain the next token + token = GetNextToken(pXML, &cbToken, &xtype); + + if (xtype != eTokenError) + { + // Check the current status + switch(status) + { + + // If we are outside of a tag definition + case eOutsideTag: + + // Check what type of token we obtained + switch(xtype) + { + // If we have found text or quoted text + case eTokenText: + case eTokenCloseTag: /* '>' */ + case eTokenShortHandClose: /* '/>' */ + case eTokenQuotedText: + case eTokenEquals: + break; + + // If we found a start tag '<' and declarations 'error = eXMLErrorMissingTagName; + return FALSE; + } + + // If we found a new element which is the same as this + // element then we need to pass this back to the caller.. + +#ifdef APPROXIMATE_PARSING + if (d->lpszName && + myTagCompare(d->lpszName, token.pStr) == 0) + { + // Indicate to the caller that it needs to create a + // new element. + pXML->lpNewElement = token.pStr; + pXML->cbNewElement = cbToken; + return TRUE; + } else +#endif + { + // If the name of the new element differs from the name of + // the current element we need to add the new element to + // the current one and recurse + pNew = addChild_priv(MEMORYINCREASE,stringDup(token.pStr,cbToken), nDeclaration,-1); + + while (!pNew.isEmpty()) + { + // Callself to process the new node. If we return + // FALSE this means we dont have any more + // processing to do... + + if (!pNew.ParseXMLElement(pXML)) return FALSE; + else + { + // If the call to recurse this function + // evented in a end tag specified in XML then + // we need to unwind the calls to this + // function until we find the appropriate node + // (the element name and end tag name must + // match) + if (pXML->cbEndTag) + { + // If we are back at the root node then we + // have an unmatched end tag + if (!d->lpszName) + { + pXML->error=eXMLErrorUnmatchedEndTag; + return FALSE; + } + + // If the end tag matches the name of this + // element then we only need to unwind + // once more... + + if (myTagCompare(d->lpszName, pXML->lpEndTag)==0) + { + pXML->cbEndTag = 0; + } + + return TRUE; + } else + if (pXML->cbNewElement) + { + // If the call indicated a new element is to + // be created on THIS element. + + // If the name of this element matches the + // name of the element we need to create + // then we need to return to the caller + // and let it process the element. + + if (myTagCompare(d->lpszName, pXML->lpNewElement)==0) + { + return TRUE; + } + + // Add the new element and recurse + pNew = addChild_priv(MEMORYINCREASE,stringDup(pXML->lpNewElement,pXML->cbNewElement),0,-1); + pXML->cbNewElement = 0; + } + else + { + // If we didn't have a new element to create + pNew = emptyXMLNode; + + } + } + } + } + break; + + // If we found an end tag + case eTokenTagEnd: + + // If we have node text then add this to the element + if (maybeAddTxT(pXML,token.pStr)) return FALSE; + + // Find the name of the end tag + token = GetNextToken(pXML, &cbTemp, &xtype); + + // The end tag should be text + if (xtype != eTokenText) + { + pXML->error = eXMLErrorMissingEndTagName; + return FALSE; + } + lpszTemp = token.pStr; + + // After the end tag we should find a closing tag + token = GetNextToken(pXML, &cbToken, &xtype); + if (xtype != eTokenCloseTag) + { + pXML->error = eXMLErrorMissingEndTagName; + return FALSE; + } + pXML->lpszText=pXML->lpXML+pXML->nIndex; + + // We need to return to the previous caller. If the name + // of the tag cannot be found we need to keep returning to + // caller until we find a match + if (myTagCompare(d->lpszName, lpszTemp) != 0) +#ifdef STRICT_PARSING + { + pXML->error=eXMLErrorUnmatchedEndTag; + pXML->nIndexMissigEndTag=pXML->nIndex; + return FALSE; + } +#else + { + pXML->error=eXMLErrorMissingEndTag; + pXML->nIndexMissigEndTag=pXML->nIndex; + pXML->lpEndTag = lpszTemp; + pXML->cbEndTag = cbTemp; + } +#endif + + // Return to the caller + exactMemory(d); + return TRUE; + + // If we found a clear (unformatted) token + case eTokenClear: + // If we have node text then add this to the element + if (maybeAddTxT(pXML,token.pStr)) return FALSE; + if (parseClearTag(pXML, token.pClr)) return FALSE; + pXML->lpszText=pXML->lpXML+pXML->nIndex; + break; + + default: + break; + } + break; + + // If we are inside a tag definition we need to search for attributes + case eInsideTag: + + // Check what part of the attribute (name, equals, value) we + // are looking for. + switch(attrib) + { + // If we are looking for a new attribute + case eAttribName: + + // Check what the current token type is + switch(xtype) + { + // If the current type is text... + // Eg. 'attribute' + case eTokenText: + // Cache the token then indicate that we are next to + // look for the equals + lpszTemp = token.pStr; + cbTemp = cbToken; + attrib = eAttribEquals; + break; + + // If we found a closing tag... + // Eg. '>' + case eTokenCloseTag: + // We are now outside the tag + status = eOutsideTag; + pXML->lpszText=pXML->lpXML+pXML->nIndex; + break; + + // If we found a short hand '/>' closing tag then we can + // return to the caller + case eTokenShortHandClose: + exactMemory(d); + pXML->lpszText=pXML->lpXML+pXML->nIndex; + return TRUE; + + // Errors... + case eTokenQuotedText: /* '"SomeText"' */ + case eTokenTagStart: /* '<' */ + case eTokenTagEnd: /* 'error = eXMLErrorUnexpectedToken; + return FALSE; + default: break; + } + break; + + // If we are looking for an equals + case eAttribEquals: + // Check what the current token type is + switch(xtype) + { + // If the current type is text... + // Eg. 'Attribute AnotherAttribute' + case eTokenText: + // Add the unvalued attribute to the list + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp), NULL); + // Cache the token then indicate. We are next to + // look for the equals attribute + lpszTemp = token.pStr; + cbTemp = cbToken; + break; + + // If we found a closing tag 'Attribute >' or a short hand + // closing tag 'Attribute />' + case eTokenShortHandClose: + case eTokenCloseTag: + // If we are a declaration element 'lpszText=pXML->lpXML+pXML->nIndex; + + if (d->isDeclaration && + (lpszTemp[cbTemp-1]) == _CXML('?')) + { + cbTemp--; + if (d->pParent && d->pParent->pParent) xtype = eTokenShortHandClose; + } + + if (cbTemp) + { + // Add the unvalued attribute to the list + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp), NULL); + } + + // If this is the end of the tag then return to the caller + if (xtype == eTokenShortHandClose) + { + exactMemory(d); + return TRUE; + } + + // We are now outside the tag + status = eOutsideTag; + break; + + // If we found the equals token... + // Eg. 'Attribute =' + case eTokenEquals: + // Indicate that we next need to search for the value + // for the attribute + attrib = eAttribValue; + break; + + // Errors... + case eTokenQuotedText: /* 'Attribute "InvalidAttr"'*/ + case eTokenTagStart: /* 'Attribute <' */ + case eTokenTagEnd: /* 'Attribute error = eXMLErrorUnexpectedToken; + return FALSE; + default: break; + } + break; + + // If we are looking for an attribute value + case eAttribValue: + // Check what the current token type is + switch(xtype) + { + // If the current type is text or quoted text... + // Eg. 'Attribute = "Value"' or 'Attribute = Value' or + // 'Attribute = 'Value''. + case eTokenText: + case eTokenQuotedText: + // If we are a declaration element 'isDeclaration && + (token.pStr[cbToken-1]) == _CXML('?')) + { + cbToken--; + } + + if (cbTemp) + { + // Add the valued attribute to the list + if (xtype==eTokenQuotedText) { token.pStr++; cbToken-=2; } + XMLSTR attrVal=(XMLSTR)token.pStr; + if (attrVal) + { + attrVal=fromXMLString(attrVal,cbToken,pXML); + if (!attrVal) return FALSE; + } + addAttribute_priv(MEMORYINCREASE,stringDup(lpszTemp,cbTemp),attrVal); + } + + // Indicate we are searching for a new attribute + attrib = eAttribName; + break; + + // Errors... + case eTokenTagStart: /* 'Attr = <' */ + case eTokenTagEnd: /* 'Attr = ' */ + case eTokenShortHandClose: /* "Attr = />" */ + case eTokenEquals: /* 'Attr = =' */ + case eTokenDeclaration: /* 'Attr = error = eXMLErrorUnexpectedToken; + return FALSE; + break; + default: break; + } + } + } + } + // If we failed to obtain the next token + else + { + if ((!d->isDeclaration)&&(d->pParent)) + { +#ifdef STRICT_PARSING + pXML->error=eXMLErrorUnmatchedEndTag; +#else + pXML->error=eXMLErrorMissingEndTag; +#endif + pXML->nIndexMissigEndTag=pXML->nIndex; + } + maybeAddTxT(pXML,pXML->lpXML+pXML->nIndex); + return FALSE; + } + } +} + +// Count the number of lines and columns in an XML string. +static void CountLinesAndColumns(XMLCSTR lpXML, int nUpto, XMLResults *pResults) +{ + XMLCHAR ch; + assert(lpXML); + assert(pResults); + + struct XML xml={ lpXML,lpXML, 0, 0, eXMLErrorNone, NULL, 0, NULL, 0, TRUE }; + + pResults->nLine = 1; + pResults->nColumn = 1; + while (xml.nIndexnColumn++; + else + { + pResults->nLine++; + pResults->nColumn=1; + } + } +} + +// Parse XML and return the root element. +XMLNode XMLNode::parseString(XMLCSTR lpszXML, XMLCSTR tag, XMLResults *pResults) +{ + if (!lpszXML) + { + if (pResults) + { + pResults->error=eXMLErrorNoElements; + pResults->nLine=0; + pResults->nColumn=0; + } + return emptyXMLNode; + } + + XMLNode xnode(NULL,NULL,FALSE); + struct XML xml={ lpszXML, lpszXML, 0, 0, eXMLErrorNone, NULL, 0, NULL, 0, TRUE }; + + // Create header element + xnode.ParseXMLElement(&xml); + enum XMLError error = xml.error; + if (!xnode.nChildNode()) error=eXMLErrorNoXMLTagFound; + if ((xnode.nChildNode()==1)&&(xnode.nElement()==1)) xnode=xnode.getChildNode(); // skip the empty node + + // If no error occurred + if ((error==eXMLErrorNone)||(error==eXMLErrorMissingEndTag)||(error==eXMLErrorNoXMLTagFound)) + { + XMLCSTR name=xnode.getName(); + if (tag&&(*tag)&&((!name)||(xstricmp(name,tag)))) + { + xnode=xnode.getChildNode(tag); + if (xnode.isEmpty()) + { + if (pResults) + { + pResults->error=eXMLErrorFirstTagNotFound; + pResults->nLine=0; + pResults->nColumn=0; + } + return emptyXMLNode; + } + } + } else + { + // Cleanup: this will destroy all the nodes + xnode = emptyXMLNode; + } + + + // If we have been given somewhere to place results + if (pResults) + { + pResults->error = error; + + // If we have an error + if (error!=eXMLErrorNone) + { + if (error==eXMLErrorMissingEndTag) xml.nIndex=xml.nIndexMissigEndTag; + // Find which line and column it starts on. + CountLinesAndColumns(xml.lpXML, xml.nIndex, pResults); + } + } + return xnode; +} + +XMLNode XMLNode::parseFile(XMLCSTR filename, XMLCSTR tag, XMLResults *pResults) +{ + if (pResults) { pResults->nLine=0; pResults->nColumn=0; } + FILE *f=xfopen(filename,_CXML("rb")); + if (f==NULL) { if (pResults) pResults->error=eXMLErrorFileNotFound; return emptyXMLNode; } + fseek(f,0,SEEK_END); + int l=(int)ftell(f),headerSz=0; + if (!l) { if (pResults) pResults->error=eXMLErrorEmpty; fclose(f); return emptyXMLNode; } + fseek(f,0,SEEK_SET); + unsigned char *buf=(unsigned char*)malloc(l+4); + l=(int)fread(buf,1,l,f); + fclose(f); + buf[l]=0;buf[l+1]=0;buf[l+2]=0;buf[l+3]=0; +#ifdef _XMLWIDECHAR + if (guessWideCharChars) + { + if (!myIsTextWideChar(buf,l)) + { + XMLNode::XMLCharEncoding ce=XMLNode::char_encoding_legacy; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) { headerSz=3; ce=XMLNode::char_encoding_UTF8; } + XMLSTR b2=myMultiByteToWideChar((const char*)(buf+headerSz),ce); + if (!b2) + { + // todo: unable to convert + } + free(buf); buf=(unsigned char*)b2; headerSz=0; + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + } + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } +#else + if (guessWideCharChars) + { + if (myIsTextWideChar(buf,l)) + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + char *b2=myWideCharToMultiByte((const wchar_t*)(buf+headerSz)); + free(buf); buf=(unsigned char*)b2; headerSz=0; + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } + } else + { + if ((buf[0]==0xef)&&(buf[1]==0xff)) headerSz=2; + if ((buf[0]==0xff)&&(buf[1]==0xfe)) headerSz=2; + if ((buf[0]==0xef)&&(buf[1]==0xbb)&&(buf[2]==0xbf)) headerSz=3; + } +#endif + + if (!buf) { if (pResults) pResults->error=eXMLErrorCharConversionError; return emptyXMLNode; } + XMLNode x=parseString((XMLSTR)(buf+headerSz),tag,pResults); + free(buf); + return x; +} + +static inline void charmemset(XMLSTR dest,XMLCHAR c,int l) { while (l--) *(dest++)=c; } +// private: +// Creates an user friendly XML string from a given element with +// appropriate white space and carriage returns. +// +// This recurses through all subnodes then adds contents of the nodes to the +// string. +int XMLNode::CreateXMLStringR(XMLNodeData *pEntry, XMLSTR lpszMarker, int nFormat) +{ + int nResult = 0; + int cb=nFormat<0?0:nFormat; + int cbElement; + int nChildFormat=-1; + int nElementI=pEntry->nChild+pEntry->nText+pEntry->nClear; + int i,j; + if ((nFormat>=0)&&(nElementI==1)&&(pEntry->nText==1)&&(!pEntry->isDeclaration)) nFormat=-2; + + assert(pEntry); + +#define LENSTR(lpsz) (lpsz ? xstrlen(lpsz) : 0) + + // If the element has no name then assume this is the head node. + cbElement = (int)LENSTR(pEntry->lpszName); + + if (cbElement) + { + // "isDeclaration) lpszMarker[nResult++]=_CXML('?'); + xstrcpy(&lpszMarker[nResult], pEntry->lpszName); + nResult+=cbElement; + lpszMarker[nResult++]=_CXML(' '); + + } else + { + nResult+=cbElement+2+cb; + if (pEntry->isDeclaration) nResult++; + } + + // Enumerate attributes and add them to the string + XMLAttribute *pAttr=pEntry->pAttribute; + for (i=0; inAttribute; i++) + { + // "Attrib + cb = (int)LENSTR(pAttr->lpszName); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pAttr->lpszName); + nResult += cb; + // "Attrib=Value " + if (pAttr->lpszValue) + { + cb=(int)ToXMLStringTool::lengthXMLString(pAttr->lpszValue); + if (lpszMarker) + { + lpszMarker[nResult]=_CXML('='); + lpszMarker[nResult+1]=_CXML('"'); + if (cb) ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult+2],pAttr->lpszValue); + lpszMarker[nResult+cb+2]=_CXML('"'); + } + nResult+=cb+3; + } + if (lpszMarker) lpszMarker[nResult] = _CXML(' '); + nResult++; + } + pAttr++; + } + + if (pEntry->isDeclaration) + { + if (lpszMarker) + { + lpszMarker[nResult-1]=_CXML('?'); + lpszMarker[nResult]=_CXML('>'); + } + nResult++; + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult]=_CXML('\n'); + nResult++; + } + } else + // If there are child nodes we need to terminate the start tag + if (nElementI) + { + if (lpszMarker) lpszMarker[nResult-1]=_CXML('>'); + if (nFormat>=0) + { + if (lpszMarker) lpszMarker[nResult]=_CXML('\n'); + nResult++; + } + } else nResult--; + } + + // Calculate the child format for when we recurse. This is used to + // determine the number of spaces used for prefixes. + if (nFormat!=-1) + { + if (cbElement&&(!pEntry->isDeclaration)) nChildFormat=nFormat+1; + else nChildFormat=nFormat; + } + + // Enumerate through remaining children + for (i=0; ipOrder[i]; + switch((XMLElementType)(j&3)) + { + // Text nodes + case eNodeText: + { + // "Text" + XMLCSTR pChild=pEntry->pText[j>>2]; + cb = (int)ToXMLStringTool::lengthXMLString(pChild); + if (cb) + { + if (nFormat>=0) + { + if (lpszMarker) + { + charmemset(&lpszMarker[nResult],INDENTCHAR,nFormat+1); + ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult+nFormat+1],pChild); + lpszMarker[nResult+nFormat+1+cb]=_CXML('\n'); + } + nResult+=cb+nFormat+2; + } else + { + if (lpszMarker) ToXMLStringTool::toXMLUnSafe(&lpszMarker[nResult], pChild); + nResult += cb; + } + } + break; + } + + // Clear type nodes + case eNodeClear: + { + XMLClear *pChild=pEntry->pClear+(j>>2); + // "OpenTag" + cb = (int)LENSTR(pChild->lpszOpenTag); + if (cb) + { + if (nFormat!=-1) + { + if (lpszMarker) + { + charmemset(&lpszMarker[nResult], INDENTCHAR, nFormat+1); + xstrcpy(&lpszMarker[nResult+nFormat+1], pChild->lpszOpenTag); + } + nResult+=cb+nFormat+1; + } + else + { + if (lpszMarker)xstrcpy(&lpszMarker[nResult], pChild->lpszOpenTag); + nResult += cb; + } + } + + // "OpenTag Value" + cb = (int)LENSTR(pChild->lpszValue); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pChild->lpszValue); + nResult += cb; + } + + // "OpenTag Value CloseTag" + cb = (int)LENSTR(pChild->lpszCloseTag); + if (cb) + { + if (lpszMarker) xstrcpy(&lpszMarker[nResult], pChild->lpszCloseTag); + nResult += cb; + } + + if (nFormat!=-1) + { + if (lpszMarker) lpszMarker[nResult] = _CXML('\n'); + nResult++; + } + break; + } + + // Element nodes + case eNodeChild: + { + // Recursively add child nodes + nResult += CreateXMLStringR(pEntry->pChild[j>>2].d, lpszMarker ? lpszMarker + nResult : 0, nChildFormat); + break; + } + default: break; + } + } + + if ((cbElement)&&(!pEntry->isDeclaration)) + { + // If we have child entries we need to use long XML notation for + // closing the element - "blah blah blah" + if (nElementI) + { + // "\0" + if (lpszMarker) + { + if (nFormat >=0) + { + charmemset(&lpszMarker[nResult], INDENTCHAR,nFormat); + nResult+=nFormat; + } + + lpszMarker[nResult]=_CXML('<'); lpszMarker[nResult+1]=_CXML('/'); + nResult += 2; + xstrcpy(&lpszMarker[nResult], pEntry->lpszName); + nResult += cbElement; + + lpszMarker[nResult]=_CXML('>'); + if (nFormat == -1) nResult++; + else + { + lpszMarker[nResult+1]=_CXML('\n'); + nResult+=2; + } + } else + { + if (nFormat>=0) nResult+=cbElement+4+nFormat; + else if (nFormat==-1) nResult+=cbElement+3; + else nResult+=cbElement+4; + } + } else + { + // If there are no children we can use shorthand XML notation - + // "" + // "/>\0" + if (lpszMarker) + { + lpszMarker[nResult]=_CXML('/'); lpszMarker[nResult+1]=_CXML('>'); + if (nFormat != -1) lpszMarker[nResult+2]=_CXML('\n'); + } + nResult += nFormat == -1 ? 2 : 3; + } + } + + return nResult; +} + +#undef LENSTR + +// Create an XML string +// @param int nFormat - 0 if no formatting is required +// otherwise nonzero for formatted text +// with carriage returns and indentation. +// @param int *pnSize - [out] pointer to the size of the +// returned string not including the +// NULL terminator. +// @return XMLSTR - Allocated XML string, you must free +// this with free(). +XMLSTR XMLNode::createXMLString(int nFormat, int *pnSize) const +{ + if (!d) { if (pnSize) *pnSize=0; return NULL; } + + XMLSTR lpszResult = NULL; + int cbStr; + + // Recursively Calculate the size of the XML string + if (!dropWhiteSpace) nFormat=0; + nFormat = nFormat ? 0 : -1; + cbStr = CreateXMLStringR(d, 0, nFormat); + // Alllocate memory for the XML string + the NULL terminator and + // create the recursively XML string. + lpszResult=(XMLSTR)malloc((cbStr+1)*sizeof(XMLCHAR)); + CreateXMLStringR(d, lpszResult, nFormat); + lpszResult[cbStr]=_CXML('\0'); + if (pnSize) *pnSize = cbStr; + return lpszResult; +} + +int XMLNode::detachFromParent(XMLNodeData *d) +{ + XMLNode *pa=d->pParent->pChild; + int i=0; + while (((void*)(pa[i].d))!=((void*)d)) i++; + d->pParent->nChild--; + if (d->pParent->nChild) memmove(pa+i,pa+i+1,(d->pParent->nChild-i)*sizeof(XMLNode)); + else { free(pa); d->pParent->pChild=NULL; } + return removeOrderElement(d->pParent,eNodeChild,i); +} + +XMLNode::~XMLNode() +{ + if (!d) return; + d->ref_count--; + emptyTheNode(0); +} +void XMLNode::deleteNodeContent() +{ + if (!d) return; + if (d->pParent) { detachFromParent(d); d->pParent=NULL; d->ref_count--; } + emptyTheNode(1); +} +void XMLNode::emptyTheNode(char force) +{ + XMLNodeData *dd=d; // warning: must stay this way! + if ((dd->ref_count==0)||force) + { + if (d->pParent) detachFromParent(d); + int i; + XMLNode *pc; + for(i=0; inChild; i++) + { + pc=dd->pChild+i; + pc->d->pParent=NULL; + pc->d->ref_count--; + pc->emptyTheNode(force); + } + myFree(dd->pChild); + for(i=0; inText; i++) free((void*)dd->pText[i]); + myFree(dd->pText); + for(i=0; inClear; i++) free((void*)dd->pClear[i].lpszValue); + myFree(dd->pClear); + for(i=0; inAttribute; i++) + { + free((void*)dd->pAttribute[i].lpszName); + if (dd->pAttribute[i].lpszValue) free((void*)dd->pAttribute[i].lpszValue); + } + myFree(dd->pAttribute); + myFree(dd->pOrder); + myFree((void*)dd->lpszName); + dd->nChild=0; dd->nText=0; dd->nClear=0; dd->nAttribute=0; + dd->pChild=NULL; dd->pText=NULL; dd->pClear=NULL; dd->pAttribute=NULL; + dd->pOrder=NULL; dd->lpszName=NULL; dd->pParent=NULL; + } + if (dd->ref_count==0) + { + free(dd); + d=NULL; + } +} + +XMLNode& XMLNode::operator=( const XMLNode& A ) +{ + // shallow copy + if (this != &A) + { + if (d) { d->ref_count--; emptyTheNode(0); } + d=A.d; + if (d) (d->ref_count) ++ ; + } + return *this; +} + +XMLNode::XMLNode(const XMLNode &A) +{ + // shallow copy + d=A.d; + if (d) (d->ref_count)++ ; +} + +XMLNode XMLNode::deepCopy() const +{ + if (!d) return XMLNode::emptyXMLNode; + XMLNode x(NULL,stringDup(d->lpszName),d->isDeclaration); + XMLNodeData *p=x.d; + int n=d->nAttribute; + if (n) + { + p->nAttribute=n; p->pAttribute=(XMLAttribute*)malloc(n*sizeof(XMLAttribute)); + while (n--) + { + p->pAttribute[n].lpszName=stringDup(d->pAttribute[n].lpszName); + p->pAttribute[n].lpszValue=stringDup(d->pAttribute[n].lpszValue); + } + } + if (d->pOrder) + { + n=(d->nChild+d->nText+d->nClear)*sizeof(int); p->pOrder=(int*)malloc(n); memcpy(p->pOrder,d->pOrder,n); + } + n=d->nText; + if (n) + { + p->nText=n; p->pText=(XMLCSTR*)malloc(n*sizeof(XMLCSTR)); + while(n--) p->pText[n]=stringDup(d->pText[n]); + } + n=d->nClear; + if (n) + { + p->nClear=n; p->pClear=(XMLClear*)malloc(n*sizeof(XMLClear)); + while (n--) + { + p->pClear[n].lpszCloseTag=d->pClear[n].lpszCloseTag; + p->pClear[n].lpszOpenTag=d->pClear[n].lpszOpenTag; + p->pClear[n].lpszValue=stringDup(d->pClear[n].lpszValue); + } + } + n=d->nChild; + if (n) + { + p->nChild=n; p->pChild=(XMLNode*)malloc(n*sizeof(XMLNode)); + while (n--) + { + p->pChild[n].d=NULL; + p->pChild[n]=d->pChild[n].deepCopy(); + p->pChild[n].d->pParent=p; + } + } + return x; +} + +XMLNode XMLNode::addChild(XMLNode childNode, int pos) +{ + XMLNodeData *dc=childNode.d; + if ((!dc)||(!d)) return childNode; + if (!dc->lpszName) + { + // this is a root node: todo: correct fix + int j=pos; + while (dc->nChild) + { + addChild(dc->pChild[0],j); + if (pos>=0) j++; + } + return childNode; + } + if (dc->pParent) { if ((detachFromParent(dc)<=pos)&&(dc->pParent==d)) pos--; } else dc->ref_count++; + dc->pParent=d; +// int nc=d->nChild; +// d->pChild=(XMLNode*)myRealloc(d->pChild,(nc+1),memoryIncrease,sizeof(XMLNode)); + d->pChild=(XMLNode*)addToOrder(0,&pos,d->nChild,d->pChild,sizeof(XMLNode),eNodeChild); + d->pChild[pos].d=dc; + d->nChild++; + return childNode; +} + +void XMLNode::deleteAttribute(int i) +{ + if ((!d)||(i<0)||(i>=d->nAttribute)) return; + d->nAttribute--; + XMLAttribute *p=d->pAttribute+i; + free((void*)p->lpszName); + if (p->lpszValue) free((void*)p->lpszValue); + if (d->nAttribute) memmove(p,p+1,(d->nAttribute-i)*sizeof(XMLAttribute)); else { free(p); d->pAttribute=NULL; } +} + +void XMLNode::deleteAttribute(XMLAttribute *a){ if (a) deleteAttribute(a->lpszName); } +void XMLNode::deleteAttribute(XMLCSTR lpszName) +{ + int j=0; + getAttribute(lpszName,&j); + if (j) deleteAttribute(j-1); +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,int i) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); if (lpszNewName) free(lpszNewName); return NULL; } + if (i>=d->nAttribute) + { + if (lpszNewName) return addAttribute_WOSD(lpszNewName,lpszNewValue); + return NULL; + } + XMLAttribute *p=d->pAttribute+i; + if (p->lpszValue&&p->lpszValue!=lpszNewValue) free((void*)p->lpszValue); + p->lpszValue=lpszNewValue; + if (lpszNewName&&p->lpszName!=lpszNewName) { free((void*)p->lpszName); p->lpszName=lpszNewName; }; + return p; +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLAttribute *newAttribute, XMLAttribute *oldAttribute) +{ + if (oldAttribute) return updateAttribute_WOSD((XMLSTR)newAttribute->lpszValue,(XMLSTR)newAttribute->lpszName,oldAttribute->lpszName); + return addAttribute_WOSD((XMLSTR)newAttribute->lpszName,(XMLSTR)newAttribute->lpszValue); +} + +XMLAttribute *XMLNode::updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,XMLCSTR lpszOldName) +{ + int j=0; + getAttribute(lpszOldName,&j); + if (j) return updateAttribute_WOSD(lpszNewValue,lpszNewName,j-1); + else + { + if (lpszNewName) return addAttribute_WOSD(lpszNewName,lpszNewValue); + else return addAttribute_WOSD(stringDup(lpszOldName),lpszNewValue); + } +} + +int XMLNode::indexText(XMLCSTR lpszValue) const +{ + if (!d) return -1; + int i,l=d->nText; + if (!lpszValue) { if (l) return 0; return -1; } + XMLCSTR *p=d->pText; + for (i=0; i=d->nText)) return; + d->nText--; + XMLCSTR *p=d->pText+i; + free((void*)*p); + if (d->nText) memmove(p,p+1,(d->nText-i)*sizeof(XMLCSTR)); else { free(p); d->pText=NULL; } + removeOrderElement(d,eNodeText,i); +} + +void XMLNode::deleteText(XMLCSTR lpszValue) { deleteText(indexText(lpszValue)); } + +XMLCSTR XMLNode::updateText_WOSD(XMLSTR lpszNewValue, int i) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); return NULL; } + if (i>=d->nText) return addText_WOSD(lpszNewValue); + XMLCSTR *p=d->pText+i; + if (*p!=lpszNewValue) { free((void*)*p); *p=lpszNewValue; } + return lpszNewValue; +} + +XMLCSTR XMLNode::updateText_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue) +{ + if (!d) { if (lpszNewValue) free(lpszNewValue); return NULL; } + int i=indexText(lpszOldValue); + if (i>=0) return updateText_WOSD(lpszNewValue,i); + return addText_WOSD(lpszNewValue); +} + +void XMLNode::deleteClear(int i) +{ + if ((!d)||(i<0)||(i>=d->nClear)) return; + d->nClear--; + XMLClear *p=d->pClear+i; + free((void*)p->lpszValue); + if (d->nClear) memmove(p,p+1,(d->nClear-i)*sizeof(XMLClear)); else { free(p); d->pClear=NULL; } + removeOrderElement(d,eNodeClear,i); +} + +int XMLNode::indexClear(XMLCSTR lpszValue) const +{ + if (!d) return -1; + int i,l=d->nClear; + if (!lpszValue) { if (l) return 0; return -1; } + XMLClear *p=d->pClear; + for (i=0; ilpszValue); } + +XMLClear *XMLNode::updateClear_WOSD(XMLSTR lpszNewContent, int i) +{ + if (!d) { if (lpszNewContent) free(lpszNewContent); return NULL; } + if (i>=d->nClear) return addClear_WOSD(lpszNewContent); + XMLClear *p=d->pClear+i; + if (lpszNewContent!=p->lpszValue) { free((void*)p->lpszValue); p->lpszValue=lpszNewContent; } + return p; +} + +XMLClear *XMLNode::updateClear_WOSD(XMLSTR lpszNewContent, XMLCSTR lpszOldValue) +{ + if (!d) { if (lpszNewContent) free(lpszNewContent); return NULL; } + int i=indexClear(lpszOldValue); + if (i>=0) return updateClear_WOSD(lpszNewContent,i); + return addClear_WOSD(lpszNewContent); +} + +XMLClear *XMLNode::updateClear_WOSD(XMLClear *newP,XMLClear *oldP) +{ + if (oldP) return updateClear_WOSD((XMLSTR)newP->lpszValue,(XMLSTR)oldP->lpszValue); + return NULL; +} + +int XMLNode::nChildNode(XMLCSTR name) const +{ + if (!d) return 0; + int i,j=0,n=d->nChild; + XMLNode *pc=d->pChild; + for (i=0; id->lpszName, name)==0) j++; + pc++; + } + return j; +} + +XMLNode XMLNode::getChildNode(XMLCSTR name, int *j) const +{ + if (!d) return emptyXMLNode; + int i=0,n=d->nChild; + if (j) i=*j; + XMLNode *pc=d->pChild+i; + for (; id->lpszName, name)) + { + if (j) *j=i+1; + return *pc; + } + pc++; + } + return emptyXMLNode; +} + +XMLNode XMLNode::getChildNode(XMLCSTR name, int j) const +{ + if (!d) return emptyXMLNode; + if (j>=0) + { + int i=0; + while (j-->0) getChildNode(name,&i); + return getChildNode(name,&i); + } + int i=d->nChild; + while (i--) if (!xstricmp(name,d->pChild[i].d->lpszName)) break; + if (i<0) return emptyXMLNode; + return getChildNode(i); +} + +XMLNode XMLNode::getChildNodeByPath(XMLCSTR _path, char createMissing, XMLCHAR sep) +{ + XMLSTR path=stringDup(_path); + XMLNode x=getChildNodeByPathNonConst(path,createMissing,sep); + if (path) free(path); + return x; +} + +XMLNode XMLNode::getChildNodeByPathNonConst(XMLSTR path, char createIfMissing, XMLCHAR sep) +{ + if ((!path)||(!(*path))) return *this; + XMLNode xn,xbase=*this; + XMLCHAR *tend1,sepString[2]; sepString[0]=sep; sepString[1]=0; + tend1=xstrstr(path,sepString); + while(tend1) + { + *tend1=0; + xn=xbase.getChildNode(path); + if (xn.isEmpty()) + { + if (createIfMissing) xn=xbase.addChild(path); + else { *tend1=sep; return XMLNode::emptyXMLNode; } + } + *tend1=sep; + xbase=xn; + path=tend1+1; + tend1=xstrstr(path,sepString); + } + xn=xbase.getChildNode(path); + if (xn.isEmpty()&&createIfMissing) xn=xbase.addChild(path); + return xn; +} + +XMLElementPosition XMLNode::positionOfText (int i) const { if (i>=d->nText ) i=d->nText-1; return findPosition(d,i,eNodeText ); } +XMLElementPosition XMLNode::positionOfClear (int i) const { if (i>=d->nClear) i=d->nClear-1; return findPosition(d,i,eNodeClear); } +XMLElementPosition XMLNode::positionOfChildNode(int i) const { if (i>=d->nChild) i=d->nChild-1; return findPosition(d,i,eNodeChild); } +XMLElementPosition XMLNode::positionOfText (XMLCSTR lpszValue) const { return positionOfText (indexText (lpszValue)); } +XMLElementPosition XMLNode::positionOfClear(XMLCSTR lpszValue) const { return positionOfClear(indexClear(lpszValue)); } +XMLElementPosition XMLNode::positionOfClear(XMLClear *a) const { if (a) return positionOfClear(a->lpszValue); return positionOfClear(); } +XMLElementPosition XMLNode::positionOfChildNode(XMLNode x) const +{ + if ((!d)||(!x.d)) return -1; + XMLNodeData *dd=x.d; + XMLNode *pc=d->pChild; + int i=d->nChild; + while (i--) if (pc[i].d==dd) return findPosition(d,i,eNodeChild); + return -1; +} +XMLElementPosition XMLNode::positionOfChildNode(XMLCSTR name, int count) const +{ + if (!name) return positionOfChildNode(count); + int j=0; + do { getChildNode(name,&j); if (j<0) return -1; } while (count--); + return findPosition(d,j-1,eNodeChild); +} + +XMLNode XMLNode::getChildNodeWithAttribute(XMLCSTR name,XMLCSTR attributeName,XMLCSTR attributeValue, int *k) const +{ + int i=0,j; + if (k) i=*k; + XMLNode x; + XMLCSTR t; + do + { + x=getChildNode(name,&i); + if (!x.isEmpty()) + { + if (attributeValue) + { + j=0; + do + { + t=x.getAttribute(attributeName,&j); + if (t&&(xstricmp(attributeValue,t)==0)) { if (k) *k=i; return x; } + } while (t); + } else + { + if (x.isAttributeSet(attributeName)) { if (k) *k=i; return x; } + } + } + } while (!x.isEmpty()); + return emptyXMLNode; +} + +// Find an attribute on an node. +XMLCSTR XMLNode::getAttribute(XMLCSTR lpszAttrib, int *j) const +{ + if (!d) return NULL; + int i=0,n=d->nAttribute; + if (j) i=*j; + XMLAttribute *pAttr=d->pAttribute+i; + for (; ilpszName, lpszAttrib)==0) + { + if (j) *j=i+1; + return pAttr->lpszValue; + } + pAttr++; + } + return NULL; +} + +char XMLNode::isAttributeSet(XMLCSTR lpszAttrib) const +{ + if (!d) return FALSE; + int i,n=d->nAttribute; + XMLAttribute *pAttr=d->pAttribute; + for (i=0; ilpszName, lpszAttrib)==0) + { + return TRUE; + } + pAttr++; + } + return FALSE; +} + +XMLCSTR XMLNode::getAttribute(XMLCSTR name, int j) const +{ + if (!d) return NULL; + int i=0; + while (j-->0) getAttribute(name,&i); + return getAttribute(name,&i); +} + +XMLNodeContents XMLNode::enumContents(int i) const +{ + XMLNodeContents c; + if (!d) { c.etype=eNodeNULL; return c; } + if (inAttribute) + { + c.etype=eNodeAttribute; + c.attrib=d->pAttribute[i]; + return c; + } + i-=d->nAttribute; + c.etype=(XMLElementType)(d->pOrder[i]&3); + i=(d->pOrder[i])>>2; + switch (c.etype) + { + case eNodeChild: c.child = d->pChild[i]; break; + case eNodeText: c.text = d->pText[i]; break; + case eNodeClear: c.clear = d->pClear[i]; break; + default: break; + } + return c; +} + +XMLCSTR XMLNode::getName() const { if (!d) return NULL; return d->lpszName; } +int XMLNode::nText() const { if (!d) return 0; return d->nText; } +int XMLNode::nChildNode() const { if (!d) return 0; return d->nChild; } +int XMLNode::nAttribute() const { if (!d) return 0; return d->nAttribute; } +int XMLNode::nClear() const { if (!d) return 0; return d->nClear; } +int XMLNode::nElement() const { if (!d) return 0; return d->nAttribute+d->nChild+d->nText+d->nClear; } +XMLClear XMLNode::getClear (int i) const { if ((!d)||(i>=d->nClear )) return emptyXMLClear; return d->pClear[i]; } +XMLAttribute XMLNode::getAttribute (int i) const { if ((!d)||(i>=d->nAttribute)) return emptyXMLAttribute; return d->pAttribute[i]; } +XMLCSTR XMLNode::getAttributeName (int i) const { if ((!d)||(i>=d->nAttribute)) return NULL; return d->pAttribute[i].lpszName; } +XMLCSTR XMLNode::getAttributeValue(int i) const { if ((!d)||(i>=d->nAttribute)) return NULL; return d->pAttribute[i].lpszValue; } +XMLCSTR XMLNode::getText (int i) const { if ((!d)||(i>=d->nText )) return NULL; return d->pText[i]; } +XMLNode XMLNode::getChildNode (int i) const { if ((!d)||(i>=d->nChild )) return emptyXMLNode; return d->pChild[i]; } +XMLNode XMLNode::getParentNode ( ) const { if ((!d)||(!d->pParent )) return emptyXMLNode; return XMLNode(d->pParent); } +char XMLNode::isDeclaration ( ) const { if (!d) return 0; return d->isDeclaration; } +char XMLNode::isEmpty ( ) const { return (d==NULL); } +XMLNode XMLNode::emptyNode ( ) { return XMLNode::emptyXMLNode; } + +XMLNode XMLNode::addChild(XMLCSTR lpszName, char isDeclaration, XMLElementPosition pos) + { return addChild_priv(0,stringDup(lpszName),isDeclaration,pos); } +XMLNode XMLNode::addChild_WOSD(XMLSTR lpszName, char isDeclaration, XMLElementPosition pos) + { return addChild_priv(0,lpszName,isDeclaration,pos); } +XMLAttribute *XMLNode::addAttribute(XMLCSTR lpszName, XMLCSTR lpszValue) + { return addAttribute_priv(0,stringDup(lpszName),stringDup(lpszValue)); } +XMLAttribute *XMLNode::addAttribute_WOSD(XMLSTR lpszName, XMLSTR lpszValuev) + { return addAttribute_priv(0,lpszName,lpszValuev); } +XMLCSTR XMLNode::addText(XMLCSTR lpszValue, XMLElementPosition pos) + { return addText_priv(0,stringDup(lpszValue),pos); } +XMLCSTR XMLNode::addText_WOSD(XMLSTR lpszValue, XMLElementPosition pos) + { return addText_priv(0,lpszValue,pos); } +XMLClear *XMLNode::addClear(XMLCSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, XMLElementPosition pos) + { return addClear_priv(0,stringDup(lpszValue),lpszOpen,lpszClose,pos); } +XMLClear *XMLNode::addClear_WOSD(XMLSTR lpszValue, XMLCSTR lpszOpen, XMLCSTR lpszClose, XMLElementPosition pos) + { return addClear_priv(0,lpszValue,lpszOpen,lpszClose,pos); } +XMLCSTR XMLNode::updateName(XMLCSTR lpszName) + { return updateName_WOSD(stringDup(lpszName)); } +XMLAttribute *XMLNode::updateAttribute(XMLAttribute *newAttribute, XMLAttribute *oldAttribute) + { return updateAttribute_WOSD(stringDup(newAttribute->lpszValue),stringDup(newAttribute->lpszName),oldAttribute->lpszName); } +XMLAttribute *XMLNode::updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,int i) + { return updateAttribute_WOSD(stringDup(lpszNewValue),stringDup(lpszNewName),i); } +XMLAttribute *XMLNode::updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName) + { return updateAttribute_WOSD(stringDup(lpszNewValue),stringDup(lpszNewName),lpszOldName); } +XMLCSTR XMLNode::updateText(XMLCSTR lpszNewValue, int i) + { return updateText_WOSD(stringDup(lpszNewValue),i); } +XMLCSTR XMLNode::updateText(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) + { return updateText_WOSD(stringDup(lpszNewValue),lpszOldValue); } +XMLClear *XMLNode::updateClear(XMLCSTR lpszNewContent, int i) + { return updateClear_WOSD(stringDup(lpszNewContent),i); } +XMLClear *XMLNode::updateClear(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue) + { return updateClear_WOSD(stringDup(lpszNewValue),lpszOldValue); } +XMLClear *XMLNode::updateClear(XMLClear *newP,XMLClear *oldP) + { return updateClear_WOSD(stringDup(newP->lpszValue),oldP->lpszValue); } + +char XMLNode::setGlobalOptions(XMLCharEncoding _characterEncoding, char _guessWideCharChars, + char _dropWhiteSpace, char _removeCommentsInMiddleOfText) +{ + guessWideCharChars=_guessWideCharChars; dropWhiteSpace=_dropWhiteSpace; removeCommentsInMiddleOfText=_removeCommentsInMiddleOfText; +#ifdef _XMLWIDECHAR + if (_characterEncoding) characterEncoding=_characterEncoding; +#else + switch(_characterEncoding) + { + case char_encoding_UTF8: characterEncoding=_characterEncoding; XML_ByteTable=XML_utf8ByteTable; break; + case char_encoding_legacy: characterEncoding=_characterEncoding; XML_ByteTable=XML_legacyByteTable; break; + case char_encoding_ShiftJIS: characterEncoding=_characterEncoding; XML_ByteTable=XML_sjisByteTable; break; + case char_encoding_GB2312: characterEncoding=_characterEncoding; XML_ByteTable=XML_gb2312ByteTable; break; + case char_encoding_Big5: + case char_encoding_GBK: characterEncoding=_characterEncoding; XML_ByteTable=XML_gbk_big5_ByteTable; break; + default: return 1; + } +#endif + return 0; +} + +XMLNode::XMLCharEncoding XMLNode::guessCharEncoding(void *buf,int l, char useXMLEncodingAttribute) +{ +#ifdef _XMLWIDECHAR + return (XMLCharEncoding)0; +#else + if (l<25) return (XMLCharEncoding)0; + if (guessWideCharChars&&(myIsTextWideChar(buf,l))) return (XMLCharEncoding)0; + unsigned char *b=(unsigned char*)buf; + if ((b[0]==0xef)&&(b[1]==0xbb)&&(b[2]==0xbf)) return char_encoding_UTF8; + + // Match utf-8 model ? + XMLCharEncoding bestGuess=char_encoding_UTF8; + int i=0; + while (i>2 ]; + *(curr++)=base64EncodeTable[(inbuf[0]<<4)&0x3F]; + *(curr++)=base64Fillchar; + *(curr++)=base64Fillchar; + } else if (eLen==2) + { + j=(inbuf[0]<<8)|inbuf[1]; + *(curr++)=base64EncodeTable[ j>>10 ]; + *(curr++)=base64EncodeTable[(j>> 4)&0x3f]; + *(curr++)=base64EncodeTable[(j<< 2)&0x3f]; + *(curr++)=base64Fillchar; + } + *(curr++)=0; + return (XMLSTR)buf; +} + +unsigned int XMLParserBase64Tool::decodeSize(XMLCSTR data,XMLError *xe) +{ + if (!data) return 0; + if (xe) *xe=eXMLErrorNone; + int size=0; + unsigned char c; + //skip any extra characters (e.g. newlines or spaces) + while (*data) + { +#ifdef _XMLWIDECHAR + if (*data>255) { if (xe) *xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#endif + c=base64DecodeTable[(unsigned char)(*data)]; + if (c<97) size++; + else if (c==98) { if (xe) *xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } + data++; + } + if (xe&&(size%4!=0)) *xe=eXMLErrorBase64DataSizeIsNotMultipleOf4; + if (size==0) return 0; + do { data--; size--; } while(*data==base64Fillchar); size++; + return (unsigned int)((size*3)/4); +} + +unsigned char XMLParserBase64Tool::decode(XMLCSTR data, unsigned char *buf, int len, XMLError *xe) +{ + if (!data) return 0; + if (xe) *xe=eXMLErrorNone; + int i=0,p=0; + unsigned char d,c; + for(;;) + { + +#ifdef _XMLWIDECHAR +#define BASE64DECODE_READ_NEXT_CHAR(c) \ + do { \ + if (data[i]>255){ c=98; break; } \ + c=base64DecodeTable[(unsigned char)data[i++]]; \ + }while (c==97); \ + if(c==98){ if(xe)*xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#else +#define BASE64DECODE_READ_NEXT_CHAR(c) \ + do { c=base64DecodeTable[(unsigned char)data[i++]]; }while (c==97); \ + if(c==98){ if(xe)*xe=eXMLErrorBase64DecodeIllegalCharacter; return 0; } +#endif + + BASE64DECODE_READ_NEXT_CHAR(c) + if (c==99) { return 2; } + if (c==96) + { + if (p==(int)len) return 2; + if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; + return 1; + } + + BASE64DECODE_READ_NEXT_CHAR(d) + if ((d==99)||(d==96)) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) { if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; return 0; } + buf[p++]=(unsigned char)((c<<2)|((d>>4)&0x3)); + + BASE64DECODE_READ_NEXT_CHAR(c) + if (c==99) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) + { + if (c==96) return 2; + if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; + return 0; + } + if (c==96) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + buf[p++]=(unsigned char)(((d<<4)&0xf0)|((c>>2)&0xf)); + + BASE64DECODE_READ_NEXT_CHAR(d) + if (d==99 ) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + if (p==(int)len) + { + if (d==96) return 2; + if (xe) *xe=eXMLErrorBase64DecodeBufferTooSmall; + return 0; + } + if (d==96) { if (xe) *xe=eXMLErrorBase64DecodeTruncatedData; return 1; } + buf[p++]=(unsigned char)(((c<<6)&0xc0)|d); + } +} +#undef BASE64DECODE_READ_NEXT_CHAR + +void XMLParserBase64Tool::alloc(int newsize) +{ + if ((!buf)&&(newsize)) { buf=malloc(newsize); buflen=newsize; return; } + if (newsize>buflen) { buf=realloc(buf,newsize); buflen=newsize; } +} + +unsigned char *XMLParserBase64Tool::decode(XMLCSTR data, int *outlen, XMLError *xe) +{ + if (xe) *xe=eXMLErrorNone; + if (!data) { *outlen=0; return (unsigned char*)""; } + unsigned int len=decodeSize(data,xe); + if (outlen) *outlen=len; + if (!len) return NULL; + alloc(len+1); + if(!decode(data,(unsigned char*)buf,len,xe)){ return NULL; } + return (unsigned char*)buf; +} + diff --git a/source/common/xmlParser.h b/source/common/xmlParser.h new file mode 100644 index 0000000..1187165 --- /dev/null +++ b/source/common/xmlParser.h @@ -0,0 +1,732 @@ +/****************************************************************************/ +/*! \mainpage XMLParser library + * \section intro_sec Introduction + * + * This is a basic XML parser written in ANSI C++ for portability. + * It works by using recursion and a node tree for breaking + * down the elements of an XML document. + * + * @version V2.44 + * @author Frank Vanden Berghen + * + * Copyright (c) 2002, Frank Vanden Berghen - All rights reserved.
+ * Commercialized by Business-Insight
+ * See the file AFPL-license.txt about the licensing terms + * + * \section tutorial First Tutorial + * You can follow a simple Tutorial to know the basics... + * + * \section usage General usage: How to include the XMLParser library inside your project. + * + * The library is composed of two files: xmlParser.cpp and + * xmlParser.h. These are the ONLY 2 files that you need when + * using the library inside your own projects. + * + * All the functions of the library are documented inside the comments of the file + * xmlParser.h. These comments can be transformed in + * full-fledged HTML documentation using the DOXYGEN software: simply type: "doxygen doxy.cfg" + * + * By default, the XMLParser library uses (char*) for string representation.To use the (wchar_t*) + * version of the library, you need to define the "_UNICODE" preprocessor definition variable + * (this is usually done inside your project definition file) (This is done automatically for you + * when using Visual Studio). + * + * \section example Advanced Tutorial and Many Examples of usage. + * + * Some very small introductory examples are described inside the Tutorial file + * xmlParser.html + * + * Some additional small examples are also inside the file xmlTest.cpp + * (for the "char*" version of the library) and inside the file + * xmlTestUnicode.cpp (for the "wchar_t*" + * version of the library). If you have a question, please review these additionnal examples + * before sending an e-mail to the author. + * + * To build the examples: + * - linux/unix: type "make" + * - solaris: type "make -f makefile.solaris" + * - windows: Visual Studio: double-click on xmlParser.dsw + * (under Visual Studio .NET, the .dsp and .dsw files will be automatically converted to .vcproj and .sln files) + * + * In order to build the examples you need some additional files: + * - linux/unix: makefile + * - solaris: makefile.solaris + * - windows: Visual Studio: *.dsp, xmlParser.dsw and also xmlParser.lib and xmlParser.dll + * + * \section debugging Debugging with the XMLParser library + * + * \subsection debugwin Debugging under WINDOWS + * + * Inside Visual C++, the "debug versions" of the memory allocation functions are + * very slow: Do not forget to compile in "release mode" to get maximum speed. + * When I had to debug a software that was using the XMLParser Library, it was usually + * a nightmare because the library was sooOOOoooo slow in debug mode (because of the + * slow memory allocations in Debug mode). To solve this + * problem, during all the debugging session, I am now using a very fast DLL version of the + * XMLParser Library (the DLL is compiled in release mode). Using the DLL version of + * the XMLParser Library allows me to have lightening XML parsing speed even in debug! + * Other than that, the DLL version is useless: In the release version of my tool, + * I always use the normal, ".cpp"-based, XMLParser Library (I simply include the + * xmlParser.cpp and + * xmlParser.h files into the project). + * + * The file XMLNodeAutoexp.txt contains some + * "tweaks" that improve substancially the display of the content of the XMLNode objects + * inside the Visual Studio Debugger. Believe me, once you have seen inside the debugger + * the "smooth" display of the XMLNode objects, you cannot live without it anymore! + * + * \subsection debuglinux Debugging under LINUX/UNIX + * + * The speed of the debug version of the XMLParser library is tolerable so no extra + * work.has been done. + * + ****************************************************************************/ + +#ifndef __INCLUDE_XML_NODE__ +#define __INCLUDE_XML_NODE__ + +#include + +#if defined(UNICODE) || defined(_UNICODE) +// If you comment the next "define" line then the library will never "switch to" _UNICODE (wchar_t*) mode (16/32 bits per characters). +// This is useful when you get error messages like: +// 'XMLNode::openFileHelper' : cannot convert parameter 2 from 'const char [5]' to 'const wchar_t *' +// The _XMLWIDECHAR preprocessor variable force the XMLParser library into either utf16/32-mode (the proprocessor variable +// must be defined) or utf8-mode(the pre-processor variable must be undefined). +#define _XMLWIDECHAR +#endif + +#if defined(WIN32) || defined(UNDER_CE) || defined(_WIN32) || defined(WIN64) || defined(__BORLANDC__) +// comment the next line if you are under windows and the compiler is not Microsoft Visual Studio (6.0 or .NET) or Borland +#define _XMLWINDOWS +#endif + +#ifdef XMLDLLENTRY +#undef XMLDLLENTRY +#endif +#ifdef _USE_XMLPARSER_DLL +#ifdef _DLL_EXPORTS_ +#define XMLDLLENTRY __declspec(dllexport) +#else +#define XMLDLLENTRY __declspec(dllimport) +#endif +#else +#define XMLDLLENTRY +#endif + +// uncomment the next line if you want no support for wchar_t* (no need for the or libraries anymore to compile) +//#define XML_NO_WIDE_CHAR + +#ifdef XML_NO_WIDE_CHAR +#undef _XMLWINDOWS +#undef _XMLWIDECHAR +#endif + +#ifdef _XMLWINDOWS +#include +#else +#define XMLDLLENTRY +#ifndef XML_NO_WIDE_CHAR +#include // to have 'wcsrtombs' for ANSI version + // to have 'mbsrtowcs' for WIDECHAR version +#endif +#endif + +// Some common types for char set portable code +#ifdef _XMLWIDECHAR + #define _CXML(c) L ## c + #define XMLCSTR const wchar_t * + #define XMLSTR wchar_t * + #define XMLCHAR wchar_t +#else + #define _CXML(c) c + #define XMLCSTR const char * + #define XMLSTR char * + #define XMLCHAR char +#endif +#ifndef FALSE + #define FALSE 0 +#endif /* FALSE */ +#ifndef TRUE + #define TRUE 1 +#endif /* TRUE */ + + +/// Enumeration for XML parse errors. +typedef enum XMLError +{ + eXMLErrorNone = 0, + eXMLErrorMissingEndTag, + eXMLErrorNoXMLTagFound, + eXMLErrorEmpty, + eXMLErrorMissingTagName, + eXMLErrorMissingEndTagName, + eXMLErrorUnmatchedEndTag, + eXMLErrorUnmatchedEndClearTag, + eXMLErrorUnexpectedToken, + eXMLErrorNoElements, + eXMLErrorFileNotFound, + eXMLErrorFirstTagNotFound, + eXMLErrorUnknownCharacterEntity, + eXMLErrorCharacterCodeAbove255, + eXMLErrorCharConversionError, + eXMLErrorCannotOpenWriteFile, + eXMLErrorCannotWriteFile, + + eXMLErrorBase64DataSizeIsNotMultipleOf4, + eXMLErrorBase64DecodeIllegalCharacter, + eXMLErrorBase64DecodeTruncatedData, + eXMLErrorBase64DecodeBufferTooSmall +} XMLError; + + +/// Enumeration used to manage type of data. Use in conjunction with structure XMLNodeContents +typedef enum XMLElementType +{ + eNodeChild=0, + eNodeAttribute=1, + eNodeText=2, + eNodeClear=3, + eNodeNULL=4 +} XMLElementType; + +/// Structure used to obtain error details if the parse fails. +typedef struct XMLResults +{ + enum XMLError error; + int nLine,nColumn; +} XMLResults; + +/// Structure for XML clear (unformatted) node (usually comments) +typedef struct XMLClear { + XMLCSTR lpszValue; XMLCSTR lpszOpenTag; XMLCSTR lpszCloseTag; +} XMLClear; + +/// Structure for XML attribute. +typedef struct XMLAttribute { + XMLCSTR lpszName; XMLCSTR lpszValue; +} XMLAttribute; + +/// XMLElementPosition are not interchangeable with simple indexes +typedef int XMLElementPosition; + +struct XMLNodeContents; + +/** @defgroup XMLParserGeneral The XML parser */ + +/// Main Class representing a XML node +/** + * All operations are performed using this class. + * \note The constructors of the XMLNode class are protected, so use instead one of these four methods to get your first instance of XMLNode: + *
    + *
  • XMLNode::parseString
  • + *
  • XMLNode::parseFile
  • + *
  • XMLNode::openFileHelper
  • + *
  • XMLNode::createXMLTopNode (or XMLNode::createXMLTopNode_WOSD)
  • + *
*/ +typedef struct XMLDLLENTRY XMLNode +{ + private: + + struct XMLNodeDataTag; + + /// Constructors are protected, so use instead one of: XMLNode::parseString, XMLNode::parseFile, XMLNode::openFileHelper, XMLNode::createXMLTopNode + XMLNode(struct XMLNodeDataTag *pParent, XMLSTR lpszName, char isDeclaration); + /// Constructors are protected, so use instead one of: XMLNode::parseString, XMLNode::parseFile, XMLNode::openFileHelper, XMLNode::createXMLTopNode + XMLNode(struct XMLNodeDataTag *p); + + public: + static XMLCSTR getVersion();///< Return the XMLParser library version number + + /** @defgroup conversions Parsing XML files/strings to an XMLNode structure and Rendering XMLNode's to files/string. + * @ingroup XMLParserGeneral + * @{ */ + + /// Parse an XML string and return the root of a XMLNode tree representing the string. + static XMLNode parseString (XMLCSTR lpXMLString, XMLCSTR tag=NULL, XMLResults *pResults=NULL); + /**< The "parseString" function parse an XML string and return the root of a XMLNode tree. The "opposite" of this function is + * the function "createXMLString" that re-creates an XML string from an XMLNode tree. If the XML document is corrupted, the + * "parseString" method will initialize the "pResults" variable with some information that can be used to trace the error. + * If you still want to parse the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the + * beginning of the "xmlParser.cpp" file. + * + * @param lpXMLString the XML string to parse + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + * @param pResults a pointer to a XMLResults variable that will contain some information that can be used to trace the XML parsing error. You can have a user-friendly explanation of the parsing error with the "getError" function. + */ + + /// Parse an XML file and return the root of a XMLNode tree representing the file. + static XMLNode parseFile (XMLCSTR filename, XMLCSTR tag=NULL, XMLResults *pResults=NULL); + /**< The "parseFile" function parse an XML file and return the root of a XMLNode tree. The "opposite" of this function is + * the function "writeToFile" that re-creates an XML file from an XMLNode tree. If the XML document is corrupted, the + * "parseFile" method will initialize the "pResults" variable with some information that can be used to trace the error. + * If you still want to parse the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the + * beginning of the "xmlParser.cpp" file. + * + * @param filename the path to the XML file to parse + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + * @param pResults a pointer to a XMLResults variable that will contain some information that can be used to trace the XML parsing error. You can have a user-friendly explanation of the parsing error with the "getError" function. + */ + + /// Parse an XML file and return the root of a XMLNode tree representing the file. A very crude error checking is made. An attempt to guess the Char Encoding used in the file is made. + static XMLNode openFileHelper(XMLCSTR filename, XMLCSTR tag=NULL); + /**< The "openFileHelper" function reports to the screen all the warnings and errors that occurred during parsing of the XML file. + * This function also tries to guess char Encoding (UTF-8, ASCII or SHIT-JIS) based on the first 200 bytes of the file. Since each + * application has its own way to report and deal with errors, you should rather use the "parseFile" function to parse XML files + * and program yourself thereafter an "error reporting" tailored for your needs (instead of using the very crude "error reporting" + * mechanism included inside the "openFileHelper" function). + * + * If the XML document is corrupted, the "openFileHelper" method will: + * - display an error message on the console (or inside a messageBox for windows). + * - stop execution (exit). + * + * I strongly suggest that you write your own "openFileHelper" method tailored to your needs. If you still want to parse + * the file, you can use the APPROXIMATE_PARSING option as explained inside the note at the beginning of the "xmlParser.cpp" file. + * + * @param filename the path of the XML file to parse. + * @param tag the name of the first tag inside the XML file. If the tag parameter is omitted, this function returns a node that represents the head of the xml document including the declaration term (). + */ + + static XMLCSTR getError(XMLError error); ///< this gives you a user-friendly explanation of the parsing error + + /// Create an XML string starting from the current XMLNode. + XMLSTR createXMLString(int nFormat=1, int *pnSize=NULL) const; + /**< The returned string should be free'd using the "freeXMLString" function. + * + * If nFormat==0, no formatting is required otherwise this returns an user friendly XML string from a given element + * with appropriate white spaces and carriage returns. if pnSize is given it returns the size in character of the string. */ + + /// Save the content of an xmlNode inside a file + XMLError writeToFile(XMLCSTR filename, + const char *encoding=NULL, + char nFormat=1) const; + /**< If nFormat==0, no formatting is required otherwise this returns an user friendly XML string from a given element with appropriate white spaces and carriage returns. + * If the global parameter "characterEncoding==encoding_UTF8", then the "encoding" parameter is ignored and always set to "utf-8". + * If the global parameter "characterEncoding==encoding_ShiftJIS", then the "encoding" parameter is ignored and always set to "SHIFT-JIS". + * If "_XMLWIDECHAR=1", then the "encoding" parameter is ignored and always set to "utf-16". + * If no "encoding" parameter is given the "ISO-8859-1" encoding is used. */ + /** @} */ + + /** @defgroup navigate Navigate the XMLNode structure + * @ingroup XMLParserGeneral + * @{ */ + XMLCSTR getName() const; ///< name of the node + XMLCSTR getText(int i=0) const; ///< return ith text field + int nText() const; ///< nbr of text field + XMLNode getParentNode() const; ///< return the parent node + XMLNode getChildNode(int i=0) const; ///< return ith child node + XMLNode getChildNode(XMLCSTR name, int i) const; ///< return ith child node with specific name (return an empty node if failing). If i==-1, this returns the last XMLNode with the given name. + XMLNode getChildNode(XMLCSTR name, int *i=NULL) const; ///< return next child node with specific name (return an empty node if failing) + XMLNode getChildNodeWithAttribute(XMLCSTR tagName, + XMLCSTR attributeName, + XMLCSTR attributeValue=NULL, + int *i=NULL) const; ///< return child node with specific name/attribute (return an empty node if failing) + XMLNode getChildNodeByPath(XMLCSTR path, char createNodeIfMissing=0, XMLCHAR sep='/'); + ///< return the first child node with specific path + XMLNode getChildNodeByPathNonConst(XMLSTR path, char createNodeIfMissing=0, XMLCHAR sep='/'); + ///< return the first child node with specific path. + + int nChildNode(XMLCSTR name) const; ///< return the number of child node with specific name + int nChildNode() const; ///< nbr of child node + XMLAttribute getAttribute(int i=0) const; ///< return ith attribute + XMLCSTR getAttributeName(int i=0) const; ///< return ith attribute name + XMLCSTR getAttributeValue(int i=0) const; ///< return ith attribute value + char isAttributeSet(XMLCSTR name) const; ///< test if an attribute with a specific name is given + XMLCSTR getAttribute(XMLCSTR name, int i) const; ///< return ith attribute content with specific name (return a NULL if failing) + XMLCSTR getAttribute(XMLCSTR name, int *i=NULL) const; ///< return next attribute content with specific name (return a NULL if failing) + int nAttribute() const; ///< nbr of attribute + XMLClear getClear(int i=0) const; ///< return ith clear field (comments) + int nClear() const; ///< nbr of clear field + XMLNodeContents enumContents(XMLElementPosition i) const; ///< enumerate all the different contents (attribute,child,text, clear) of the current XMLNode. The order is reflecting the order of the original file/string. NOTE: 0 <= i < nElement(); + int nElement() const; ///< nbr of different contents for current node + char isEmpty() const; ///< is this node Empty? + char isDeclaration() const; ///< is this node a declaration + XMLNode deepCopy() const; ///< deep copy (duplicate/clone) a XMLNode + static XMLNode emptyNode(); ///< return XMLNode::emptyXMLNode; + /** @} */ + + ~XMLNode(); + XMLNode(const XMLNode &A); ///< to allow shallow/fast copy: + XMLNode& operator=( const XMLNode& A ); ///< to allow shallow/fast copy: + + XMLNode(): d(NULL){}; + static XMLNode emptyXMLNode; + static XMLClear emptyXMLClear; + static XMLAttribute emptyXMLAttribute; + + /** @defgroup xmlModify Create or Update the XMLNode structure + * @ingroup XMLParserGeneral + * The functions in this group allows you to create from scratch (or update) a XMLNode structure. Start by creating your top + * node with the "createXMLTopNode" function and then add new nodes with the "addChild" function. The parameter 'pos' gives + * the position where the childNode, the text or the XMLClearTag will be inserted. The default value (pos=-1) inserts at the + * end. The value (pos=0) insert at the beginning (Insertion at the beginning is slower than at the end).
+ * + * REMARK: 0 <= pos < nChild()+nText()+nClear()
+ */ + + /** @defgroup creation Creating from scratch a XMLNode structure + * @ingroup xmlModify + * @{ */ + static XMLNode createXMLTopNode(XMLCSTR lpszName, char isDeclaration=FALSE); ///< Create the top node of an XMLNode structure + XMLNode addChild(XMLCSTR lpszName, char isDeclaration=FALSE, XMLElementPosition pos=-1); ///< Add a new child node + XMLNode addChild(XMLNode nodeToAdd, XMLElementPosition pos=-1); ///< If the "nodeToAdd" has some parents, it will be detached from it's parents before being attached to the current XMLNode + XMLAttribute *addAttribute(XMLCSTR lpszName, XMLCSTR lpszValuev); ///< Add a new attribute + XMLCSTR addText(XMLCSTR lpszValue, XMLElementPosition pos=-1); ///< Add a new text content + XMLClear *addClear(XMLCSTR lpszValue, XMLCSTR lpszOpen=NULL, XMLCSTR lpszClose=NULL, XMLElementPosition pos=-1); + /**< Add a new clear tag + * @param lpszOpen default value "" + */ + /** @} */ + + /** @defgroup xmlUpdate Updating Nodes + * @ingroup xmlModify + * Some update functions: + * @{ + */ + XMLCSTR updateName(XMLCSTR lpszName); ///< change node's name + XMLAttribute *updateAttribute(XMLAttribute *newAttribute, XMLAttribute *oldAttribute); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName=NULL,int i=0); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute(XMLCSTR lpszNewValue, XMLCSTR lpszNewName,XMLCSTR lpszOldName);///< set lpszNewName=NULL if you don't want to change the name of the attribute if the attribute to update is missing, a new one will be added + XMLCSTR updateText(XMLCSTR lpszNewValue, int i=0); ///< if the text to update is missing, a new one will be added + XMLCSTR updateText(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the text to update is missing, a new one will be added + XMLClear *updateClear(XMLCSTR lpszNewContent, int i=0); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear(XMLClear *newP,XMLClear *oldP); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear(XMLCSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the clearTag to update is missing, a new one will be added + /** @} */ + + /** @defgroup xmlDelete Deleting Nodes or Attributes + * @ingroup xmlModify + * Some deletion functions: + * @{ + */ + /// The "deleteNodeContent" function forces the deletion of the content of this XMLNode and the subtree. + void deleteNodeContent(); + /**< \note The XMLNode instances that are referring to the part of the subtree that has been deleted CANNOT be used anymore!!. Unexpected results will occur if you continue using them. */ + void deleteAttribute(int i=0); ///< Delete the ith attribute of the current XMLNode + void deleteAttribute(XMLCSTR lpszName); ///< Delete the attribute with the given name (the "strcmp" function is used to find the right attribute) + void deleteAttribute(XMLAttribute *anAttribute); ///< Delete the attribute with the name "anAttribute->lpszName" (the "strcmp" function is used to find the right attribute) + void deleteText(int i=0); ///< Delete the Ith text content of the current XMLNode + void deleteText(XMLCSTR lpszValue); ///< Delete the text content "lpszValue" inside the current XMLNode (direct "pointer-to-pointer" comparison is used to find the right text) + void deleteClear(int i=0); ///< Delete the Ith clear tag inside the current XMLNode + void deleteClear(XMLCSTR lpszValue); ///< Delete the clear tag "lpszValue" inside the current XMLNode (direct "pointer-to-pointer" comparison is used to find the clear tag) + void deleteClear(XMLClear *p); ///< Delete the clear tag "p" inside the current XMLNode (direct "pointer-to-pointer" comparison on the lpszName of the clear tag is used to find the clear tag) + /** @} */ + + /** @defgroup xmlWOSD ???_WOSD functions. + * @ingroup xmlModify + * The strings given as parameters for the "add" and "update" methods that have a name with + * the postfix "_WOSD" (that means "WithOut String Duplication")(for example "addText_WOSD") + * will be free'd by the XMLNode class. For example, it means that this is incorrect: + * \code + * xNode.addText_WOSD("foo"); + * xNode.updateAttribute_WOSD("#newcolor" ,NULL,"color"); + * \endcode + * In opposition, this is correct: + * \code + * xNode.addText("foo"); + * xNode.addText_WOSD(stringDup("foo")); + * xNode.updateAttribute("#newcolor" ,NULL,"color"); + * xNode.updateAttribute_WOSD(stringDup("#newcolor"),NULL,"color"); + * \endcode + * Typically, you will never do: + * \code + * char *b=(char*)malloc(...); + * xNode.addText(b); + * free(b); + * \endcode + * ... but rather: + * \code + * char *b=(char*)malloc(...); + * xNode.addText_WOSD(b); + * \endcode + * ('free(b)' is performed by the XMLNode class) + * @{ */ + static XMLNode createXMLTopNode_WOSD(XMLSTR lpszName, char isDeclaration=FALSE); ///< Create the top node of an XMLNode structure + XMLNode addChild_WOSD(XMLSTR lpszName, char isDeclaration=FALSE, XMLElementPosition pos=-1); ///< Add a new child node + XMLAttribute *addAttribute_WOSD(XMLSTR lpszName, XMLSTR lpszValue); ///< Add a new attribute + XMLCSTR addText_WOSD(XMLSTR lpszValue, XMLElementPosition pos=-1); ///< Add a new text content + XMLClear *addClear_WOSD(XMLSTR lpszValue, XMLCSTR lpszOpen=NULL, XMLCSTR lpszClose=NULL, XMLElementPosition pos=-1); ///< Add a new clear Tag + + XMLCSTR updateName_WOSD(XMLSTR lpszName); ///< change node's name + XMLAttribute *updateAttribute_WOSD(XMLAttribute *newAttribute, XMLAttribute *oldAttribute); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName=NULL,int i=0); ///< if the attribute to update is missing, a new one will be added + XMLAttribute *updateAttribute_WOSD(XMLSTR lpszNewValue, XMLSTR lpszNewName,XMLCSTR lpszOldName); ///< set lpszNewName=NULL if you don't want to change the name of the attribute if the attribute to update is missing, a new one will be added + XMLCSTR updateText_WOSD(XMLSTR lpszNewValue, int i=0); ///< if the text to update is missing, a new one will be added + XMLCSTR updateText_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the text to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLSTR lpszNewContent, int i=0); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLClear *newP,XMLClear *oldP); ///< if the clearTag to update is missing, a new one will be added + XMLClear *updateClear_WOSD(XMLSTR lpszNewValue, XMLCSTR lpszOldValue); ///< if the clearTag to update is missing, a new one will be added + /** @} */ + + /** @defgroup xmlPosition Position helper functions (use in conjunction with the update&add functions + * @ingroup xmlModify + * These are some useful functions when you want to insert a childNode, a text or a XMLClearTag in the + * middle (at a specified position) of a XMLNode tree already constructed. The value returned by these + * methods is to be used as last parameter (parameter 'pos') of addChild, addText or addClear. + * @{ */ + XMLElementPosition positionOfText(int i=0) const; + XMLElementPosition positionOfText(XMLCSTR lpszValue) const; + XMLElementPosition positionOfClear(int i=0) const; + XMLElementPosition positionOfClear(XMLCSTR lpszValue) const; + XMLElementPosition positionOfClear(XMLClear *a) const; + XMLElementPosition positionOfChildNode(int i=0) const; + XMLElementPosition positionOfChildNode(XMLNode x) const; + XMLElementPosition positionOfChildNode(XMLCSTR name, int i=0) const; ///< return the position of the ith childNode with the specified name if (name==NULL) return the position of the ith childNode + /** @} */ + + /// Enumeration for XML character encoding. + typedef enum XMLCharEncoding + { + char_encoding_error=0, + char_encoding_UTF8=1, + char_encoding_legacy=2, + char_encoding_ShiftJIS=3, + char_encoding_GB2312=4, + char_encoding_Big5=5, + char_encoding_GBK=6 // this is actually the same as Big5 + } XMLCharEncoding; + + /** \addtogroup conversions + * @{ */ + + /// Sets the global options for the conversions + static char setGlobalOptions(XMLCharEncoding characterEncoding=XMLNode::char_encoding_UTF8, char guessWideCharChars=1, + char dropWhiteSpace=1, char removeCommentsInMiddleOfText=1); + /**< The "setGlobalOptions" function allows you to change four global parameters that affect string & file + * parsing. First of all, you most-probably will never have to change these 3 global parameters. + * + * @param guessWideCharChars If "guessWideCharChars"=1 and if this library is compiled in WideChar mode, then the + * XMLNode::parseFile and XMLNode::openFileHelper functions will test if the file contains ASCII + * characters. If this is the case, then the file will be loaded and converted in memory to + * WideChar before being parsed. If 0, no conversion will be performed. + * + * @param guessWideCharChars If "guessWideCharChars"=1 and if this library is compiled in ASCII/UTF8/char* mode, then the + * XMLNode::parseFile and XMLNode::openFileHelper functions will test if the file contains WideChar + * characters. If this is the case, then the file will be loaded and converted in memory to + * ASCII/UTF8/char* before being parsed. If 0, no conversion will be performed. + * + * @param characterEncoding This parameter is only meaningful when compiling in char* mode (multibyte character mode). + * In wchar_t* (wide char mode), this parameter is ignored. This parameter should be one of the + * three currently recognized encodings: XMLNode::encoding_UTF8, XMLNode::encoding_ascii, + * XMLNode::encoding_ShiftJIS. + * + * @param dropWhiteSpace In most situations, text fields containing only white spaces (and carriage returns) + * are useless. Even more, these "empty" text fields are annoying because they increase the + * complexity of the user's code for parsing. So, 99% of the time, it's better to drop + * the "empty" text fields. However The XML specification indicates that no white spaces + * should be lost when parsing the file. So to be perfectly XML-compliant, you should set + * dropWhiteSpace=0. A note of caution: if you set "dropWhiteSpace=0", the parser will be + * slower and your code will be more complex. + * + * @param removeCommentsInMiddleOfText To explain this parameter, let's consider this code: + * \code + * XMLNode x=XMLNode::parseString("foobarchu","a"); + * \endcode + * If removeCommentsInMiddleOfText=0, then we will have: + * \code + * x.getText(0) -> "foo" + * x.getText(1) -> "bar" + * x.getText(2) -> "chu" + * x.getClear(0) --> "" + * x.getClear(1) --> "" + * \endcode + * If removeCommentsInMiddleOfText=1, then we will have: + * \code + * x.getText(0) -> "foobar" + * x.getText(1) -> "chu" + * x.getClear(0) --> "" + * \endcode + * + * \return "0" when there are no errors. If you try to set an unrecognized encoding then the return value will be "1" to signal an error. + * + * \note Sometime, it's useful to set "guessWideCharChars=0" to disable any conversion + * because the test to detect the file-type (ASCII/UTF8/char* or WideChar) may fail (rarely). */ + + /// Guess the character encoding of the string (ascii, utf8 or shift-JIS) + static XMLCharEncoding guessCharEncoding(void *buffer, int bufLen, char useXMLEncodingAttribute=1); + /**< The "guessCharEncoding" function try to guess the character encoding. You most-probably will never + * have to use this function. It then returns the appropriate value of the global parameter + * "characterEncoding" described in the XMLNode::setGlobalOptions. The guess is based on the content of a buffer of length + * "bufLen" bytes that contains the first bytes (minimum 25 bytes; 200 bytes is a good value) of the + * file to be parsed. The XMLNode::openFileHelper function is using this function to automatically compute + * the value of the "characterEncoding" global parameter. There are several heuristics used to do the + * guess. One of the heuristic is based on the "encoding" attribute. The original XML specifications + * forbids to use this attribute to do the guess but you can still use it if you set + * "useXMLEncodingAttribute" to 1 (this is the default behavior and the behavior of most parsers). + * If an inconsistency in the encoding is detected, then the return value is "0". */ + /** @} */ + + private: + // these are functions and structures used internally by the XMLNode class (don't bother about them): + + typedef struct XMLNodeDataTag // to allow shallow copy and "intelligent/smart" pointers (automatic delete): + { + XMLCSTR lpszName; // Element name (=NULL if root) + int nChild, // Number of child nodes + nText, // Number of text fields + nClear, // Number of Clear fields (comments) + nAttribute; // Number of attributes + char isDeclaration; // Whether node is an XML declaration - '' + struct XMLNodeDataTag *pParent; // Pointer to parent element (=NULL if root) + XMLNode *pChild; // Array of child nodes + XMLCSTR *pText; // Array of text fields + XMLClear *pClear; // Array of clear fields + XMLAttribute *pAttribute; // Array of attributes + int *pOrder; // order of the child_nodes,text_fields,clear_fields + int ref_count; // for garbage collection (smart pointers) + } XMLNodeData; + XMLNodeData *d; + + char parseClearTag(void *px, void *pa); + char maybeAddTxT(void *pa, XMLCSTR tokenPStr); + int ParseXMLElement(void *pXML); + void *addToOrder(int memInc, int *_pos, int nc, void *p, int size, XMLElementType xtype); + int indexText(XMLCSTR lpszValue) const; + int indexClear(XMLCSTR lpszValue) const; + XMLNode addChild_priv(int,XMLSTR,char,int); + XMLAttribute *addAttribute_priv(int,XMLSTR,XMLSTR); + XMLCSTR addText_priv(int,XMLSTR,int); + XMLClear *addClear_priv(int,XMLSTR,XMLCSTR,XMLCSTR,int); + void emptyTheNode(char force); + static inline XMLElementPosition findPosition(XMLNodeData *d, int index, XMLElementType xtype); + static int CreateXMLStringR(XMLNodeData *pEntry, XMLSTR lpszMarker, int nFormat); + static int removeOrderElement(XMLNodeData *d, XMLElementType t, int index); + static void exactMemory(XMLNodeData *d); + static int detachFromParent(XMLNodeData *d); +} XMLNode; + +/// This structure is given by the function XMLNode::enumContents. +typedef struct XMLNodeContents +{ + /// This dictates what's the content of the XMLNodeContent + enum XMLElementType etype; + /**< should be an union to access the appropriate data. Compiler does not allow union of object with constructor... too bad. */ + XMLNode child; + XMLAttribute attrib; + XMLCSTR text; + XMLClear clear; + +} XMLNodeContents; + +/** @defgroup StringAlloc String Allocation/Free functions + * @ingroup xmlModify + * @{ */ +/// Duplicate (copy in a new allocated buffer) the source string. +XMLDLLENTRY XMLSTR stringDup(XMLCSTR source, int cbData=-1); +/**< This is + * a very handy function when used with all the "XMLNode::*_WOSD" functions (\link xmlWOSD \endlink). + * @param cbData If !=0 then cbData is the number of chars to duplicate. New strings allocated with + * this function should be free'd using the "freeXMLString" function. */ + +/// to free the string allocated inside the "stringDup" function or the "createXMLString" function. +XMLDLLENTRY void freeXMLString(XMLSTR t); // {free(t);} +/** @} */ + +/** @defgroup atoX ato? like functions + * @ingroup XMLParserGeneral + * The "xmlto?" functions are equivalents to the atoi, atol, atof functions. + * The only difference is: If the variable "xmlString" is NULL, than the return value + * is "defautValue". These 6 functions are only here as "convenience" functions for the + * user (they are not used inside the XMLparser). If you don't need them, you can + * delete them without any trouble. + * + * @{ */ +XMLDLLENTRY char xmltob(XMLCSTR xmlString,char defautValue=0); +XMLDLLENTRY int xmltoi(XMLCSTR xmlString,int defautValue=0); +XMLDLLENTRY long long xmltol(XMLCSTR xmlString,long long defautValue=0); +XMLDLLENTRY double xmltof(XMLCSTR xmlString,double defautValue=.0); +XMLDLLENTRY XMLCSTR xmltoa(XMLCSTR xmlString,XMLCSTR defautValue=_CXML("")); +XMLDLLENTRY XMLCHAR xmltoc(XMLCSTR xmlString,const XMLCHAR defautValue=_CXML('\0')); +/** @} */ + +/** @defgroup ToXMLStringTool Helper class to create XML files using "printf", "fprintf", "cout",... functions. + * @ingroup XMLParserGeneral + * @{ */ +/// Helper class to create XML files using "printf", "fprintf", "cout",... functions. +/** The ToXMLStringTool class helps you creating XML files using "printf", "fprintf", "cout",... functions. + * The "ToXMLStringTool" class is processing strings so that all the characters + * &,",',<,> are replaced by their XML equivalent: + * \verbatim &, ", ', <, > \endverbatim + * Using the "ToXMLStringTool class" and the "fprintf function" is THE most efficient + * way to produce VERY large XML documents VERY fast. + * \note If you are creating from scratch an XML file using the provided XMLNode class + * you must not use the "ToXMLStringTool" class (because the "XMLNode" class does the + * processing job for you during rendering).*/ +typedef struct XMLDLLENTRY ToXMLStringTool +{ +public: + ToXMLStringTool(): buf(NULL),buflen(0){} + ~ToXMLStringTool(); + void freeBuffer();///

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 z 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 z z;3>gVgS&vI15Xd`2A&Z-Gq?x1C%6}Q4)9#ydBF357XU8=UIe@tcnR=Q;ANB#vEHAz zVmaS{Tb)-ndvxdfHlOu5w$pOw`_M<#ae6uM3gDH%tAJMnuK`{QybgFh@CM3XS?i~r zHDQF$KkKKF${&m2T%Yf(^|{K*Z;I*kLe>l%p>;+%UJvuRG0*xu3C(wMOr~q*wP<4f zeCUi;*E9>pn{3ZA+ z@YmpP!QX*@0RII31^gTM5Aa{$zriEOl=8A6^VfylW(SW19tAuacnt7Z;Bmm?fhPb@ z1fB#uDR^@5l;EksUBJ_UyMku`cL&b|o(0?k+!NdjJO_9#@I2u8zzcvE1TPF;6udZi zN$}F(-rzpq<-jX|R|KyFUIn}wcy;ia;I+Z)fY$@B58e>GF?dt(=HM;CTZ6X+Zx7xP zyc2jA@UGz9zOg|ANT<9LEuBchk=g(9|b-Jd>r@$@JZlPz^8%F0G|my z8+_(t%};9J4BgYN|24ZatAKlnlLK=2^&qu|HE zPlBHYKMQ^i`~vtT@XO#=loxXJ%b9+^(ixl^yk-B{?!Bh+=3%?-2KX)TJK*=gAAmms ze**pt`~~zDL)d=fy^fRW8lPajPt19b zOl#*j&XKirI%Gakv^xfr4A;kV@pEe~A}<_IX0PiJ}PdNAMM z81G?u=z1vNctwowwEW-o5JN4ebN$2uj{_bLJOOwj@Fd{Lz>|Zg1Wyf~20SgeD|iNQ zckoQ$nZdJwdw_d_X9M>F&jFqjJU4h=@ciHf!3%>I0WSt#0=y)6De%(Z-rzpq<-jX| zR|2mBUJbkkcunxy;B~?4gEs_k4BiyHIe1I(*5GZy+k2w zckrL!zriD<__Nda*A2)1BX}h6DBw}Sqk+c&j|mA@C4uq!4rWe0Z#^=96Tj> zYH%0ubl|Sw8Nl7aGlFLZ_W;id?g{P%o&!85crNff;Q7GwgBJuZ3|mXR{*aBUIn}wcn$Dc;B~<3fj0ne1l|O^8F+K>mf)?y+k&?T?+D%rybE|Y@b2I} zzv^_*n4q;1j?nf=>pY3O*ftCipDy+2C`* z=YuZrC5oF5FCtgxCshr-z^3cy2&(u!eX8GR>X19I+`>yrO=JV$#_;2tCj-}Gg zG5!?X4ju_S3V1Z|7~nC%V}r*9j}M*@JTZ7u@Z`#0S+C={Ft6`s$3RB&esHzkIHB*K zIxf!lPv|(J(D%ybQPxcv#Ew3SJ$&26!#-I^gxd8-Oz&n6<0`CIe4ZJ&e5Aa^#eZc#HoB#g0*=>Wshky?Q9|1lJd<^(F z@Co3Pz^8yu1D^pt3w#dvJn#kJi@=wFF9Tl=z7l*D_!{s4@O9uDz&C+!0pA9`9egMF zZt%U}`@s)_2Z9HI9|b=SeiHmN_*w9C;1|Fzfro%!1-}k{6Z|&#UGV$h55XUUKLvjd z{u2B(_*?LI;P1gdfPVu20{#vBJNQrV-{2Ac-u(Xa56H0}2ObeTGI&(*XyDPoV}i#5 zj{_bLJU)0r@WkLr!IOih1WyH?8r%gu9e8^14B+nInZUDvX9dp&o*g_Vcy92#;Q7G| zf)@ra3SJz%1b8X%(%@yly}`?Zmj|y1UKzY9cy;ia;I+Z)g4YLc2;LaHDR^`67T~SG z+km$NH@$qi*=?P`yMT8E?*{G*?g!ojycc+H@IK)E!25#_03QTC1bi6yaPX1fqrgXl zj{zSCJ^_3p_$2Tt;M2fofX@V<4L%oqKKMfL#o$Z8mx3-Kz_P@X*f=2?60v-)K26!y+ zINwEbve|;R-ZT(f=JZ!fO z1Ro4O1bi6y2=GzhW5CCOPXM0;J{f!p_%!et;IqKzfX@S80KO1>G58YjrQrVHE5KKQ zuK^DLUk|E$$H7m6p9Vhzeir;3_yzDw z;33NUI_7sa$1g3<;{3o1JwEC`$9x>d4_F>Le}(gN@2aXZ#LAxuQ^%RVrt(g|0e%bo z4){Ir2jGvupMXD89>p;)iCI5S!u-BtEdS3s9xQadMRv^JVe)Y;4_(jB_3%Q~cls;v zH_As^^)rWi9NIgTch>&^{t5gG_&4w$;J?6sgWEz+UtkvdPjl=if=2|83?3CcI(SU* z*x+%&|D2ZL&+lYzIL^NS`NZH!!IOih1Wyf~2HXWa9e8?hH}H($nZZ54J;A-ebAaap z&jX$hya0G1@FL*Fz)OIa0xtvZ16~%qJa`50O5l~jtAbYtuL)ioye@cs@P^=x!JC3N z2X6`98oUj7JMa$RoxnSTcLDDP-W|LLcrWli;Qhb{fDZy60zM3U1o$ZMG2r9CCxA}^ zp8`G&di<7@SWhh!S{mi z2R{fN2!0qm2>dAcG4K=MC&5pHp9Mb$ejfZH_$BZV@GIcgz^{Yf1iuY_7yLf>L-0r7 zkHMdUKL>vS{u2B(_*?LI;P1gdf`1180{#vBJNQrV-{28k%zj4L=9vG)C9IkIc3VX7 z$ly`Hqk+c&j|mA@Py!r!IOd~2Tuu}8r%gu9e8?hH}H($nZZ54J;A-ebAaap z&jX$hya0G1@WS9lz>9(x2QLX;8r&PaEO>eFir|&NtAJMnuMS=lyf%1U@cQ5l!5e`$ z0dET49J~d1EAZCfZNb}tcL47Q-Wj|rxG%ULcu(-&;C;dSfe!#52tF8mDEM&jk>I1j z$AFImp8!4)d@}e{@af<)!DoZd1)m4L0DKYn67Xf<%fVNGuL55K9ss@`d?WZ~@U7t6 zz;}S}0^b9^5Bvc5LGVEE!{A4hN6+Z|zN3ZpeMIQ@9sZUtx4d@*+n;8)9aDA8gLc~q z@KfMtz=Ofhfu9Gz2!0tn1pF%ab?}?ux4>_M-vz%9{t)~T_!ID_;LpHcfWHKP4gMDV z9ry?EPvBp`zk&Y%{{?PKYYV42zW)M`2p$4cr|(BX}n8%-~tTJ;1YqdxB>J&kmjwJU4hA@O8E_x)a^MxfD}h%5uLfQNycT$E@H*i2!0UrI1aA!96ucRD3-DIp zZNS@sw+HVC-Wj|LcsKCw;61>5f%gXQ3*H}mAoyVLq2R;8M}m(A9|JxPd;<7H@JZlP zz^8&w2cH2x3w#dvJn#kJi@=wFF9r7pUje=fd<}R2_f^P@k0lo`-5BNUt z1K@|i4}%{8KMH;f{5beY@YCRD!OwwT0KWu&8T<{ULVnNZ?Vxqk%^Uj|m|O{15W{-3Oo&XTJUt>uHYHK-N7?~X93R&?g{P%o&!7=cpmV);Q7G|f)@ra z3SJz%BzS3XZ}76<<-se0R|c;NULCw9cx~{y;Pt>8fHwkf0^St78F&luR^V;G+ktlg z?*!f%ybE|Y@b2I}z7>g6{_31HKo0ANT?AL*RknhrxrukANQoKLLIU{0w+7_<8V);FrO#fL{Z@0e%bo z4){Ir2jGvupMXCDe*yjy{1x~c@OR)Jz(0Y10sjX69sCFQFL0YYop9jAPei=Lj{3`f$ z@EhQ_!0&+H1AhSi2>c27Gw|o&FTr1fzX5*-{sH_G_!sbR;6K2Bf!iFj<>=c9M`6$a$eceWp{cd%dH=?#meFIu>JGbE#G71Pg!-$mEBfBEoVSZXPvP9 z|I2eZeTC!sO-uft^Ll=6ryq3uoN2z!E4j4WDyjPB0lTd-cop!f;MKuvg4YJG3tk_* zA$Vi(rr^!Mn}fFmZw=lSyd8LZ@DAXez`KBV1NQ~*4(pY0zM6V2KY?y+2C`*=YcN(Uj)7cd>Obu_zLh<;H$yc zfCqrD2j2+3349CqHt-$byTJE=?*l&oeh54e{4n?t@MGX7z)ymo0zU&D41ON`0{A8H z5b!JDSHZ7=-vGY_ejEHQ_&x9k;19tcgFgX(3jPfI1^7$wSKx2J-+_Ms{{;R8{2TZW z@L%Az4En#U*$ypmHwHWscogtx;4#2sfyV)l2c7^t5qJ{tWZ=ocQ-Y@kPXq1( zo(?=cxEpvz@XX*I;GW=K;Mu`*g69U$1D+4O0C*wrBH+cqOMsUGFAZJ>+#9?sczN)O z;FZCvf>#5t0bUEd4tQPgdf@fJ8-h0iZvx&7yajkm@K)e$z}tei2k!{p8N4gFukuED zoc#>B9Q_cch0Y7{#qvs)I}THnHb1X3AGZHwaz3Z`wepVc3u%S&JAHuFPg2U+4>sel}h|@n<9-1#) z)alhM_qOup%5L*h>(l8ymA|v3AKr-9D^p9wx2d=B_L@CD$Dz?Xn81@{ME0lo@+4ftB{b>JJo zH-T>f-wM7Rd?)yB@V(&s!4HB5f(L;g1wRgc68tpyS@3h<7r-xphk#!NzYcyA{5JSq z@cZBo!5@P^1%D3y68tszTk!YbAHhF^e+B;z{sa6MxXtaae{+9sI`&V&BZ5Z;j|v_g zJSKQ-@VMad!4rZf22To}96Tj>YH%0ubl~a1-M}+~X9o8G_XPI>&jFqbJP&w2@B-k4 zz>9zv11|wy3cL)s4|qB73gDH%tAJMnuK`{QybgFh@CM+Gz?*i-<@SWhh!S{gg13v(M2>dYk5%6Q+ zC%{jEp8*dBKM#Hp{4)3z@T=h0z;A%x1iuA-2mBuR1Mo-SPr#poKL>va{u=x(_ALg+Bu)hGYH~@QC1%z@vai1CIe73p_S>T=4kd3BePCCjn0i zo(w!WcuMfp;Axa6Ebi>54cm{s&hl+mziVvkm2XnQnLli~qx)A{oRUrt+i!fz^6>kC z)2iheT*_G|Y(I0x(oX-+(T{GLpYuvC?KW3c$2?%SWdL_q{@JSE)2d(NpZb|p-dR5j zxCeMva8GbA@EqW|!1I9T11|tx5WFyW5%8km#lTB|mjW*X?gL&9yaIS7@G9Wdz-xfl z1g{NV2fQA51Mo)RO~9Lhw*YSi-UhrKcn9!K;9bDGfp-V*0p1I|4|qTD0pNqchky?Q z9|3O8x~rSrHX3{^_&D$h;1j_ogHHvY4n6~XCipDyIpFia7l1DUUjn`id^z|^@YUdJ z!PkLr0N(_@1$-O$cJQ6xyTSK@?*~5!9ta);eiZyT_(|~7;Ag?lfnNZ>1Rer@75qB* zP4L^`cfs$2KLmda{uKN<_)GBD;BUd-gMS474E`1TJNQrV-{27(XEV{wv40012|NmT zH1HVUvB2Yi#{*9Qo(Mb%crx%5;HkjVfTsm_1f!@);_j|LwL zJ|27`_+;>@%6FG>_Jf_Y`l+nLI&I^;onFN{@Y6NI|No!cZPQg9^QdEpAWtOd=dC!@TK6(!2Q9OgRcNz1-=G60DK+z2JlVbTa-sH z>wFy-S+C>Y9s73MHkEh2jyu42f$ss|3%(!xAb22n5cm=BW8f#iPl2BS4+cLEegXUv z_+{`b;8($~f!_eX34R;=4){Ir``{13AAvsxe**p#{2BOj@E71O!C!&D0e=Vn0sIsA z7x1s(-@t!>{{pu;W{uX(aU3IfMDWPqQNW{uM+c7y9veI^czo~#;0eJKgC_+~4xSP` zHMk3SI&fF;^x$sb8NoAydw_d_dx7Tw&jp?bJTG`Y@B-k4z>9zv11|wy61)_68E_x) za^MxfD}h%AuL52TyasqJ@H*i2z#D)!0&fi76udcjOYqj1Map2X6@87`!QXGw>GRt-#xWw*&70-U+-jcvo;=a6j-K;Jv{6 zfcFC*06qwO2>3AY5#Xc1M}vE$$H7m6p9Vh*eh&Ns_(kx` z;342w!LNhg1iuY_7yLf>L-5DoPr;vozW{#){u=x(__f#(3v1)c{yA9w-qLf}Qfi-8vhF9}{6ybQPxcscM2;FZ9ufL8;r0bUEd4tPEA z`rr-08-q6iZwB51ycKvG@OI!Gz&n9=0XJun*3E9~4(pY3O*ftCipDyIpFia=YuZ*UkJV!d?~m;_zLiq;H$t_gRcc& z2fjgh^NP;?!*P!JL`@4F51ycs)8|-j4GWiy-L^^9F%Q^nTa-Vr>V)l|oLSjfKiI0@ zR4UtSjjK4ll4HJ7(^@&NZ0V~yJ%{BfEH_tn+gA0wPTvl`1AHg=Zt%U}`@s)_2ZA34 zKLUOX{5bdt@RQ)D!OwyRgP#Y#0DcKP1pEs4HSinYH^Fa%-vz%1{s8m zg1-iT3;rJb1NbNKFW}$6e}Ml4{|z3&f1wvf$;xD}q-BuL@orye4>U@Vemj!5e}%25$=99K0oXYw)(OcLwhY z?hEb*-V?kxcwg}T-~+)2fe!{B3O*csB=~6XvEbvuCxTA~p9(%5d?xs8@VVgg!54xr z244#95559?75EzP0PywT8^Je&Zw22Dz7u>m_+Ie+;0M72!Gpk$f*%Jz34R*-3_JyRD)2PmX~A8=Gl08;X9CXxo)tVB zcy{od;JLx`g69V>2woVxD0p%3lHjGmy}`?Zmj|y1UKzY9cy;ia;I+Z)g4YLc2;LaH zDR^`67T_(xTY^iG|H^ z_+h`F@~!Fgc~*XtRVP<1r-%KX>uac66EMg5XTFzBYWYn$$MP;DO*l;77ragP#OH4SohZ82mi=Mes}DA>dcRuY=zNzYTsD{66>t@JHZJz@LG? z0DlGk2K+7fd+?9opTWO^e+T~w{u?}kr}Z)N&y{0dNAQT?k-?*aM+c7y9veI^czp1L z;EBPLf+q)037#6<1w0+ND|iNQckoQ$nZZ54J;A-ebAaap&jX$hJU@5=@Pgolzzc&H z0WS((9K0lWX>f1wvf$;w%Y#<{uLxclyefEg@S5PY!Rvt61FsL>5WF#X6Yyr>Ex=oW zw*hYl-T}N5co*<);J)B~;61^6gZBmR4?YllF!)gL;ou{{M}dz49|t}Gd=mH+@M+*P zz-NKa0iOpxAABMBV(_Kl{@^RXSAnkq4**{ez7c#g_*U@k;5)&0gYO034}K6l5IhL{ zDEKk(6X2)7&wvMmp9jAPei{4<_%-kw;J3i<}o)J7VxCgi=xEFX1@Lb?|!1I9@051ey1iTn{3Gh%ljI zZwB8Az72c__%86>;CsRMDZgIZ`F&HPqRx$^MSpEL*=+|@-mi``ANKpNQFWc(#ri%g zbYMmCdg1#w%YRwVYhB5++ii!`^O^_jw!`2-;77oZf*%Jz34RLv40tg3dGL$im%u~7 zuYz9(zX5&|{5JSq@cZBo!5@P^0e=Sm0{j*D8}N7FAHY9>e*ymn{vG@$_%Cpq*I$3; ze{MR)H-Se4j|?6aJUVy`@L1q+z~h0(2Tur|7(6L>a`2Sksli>q(}KH#rw4Zf&j_9w z+ygu-xF@(5cnX+76MQ%L9`JqO`@s)_9|AuNegym|_;K));HSaQf}aDw0DcKP1pF%a zb?}?ux54j%-v@sP{uulz_;c`=;IF~og1-m<2>u!TEBJTtpWwg2BZQuQ@y|Y!x#9R; z2|N;b6!2)^F~DPi#{rKAo&Y=%coOhr;3>dUfu{ja3+@V@9^4Jw9Xu0w7Vxa#p5R{K z*}-#y=LXLMo)9T@E+hj!Fz-E1@8|&5PUHBQ1Id4 zBf&?5j{zSCJ^_3Z_!RJI;4{EyfzJV-2fhG&A^2kOrQrVHE5KKQuK^DLUk|X;T5Ihh(2>dAcaqyGir@_yHp98-DehEAT{0jIr@EhPa!Eb}#1-}P= zAN(QsWALZo&%s}UzXpE`{vP}z_-F91;NQW2g8v4OkmH{nAnT4}KL|V$cogtx;4#2s zfyV)l2c7^t5qM(oq~OWHQ-Y@kcL7fao*vu{JR^8!@GRh2!Lxy92hRzf8$2&~e(-|e zg~5x07Y8p1UK-pRyexQm@QUD-!K;E-2d@cU8@w)feej0hjlr9OHwSMC-Wt3uczf`U z;GMy{g8PE|f%gRO4c-^LKlniK!Qex|hl7s<9}PYhd_4F>@X6p)!KZ`I1fLB)7kobW zLh!}lOTqoYSAeeqUjrThz8-ud_-62};M>7>g6{_33%(!xAb22n5cpB>sR{1SKw_*L-h;5Wf@KNAnz{i4* z2cG~w34Aj6RPbrwGr(tp&jz0ZJ`a38_(JeS;7h=lg8PFn2VV)k3VaRtTJUw?>%ljI zZvx)}z7>2s_zv)0;Jd;1g6{)A0DcfW5d1Ls5#=!&*lZDOWfL6O{-11uhMqU7v*pEh zIKAax8>n{MQB}u0Y_}Z;KLLIU{51Gk@L=%s;1|FzfnNr{0)7?zI`|FnTj00B?}Fb0 ze*pdv{4w|w@Mqx9!C!*E0)GSk7W_T<2k=kepTWO^e*^yk{uBH+xGmRTf1ZDCI>se| z+rcA&M+T1y9t}JOcuerv;Bmm?fyW0=2%ZQ$33yWQd4UIM%ncxiBNa3An;;N`(9 zf>#2s0$vroI(QB6THv+8>w?zP;D^8ug9m{h0Y3(Q9Q-8sDeyDkXTgKP&x2n8zXX06{0jJ0 zTV9<|$UfZqbY1AY(u0r(^EC*aS(Ux2>?e*^vw z`~&zW@Gs!sz<+@M0=MP<|Ev1{TaNvG@QC1%!J~jj1CIe73p@^ZJn#hIiNKS9Cj(Cb zo(en-cv^5*@C@K?;O^j=z_Wm712s_)hTM;CsRMgC7JB1P=l~3Vs~? zB=~9Yv*5wt=fE$3Ujh#SzY2aG{3iHq@Vnsm!5@M@27e0v9Q-BtYw)+=@4-KSe+2&o z{ssIS_z&=3;J?8m0*?Y74Lk;TEbutsalzw*Cjd_bo)|m{crx%5 z;HkjVfV+UF1$PC{0PY5!5j-=v2e>DAHt_7=Ilyy)=LXLUo)5eLcp>m2;Kjg;gO>y^ z1zrZ+8@w!dIq(YL6~QZmR{^gEULCxqat~{}tF*P1tNff}y_qAZuue_ajX63`gLuc3i z|HBK!^=TmwiR)>ApNm^3xBY(^TgJxbPVv9Qd9$o|3FU*u9g}~DY4gP8N&b0cm$+Q~ zRLn6wS?LcXRPit0eCC zkC{8e#Cs@TA>K>*Zt>pA?}_(O9<7C;zRHV<_fy_eyub3L;scbQ6(6YlyZA`u73F}* zQOf&?k5=w4K1O+n_*mt?#3w1w)ymLh<*mf0D4!-iRry-+Y03|aPgnj%e3kMftqrYK zUQ>LH^4a2Rl?RD$QT|+ftMYhl4DC=}LVTz4F5@-*_NxLk^@w3XGiU%uqk&lwg%BzWoD4!sHMfo=I zJIbGl-&5YOgP{k?r;0yPeq8*S@{i)rm8X@D!WYUbh<{Q(PyDm;o8n)TNA6@Omik9n zLp-+fh2npoz1&&yns@@0k1ikC36&QRPo%t?coOBy#FHw&ES^kx+Ae0fk}Iz%oFckMBGJrvaaTNGb=AHo<(^l@vO=xi~A_wEnZf6@^0pN%PB7?UO{;S@rud^ zh*ws=NW6;j!{XJH$M!YLQ(d`_cunQq#cL_wC0<*(^Iv`)p?G zSiFgHSJ|X!s=SAITjeXnJ1IXR-bMLCabM+;WRs=4^0eZ;m3xc#QQlE}kn$DcgOy(u zAEG=$PxJZ?RqiG}T)DUSNaY>G$0(mBK2~|K_&DWn#m6g8B%5>-lvfa+tlU?8it;t$ zbCjPHU!eSh_(J8WWK(jH@-pK7$~%a!RK7=itMXgo+mt7fP1WtntBW5}K3x2`@)hDI zl%Ezqt^Bw6IpyxMiF{spW$}y3yNZV>_ZPpS{G|A8W@jS|J zi04)ADZ41ul(!MDsr;yTE#)u7Yb(z%$gHh8${UH-SAI;qf${`{O`V3y2a7jS9xUEO zdE_CcPCMmA#M>)xDDJ0xuy_yUo5Xu64-p@xJpE9!JR_7>79XX&x%g=1-NnZ#A1^*$ z`Ev28%CC!0SN>9bhI0Ebv-~raCla5fJeT-v!)LEjuiTG0GL&TRU|1R#Ye9TBwf4TBxql~XmK39CD@>HWuewFgfV~np> zzE*sVa?i0QzgGEf@c`vM<4k^?@?+xbmHUi0`3=gePcXhw`8n}T$}3Ma`OV7fPBOkl zdGpD}w<>Qk#rQVm4W}C4uKeLNW9l4IK1w`L`4jQO%InTGb%K^4(mazt zp?rq;S>-pxgO&TwH+9Y__gY~5yz;x^7nDa_Xz~}8rx(AXe31B6h zSLOe$F!>D1{lwjrM_6g{nUo(8&#ZjXDwEHmyvb_g9?HGe7|*Kwx_EZwbJv=D4&^BW zjOSE7Q9PIO@8Y?Yd#^KfiYOl>UPAd!@mk8Wt~Yh+D6b*jRJp%+Gv)WhTPXM5VCuJ2 zK1aNj@(}UX%A;;Hb=oM;FWy#pd+~P4=Zm*jo_>?5-$D5p@s7%uig!{Td$Xz2S$P-n zF3M+%cU68_+*kQ$@$SltZ86X5r@V`J59J5NdnxzYYU=b>zEHf6^3>Z*exUM7;)9e= z5Ff0(_I6Wei1PX3LzPF{Ve-S2`-u-%K4hoKk5K+Xe5CSPyG(wR@}|3uk5=xp$M_iK z+4dSAt9<)D;}ewE-fw)W@&)44l;0DduDsX*Q)h=Tovy}G~pRN3s_#EYR z51Bf1m7frwr#yF{$KNerCeEnfle~I#8LB^LV_dH^Jner>*{>mdC zHTl)b%ZRU0zFmB`a_?iN&K~8R#Sba}CLXA~*l|i8({DqdFkTJeg?U4l)WYRb!q*Hyk=yq@y3=S-dY z%KM8qQ2s`|p>ogjrcNW}8^s$dzb)QG`ET*&%6%`G`Yn_{7H_G%+C`IZrF^e=YvrHC z+bd6U+0^Nv+Q29dfAuDqA{4CPzJ=PJ*B*VLJ(e5d$)<+<*e`~u~( z#1|_6DZWH`k^82Ozw*)IYm_&CVDf8~r+R2SK>2y`&B}j^Z&BX#k*Tv)d7$_<<*6T= z{9fhF#P=x=5f4&c_lc=*f^Fw(}@t?}qirXWZpZdFnh(}bO?UhMIQ{GBE zrt-z&v6bHxPoO-*Yg0d^@-pHs%G-!%RX#=BQ~6Wz!pgh7G0*F*JXqXEd9Al5Usm}^ z@%qYNh&NDP;GL<{RC!JDX37_dw^p9=y{Xedd2{h@%6EwORvseWN4eVvQ-84Xwc^8+ z{}Laryvj#YXN2+~@o~zdeKPqe%5#d(RK8z)w(_^)bCv%RU!=V9XY;&kl`j(yQ2s@H zopR4Frp^ZC4a7GpA0)n6`6cl!$}@d6^|vaYCB990@^2=;UHLrm9m?l^H~F2)kNz;e zOZn-a#&;|CwncFG9_0uBGrm`Ou-*7R<#{6;->-aIRO1JfpN?+)u=3$pner~;$(3IdPocby972~;`BU*! z%5x?(%j2qizIb}&tHm=YKPH|L9XHRT?uUyKf ziRV@xHHpdRQNBPtukv8=lFA3kp@gNBkC#IRODj()hYXfc-c#ILxqE7}oIc8Pxfrjc ze6)B~p~_>poBE@b zyNQocUPFAW^7Z25lt<5K>Q7fbP<*!X1er{Jp7MO+^OcVgU!eSk_#)-*nN9uW%A1L= zP#z$@Qu!D0)yiXMG4Tgi~NPN5UK3Prvfbs|82bE{@ zH2Fiy8;b`j-zt7sd8BNnPLT2%;zyMC6hEqbzW52{vAj(E)5^1ppHrSDyUAZwUQhg* z^6BC?lt;~B>fBVmSp1gq0y$0ow(@MbjNegSJh$Y||0EtYisQk5T9cwCA6L1*cw*(}#gi)kB%WNk zdofc#i}C^DKFS9bH~HGi4~f@Rp0|X_*Hb=7ys`54;!TwIDQW67RlY#Hx$;2qF3P`) zcUA6D%GB?sJXqXUd5+R1-(C4M@xjUqlri}s$_I!~QXbjcd1Cq7U4eewCqGy0f1 zi3VxR-UMw$)8vLUHpRbWED*Qvhur?j6YJIqq6bm%EyVn zQ=Y1d$$wBjTKu!}v{g<1yYd{>j7N;>xcH~}i$_s@s=CR?QU0xl@g&MK)-;|}c?0oe z%IAwGS01C5sgpsun|Ma$)x~Txs=Zk z&#nBRcz)&OWT;6YMT*-MSO+wec}PiV>LH*HYxWJ-=aKNe5>*bEli#5%5RGARGzY>$?sC` zE52L#Tk$>0d$clj&M2?g+V~~qRoWWAr@VDLVzyYi~t zO`S~2SBYm<{#o2Zc`-jz$4mJT@$AYEi|14xqlc-JNBLRtV#?inntXBP>%_|{-_XnC zt18beL*QyCUoBo&dGtP}PCez-#TzLfF5X0WfOu2ocf?yL&)(NOZ+qo!#5*Z3-OuEG zl?RFUQXaX#$@f#fU3{?ef&)x`nDPMeQOaWuH2HDLvx|>cK2&^y@=M~Alz$hWth~}7 z^So1(FBYGs++(oGPglNOe5UfsLri{-a)0r;$}N@?Vti5dWt9;uMqrq1<<>@n6b!i~m+W zX`0FZ7u|93PrD>;R~~h`$wyY+TijK7!Wkx?Uimq3H|0}jn!LO62D6N3R=!R=i}Ik^ zChwtq!5rgRm3N(M+*5gtdB(FTFEZbFcI8jRb11K~z~pl(KP{e1d9{TmpIiAY@x03C zFEaUj$`da(o?rPi@dC=zEiw6m%D0ObR(^k}$rn)`w9I%><%|7|7gIidx$)x42dyw( zQu#CSQp)SEG^9?3nTY0OE#_K5Wxyg7vD<8Sp$qpDo?uC8Q!)R-XBo@dC>KJ8rz7@+0Ct$~&Ae`F_f8iT77t z?WD<%P~QEN@sY|Oo-saJdF){0W0Yq(XMB?K0Pz{hPn3Ws zQXb`!$?sC0PyDd*?&7DE`&>44o+!767=NbxsrYl{Yp$653+2PE8h@$0(>3F-lsCC< z{GIYxH;lhmzEb>ya=)7<|517MTgE>r&wtzaSLGwbzbStp{#|*aJEqPrb0d#{W}Z;J$IY@>CCuM^v8Vq47w{(>*dCS$Xcq#-k|@7LTF4?h})brTl_;Y~^*H zntUAPAH?G-U;NDE<0)_V+;~FeFU1on@B6~!6DxOlX*`MY%i>9uuY6_lDU>gLZ9Jv& ziEoUjQa&?-IV)_yDP8$$>g&rPxjfk zhw@M2xs^}alFc>(cR z%DafKQeGg6sk2VGcU0qBmHR|9zEk;4@jc3)MK}3<${)rs9;iG)EaOL%ZxTPIyl8Bb zKdsy~j`0i1Nof47@@R>S{};<~@lX3L z9$C3d5|fXue1Uj;w%d~)S4#8WE|N@ntI$`2}?l6vlHX@0`+j9_5Kr8Ly+f zv3OnOtHm2BcS~*R_$hBH-cxzaG$!9$c|{lFBa{zHYkZ{gkaWgJDgWYXe6;fH8H`U* z9^K9OMCGr=Cn?|NZt|0r_s?j2fpXVO#uqBzFTPCq#>^(aN%>5fBI=0pm*U5i+p?NE zx0G)cf2Z8n)8u2uc3k|^CT269QF$gW<9U_4WH+8qd7&J}3n_QcX}qxVyt#}QSAJc* zwDR}4O}>nBue`>~DSswjUir;@CSO_kqWs3ID4$orcn#%y3mUJf++NstUFA!}>nZP3 z#N^v3e^bnOH|4d88(*&cjrdCCJ4%@RdgX&l8sDZoM0~q)_fjUmLwOJJoyw1h?^2$q zw5hXOc}wv<%D0N|RUWa7sS~KYhWKISOT~kfzZXBEyo9%@e@6Kf@oUOE`)A&B+%W4_l zuY7%N;|G-Qs$=}1^3rvUA5tDH9;iHOeUm?|yjTO{LCTXiG=4<+2l1oILmHX#)skGyA%_InLjz(IJlW zC4sBqxstQn)UYkf)#1sOtHU8JSBLXhuD({aTy^tXuDa_iSG#4FtIbu*)xTNG)xQ_Z z)t{;5>d(&da9>8p+D{;lhrO`d5{4zKP9pHc;7P!ff+qt{4xR!$C3q_E)Zl5rUBJ_V zrvrBdPY<2}+zs3vJR^7}@XX*@z&*gTf_s8z1NQ>Y4xR%%CwMOK+~DDU96J8K@7T_(xTY2JZsi6}%g`FL-xwKky#lJ;8f{_Xh6+-WR+dcz^H#-~+)2fe!{B z0zMRc82E7T5#S@iM}dz99|JxXd>r_A@Co1(!6$)F2A={x6?_`_bnqGAGr?zp&jz0Z zJ{No*_#0D@D1P_!8d_# z2Hyg{6?_}`cJLkGJHdB>?*`ukz88ES_0lxx%75p0bb?_VDH^Fa#-v++}ei!^6_4PFM^8{7xHEOZIq>t~7r-xqUjn}j z9s+&^{3`f0@ay0=z;A-z0>2G@2mCJhJ@EVB55OOSKLURY{sjCf_%rb5;4i>mg1-WP z4gLoFE%-a|_uwDEKZ1V({|x>G{44l3@bBP1z<+}O0{;yj{sH}t{{R1={~z2A9uYhe zcx3P>;8DS&fky|A0Ui@P7IFW^Ww3ZSE`!Cx@l>#!GRQleybSUV$6cU~4Dt>qFN3_p zaaX959y|lM8@M}oM(|AFnZdJwdw|QJ?QqtEC*)<&b~t$%v>lGipzUxx2h^8A+u`J8 zaCSH@gR{f&JWyW-XNQxQ!P()s49*V6WpH*lE`zhf@j|dXGB`V&ybR6`$BROpV&F13 zJDfT)I6EAdLD=DVDX3o>ybQQExDR+)@N(eg!7G4Q1g``xgO$TsKQbse9G5}K;kXPk z4##CMaX2o6iNkRjOdO8OVB&CG1`~(lGMG3VmqEkfcs*D@GMG4=ybLA|$7RrPI4*;R z!*Llj9F8}E@CxA}` zp9DS`dXj~9efA)PVimeyTSK>?*-onz90Mm z_ElIN$#d9uzzncyRC#;32_7 zfrkbU10EJU9C&!}2;dRHBY{T-j{+VQJQ{d(@EG7R!DE5P29EgVfu{yf1D+N<9e8^14B#2TGl6FY&jOwmJR5j+@EqVd z!E=G<2G0YY7d#($e((a|1;Gn}7X~i^UKG3-cyaI&;3dIJftLm^16~%q9C&%~3g8vN zD}h%AuL52byc&3Q@EYJX!E1rn2CoBN7rY*Leeee04Z$0MHwJG4-W0qUcysU;;4Q&h zfwu;41Kt+A9e8{24&WWZJArow?*iTxyc>9T@E+hj!Fz%C2JZvj7rY;MfA9g|1HlJ@ z{|!DEd#^&jFqjJQsLw@I2so!SjLV2QL6#5WEn0Velg0 zMZt@K7Y8o^UJ|?%cxmu5;AO$fftLrb0A3Ni5_o0sD&SSYtASSsuK`{YycT$E@H*gi z!Rvw72X6r05WEq1WAG;6O~IRiHwSM4-V(eOcx&)B;BCR%fwu?m0NxS26L@FvF5q3k zyMcEH?*ZNuycc+H@IK&u!TW*t2Oj`F5PT5$-{6D6hky?S9|k@gd<6JN@KNBS!N-7) z1s?}K9()4$MDR)AlfkEePX(U_{tx(c@EPDU!DoTb2A=~y7knQ0eDDR}3&9tGF9u%% zz7%{J_;Tk-vYiB{9o{G;M>7>fbRs~1-=`6 z5BOg2ec=1S4}kv%eh~Z+_+juP;77rafgcAy0e%wv6!>ZIGvH^z&w-x@zW{y_{1W(O z@GIa~!LNZ|2fqP+6Z{tVZSXtbcfs$0-v@sH{t)~T_+#)V;7`Gyfj6g(Mta_|)3DZx{Lrv^_0o)$bEczW;*;2FU)foBHK0-hB- z8+dl`9N;;@bAjgu&jX$pJRf*|@B-ik!3%*G1}_3$6ucOCaqtr0CBaL9mj*8bUKYF@ zczN&&;1$6ufma5v0$vro8hCZ^8sIg-Yk}7WuLE8eydHRc@CM)w!5e`$25$o16ucRD zbMO}6Ex}uXw+3$m-WI$aczf^;;2ps`fp-S)0^Sw88+do{9^gH}dx7@`?*rZ!ydQXf z@B!ch!3Tl=4L%rr2>4L&Vc^5TM}Ut69|b-dd<^(l@NwYd!6$%E1fK*x8GH)(RPbrw z|A0>ip8-A-d=~g@@Hyaf!RLX`2VVfb5PT8%V(=y4OTm|cF9%-%z7l*D_-gPq;A_Fx zfv*SO0KO4?6ZmHEE#O;8DS&fky|A0Ui@P7I z5mVmj|x^UJ<+!cxCV^;8nq^fma8w0bUcl z7Iw(t?Zvfs9yb*X~@Fw6*!JC0M2X6u161)|7Yw$MUZNb}tw+HV4-VwYL zcxUh~;9bGHfp-V*0p1h57kF>*KHz=9`+@fd9{@fOd=U8G;Df=3fDZ*920k2o1o%ks zQQ)J&$AFIo9|t}jd;<7H@JZm4!KZ*v1)m1~5BPNO8Q?R)XMxWKp94M@d>;6G@CD!t z!54uq244cc6nq)@a_|-4E5TQRuLfTOz7~8P_7Pr;voKL>vS z{u2BZ_-pVt;BUd-fxid;0R9pD6ZmKFFW_Inzkz=T{{j9J{1^Cda9>Ec{|_DnJSccD z@ZjJfz(azE0uK!y20ScyIPmb`5x^sYM*@!w9tAuqcr@_n;4#2sg2w`n4IT$PE_gif z_}~e^6M`oK4*(AYPYj*}JSliG@Z{hrz*B;!0#6N|20SfzI`H)18Nf4wX9CX*o&`KB zcsB6t;5oo^g69Iy4W0))FL*xi{NM$^3xXE{FAQD;yeN1v@Z#Vlz)OOc0xu0-2D~hI zIq>q}6~HTkR|2mLUIn}=cs20q;5EQ&g4Y7C4PFPlE_glg`rr+~8-h0iZw%f9yeW7y z@aEtxz*~a10&fl82D~kJJMi}49l$$+cLMJW-UYlXcsKCw;61>5g7*UN4c-U5FL*!j z{@??^2Z9d*{~LTT_z>`+;KRU&gO30o2|fyZH24_svEbvt$AeD*p9nq)d@}eH@TuU_ z!2bcC4n6~XCipDy+2C`)=Yr1zpAWtOd?ENE@WtRuz?XtA178ll0(>R-D)80dYrxln zuLEBXz5#qA_$Khp;9J1Ag8vJ?4SYNJ4)C4eyTEsY?*ZQnz7Kpq_yO?$zz>2S0zV9X z1pFxYG4SKyC%{jFp8`J(eg^z3_&M)^{s#Ol_&f0T;2*$0f`0=44E_cD zEBH6?@8Cbce}ex4{|)X71^55KgMbGG4+b6_JOp@1@KE5P!NY)u1rG-v9y|hgMDR%9 zk-?*YM+J`t9vwUecueqE;IYBufX4-o2Ob|h0eC|2MBoA7f#8Y3lYl1$PX?YGJOy}4 z@KoTb!P9`J1y2W_9y|khM(|AFnZdJwX9dp&o*g^~cuw$K;JLx`fae9z2c93i0C++0 zLg0nLi+~peF9u#5yaaej@KWHV!OMV`1uq9)9=rm0Mes`CmBFikR|T&IULCv!cunwH z;I+Z)fY$}D2VNh%0eD03M&OOXn}9b3ZwB5Ryajkm@K)fh!P|hh1#bu59=rp1NAOPI zox!_+cLnbT-W|LLcu(+N;Jv~7fcFLO2i_li0Qf-gLEwLb4+b9sJ`{Wy_;BzM;3L6D zfsY0s13ngf9Qb(f3E&gKCxK4}p8`G=d>Z&a;M2ipfX@V<1wI>m4)|Q~dEoQG7l1DW zUj)7wdE<1YZTd8hj1-TJUw?>%ljGZv@{2z8QQA_*U?L!MA~L2j2m{ z6MPr=Zty+ed%^dC?*~5s{vY^3@I&B-!H<9+1wRIU9Q*|MN$^wPr@_yFp9Mb$ejfY+ z_(kwb;FrO#fL{f_27Vp<2KY_zTj00B?||P0zXyIF`~mnw@JHZ}!JmLX1%C$q9Q+0N zOYm3VufgAdzXg8>{vP}T_($+h;Ge<2fPV%52L2uV2l!9$U*NyNeWBt0KX?%Epy0v4 zgM)_v4+$O$JT!P1@UYyfkZ}7q3L%@fE4+9?#J_39s_$ctv;A6nYf{z0q4?Y2WBKRcm$>3AKr-Dxd z{|9_J_zduw;IqJIgU0bdKg z4tzcM2JnsGo4_}NZvo#5{xA48@a^C`z;}Z00^be32YfI1KJfkE2f+UWKL~yZ{4n?t z@T1_zz>kBU06z(S3j8$q8St~<=fKZ{UjV-dehK_C_!aQ0;Mc&fgWmwZ34ROwHuxRz zyWsc0?}I-8e+d2v{4w|w@TcI7T_(xTY z2JZsi6}%gGckmwIJ;8f{_Xh6+-WR+dcz^H#-~+)2f&UFY7<>r$Q1D^k!@);@j|3kD zJ{o)s_*n39;N!t3fKLRU1U?yj3iwp;Y2g2WPY0g?J`;Qv_-ybw;B&#}fzJnD0KO1> z5%^;8CE!cJmw_(_Uje=nd=>a=@HOCT!PkMW2j2j`5quN)X7DZGTfzSY-v+)NdXn0AN&CLf8Ynf4}l*BKLUOf{22Ig@Dt!C!B2so20sIS7W^FedGHJ1 z7r`%qUk1Mdeii&0_;v6b;5WfIh;6K5Cf&T{gg@yb7;6cEHf(HW+ z4juwLBzP$B(BNUf!-9tc4-XyzJR*1`@W|j%z@vgk1CI_K13V^pEb!Rialqq(#{-WK zo&Y={cp~rs@Idgy;7P!ff+qt{4xR!$C3q_E)Zl5r(}Jf1PY<2}JR^7}@XX*@z_Ws9 z1J4eg13V{qF7VvodBF36=L63VUI4rxcp>n@;6=cTf)@iX4qgJhBzP(C(%@yl%Yv5! zFArV;ydrod@XFv-z^j5+1FsHV1H2}9E%4glb-?R_*8{H)-T=HIcq8z};7!1rf;R(i z4&DO1C3q|F*5GZx+k&?PZx7x9yd!ug@Xp{}z`KHX1Md#r1H31AFYw;reZc#I_XF<_ zJ^*|m_#p7V!3Tp60UruJ4175F2=I~MqrgXlj{zSGJ`Q|5_yq8Y;FG{7gHHjU3O)_| zAMokmGr(tp&jOzfJ_md*_&o6W;0wSPf-eGJ488<>Dflw*<=`v8SAwqsUk$znd@cAo z@b%yuz&C<#0^bb21$-;`zu?=zw}bBh-wD18d^h+W@V(&s!1se60RIpCAowBh!{A52 zkAfcqKMsBZ{3Q4(@YCRDz|Vr813wRb0sJEPCGgAOSHQ1=Ujx4megpg__$~0;;CI09 zg5Lwb5B>oBA^0Qk$KX%EpMpOFe-8cv{3ZA+@YmpPz~6$u1Ah-Cj?If9snK)o)|m{cvA3W;K{*LfTsjc z1)dr_4R~7cbl~a1Gk|9V&jg+sJPUYM@ND4O!E=D;1kVMY8$1tqUhsV2`N0c-7X&W^ zUKqRxcv0|T;Kjj9fR_X>1zsAw40u`aa^U5`D}Yx7uLNEhyb5?#@M_@I!E1om1g`~N z8@vv9UGRF~^}!o}Hw144-Wa?IcvJ9Z;LX8XfVTv11>PFG4R~AdcHr&7JAiit?*!f% zybE|&@NVGU!Fz!B1n&jj8@vyAU+{k5{lN!-4+I|s{x|qw@FCzs!H0nl2Oj}G5_}Z+ zXz(%MW5LIPj|ZOsJ`sEp_+;=Y;8Ve;f&T+O9ef7(Oz>IYv%%+p&jp_cJ|BDm_(JeS z;ETbRfG-7K2EH781^7zvRp6_^*MP4DUkAP(d;|DK@J-;G!MA{K1^*X(8~Aqc9pF2` zcY*H)-vhoEd>{CJ@B`rgfgc1v1b!I&2>4O(W8lZZPk^5UKLvgo{0#V6@N?kj!7qSc z1iu7+8T<^ ze+B*;{0;b9@OR+v!9Re11pfs78T5;HAJzgO>p>3tkSq zJa`50ir|&ND}z@7uL@obygGOd@S5PYz-xoo0j~>Q54=8j1Mr66jldg&Hvw-7-VD4s zcnk2B;H|)0gSP>13*HXAJ$MK3j^LfZJA-!t?+V@xygPUg@SfnkzC9Pqi|^T6kWF92T%z6g9V_!97?;LE_5gRcNz3BC$^HTW9vwczW( z*Mn~W-w3`5d^7kK@U7tgf^P%g4!#3?C-^S#-Qat`_k!;O-w%EO{6Fx6;D^8ugC7Au z3VsayIQR+hli;VoPlKNUKMQ^i{5<#t@QdJ=z%PSe0lx}<4g5Oz4e*=bx4>_M-vPf1 zeh>UU_yh2V;E%u`gFgX(3jPfIIrt0km*B6!UxU8^e+&K&{5|*w@Q>i1z(0e30sjjA z4g5R!5AdJhzrcTk`y#;ofAApSLBWH82L}%U9uhnhcxdo2;9#^&jFqjJQsLw@I2so!SjLV2QL6#5WEn0 zVelg0MZt@K7Y8o^UJ|?%cxmu5;AO$fftLrb0A3Ni5_o0sD&SSYtASSsuK`{YycT$E z@H*gi!Rvw72X6r05WEq1WAG;6O~IRiHwSM4-V(eOcx&)B;BCR%fwu?m0NxS26L@Fv zF5q3kyMcEH?*ZNuycc+H@IK&u!TW*t2Oj`F5PT5$-{6D6hky?S9|k@gd<6JN@KNBS z!N-7)1s?}K9()4$MDR)AlfkEePX(U_{tx(c@EPDU!DoTb2A=~y7knQ0eDDR}3&9tG zF9u%%z7%{J_;Tk-vYiB{9o{G;M>7>fbRs~ z1-=`65BOg2ec=1S4}kv%eh~Z+_+juP;77rafgcAy0e%wv6!>ZIGvH^z&w-x@zW{y_ z{1W(O@GIa~!LNZ|2fqP+6Z{tVZSXtbcfs$0-v@sH{t)~T_+#)V;7`GyfjLrvgt6o(4QEcslU(;2FR(f@cEH44wr%D|j~W z?BF@TbAsmr&kddjJTG`Y@ciHfzzc#G0xt|+1iUDCG4SHxCBRF9mjW*hUIx4@cscO$ z;1$3tf>#2s3|fklNYl7DTuMJ)Yye@b>@cQ5lz#D=$0&fi71iUGDGw|l% zEx=oXw*qes-Uhracsua+;2pp_f_DP%4BiF2D|k2X?%+MZdxG}@?+xAuyf1h^@c!Tf zzz2d40{Blsrp&EQ+Ww}Sr*z72dk_zv)$;Jd(ggYN;~3%(D0KllOg|G*D|9|AuNegym| z_%ZO~;3vROf}a9E4SojvEciL_^WYc2FM?kJzYKl_{3`f0@ay0=z;A-z0>2G@2mCJh zJ@EVB55OOSKLURY{sjCf_%rb5;4i>mg1-WP4gLoFE%-a|_uwDEKZ1V({|x>G{44l3 z@bBP1z<+}O0{;!}iv;)o!GnMY1rG)u96SVgNbpeLp~1s|hXoG@9v(abctr3>;E}GN;DO+Y!IOX|1y2T^96SYhO7K+R zsln5Lrv*<3o*p~{ct-F{;F-a*fM*5I2A&-}2Y62KT;RFE^ML0C&j+3#ya0GX@Iv5) z!Ha+w1uq6(9J~a0N$^tOrNPU9mjy2eULL#xct!9^;FZCvfL8^t23{S!26#>ITHv+8 z>wwnr_A@Co1(!6$)F2A={x6?_`_Kj72BXMoQHp9MY}d=B_r@Oj|#!54rp1YZQc z7<>u%Qt)Nq%fVNGuLNHOz8ZWD_*(FF;OoIRfNuof1il%33;0&>f5EqbZwKE2z7u>G z_-^n$;CsRMf$s-D0RA8NLGVN1hry449|b=KejNM+_(||n;HSaQfS(0F2Yw#>0{BJn zOW>EmuYg|#zXpCC{08_<@LS-w!S8_I1-}P=AN&FML-0r7kHMdSKLvjV{v7-T_)G9t z;IF~ofWHNQ2mT)X1NcYqPvD=yzkq)Q{|5dY{0I0?@L%A+!F`e8{y%sS@Sxzqz=MN_ z01pWs3OqD;81S&*;lRU#M*xoq9tk`$coguc;L*UNgU0}m2_6ePHh3KHxZv@?|Zg08a^?3OqG<8t}B>>A=&2X8_L#o(ViNcoy)i;Mu^l zgXaLx37!i)H+UZKyx{r3^Me-vF9==;yfAnX@S@9;I051t%3cNIU8St{;<-p5> zR{*aFUJ1N1cop!f;MKsZgVzAB30@1lHh3NIy5RM|>w`A{ZwTH9yfJta@TTC+z?*}& z0B;H23cNLV8}PQ^?ZDfEcL47Q-U+-jco*=l;N8HxgZBXM3Em65H+UcLzTo}9`-2Yv z9|%4O{BQ8V;6uQNf)4{94n6{WB={)s(coji$AXUo9}hkOd?NTH@X6p)z^8&w1OEqn zI`|Cmnc%a)XM@iHp9?+@d_MRB@P*)uz!!rr0bdHf4178G3hJHU5>?*iWqz6X3S_&)Ic;0M6}13w6U2>dYk5%8nn z$H0$+p8!7zehU0F_!;oC;OD^4gI@r@2!09tGWZqntKiqbuY=zJzX^T|{5JR<@Vnsm z!0&@U0DlPn2>dbl6Y!_t&%mF9zW{#;{tEmx_#5!I;P1fSgMR@32>uEDGx!(qui)Rn zzk~k({|WvJ{5QBS3f%t(4+0((JQ#Rz@DSi3!9#(E1`h)s7Canyc<>0|5y2yYM+T1q z9u+(qcy#a>;4#5tfyV}q10EMV9(a841mFq56M+YS2ZARCPXeA4JQ;X$@D$)F!Bc^! z22TT?7CaqzdhiV38NoAwX9mv#o)tVBcy{m{;5or_f#(L#1D+Q=A9#N70^kL~3xO8~ zF9Kc^ycl?K@Dku9!ApUc1}_6%7Q7sIdGHG06~QZkR|c;FUKP9=cy;g^;5ET(f!7AF z16~)r9(aB52H*|B8-X_lZvx&Fycu|N@D|`L!CQg125$r27Q7vJd+-k69l<++cLwhQ z-W9wXcz5s~;61^6f%gXQ1Kt@CxA}`p9DS`dXj~9efA)PVime zyTSK>?*-onz90Mm_2)v~91l682rl+BJaP_ptyFkxi$WIdwsXZ}F&s_0P>h_Ce zj?ednwtw~gW%BzaA4WZl@n_;;)$Io#CGY62kJp=shts_686_Ux8Z=TPrr`ag>2Qnx3x$lcs|=Jt3x z@jU8rO-~8%M&fxjpU>p`fKL_AulaZ;|F3uf^*F|Ffcv_;^Dn6RWG0_dypXy*?^Oct zEnZmjc0E^#7g0}W=ObQJJ-zXF;>FYp8IRY)oo8|N*~W{Bx6$)J665_G_j}w*zLfSv zH~GWjWjy}N@%g@T>bA2$Pv`OC>zvvAcV7wfMJLDS`zmY0p(>*J2=^|a!RG;g=7Dfn#hCYra~bwIqSy4|i9;?30inEAx*>&~Zz zdTitQ#9OM{b!Z{pTHRhhOcZacZu6(b+pBjr{W1Hw<91Z9W4xAlXZ26UM>uYeNkj6- z5%4qMVfy>;i3gs>@jzcUosWG0Ru}KCZr6E)cn|dgX0nH%|E%PDY5thWM;YLMo(UZf z^tH9q@Xa*&rjYLhK2Uo4>bMn6&jIoN>h`$(YoPmhAE=(kT&QJsJkU2ty_D&xFFsg( zr153qL)Gnh^1b+QbvvIdgWUd+>UKUI#Yd~BH2tH+$ExQyz6SC~9iQ(Tr+IsfeUSWk zb$h;w`L{ctiRyMoloX$&Zs%FYaocI8B7gLd{1naGbr>T)P2FBUtc0E;kbfjTU3&_c z!IKYm&*$wk+qqOQUsP~B(C5uh5TB_%_W5y-_$>8SrsuQx9QDD*a}II)=d0VEzK+lL zEmXH_byj?ly1h+;!D(R{=E27^$cb{afi9{S*C8+|ABZ%yIXzM+Yk4j z&oRRF6`r1_;_bC3w;6ZCNdNi9qg-F*>G=UZWwg5vYcy}S_qOAKzP0Lh{yoOH-dX39 z*UTs7SpWI`j?ee4*Paw6AAX#h-=J<^M+_7np#Aps=>^D_81MFM(w@vg{|qp~al22= zkn+cD*Mt18{P}u?^yIUbe?EI$U4Wju;2))@tIj{887kHUcb+}f?RjRacyD$4ykBXe zoA0Y`uQTsD?r*yuOTM4xQ<-rKPjY((sYfxsNBnQ~YQ~FBcJo8jvm1XTK1@Bpc&90D zeuTQ6{~_`2>h|^TBk`@eze<>WkzuO;?JDPZfNxnAGn~&Bc8C0A@P&>C`nK!14b8Yu z#P_JTF$2Y&=6^of91rld&1~mmkLMbYpXhj?&)a`ce4mcn))amR&-jnqb3pUyTyXy9F zS8}e~e^0%+$@dd~pq|zE8Sub)ZqGx_+vBhz_yF-onzzr(%fZiyKheB>9tk$z9oOsG z#Gh*3-Y=@_xWE108S*p4pKFhu=YP<1S@JJE`JfB@j~mPJK;LW4XEy6xRXmt|8S86e ze2Vy6&F3?IQ9PvP?e#{ig>L_Q&D--yIq<>aA3XUj;7`OqYTn-Oim}KY*Xu>ZKWV;- znSUL}1AI+0o4@)JnJ+p^ezG3t_QGa2^js4EqW$)|<(2pp?XmkY=VEuB-h4&zZ`xy@ zUs{U)P`9r~rZ^tpYpnCnVCKIU@-M`HX^-uXwZxr|PxoUg^E_2we45Tby6Imn{*SsH z_lI~;?Xi!?+)Lg58Jh29db*2;@#GhZhf}xrF>Z=S@OX@6ZhuO3`#Q0JcqDZ@|1RQD z)I*xkGb3(H>T$8xP`=HtJ`sVipNziXV!VUUzdaw$dFp zwdR+abtvn&{lA9fj|Pw*CZ0}v?DP9U@m%Wm{26nVJFYihRXl^{JD9xc#}+C!77=U3`{4F6{e9o5km;=Q2GX z#TTgC=hMt<-Tpc1_WayVJfDu+-;8@!yr8l|A{{ z;!8aFyW-94+XwnOG3h^fMC|=6re{FE* zaA{n zV|DxZ>MGtu-QMTlCEirsUUz)}PyMgk-$L{DcxVJZUA&d%?R+kaw^q0FkG#$8Z?A68 z!#N!Hcl?xvd>8SK+H>8k=S=8X4}Ki{CiDl}?#{o9j%%;GQh`?x@8-#O1D_?{L-Te` zjyP_=Hk&GcoRWMm%~vu17<`92Pp{_@@2h#ce>;P(5bv+~f~G&tPPc!#KJM&&lK$cY zJ^63qD>Xma^t9UL_WZ4R`@Yz7@zt82Xz~qryZIrSxAQqJzE<-MO+M8gH$P1CTZ~T< zU$6NE#@~u>RJTtY<@UNgBelm~Cl7Euz*pNI-adPNTMzmB;-j_4u7C7>ZvR+y`+X)A z!N-V?)4W}W&5rv!PX2@Z3-JlsW6xtT_xtZj;&`BMlIE9~b!#p@MSZOCanLhI^8aYw zp0`hlZ`SpX83$NkN-gXGs} zPXV)@tHsx;*Eas9V^F_6&n3S>^EpjE<`MVtUR59O_VrpJ$DQ|0Jo$Fwo7C;~*bMMP z;#)Lt&kyg!|5cA;<`e&@JFeGDh;P%p9e14gcJ&#i=c4!yb$gu<^_bhg)8iGzcd1u3 zJzKYsP7i-1$sb@3&K~K0nIrMQI&m7OS#SduSZtqIR1ALWr ze`PQ|-ymP=q}zW`d+hqp5I?M5Fvy>x>*71?X~CDn^cOzmj$6?)ZfnN_ecLo2VDdY~ zpKAXx;~&JIt2Z;A?zG#pOMC44^mW|d<6@ZP_h`P1>A58C-L9Bt-2P)a?qZWK0Nzsk zq~`6qO%y+^-rVf3{o?y{K6d_pot5=hw>!Fu_#ySVW^KSn|b$i@46Hlq*em41u;+HjF%*^w#_)GO& zCZFS^%sOPb zUCzIoWzQ>xk$4O(zcLeVT zK1TX4=sdTWc|H+;uj6(xp6i-BpN}3NC;n62-p{!Sp8C4m^IP-%On*1=OFAEWTx}Q6 zru)~vPyHEs3f*w~gJ^!7>F+BZTD_I=ui}?=TzlR~f75?YE63;if@@DW)3ZhLA=EFK zW{0noQOm^Sx zzoYAr!)(_G#{+!TJ@Z^8`FomQZt`Emz5Ur9xcvb-?jDn`1>R3QiRSJ0E)Y+qKFI9f zlj23x`^DPkx-^{>Ggu`KFqGWcu$w{)yzj=sI*W`CPBuc{bM` zd%br+yrueKvt7|%yZO%Q_WOA2h__aMY5JEs?r;8UCEr%_Gfe+qZ`}T_>bAeMczgBn zrhmNS{`#j${+n)Z0h7N2`G=D4uKk_O<2Tb=|NS|^3xihzukN_>{ee2Jz0R2c`Dv2x z?a7~o{AJ1a)x14FM0w|byJ9&W=)*@t%z*qx$q&+T?R7a*vi;o?KoPnvNzIPUK_`R#asue?5Q+WUJcKDgt0dn$^xhF|7G9q@!5G^g#2gmiP~eoi2KdW z-28cadrIF&&MNL5x4-x-oljUZpKap7wZEa6&tKo&{?O|7c5xy-BO2e^PAP}_l@*(Jiu4j?mqpw!ZVUjspB>^ zk`)O$)I_Ap7|zzz>^;m&dq1i{6I6G z(BWM_sQFyRPl;#IynQ|1Jc64)taeaex96zl%b0Ofiyv3FUo;Ru zVgKsWam7!0{HFL>^_iyshxi5cwZ?Nqa_8gCR~LVw&--?}CWyaMw}r>VU#r{WAzoy+ z|E;<`&({=xsc!f0c=5}&R^Pu{3w}!cs^;zU+H3La>h`{LiNw2 zRDygb@!OiW*Q;a2KdI+2J?kKULj11g?Yg}df2Tgw^hAy7&i{eBUAO$=k3HT-{F%DF zpFG)df7h8aB>zJ5c0PL@5AfyJ^UMG?YSZzQr&*>TRfEgt1pvTpX9OJ`Fp**co@yw=lxOOhsDEc-kw*V zfQOFljvG$%c6-x;R}>Ggd3!z?4!%b`qUP=TJP?nhZodc}$DL1P`&V6`4B*wpqiWu+ z&p7bI;?Xp}(yae$@fhk0j3-AFN={0X(A9WMYp#Hb%Sty=K-OlHXcoy{}CjVPJtGa!@OPbJ~kJn3! zXV<)4=M~~P)a~OUVIsFDmwHY!ZhP=O;(0W0_j|Aaw#N>bxIUTHVz6syH6#E2nN> z7fctg;PE@sQ%T+SBunb{R93g^Qv$q|cok26A@~jPs-AqTWbU|LuOePe^LD>acii7~ z$WqDI(0o+0ZZE}as@wBMqU7$lx%7I#o}X)q*Vep!UK=Q0N8Nt0L%gp2tIwW?KZ@5= zxBESB3U}NF>LtuPr-(OFA8q`WcwU{SeO#nY>GtIIcpvfn>bXsiFO{2bqWztWClzm| zZeI^pcRawCSD)VpnEY_Z{jLAM(DO{Zh4!a6J@Hbz^J%5N!+2H4{q>KKd2&Zn|?U-j{3 z+==4-)$JEM#Ru5G>h<{p#{+y>_4u@p)36!balQG>;)6UrwH^0&ytRY;RPn*uV~^+E z;zQN#dElA&aCLj03765Gr`OYokI=l`FSW%-s@wh2U3|29QM(_(Z;OxBd`^=Op2?k$ z*Hep+)4UzGnD_+sCZ?x@_#}0E9k^C}in_i2IV?U+-M$d}DE^PSJ^o{5cIP==-JY+q zfmacqsd;<89Spune6}b506bb2cRq79-`%W34)OWw=Z$v}U#M=+W5>l8tIsm|SK@{3 z=KJjH*7RB3ahGX+w#io#U#V`7pWfoDJib_bwR$Ggb6R|zy1kG0O?<7oeV$E`&7IGB z_41~t9C#P;4Vo`$E<~1t-xA-f`DUg+bar>#t?KspPa?isJ+8^;6W^w8^EJhHsN3!8 zF22*_)5UkG+w13T;@)x3i|^6AJwJrY;m&`b`mRuaCR$$nka|@!|5@Tk)Jq%xCVtH0 zm20lv(7{wio0HS+ygh>qg7)y;1AEpy!YJ}C8ZX5B73Zugxb z`QxeN?`yuR`Nz2V+@6Q(_W8G%_+xc@{+urURNdakJ0$)}-Cjrif_&=y?zqo2-^k3T zj`&M;JD;(R``fN5l7FN5I;Q8H_$PIHyag6;$Niu_%H&6izf+%P{F!(e-Cy>8e*A*| z=ablR=XI9m3!9!&;$O5sk@11zU)3Lk{__v##Vct~HIt7~$nCGH-rsmF$LITgYLERO z;4zZ_rEc$6Zx{E)H2?koUyq3YrJl$H(iL{c4WjEPb z-XBX`#O)8Jp2}RGj};H0p3U^%0#8@e?Fp&*x+dR3Je0b`;52eJzt5-Mq!Qi{aBWk{^$v+W~?D0s&-EpI;=QjCT;xW|i@ws0-miir& zk6ps;sjd5^pYbK)aWsF>_zCd@>h}J|2k}64`@EUFq}!iFeUdq0)e=vkKF9Qr6R)fD zv9G(2LjIR{D($hybCXi;xCOPxUO&$k_vV9^cJpbp$DU_Oflm-mr+K@-c8S;5ap#%s zidV+%_vTB8XV4yd{_iQ?PptGIsM~X6S@Epu_W7cph`*5 zndEb*+n&qfIn`}XsB&&kF7Gn zS4Q2QpFc{zthzm(Ggor+<<)JzljF|k38)t^{VT;QsW&zLTD-Y#ul@Po7?s_gDw?;C zmw&{osmC_wx7Xq|)bkpzUd8RHr5@e*H1Rs>>&<*FiPu%HYVvuix;^#PE1P^D@doPl z=g~q`bMuYVW161v;!V_>ndgx=j?ecsQ@6+Sit4VnP>*cJZCu0cZ>c^j_@94>Qd3;l zGposG7jLcJ+W2(ww(4<=pAv7UKG=AKT5f*_b$ebdBHmHGjL8oY@1$;@=(mV>R=2Ms zo{P8E?b;XYPf?}X?znB$?c;Q`co*%leiQOB>$pAbwZ}f6Rub=^9>&bSqj*Pk`@Z=Z z@m@M^CzH=w*X{S_CyRI1o`@#D4g8jP7f(KBJ-6TM6~w!0KECN6;kZ3#8j?R|L;jNE z^L;(E$Nt~w^#hH|Cx2CZfV$m}DH^#wUhghGNb@(%xNF7x=(x5gNMpBWi01A3=Wsm0m(gBc>gNNr zh5SPCVcKKw%Uu;8p>AJC$7k1m0KW_V9z1w+|MQ9Jc%W~IZkN4p zmREeJZdYov5$(mj>#$UOf%Zf->wL&@f9voPJX#C?`4r%Jz)L#re7=EaJ}tx-tJ~vb zviLIfzGizbLH{$!uh6_*w;C)U0L|OiFaL^fQh#XX^9g#QwejB{4?MBs&gWxjzkS{*4&GXPtLD>~?V1Jsi@;Zd z?*>2SxO4y0GwyfsZR%6Z`Xp-We;qP_7XU8_UdeIi=kz_}b{F5NZlCvWil0)q_unG4 zb9+vBJg@jM_5aLu;5hN!>L1MZJ`z8tZm*N0wRijXs@r@U@iXc+KV5vk`T~3WJMQoJ ze=Pa`G;g0TQgra2&*XTZ?~vy0b^AW?BkGUMxZygw`QdsVv(I;v9QQY$S>UU|cY~h* zzYKmK`~~Wz&@=QGaeO>SyJZcwrJ~uRP?_VVmzolN?^yhWl-~KHNJ@v%zXip;3 z(@p%Y`X1xsp#MJPe~3TO9=mSQyZWDJ7RR0MNA={(ia%Dj_eq*T&oIe9)x5ngvDxwY zzGv$8_4O0UKUcTcSJAt<^LeSB#+*+Iicizyp`2NtM&hqEZ|6A;{Dk-$&D-bYpW<)T z?Qs&ZyE`AR2Z+DZyq!;W@Ur6XHE+)+^}#!ef6%Uqt$Cmav(#r4eR3*?jba_6HT@9(qymBfR@GPOS2-{0{7Uu;kRO2}Uk52ih~KX`9< z+z{$^9a=l?@4T^G@}V@Jz+AUP>Erf9RPSPZsN(^?n4WoVmb|woL0`8gtoGRZtL4D^ ziHFy`-L5r``$k3&(`yjb>Be$`CQ^Lv?r;VPaW`n;;}Su`#*!{uIuwf@qLRj+4N=&{^yGh zgWUPd)OoHk|2|KAuKG0Nuf^x9_cLDaZ?|W$y1kCNDZW%aqsfmR?0>(UblmxQG|k&_ zrws9*zv_6PZ>8q#^GnsC{_~R@5A?0pd?7QRsKfl{t2plb9G~Xx`TT(6?s^_I>v-Gr zR2%N**J|Fb=TOJp{IU>#g7*CUK=ScC`?2!~w`Z&N*!_E3+?#Lb++cTpp3c+rQ+%7J zr{yUB`7h#$JUzWeyLqqw5D)a^`;Bq)UiXc4J&ES+>(=n&{Ck}7t|!yHU58oV2`0Gt z6rTJ%@g2Gj+0Fi1J<)$Y{Up~@d3tt=@6sMSpJbE$=QoO{@$?j!;^y~wdRB;g^Vz4m z`E;J1t>D?Gx%mvV_!}Tnhx9e~myyQ$bpUsm$EWS_I!S0uH zv;5~P%yvD8r>E8&*AHlqoln8J{_~~hxt`0@Q+2-U2R%J`7WmH>Tj+WoPfy`R{=L9r z*YkPud6)S2TuWUqpn1Dr_Am4ArB=9JNb`0bim&wVMOL|9#FH<$+V#V_4tBpBUE@E0 zXszqTJUw;S`S%ZwyXV6z=6tx$ybn@(y_;XJeJ16Ai}0U^NAGx51cf9&vEDHe>HFKL#Ej5fByZA4-fmNV3+Bc0KU+8 z(Qto)IZb|x@qWhb_0%f9Y#3r_SoO_7{T=1lb#Zu<2mXM|2>HucfLQ(ldmXV z%HxBeXB_1BikI>9%-HFU`$4xiqFK-OyIl9?OYU~PoTn%99{+w-yn^QK{mGDf-TXTp z*Y20o;vdylnfdhG=jOdVOT{Z`e^!%!44!jVK-k- z`|Y}AI^y5Ah(FPupyqKDaMaDeQ14;-=ZU{k?`Hgz_&4?M#t$BIdw!_f^Fzbqt~bzm zP6_?zKg2)bdhj^rzx(X>8BP~}qdnbC;IQ~Jb$fp}{z>QcU64lEY>#;x&6m}2=W|Q6 z->zH!Q?7?pxARYO`pfcRsgJd+ht>SH%;n+xt9!U2*eCJ-*p-fBW~f~bbo(Ia`cJtxXYnuEe@u0eH_PkpBj+>95c{`u(;*r$td>)8LQ9o_^Gv9T4 zqNxuuK2f}po|n=X-!9%%-JVy2+;e-pUP`>V=Es5lu`@7E;XQuD`6KG}W$`4-@- z9Cv=tm#&*#=g1HI_f&B_(AQeWwd>qUysi37GoN+P^BKJ8L$^P<&L@-UnIi7}y~Fh$ zx%o7jx9j{=Je_(()06(On@{iYv*O<0k6h=8oA>^H;qXsg&!jyuO#eag4C*b7PkQF& zy}yTdi@5jq(q0nJtUY&4&zR?KPgeB`#tXb~y`QeXJqy}u`Mka!gv^V#=R&x==8 z&t>|Pe)4}F$mh88d#Ny-waO?*07* zKgGSjUm(v9x4*6S*yDe+czbpGIDIJILESzt=lbdPbX2$J+lk_xJpNX^v&Sp^a(lX{ z+x}VNUDfS%&OPyN9?$sO?dh&w$E^QEaqs7}9}&N*$7gAi|0Uj2d+hoY^#ybKz4_ka zy)|#w?WTAib-Oh^dWEB-`#Mw*_t z;(u%2J|EQ&>h^d)cY25T3+=Jz+mGVj&kaix%%!zw~vky-({4<>seo-uAQ-pQdhmE{l6Vzchbnw`YOoOPTfWEWSv+r17QV zOFVvEe3`oK2_DAnU*Yle;;Yo7nf?ai-p?03FTPgu_IaUTShs(*dcwc{6kQSztLxUr z^fwCU=GST7UY~y!-=J=flfmKL{3dmKoa}Wxz!%hWoP3x3HqG1Xy@?Urp84wbaj{+8 z`+285Bf9zNnr~?4zfU~6ZkK(%IU$mp_kK=k!^p1h&>nl85D>+6@8^-e72l=#+~)P~ z=%{Yq`?;gpqq)9M^X<+0j}} zi^lYyKO~;i(~~onn?I~Qsm(fX7x(7V#&+|`Jw4OGgU506-p>oI=y-sysD1lGKVNi~ z=(>M2b=QUW)BPTfBL78Ad#KHB7aiQiYZH#F9ZKTx-i$E?mT26FEItK0Qy;dp@W zo35vQ0-7QD9J;?An{gkDKhqxjII5M%?SG?gzfa??_*;)R32^i8)a}oYJQRPg-phx=T_zloUy61O%j& zlu|-M1O!146!{VQ&idZH*7H8>d-(_E!(yF%_BnHA=A1JZZ=NSwK8Et#uO67klJmG% zFbn!?Yn*)l)7d6Oh3$vJO*f*#)c*uj=BqCB6!XU+ZZ38v4F<$k33=X$j=FRq^AZhenMkdIsdJwwz} z!sTn250m$IzQsJ8?~xTmPf0DekIN4<_rLG9)x5Ow*DQS1F64r23}8Jb3cE< ze2&-iCIZh?0{x$Q`F0Wba`XAhbKWkP`}dQ-FkhtnXt!NTmBey=pJ49aSGm)?i8l{V z&6~-&y{eQ#zkeU;0`peNf9BqY$106{3wh$0VFRse?%%h0z`U*U@!k8k^<~ksO6yz4 zt?vir;O&*?`G?u&9ppUkF`@$U{(YfugU;uUtG3@tR7Ad$dSbZoJT>njZ|>IjSS93p z$+;b~R)+VLk99qV&Hei`pPT#l9Tlj89{;|hH|G7-&+9TLsv`f9TyKP`!H3BCzPF$G zP&w!Gq4_ZR6*oy8tD|SQJg@W5%ty$NIj>m*`BCzY&VM!^C@=54Vol`z`<rljIy{w)$}Y zzRfx2lf8VB2FUyOXO1xU?^m4M5cw(U;W!sGf=`okoVy#tKaq2sJDb3d=y}KE?b4?3 z8D4&IGq``h;+W=e|NgyLE#WiO!*R}Q1)nYFI199f&y}}u^MA*Do;(4cr)`k;@2{KL z7QR4v?&mGq!57N;{EgopzDVB8E%&t}dD}N}H}rg|JkK-KHQylTb-i!RuX#P` zyQ9bV5#~3%{Iv+YXb<$<^zxrY;4yk4f7{Csjlh35|HaEU=!G8Nzc&BX%NOd6yzg7g z{rdnz`XIkW^U3{fw)tky&zW!XyiQ;A?3MR%`$^n>@cnXr9y}ZL@LumS^+*1I^1MFV z$=tvH>b&_OZcu4l2ie}C6W^Y4`B zc;1+wkn?j*_Ce_P@7HQ-ep>llZn>YBe=jfK{E)eS|Ka41(0^9>1)PV$aQ{BZqC?>R z{fc+YFQ|u~_va5q{*s*QTW=WrvV4$RZj6uN|7gGEaeiwA-f}qdSJlJ&i#|2KF2C)L zxBr>HQvV^h+}tD3^Hlj(E?;US{7-o_=X1jzLdyp4JHPyhp6D@RV|nKl(U$DmllKZ9F`UJZ6-z1xzqcCtvQCTW|vM zA#%PR^$U7<&(j^24^@7*>p3wIJsIV^u5o1&JhPnn2b1Ah<;?#z1sI9PPlab! zex36a)8HAE&+WXdc~0f|d3k_&F8Ok|-AhbIPab(=*R$L_r~0{{Uog+7Joo2;pP^CnUG$zMXyM^)#Q{j(L|O z@B1qAhUzcn`u{R-B{6Be!3tY6GzUtvTlwOVgHr_mpa{m52 z95HX_&BK4r-=Bx_YtY{=)%)`>!TckQhw~6?E%Kw~oQJ{Y!{wZZb3qRc{HgWjJjD3| z{zA@qs2cPPfwAi0JZv-X>%;(En zxqPCH$S;(KIG=33SkCSK*8Fohw|k3C=vgY~c3&Cv(7-K?liU5Q`5ifz``UU|s)yUX z@n-Z-@wWS9=kIU#Z_KA@p1GYLIp<>@2hwgq|1@v^Z(zPo<4NG=XTABCa*i|YSLoRw zXMU#nCeQDhZboIM|#ezW(Ml-y`P-il*lNeLKg@e^5W4FV%LT|GJ#dm!0N6$~n(*cjNhT zQO^rLU#gm4mWR0Uj}3b8eg^e$yFWJH;XPmS>_L8q_qy5Ne3#ZYuj}9JoR9f@dFK56 z<7A<)(eKY&EAu}!PR{cb^XGEzpQ-ku=Y{7x%>R;yy8Wm0KIC7@E4uOj8}#trf5zF5 z{43=-o*=EXH0 z?kAhg{rj5!4ti+dnAVHWi;`!te?G{=eo}OyMNF9XPo(e z8V~1ti*r6^&mHGHZn7uYB`mkG#*^DEXTEt$IiE+7E+gMm&i$fE&_e^8w7#5&ndV#M zoQIRv^Pze;5BaX3KT7KN=b?}D_vc}&c{IMg(a#h6-TC|TkmE=6M@#+wJoGe=sh-4c z9!{Hg)j0Y5O8pah;w#U2=p6LWzy_^vHP`c1(8GHjKka(>@43CwUPXUG^>aPCI_G2N zzc%l#aq{`qG^c?S@Ilixev{YX}R2vMeky{bClnw$Iio#y4$!{<}B2k7yAiFrlkx!kJ}c#7Z9 zQ(5`iZoL+pFVZ->I8XTy`KrpZzl!-1<>?J0$d55!rab5WD|7!owDablDbMZln|U2? zJgpvKob^0EAAwi;9eMvgv@Pb1yq;8#k@tOqxqlzmLGx7_Ki`)oe1e{Jaz0;rnXi}k zaPz<2e51U+yKZ~;5Ay~@be24P< zIXq{cBfm?Y%=J%r0pBC%b;G)U!4JtVxc-Gf5AStno8=EHpV8%Sn;)0+b5Q2L(f_^t z3zz@Y{HUBOxF_i0E%&_TTYKC6*-P|a@_JtW1NZaE{|EQ)ql^3oe%k99Z+=0}{rs!9 z$e)#S`~}{@ugEzM8~=k}lXKqo1)>H0ygY-OpS_XbH|71D7mo`6NzNNww?~I}(ERW? z_E}8$FUoVdOJl=t%Q^n-apAwpzjw>+6b~Lf#61jfLHEq>D__Y4y2MBRkvyH-52GZ2 zKbA8;&itADsoVeaB}Bdp7pMQPwpRqc-n^UgJU%}#f3ALh|7BPr^#3L2eU*>QU&@!n z2wSneiKB(PfBqeTM^6%dzF^QZ1bSztVE6y7`=G{#xGHdAy|Pc`FZfp3VGU zIX~A|GS`nK352-()S!p=yjy4a$o%Jlk}h8?8I~JW&i4tU%%jQsyZpNR6Ts3mf!>Q zB$4xVq`i4!IrH1i6L}sj6?#(0`G*qb$@owku8;W`?LU0K`rO>VpL0-Z^naipj&o`n zcq%!M8yV8V)5z<(d25vpKFM3|&*uL9nXS_!pH4koZod$CdO4RHBNQGg=W@$tfcy7h zmd*&D;f<$cCU{2maJi>4!!yab+?!e8S>#;q+-&e1^3rY|GG~Y9lJhwHr+FTE5tmP# z6Zw2{_UF$9FCgcBJKnsQJfk}vHp-2B3HdDNbMwGU$$1=zoEKh3&Oh`wFUN=a-eSvq z$XAf_xRfbByt16{=Z~0I^1MR<5K#}GF^sR|-rjWO-#2hD59`MG#S zA>?bx`T8{`_yd!J_X)_kT}~E(*OPO*bSwsMAm?`Zw>Z3+obzA3B)o;3^B<)&yp^1P z_``gK_Rl45eW#Q`zBOaPISKofp)9Hstg|_=RBOL0v{~rA4*n*59Pz)&vFd=b=rKGe2nwL)sX)} z^TzWhgUtQ=Z?~BbR}cIDGyh6GJzf9D)zQCAKFE2L8gM^9%6y0NEnWV#`3Nnyu=8Fu z!|(swe3yEPxqS0l$d6JF&u>gK_m>;JHu8JCo*w3Y|G5bAh3cSZjF!uJyJ0?A&iUz8 z7x}4j9vUp6@fwXURF9i{`WCdELB4tB)T4zS(-_pDNFB z?lqq;=W*kN`9k?9*MHC4{~Z0?e2((7T)spDjAyZYh4ankOXbJi`{9}mkzX$7`Rc*u zpUL@i_P#J*A?JzQpUuDK$A`fGB84q@cO&$l)qb+Tjc0aa_<1?!p=J~KO7(NO%gz0K zv!=-V_us~A2LDn0AGzi3Ghe0tMb5i5M}CcbhVx`C!hheGIq1RTgYvQ5`nEU!S`eSkn?)YR`bJhet*1t zd*qLJe#ZQ$oS&DQc0m4^e3+YuL*^&s{6nRV$bZL&I=<~UKPBgeE7l45zcoMHe}aNcH_U-o*Ibw&Q7=e4_s-*edfobs34Jk;-j{EzZ+&MWnVUzPKC ze%Jh({BxHN?S=de`EKW1dc$wZYdA077w-Su%YEjzmFN5Xg#D2Be;#Hz^Iw$bfiz$L z@b~BXK@aceSdKCO)$92yf}TPH!tehm=;8f*%gg5X)nCTVL*aqQ|0d`2;*t3yIk(HH zLC8OrzjDXVzdnNhA+P89R}6+flYin~zgiA~|0N&j@~MZypUe6F;iCCVIbWAs3`72v z=eNvX%lU`IA0z*k5B2;iWA5Kq-{1US<&(K_t~LKpp49m@^8lZ20lsc_ACBclmDhCh zcELQH`|U>{A6@y1uD`}ecuYCZOQabEk1cQL@(<1Z``f3EM&7@V{l0lz_2hIt<;Nf& zU(OZZZ=TTe(qobL?r%rnXP`Q^HK z6Wuq(>$f?l;&nWUJ{NO-h6O$NbI#K+7u(IldGluK;p=$FG~}D5;bZ1MGH;*EwGh^x%1Q^|yBZ+BqL{JRLp>%Xf9Tc<%SJ=IzzP^~gU1 z`S#v=Eq2c9RqTm26Zt$EKj*)#d1v+WeaqCKhj)H*h2^^{&+(ix_wRepFbne&L-WJ! zI3(z}PyYSwc-g$C*Pm-P@;$xvonl@<>&5NzPtb!u$4txRc4<2YJ^j_s?eM*GKIV2Q zF*hv#{&v}DK2SZJhvc6kKhPWJMCb2smsjRRG=6TEM)S}!ME%?@qs)iNx!m>U{{8lm z=VKn;@NJdOYc&sg@cO$q|GUjcdi@C&AV1PuuU_UQG=9$iji84H{?T$d|5X;EXT173 z-&>vYG3P(SqOkn?^FPmgqIx(FfyKyA^v2oG`TO(#tNCR0a79utL67eZg1#c~4No8G z=ie@~d@25OU6;wS6g@N4&+o~$i@=wf&rzO*KSkh4mti~yyzz7ldhk3a|GCEV+`OEe z`&*gi$ou#0PYil!;Gwo-HFsP(D8tX4nwmt)TPnEhE+gyrAgf2NqPRS(DYt8+ePPvw>9S)1nl{b!wdHH|-k zo1efc+~x|lcB__=>x4ti+ds+P3fU=(*$VpI6N9s)s95aRc(r`7lt&?YG0s?|J!q<}H=) z@A8Q^qUV9~{JutI^QUrt&Khd|P~O7zd}02(oa4D_{>1Y*o3LE}{{K(S|5KhDsO4tl zKh*en+&W?&iEsY`{2VlK3-WE0zvMpG95jEY{%mgidA>rvo$~3N|6txh&gVt@t;lzh z^Zxur=3V5>$KQs$|9yd;=Kl8uo}2sM7wEDbJzdq$`FUdAU7o{@zsU~dd&@(eKQiwl zPwaf;PUQQ_IsO{E;63H+KW{!z&f`Od-Nkt=;mv=$ z2>hc6dS;vZ_uJnvAFg@hcJJ{G#xqjB$n9?@gB}`K;mzj@^HuT?mrr*9J)_mr!TAvL z(rG!LoS*g1`@38`_xnxrvYH>w>p}FF_2#*h^Y`buuepD}{bTbg>gRd;{D;tARnGZY zZ9Y}&#plse^XkfTzb^7E=Iw~)rADtJLhBe{O0`q z`D}IyJss4O*zKRw%sa}5I?r|*`Ob13uYw-l@v3bE`BCQn_Y}^WFV}qXdDrd?mixJ! z`}5(ThXxjS^ZcjzB6)}#f5=(%tW*z=OC!yDddH<5&ilK6&;9wad2h`F=R3_g^!N7m z=LXK-pZ}@m{`U(Ko=49*jWeHH{w8z(`vU)&``-(w{{wpb?*$w;U$6e2uD`_vda_-DZ&W{fhM4d2Ji}$=cgs(>o@7_xP`6=b+xaF3=hP?m%f&u1dl;|w@>|STE6?*h*Mc4zD5~?2_1to!{tPcJ4{@F+=;0l& zibdelUx1g^Qy zN6T-+e^S1NTd(AI;Me5be||9kS>D6tOaFrWE%_4X|CryAH+9$D-rPlguhuu4%h&rA zK11W=eiCvo{QMyE{a(*^<}YPk#)Gr!M=0pc?|eJ%JY27nwaoca_;|K zZKOx-z9!KWH@OSE&;_|hV!vB-=ee(C_kwRQ7!27BCCqq7p zJe3RlX&zP1d48Q7`QJ27E~sh>cr4}V8_fOuEAtr2^YdWml<3*7;}ZX{!#u9?Jf27X z0Qop_9)|}8Jv4AsuSa|h+GP3o%JcZ}#yp{XNFeN2k5uSSEPvtrl=&Z;2hLCR)X4jO z!aS*ZI=kb1-!#Z4mtS)Jhj~hQFSp!|X^~GQZ{z&Bc^Y|3=XKH}pH7~~d5I8sh8{hgNzMLvUku=AtlS>){hG6VA2=krVRs`Al+uwQ?eSC{koH7s}Z zaNnoR4SMi=i1NJ8GglsXZ8?v7ea!2~>j%PqO*gM6f9(7h^M>-mF~a^LBrp0K%XvJh zWFCW0{{Veq1pe5(sd`3+)dxD|L%;t$qkZPhm1qBBbN~BCIrF2ZrSc10|0r|+dr61Q zKUALcmbUaa zoTBh-qX`7{sk z{qsLT&k*RN<#IuL%E0@{%LT%IO(_c>AiwQ=OF6jzy`ISB;e(Xt`ZhN&#Kmd9JsyD< ztbm>(UVc#o{-60^EtmgrPDS(#mGgL*z7qUndE!9Wuf^sgJx@>>`Qh>mEBj<+rE$DnLc2WE7WdxqDDtgALXR-T7)vCcK$hp4ptHUSBIsXgHr^tDH{%X;cMmGzOUQBzmRkLZf^(wQqG>8?cr^?IDL=l zxp@cA>vllCv*(FA!Z)a&f%E2@Cdy^jUy>f2%o;~6F2zo--`Tu__A3?sk`FQnkevX)*)Hu7ivSI_UTtC0k ze4^JAG7x#+mz(?FBg;DoufNl{x%K+HKj`7D*QKC`_r0@pAHl!Z_)EF*cQHRL=YX%6 zpY=S;VDy}qb3dtQ{)3! z|515b4JTt#8ujD-4P>kniIp3#kG`}h5eqMeU^0(xlxSreQcjPUcxA_?P zyK-)qY{TLH_sc#GdU)G?p5^bW=Yi}0C4&5G%YVtut>aEa(&-ozEk{p3N@Zh1+{Yvb%;~ewI^7ZcT3r$8osyw^%Vdl~0D_qal<}u|jBZvKi zH|A%!I)PNKXTwzV``&Uo{H*dRT)yj!@O{uM_&Mb-xqQ^w@HiR|x8oS|*m7?7m*(;0 z%rBjTp7?TZ_uJ+P<$T{#elGI<_w1IM``?eNH4pj3>f!M=$^y9mJ-dX9;7OHl>()2Z z5_obs$G^-xg*?B@&tHoC2XaneqGj+@@|rH+)ZG6b+(z>>%Cl$ga`dG0{GIuI#sXzr z|A(I;AEG?>+f1LsGsrpqI4j_pKl5=W%brdgSxV3%Gf! zu>oFC&gaE7^TKk@TZfIv7m;&A>@_bY=luU?9+$BI-w(9dgr4HcbH6%ZUefczo00dw z2e;6?wDO#XKh4X?`F^SG7WDYvk4y0tyqxmvIb~kn^W&M@H6JQlrQG;ZzAyQJF#5<`(@+J^Qni+y%m9%*oB@#%5%BhBJhRg z{`aT;GB2hcF1Oxp^!vUs=%Imq1>I`uz4v|dJ)URYgPwBgZ|eSGKl2K5F87-VJl5Cf zsiHhLXh;NJ)VvyF`hV^Bnb(kab>sOX=%Ilv8h=XXA$zghI?B&;zQ(+soIO{A9vWEd z^~BwWp1K-8w^!DnhxhrVR0R2^=Jmav2@&{qbN_oK@%Ljqjn%{X?-qd{HTUngFa8aB z8fhM~xcS*{-b9|nd8q@)H}(9WxqttB!-L58*El(ELn82H=Kg*1Pt8A457)Q+A@ut` z*L;|lzZrpN{}w&Xv|gLt`c4XZXy6OJJn84X-?sc%^|yBU5{J<+%S5KcDS8?4%N-nnA2y^wRS&m&oYTm!_4dO)&fnh;FPpc~_=iLao4CGb(9=%N<4NhW zaQ{BoAIvvtJaydscRYuDd-bGqe%^eG@_gU;#d+kn%DKH_{Q%!4XMU=AN3Z{Y`3~h9 zx#d>BfSx|eCvbk-ysw=7!!IJ=-}980-~&D1Vg8Zl^)4g7Pva@)mb*6s&vXU(ZPPf{>*f1K;K$96D9`g1$$vtR@14z$D$o6AX9OPgDtd-!-ngG+ zGxzTwu4(SyKitoJsCxMN_`SJ*-*1U)SnkKlmv-A_z4-|FKhBF^N8Z1`_e1kh%5%A& zn2+)NzPW$@>ZKd#pP>ABx7<2E!zanJM-KaobLLazJP%g(Ch}9|!T;vKew{X-E??vN zd)-3*UWj`b;PyIgK1+G7SF+od*Y@S}sGj+K&!?HsQBO*@ptI)weU;_!px?iL@|wAS zpX7jFkoWJ0?0OeISIgzV>ii1#?~`nM4?a(MuGh%>aQ}YDr4QiqmCxhW>*#Org>t?S zym$y-Ea&^%eUIV(eTE~S!e4XPx{vYEpYWyX;qxWNU+|T3e(sL_H~e!sk8kl_!k2rV z=pXnhIoGSqEBG2Y&!4P&4fpR?%=`xah4S^>{HzXoc<;mSTK-Gr+5hod^lXrqaXk;s zH_2N$@A3}$E%IK@Z<=qFw{hO&U*xyTn>j!KAAF~r^Kc{(Bj`~=TukR(D@1}vmGgDE zW@Pwo_3(A=toa@}kMrrHAir16I5FxS&92J(mHJdbh2{FHpH%U6hr{24j7%g$Kv zb8_}qi5(-{{c3E`gU4;ykiRU?Px2csf7tw* z{Dt!j@sPhE=W%tJ`AzvCm+zer`P*{tC!-R59G<5=S&KJDCg_=3-c#( z?w|LPA^(S*&-0~ek$)}cdW}g3e=ASqUT5p2hyN?*arHa%|Kyy9d!fij z;$b#W(Ty`wCU_J%*DG&kcr-b;WA&`?7;=WoqR zsE5bT&Uuh85yHpJUornc^{fnt7<4&#&V7kNde?D%XvKcq!2u-ob$h~2)t^D8%AKX`@@r< zhxd9~s3`K;y`B~3Ipy3y`^|I9xu3r>&m-^XmRqP8`t!*LIB#p-AjCZk@Ob-41ish2 zfOcRS#_H#O-qE~>@*Mw4^P=*Rt|xm5^c0u#{lh-<=3f6}^ODN* zeMPyF=qW32?fPe%mzHz?zhhor&f~zKQs}8Dui<(UmWEfBbAM=VURBQ5-(SsZ%DH`u zmO)Q-Ip?jtd2Knz^Ot#DImh!!S@hJGbGgaO!5hliles*+v79|6E5Mt|*;BqEyt$mm ztNrFJy>yj3ma`^q_<%jW$( zPgDo_fpX60=XK#9$vK}5>cfY~Ii8LU;6vq{pPmijAImupI~u`9$T<&(8pB7)IS>7s z!pF$D+;+|2W93}m#?9ehdfT^Y3;1|1-=Zaag6AK!f=`ljesX>YpCae{v~3NaCg(W+ zHlHr%{Q#reAU{LS`Kj3!K14^Lic^9{zZ#7>e=O1!)LjI(d%lr!S@8x_Qd1Stf|6KQv73z$h&*WUM zW#%h9kJ|Uh4{0rqPIlmr3zD`&4Tncfqz+v}?%jT=p!#9d;yCJ_u&i&+y z`IqwMuBSTj?pApd=LOBL zhPa1;&h8J*%(pAg&y};xcghdAe1@Lr-!1RwJa#YmJ~=;!&NkogdD7m-mlJ)_|Gm6`%g^Zt zKPqRwYk&A@`7W1FHUNHH&h_eO{xrnJ0=%Dap8210{=?z}(eqr+d01lpm*?-y&+?xK zYP#2>>x0m9Ue4!T@sHpaJRf3yMb6{QQ}dtX{CtpaFnX@Zzl{>Mh8@js$oqtG;BWJr z^0Cgd4MERs`9kNd&3}>e^J(^>$p0$m{(Qy!zURG%A^)44?~^x}Ka!7g^Y*LxV>!2b z)sN8=kB5ukpTmAVG57D6%Re0XCyWL7zIVF$b2<077v_J-Te|)@qtNrx^U-7AujF@J ze!^JzYdOc?YaINI=RcXhm9yv6c;pxJ@cHuEyqKKFjj~hF6HPswpHIxA%eh`p z%wx)VJgGSqJ+bB74;Px3(Q;e3?Gj@e@^O{V>HLU!e0gu@>!u^0P|oqh`vmUa&(q30 zvGN?x74wwxMXtZZ4D=+G^Lafv=%IlD+%0vzey{l;d5G(IY(1&e)4_S|ndq+(%Fp?+ zolkV$-#w1!e*f0ImgX(5^S_+)F?({)LVvAL9;evT)jUMw;XK?m&m!kMRGW>S406uH zf}n>6x@djZx$%5&-c26j{GIh=Q%?uyZRViAo;MGRoWDO0m(3e^^AKw;dK!ejKMxh1 zzdsM7%v-9boEuNzQ}pE1I1@W>Z|+}rerKLvdH!L-JoFUi!$5i0lXpJ6xSUV?!{#OB zd|wo40rKVLFI>+=^NMozq+E!6CC}%X7nPTFJy{kZUr^5D!%Fiia?Zmg^QxZ5S&W{} zn$J3}f4O;AIgev$mmuF=&i;|+J>*=k21}9eCFk|=pUrD&JUkAxUWWV-<>_P1{p+XC z&GV?Ipc{Yg<=D^L6>tv&#oQkj20eJ+m+~C{pXM96nt{JvzQTsI>$AhmTgrn! zM#g?6SdF}Yz4CAK50&S3JiP{a|9a)4weU8|^K)h5b?|m_PHdSk;T`1h-8__B5AP;V z;k?X7ct<(6SIJHAPIB%i9X7+ecs^|lyu19R>yNPw?q4T8w*%f&d5-_+E_iP_#}juq zypNpkiyE5umDhI5-E7`p&ivx9(KA3^-sNNMhYyl--Zq zxogcQD$mce8IL1BSzf@6f3Epdc{1nGPar={&i%Zf`Am6Jmp^CjUr)?_633Y~T%3N6 z?%1H?`XgU=W4oST%#Uil__|l>JLHdguY*g?XKOt3Tu-&{k)JE)`;kdO5B?kj9tL&Y z;h5zYD9`g7MNgq;k-VJiUt{iH_bhT6`6bG8J65;Y8|6F?Fz5&5H+dfC z0(`TafB4LN3m*pfc{%Y#o$tm7tO)$dSRkhB-(Y^v^FMp$duZ(*K9`TcGlW<=nJ&66q5{wTpcHABGnO6Dn)=kb4=`E8A#$BmAW z(DRF&pTB+$dT5|A4`2GcSs*gJsXWAue{|4;=YQ1G!TCG$)ERhu=j%v=D9ESI(BI|a zx!=DuPpk3s_2rdwK4wqtsOU+X;r-89tIZ#2JUlLKj*k5Aa=tIk7YqJG&iyb`9Jqh| zx2*Y7<@r8gs`+#IP`6$W%>C=XN#dgaFXb1w`~dR;nupEKSDO3RdH-kbU(YQXFJ|!g zc96Th{=b{gf*w2$Xt~@UD#wSvl@D~wU2a}d(MWU@y#L%lW(> zkr?^7p6@XCuOpXDf_!}C6S(D$G54<{Z!}Nf<^M2GDChc?Ns9iYa*qFud2%_&-#8ia z{&nY@=Bbot&!gnXr;)SgX$rW1-TCj7@N~*2cI#X719(O`dlILDhk710H9WnXuM6Lr zXO@?9{p->o-&Xqx`vYm=S(Q)e^3%+-$vMtf=AG5U*OAKU(34$xE_a!^pMPYYQ~5k@ zx&6|kC%2rhU$aBtdF33>JM%(vu2vh1qj^_mnAYa$>)#mj)&ruNhhH{R7s(F1m zcgQ#9{&nHbh0x<)&;820v3htO`+w$5<(&VTh0)X8^RLWX%165Wxo{C2SEuQ7SwiRS zgC4wZnZpiLa(=-4L-iDK{>;3Moa0GX6g~d+**xa$luzP%nwb07VJDlnSH6JD?=$Zp zFY5fhc_%qv2a^=Tc)G~>I@r;?hdhnz*=yci&T+;qj-GCE?pJNhd&)V1znJ%yCyNxe zfX_>yr*u}gxBx$g{?Ght{&W35=@&~P-&Z~S+}*Pjw&Ne#j(ol>2zv1QJIeF*>#X@9 z_4D;QYH8&C>%Updk15afon$^h{q#rX13k}BCT6(T-wOmi__;>?h24aVHutX=*DQl$T^)Jf>zRL|er~TV z=CkG859ilJ&m4I+x4y?~!{^JHk6jPGK+gQ)2Jj{F0&f1(HH0sfKZ_Ig7om;dpUXdU zCm@!Zua>hXeq-d<$l3Fs`ImBzb72$YqjI|Ry_jd_>%DyarpRxQ^B;a?9+MCC^FZP^ zLq4{g`(aD-P3qz6Z;a;1Z;|sjcEWtCoa0a40{Ia@_`KVn%4-JgqbOrc1 zB1%j2B+bYiKR-4#Pp%$*zFgy+kD33U`5ujDv)jI{TVc6FwcK^i4+K5#c`_9{irQ z){FC*uq}Erc=OrGJd-!h9nRk$XRLPUIi&G$KKlecG|)@S<$RvEd=B+<{tC25PmYXy z%z0Ss{Qdci+5!1o>fyLLJLhBeoOS;Gd=~479{>8?0CWHP>>KmLTHge2`%dhH9{+l1 zq^_ixSp>(o_xV*hE#)zS6SeL)Yt?rWSJf5~3( zvvMAn)|;P~r*rEYwKwwBG|oEC7ns+O^98F*ALK8nhx57D{G$A|>&ep>`OESF&KH{h zDChnY=!d+2-SbEDpOokOy?6bQzbfZ=A`OIJlk+@kv_WwH`ew|*@EgiEa^rkzep6o6 z`OqQA-;y)mZYcbYobS(HoBt~3dA!@hkpD%_c|QIz{GR8VhQlAo*|T^g{5Q|1kA^># zCv)Tf$NZ6;?>}RWL;kUxpT9br`_~^wj7R>7@|@?(6X1Wy3%TXCngo9)=YE)JGW@xG zxXXWP?q6q2GzIy;lxNRS^G~$j@^kn42>iOaf1R=TRP?`6Ki>~rH-9Z(3@`>bp-5+M|U)Q@7^w2<6&3`r5lY0(&)_Je{pO~-LcsPF#obxf~ zv+7**B++>0x&9aC$>iLg1wO@i#(JMy`vyJu^8=OV=kYl6;Hl(%eM~bSo<<(a&3~T- z@N{x+uYL>R{`JHoi{R;%Pwd8@YzaI>&ix_7Qg{YA&+Be7_pkRYTaJ7t<@q{wD)<2* z`18R$f3O0cO&-&&SM*izT=HCQoF!Jn^T~O9YrO`ZUw+s13|R}$Bj@q<(=Xr!wDO|n4J56lP$=Xkn=cMcPqSa_(>E4#2C+*kPb+e4JbEk3kP!x6<*A z=U4NbMgEog`Fc7(=%InE%I9-E?;^-|I)|Ru>fwBzG54?cWjT+06OFT&>t7r6&_JlS z-20Yqraa$gHv9oS0li*x02|DkE1$siC%k}sWaasJp|yDnFMq^5y7FAFxEImmdt>vK z>f!U^jQNLhKEGmILQj14bDY)96L>z&Jh7bPzYu|ca2frrwOo#8rg=*BaJ?E{LEiTi zKf>F3J+Xg+x0iGOX=&b3&h@=!-pTW9SJBg1&T&pO?t~(t6oD-PB~wf z&zg5r4?h?5xsH4f`4!g_^#)mpnnGcn7o-^G=ewduwWvaP9DzU_MRG{b#eee?90w^G}r5`u>LH&XjYx z6U=AJ`TBCpe2$#g9r8Uy&!_ShZv5NK=gYbOM0te#0y&@WbIki|9(Wx4&U}&b-0sVM zM~{EqWY}Z)Qsw#cvL2Z)mvbI+K0*F-&&QaL)Oh%OiS!5ZzK=0qsUD7Vm-#ArE%!Pb zc#57i@*!^C@|%Alj}tlUFBX`8DIe_er_49VxxFGiL%)ALrGoiJ<$JlFwdR}Ud!2vt zCwjh;^L=K>bNDtn_s<{A{p%)^Um(9jd2Zj9f5X3)b3ZBg5B#9#asCJYR?heNcVENL z$=S2-E&RIYbN_`u@VrMLR?uH~UNJKKf1am~3V$Q#dew{$*Vof@AK>Vi@aUXI`Oetz zm~yVyqqy++a;{hU1n{JuH%bIg<@uB(@DR@rB!g%1{6z}5f1PM>N_aNqC%FA2-Usj; z^5)J5n&*gB+uf!OICOlIeYqNgIALe zcKHE0;5Fp`y770*1+OJ%&%oU9I-ZZp1Fz@#)V%Np^5d?5M1FW9Ir}FUfH#qIJ2ou@ zZzkt{Ual~_g}jbiuLMQlt>k<^aHJ@_wVeHPio@H=+25xGyuF}g#UK3v|yZI}O(QC&B0p7m?w@Jv!KZtkygqz}=Ls6XXL%m8A$*SKks852_58K@ ze9vDrMt-5^kDI{#>)kb*!j~$~_ksVIe=e`(_QM^`kl&{FGyJ@KH3I+F+`q0}uQ_^l zsfXuhRz~1|neSDe%T3h+J-(MS_pkrXH9w#pF86)}Uc4px{p*-V&Hd|@bz337Qu9{R z&D*c${`Jb?A0oe6d49f#(;B`;&OfX+_pd*`Hutah4Qqp*FZj>(KH;+YmvV03@@_d1N`R!o^MAYy z8xNTu()f9OXJ8-XqpDx?X?|Gwa&DZN`yzi-&hNvnH9skD$ue`hb30CS-rwcox!=#2r_lJh{o{Uwo)nq+ zm_7BKzrQ~$G{2(p)OP)G2BYUEd1B|^m|vB1KN&Lw`D=2{+gbB;8YlDTha!JNd0yZ7 zaTxq(IhR{#IQ*tO)QzXm2>5Mz4(DY?!ZT^P^q!;Ozj*nd#=!mSR)39y``4|KOo0EY zo;hy0l_$de>qpHd!GBYp$H^&^;Sc2;=bkC>-{st%KTU<-m-BTh+I096IrpPPpTM8W zd44JSOJeCujYK=N_aeZeCM}T!Q;#8IxnyW zo=~17O4wiYUJFkw=g;T)?hANQ`Dd;t=Q_B5on?c0bIo%CmrwX5^8R&@LFUQT&-s65 zp2G8P>(P_a^EMmcAIN#!IAHFdpKr4f`BcjD_;bxXjhyGNvTQ;=ot)!rYwn-7AHEs+ z^vd(|X_75)|NQ(m^HAmgcJr3^E95iExgGnO`{&{Jm}gf057$#|D|)iZxxQn}{qy!s zwjrNgc|Lzvo9C2s9^!0AK9`)^y}r4B-hRHhf8PE(^W5rTf65)`AFKU}^Eo8~e;xD{ zfkTDd>g)T1qjsVvkNWv~RACpqkbJ-UhkMM6$g4WPZ(dAZ$9c5f=qVxR_{*4=lpk>U zv*x*ZSPSfM{&Ej`$|ye~a@b#V{u*9Z&i>iv<>cJH=glj~dBCi{7d@5aoQI9(RpjYn zge^DLKICi2Z@Y00H?Jk<=eU^rk*_0f=JKV@>&ZFJ{^kwjYh3=Mc_aA;&eMK_{>Ji9 zcN}YR0Nzy2`T54YnVb_o>>%c^OFM9cR5qPg3k-zNae~G}G{)GIG z%5#2hn0MFsInE|m!_Qwgzp5V2PnT=R`~JebtNJ-V$F3v)vwFDP@i)Tvia*0|dHMYj zc;TDK-|_O>Bk=sUkiV-u=jWh#Z;hYhtZ+O0{CV?x>f!u!x`Vv$ug!a^pYwD17vvwR zhvSL#Yxth~9{hLZInD(4!}mYUpLqE*50Ll$%irKnmFIZoJ%sny__^F!kHXIn{~i9P zdN@CQ9*6JkpTPU7pY!wSAMn4`!}0tZfp>k1{6Aj)Y6M>68S(?Yz$Y5svnRi4X@@+y3PWIn`OZi3gy z5A%GBd2ID_9=?2oyzi^t!s9B>@oab(zHk2*KHOXG|Nes~R1cRsFAzIi&mRe%Sa~k@ zdIUZpGV&w6q9N~lrs(j|-g48#fTvLpmzzFj_?{sa zJe~4fZo$~$d!;z=vEFj$#)W5450{%XUid!GJd^TVZl?Ii`@Y?Lytmv*36P&C=jXl) z=9A=g-TTHq36Y=T`9||;a`wbcg#2_luQzowpCM=dqWMfY^Cc3aXSSTjr7Pxh<$PZ{ zItlXg1pAs>^lZ{U_Y*?hB{ z%iWR%`LE<$?)7Z&ZE`NRcXs#=IhWfv2Yi>D+v~9TZutp!-uq2Xx!j{|kxwA!a_h8%Cy{fxm&_B%x!mUM zkxwS)a-(*Dr;u~GA9RF&Am?&7nx~a>xg|RxpIXl4erKLu&UwDj8Tn8-ms`0jJfob; zEz%91RnFyR><-T?=W-MDfM=I;edn9!lykY0dLo}&&gH%|&oAe4+x0>|ubj(0WnNIu z=VP8e$QSl}LSJ}M&jw*v|Wo3CnkNkK?)D@0njx4?jmY z7>oR+EPTwKW6s~dE?i<9^7S=-&i^;&4di@&#T<{ke;qdQ1b8FmIsb(w!W(s*9 zm$~q^a?a0g^Y(Ji=gm)%?;z*<$;tEJo#lhvI1kT{XlsHd7+Zn=%{(aLlC_Sys=Bj-fytnJiXSJ&!fUSk)NqNk2mAZ zXUn-Fd(G#{ySs6w-i4ld^103@nETgBkDD)0p5uRQo=f9Q!0oad+jnR|_fW_D9YGJ? zf37^QmlfZGd`{)LKYSDP@bVSDh8Iu|=i!98fBmuaUgQ^P{Jg$)E$E?v?A~&V?n8cw z@|@3Y=F8;V-_DvZmsfHto^L;TK9?tVUf+C$e4z8~=4<5qzHq8<(6d^e#N|hruaa{- zcg+2t7gyo{de(aRVdh`R%eelp%)gZL|H+DR5Iq~@72I{zk>;D_gPmVC|4Po!%M}ix zXPbPVTkc5n9r6b5dGv$%E_rn4Ilo2E9{ICqVMP|5nb|`QzqChhD#znAlkN4lfvIVtCHY?t|I zc^B7{{220Q_N@I5dH*`> z^zY$UlxNS-Q}CbU>^W|J)$>EAk@v4ZZaV|Nrab#MoQ26L$1T0c%I@0{0}+5mvqtGKQEo|XXKwL z&+kEOGWX9LH@%7cbLIKGcxmpR2Yz-7`IpKU2!#FGa2x)YoS&m#-GRT7bGtnI1^%!6 zfa|$-7aript^kier+%o>+M<_qusf`B1lB#h;)jxtz=0Zk|%k8lK8O3~nR~u~hbYhcbDI4HPbX*3@xS32EAx4uu!^T;{QnD3DH&tso9FQj~J*VFi4w79Pyn>wlGo!$($=P2yD!hiA{VAfs zYsuN)I6AzJoc)7iz#GZAy{5&4H<7ddi&*exa`xwq1NYB+Up8;0d{H;gCF3IBLe6>4 z7Z2WA&T*bM?;z(qw2zN`CppKNFaf-aoa4-q5Z*)1))ReK2px{Crkw&?s?bL@bPkv zzk3?^L^;QqBrSZh=iAMv$~pc{>5!i$=lHv&hkqjH`1^*yXUqA1uTm&{rsrugz~{=p zcJr{@e1V+vkUkUg^E_Xj8NN);dAOJb{+XQH@yo366>`qc>}>E=a?Zo#?C{lc&cme~ z@U?QzL&jY2^>U8CN^bZ%&wJ&8eu}{!4}7$K)J;<09}Aa*qEu^Y7#wf8V0We=q0w zyB33=mUH|aio?&#IsPXl;AcJmxD@<~oa5hK8vc`<nrYAJv4v^1MbZ_-oI1*M`6K{8}COzn(|02iM!t0LMAJ zK0GpCR^;Owz$@$hZ8~>cdTCz8^X?7f=-iQu+HYHe9^QHH9~!~^^WGJj!lP=+QuK#8ycv5+4=iNKQlgasWdy;p9r;t~5 z`F!2sspZ?8PwfH!K)%!YuRY;u<<;oD;ORZT*Bc%x@9OfG`oJe>o_XG5U0--+<@tVT zN2#=OIs;=TyFqTW+R-$ouCBCz|I~ zKEBI8H_s>MdGBh2(6dSNkjmwUM&Rqs3##XiJD-;8BlHxObARY+UPR76j5qhsn;kMQ z##o?~8_!Gg5^{d-n>!fGEhXo1q^D4`y!#~vcvqugqn(_(UKi~5440s#mxgUNs6W&hF=li%> z@b+^4{~GUR!#m3PK4IM)cnrPHa^4!x4c{Yw3XiFL1vd}NBJg7KkdLi=6_<}TKYYJn z?w|kpY60@yHU69Ky!y0(0&5E4j8z=CDnB%17msm;9=|4Sa@j$*-<+!B15#aXyi`J)YM04(Lx;{X540hnSzCTW3#&@G*BozpLt{epsA>pLh@SyQyBrm;CpF zU!eX={qR0>dwlQu`=FntdU?KT?+3qFx#U;z1K^h^m;AcqLGWSfkHlx|hrIcf%!jL9 z;*^frHx`3TiZe5&R{Z}YVu0UxD$iO++~uT=jfJ}r+zKU(!782K3Zf8?)Pp4WdI z{2JvFpFf#jt6btUVFC1|YG2}0_M|s2ehR!y_42&uJ?+i6c?P^(_42$AeAb)q@ErJf z)ywm~`}5#8ssHl4m%jkMkLpFx^dk5z@>h+|IWK|Vrd;Cl^vmG4E0_2*y$XJZa(Pal zV}78fn`fTKtk`kom95B@Q<;|3mpqdKl=xIn}4zd zysO%m`~5hhJMlG@OTNF?1$=eolJ9SJ1z%gaJa>Pr2ELAR$@lM92k)+2^0E7x;Oi;x z(Z#v*=C#0kDi{6CwZS)3F8ZIlfp4N*;y-;I@J*FV{5M_~{6>wN#Q!qpo2y>pzh!rC z{ngBGRyz{^!u6oH`6tY`RQnSD>FYz^SGmN0n;zhOluP^{V%}f5wC~e5fPR2-iT@fs z!3Qdrcs{~>JLU2mJ-H$DgO!W^y{|TEw zpRIa{|2xcWeN%7f=c^rwfA3Ab`NPa})xN}k(q_=_s{C^E0B*23c)oIp|2*cqDVO-q z+yeSNluP_K*%Ev&q=0zotLukOO^#a*6-@%$t=<`JbK(eMGs$e+l!La*5~cJm_1LFEG#DY5CyOl#AY10DhQq z(Qmyg_)*HgGB}wNqr`utO2}S;~(#eCBBIbCmyJ_@~9- z=P4g*_^2`97b%zV?di-fvG`MCq2ECL%rSPhDg(b%_3Imc)Hv|Vl=m_G$8zw?mG5DA zpYh zXX*0-;P!l3{vqJkTl(T4_zlVrGVQ`#<~J&T%kV9$pubu9D05#m%x|^$ZOrE>Kg#Gg ztA?Gsl*|1tWbV`WlpB3R2>QEKpKJK%%Z@Qa z>^!P`6JzI5=JSY=yi#dc@@gxZnvc>(h$l}p@SVg8iG zPn!h$Pg^`Z8T=XLzZgF+ZUBElx!Av}5&T8v_Zt0{Veprgi+*@B_$$h#|2*$d@P*1n zABup#pz%lZ|-ZYbHI<$I7|G$VcuQ!!aqJ2`r}kD`SRg;;Onbi^5vcL!8cGY zar^TEaC`n`hgsk~RWETn>>}`9$`={`D=r4N=UIMWzOm~2n7A#x1p3~}#m;4yg4^>f zgD(T$O!beMbbnxej>bXabNJ=ZZ=rhOzcW8i^%A#PS3tj&>LqSDv%$AkE^+&Xxjhf^ z)|JrrRlUURgR8(V({#n3=zqZbt6umY%&$w*SHaUuqKj+^Ho~c~QPwj2s_W1ux%>AmDeoo`<(BG~8%YD7gJX`g`E9OFfuj=J~-(a4r zdb!{I?|?pEx!ms(=Jt60lk=c2P`%vm9e08cQ7-Ry*W3lZyK<@Lzr7pW9=Ct<9`HR? zFXNvt-3vZcdAWJMZn+QK9#_BWe(+(cm-t-z0C=JDiN?<22f;@w-^K739|A8@evsi0 zJ`7&0e7NCP&j%l)T8hz0d;QK0< z`0ug+e1GK<|Ar^Q?Q#39o&rBW^%DPgm>;BE;(zng&1FMv;0F8MY1Mes)Dl3!mk4=Wdc=DY;`p~@w{nqCHv zDi?kJE8r~_@A@kE6pKI0e428}_w!$a{&3}D|B%BM(aLu=<>46S$0#3R z_{+?XRW9Y^lQ&@Jc;!+~O5Xy{(0-2J*eQG){6y7DIVpMv{3PX4PDU*PUt8_SI9UC= z;4@S&&uREQ@Kcpb`C0Hj`02_eJ}-O#eui?%_jf-8KTElktFJ!-KgZ&KeGGn{#n=55 z`~r(_{u%fz%T;MZFGjBmlOxA^Suz~?BJ@;3K-@SBuN`KkK>{1)X>equj@Z?5?+<>v?Hx2ayr z&rv@?KUcYwpRIoex91T)Wj;^!Qhs8;K!2BVDL>DdnP{@;CI)SbXq5;Lln7 zJ?1Z1{G27wzhv=|8S4>$MY)uRKbgO#T*|{eU7%m6T*|}qUBT^nlL@PVzo~jD4_`2U zTe*~nb61Dnp4Vtx1ALL{r97;@Cir{Gr96DY`~&3@&)I81|B-Sj4+pFb{)xpm?gsvu z#b02)*y1zRf&NR24_g=fYm5KQ{9B9P)gAipl}ovrv>y16%H_VcS|9vpUiZ>C(zZI}MwTUdOvZNRs(xNiXX))p_? z7QCOug9E|0vG}y@z_+#dS=)ner(DYIl{xF$;W%#jnT)-`(Q(<$&+0T*~vp zT=1dFr92;+2R=->l;_HP@H(sgeT#Xa>ZLp%QULu(Db3{4#(aObstOEQfiZd_V?che7bV6KXG616P1hoefI-DLAltUus`?=`UGC>Q&y1;Ni&F7_8OKTr8_ru^ho zL4SdAneUrX4Suomz0C9SbqM@Y<>whYrM2L*l*=E^uLHka{;K2cZ`Olfp;mcn!vACzK^jJ2!mg%T;loKq2Sjk z|HJ63V&FF@zuoXRTETBr{-}xD{!_qjRzAb%uW`8gFY*75`K_v#d>lCycIAzrpyV~z#(p`HR?A)PT?C-$*cC~Y$(ci+{o}V3j7~4_3%%7gce4cU{-}sgJoyw0g z_76H7cJ5YQYxon)?^Q16{LDN8`umk1YV=c%1blpCIExz`#;0r9ii1||%zx_DqpRxE!$AdqoT>L!q1n}pT%K-9aCxXAIT>QUr zI{3>LzxO2YSCxzX7fuF$O}W@#JOg~8aEMejK5i!XdlsL3 z2KWaSKk-cPkCco5{m%mbSh@K581qjpUUN3|pIdzGbHKl__zldzvUth4(0^m`ZL!uJm|kyE`IKLKKPFo|AP6?7C-L-=zq2NUKfJ@Zt*Xf|7r2_XF>nB#fM%5zQp2R zG4CS7n51$3x!_{xS5q$cJMR+k)s;)RdgD^?HI<84O&6P_#c;WT1 zv!(JiOgnPO4d8v0x0v{EItRS3@_~lG%-mkj@WhSK_gB5t|Fdrb-$uE-r+j!b__oTW zU3l*n@a>e#xsEU13ciE#e#W0!w}IR1BF?%Ue30sYF#6l(g72tY;xqIP@ST;5e!@KP zOy$#!oxhpe^Yxi`LGM?++}GZBgJ&z3`#R_z@Lc6mp64>pS1t=FSGyPb0_DAoKjqAK zRWAA~nGaDe`tO5FwezS zkAoK}m-sX?AFW*Ca6j{6<)ZKY1niV37ySXuH&#Do9l~ADPV0`~cO9{|~$f{Sej5ILR$9fghxLSvU3j%isa!;^#)M zf*+#%UgQ7N*T5^4k2U{!7o)V>o))S z0erUdvBu9HKY?Fm@xDKUU#(oqgZ~%sYn6+gVZVZ3uUzyKegmJQT=X@+gWsfF^i%%; zzeTy|XZ#6%n{v@#{1^CK<)XjwZ}54_rJUUL5BOcmMgQ~?@Ov!2C}Vx%_gVbAF5nMX ze6`iUAF}u+tAo$C_zr7;KWg#a)&#fbdB11=xay@mys#GZ3zSQFIBIS1CzVUR(b^6C zS>SV^EH%<{=TiC zUrV{@59$NnO}Xed-x_>f<)Xiz`FhHwTt)gq-$S|RH|Yo7)8h9s?`83W`$NC6#k+0; z-rM5WFyGALqX$60g~h*NzLmw#-WK|;EuK3NyxUF^e`$Bhw*&8|dMTf`Zx6nWaw(r5 z?f^bOxvbAWa4`7x$|Y{CJAx0g_?bI_@2FhzYtGK#J1ZAEkM9DWsa*8$XM+2ci~bKE zc(!uUZ=405t6cQEWP|4`7yalQ@LiRQzB(6tH|3%~IuCpg<)WXJ55AXj(ce)3zPEDG zKff#ZaOI+3JOq4%a?!80JNPK&Ql5M70X|x}=(F|&FR^&(Uf^RbK6xm3nZ-}q8@$}& zR}BLnZ}EqQgYRSUw+q4dv-qzgz$aR~*C_AcrbXQ+L!s(JDDG^da?g#0QzGs{^}v% zCn_Ik?4J_^kEngQ-#L}u{Lw1#nCd0|86of)YG3@hrUv{ZiIIMs_k z2SmVUseSS1;VAe87T=)-{8HuO&t9$IC#ik$Cpg8MA2$_zhUz81o}30gTkVTKQw|5e z+~R|d1ixCj8eISc#&&!RZ*|?D*em2j7{#Dh>d~v^vz+Y27%Ut*p^DWhm%!ACj z82W{(Kh5Zamw>;i{8+>DF9m;Fxy0cI=I>bizRRFrq+FhhyDtadLH+4x>>qmt_R{M4fMI{r#xT# zG0#^n_#u zuZR9C?JSo56pw_>s4O|Ds&(>w{atCu+LlkMB0{-&8O5x4Rwu59Lx$_L>X+r*au5zw!?7 zzm>~<-7pXQALUXX-gPIqe%>)-m?{5{-38t?)BH7Kg89QscZ094TpnEPmm9@J*FVJR2SX-(0!a-|JEEEtN|=H+l@bk8&yJZ!qtxT;j9qF7%bKnKaWt{w-=fQVZF6HFA z7r^&WF8SN-CGfqJOaAtM8GLVx4|xTAxW&i63O>T(jjw@^viS7Z!AC2X@^kq@@Dk+` z=j+}8AFEvA(Ctm|QstuG@GbCh%0(YzUanm9N4^dHc;%vhm-z(cqW|n2==W7F`g0e7 z@26az_fy^lKTx^k%fR=*4^l4iU%)(I@w)e+4_dt22jEo}zkzwk;>90AU#nc={}uCk z<#NAAegyqw<#NC8F>g>V`sRqs(Qn{qtV=L$vE1zNPAIAJ^4Sbfi}&+`UuF3@BFmdk&Ia$HcEr!Ka=?2j7eD9b zg7;J|e!iLqzOi!g^Z0!5-WJa+0H0&|`AQ1jx-0aXsU7ii^C93{DHlKQWxj=S@pJ#( zpx;{gD`s5i?%l!rDL=~;jG8^cw^1(j{5gAp4^WI zF!$9|0&b5J7LNfhP`$M8J;s6$Q7&=YxD_`Ll&Q130|vQ z;(2!!c)fD*zq}fJl5+9$d*%%mKQjb)trrd<5&UI*T)T>QL+ z`4r{i=f3sOPqX+E=7(GS+DXtKY4Os@;742hSLVlB{PG6qkGJ^9M(`6Y{w?#9l*|2I z&;orH{A`Ptw}78(@klH9`4&HW z3iyQS1Xrv zzhr)`a;eW7kB0twi+_0x_#EZZZ~5$4@SBv2or8`Czs2G&oB)2Ca}+uk_%q7I&VqBnpR;)QeDD{Pi=Fc>0Dnoj*m>we z@K-GU$t>{Kl#87i7lAKSF73-A=5HvM^+mg00{vUcTTJ_P>!slDC>J{iTn4^Kx#-Wo z9Q-}y*~ZR#SAc(@T*eKKW^VTne_{TirLUh2J9a%FLY4O*X|84P8Z-jn{#b@6H-o`c0LK{hjXx-(0!q*S-&YOXZ^fka-{F zqJR8;==&-c{dEt3_g60Z#SelHP%ipG4}%X>u6pL%D;NEB^P#u%rT7u>L8{--#OHqI zgO$sEw^@%uZ|CE!kAd6yxZrVcJ0D+u0^H8WMGL^~e5`vC+|I`zncMj|_-W|ve5`r~ z+|I|{p9Qz`vG;S}c0T^W+|I{E&qHtLW1knn?R*Tp1a9YJpO?Yyd|bqQ7tI%0H&XNp z^mcv?d=-2r)ysTALkfO7bK8EO*Lr01%Qzy-6pxHe%^wvnCB?p#pu_0 z9r|~qdCC}V_(9CyRep%!w=uWvXDo!B_f>zM(MOnnsytx$r$#Tj4Gcfz4cPfKTXgFg z{toeeU5BXuCmSAk6LC0EJeY+3>YK2LDUB#A62Yj2t7&(0F{}=tU>@bpi8? z9Mzc%xA_!t+h5}#ak~>d<5G=-g{5EXbLg*9F7b>Ir*Q_gBklhC z%tOj0o?{n7-&OB-i1Ggk=Bq0oX!ssqK)nfMH_WjDyi%x8w!F*jS{uy5*{*(27CH_;vGp^UTN&Mer9#*~B-}M{lZ?yC$ z6Q_1l{S?2xVct`@_`lD$j$U+P|1su0E&sE>ga1?1AMt+oX9;m?$JKwy_mUsLH&%YL$*+r;_g3ED@XwiVrhFU2_xurdwy^jG%(qgW zYxF-c-`e71euABT7QdGHHp-=Z{>gk>Sx#)Xz}xzZ>Rhk@rU`b8fSUFcKZc( zj#n6?}nm$(J*T(|m#YbE@%Y$Zz0-l{Xvy4)dLq|7Q5~ z-=W_{xy)m(`3Ja9xzwu{GtW|fg|US+c zykez$E^(U2RQpmM26O>GPr0Of2lL%E-9ya%j_&Gh=Pl-Ys{TBq-+eXchbfnO=1S&! z=7>(}f%R5*^rDmY{V?MFx?Ze)N~H>?ByZ&QE7|1ZEZKDYcCw=VcR)rjUbK_}_hf`1!r%Pl)+^)r1nIE8B?rYNx9KGly-RaB^uQAbl;#c`5;I+!d|2vphS$01w5&SWcYZ!iyA`Ch&y;`X!pDRFxRJYx&JUnz&XZw3Cl z>LqU1GVf#Q{~=EMOw~{Ezp)Q^i*oV*UFL^c{!iH2(Th&<{dwa3x^~g$OZ*?(7k&<~ z{JD$y8mbrn`}Tu=J4@e6oc6=2pW^?!%nw&CevRty=tU>??`D3u75_oo!2k8sAMyVx z@Qj@-f7TcP-c$8rzm|EXrN5JTmU1b#KQPa=c)x97zrf;SnD1usY0USu_?67}RxW-% z%)C&!l&g$^h}$-LU-BFcWxlO)d5(@_ex$}#jj&N-r}z@-&eWBbDcr3Gf}zR z*B;CdvUrI3Ar?Oayz3B+bD8n~0pgODLHwV`*iMyY=jRmqtij%PCZ*u>Q}C^J^tLl0 z1wSnXe=G(6Ed|fq$vfRT;{7seG!7E0)0x*RmwdU2d4t85Fb`XN^v)=6MVc>C-p*$} zQn|dRd`P@sM#QqS+AiQN7T<>XREw7|KiuLm=0{olQRc@g-`*6KZ=NwC4%lu}G&tiU?#h+z9Ma#)3lgM|>?@+z?uu+z` zKf4mAaUr$SZ0y{eLcfspN2y-MtGCUDonw_(7&}KWKSBAfhTltEd^3pu^D*mBQhlBI zu`CDnPf>oN;cqfOUAgSjTr(H?GnG#;`T+BDl#4%?GCyCrq}weIc4jF*)Yv(S`6bFF z4$m^bT=`U^AC(U~S1K2OW;4H9xtzbIQausC$vGNf{ zKZm&2bN3kQKU2NL|8M4BD6cYhs)r)oua%b>emC>)l#8DqG5=Ay__^EOu=9)Zy^WpY zhMR}qFl=V3Cz1{{VC=DCFW}=&oh2@9}YWfE0^-z z!hBujn|B(4e<+~Z) ze>D6VZq*O_Gas#bdA=TIK1R9J16vitPMPv!jQ!cnE0jw-Ut_+Ha%msCmcY*b%4MNZ z9`gg0i=D~L1Io8C>7Gs8s~ld*`byOgH2PpJJ>r^lM9ZH!`R=%aN zKj695JpBo?zDf0Cjeb7!LzRy-y!%+B8&kfO;X5&(qFmA)!~8JiQchZ#AE{ioF z{vYL1{wFcNR=MnFIgR-Z$|c=-%x_XIfB2etg%#(%kuUW=DhK!2abPi20;^0g(-%;#Ht=ka(yeMGsGt63@d>JyOe zW2zVXdoq9A;ujF7eXq*Je%F1xc_Z=J8BeNS?BCD&r<9AGKKsIs&5t2IJL4JEi=Fwb ze^$BJ`6mS*vLEa`uX?d_GV>QK{u*)mUW{_F-+g~?K8*P6jF(j}_RnMeE6T;rODXtz z6Jh5y)r*}e#Ob_W6U&Lo;yshbqKcf#qy3Sc^9z+Rf*RPlLivw|cc}%xQh9&FA7p-&^7Gepe%P%J`m2>MHu`zY zuT}nx@pHF&=&x5U_Dzm;T|NlVNABaw#YG zGM}eh@?~%X^h1?PzMRhdF6E;CgZW71qTjC(cJ5Iw`d65bQ7-yDo1ni>x#(|XUZGs{ z>x7|yK)L9TV!pp}(SOAJA?2b!q#1Ss%0>S?^ZCj}zuTeEhm?!{V&;!37yV`t=o^)b z{$%ElD;ND5QRt(}MIU9pK)L7_GoPkh^kp&FvF9~zX8x4wrGD741$w()ZD#(A>PMOK zwvhRA7N6D%J1;2T(CFV_{-Sc}*X2)v{w3v7pP$4073Cpgr^{667b-6_yn*>^%B9_T zf%%)t_b~cBr@_wK$`=@ZI`c)!?=XCg!=Qgpx%AJAnSY>sTvz9U!w-l4BjuA!J+S)` z;GZa${MznF@XwU*Xzct#+^c=eKMMNAs_$?7Jem2I%1aFYiTT&cs|_zY8g{<5_$AE0 zS1$YLyBq`kPs-(er;_=P%H_T05#~QDm-5i_SlIbRd86^?LE`-~_ORNi+~c7CP4!Zb zH8TH0`4nU4R_1>xmvNhoj)$FpluJ2$l=%|nqOU&zdb?ab%X~HU^G=g))``&D&Y`D5znR6mod(|9;gB!cl@#262K4<@UuNR}9`kLKe{J%8^qJ6a zt9*TU#j}!jr~8Emn)ZXh8-`4{RzsYJO_yPHPLQ_Fbs$(3t8Wbjnp+F~nKq3@=mKv&esEvX*b)m(4K#$SCYSkgnvHb_##rl* zn|E+aQ%d#N0und11gfS^<c5Vx0F=g;%7T3sJ)4#ukLJnZ^?33ivFtPa%#TN+{<*g$i* zp}uNbA#OZ35NM9Z8VW*FLsc!Y`f!uK)mM@g4IP^1*XCd(^{)l=YoMhmt%aQU?>Uv> za5R=3jMj&9LXk)~QsT>rPK(Auje)9QEL0ngIDUHQvYSG&>dK|c@+hAp^-VS5r7E); z>!VdERlcl9C>m~{2OvO>R+SXQBGUrF+F*SX2gBuHZgnUYtZyjsW!E>g1{>c`qGUz_t=WNSED~r4rS_m8F3w%5*avZaxHX+ce{NMv zl%5WAOM!4R3JqP+8cOMMmOs=KtZWDcs=`f8A*v|of?#Dh5(`zE>r>urez0Lma9T7_ zL-9+}Fh-!KNsJ5v@PWee3+}W<{e^-hK3N)YnW4Bn8Jq(Hsgnb%^V+VH^HDV}o5SKj3o()%ww&?#HZjKW3-*6m)Dy52Cgkie0im1fr?O+s{f{PsH5D-`|{m5 z+8n0#1dYGXM|@bgv9g{X2mpU(TsWL+Z>NG`sh@r+4uzVBHc-ceNdarog((hAbEW2o z)HqQQ3&zSr@f2}Q89gu|$1kKmf}TP=d~7q9Kxs>?w5Bvd`9HL3GAN+=qc0nOZfaJ$q`(Oy_Vb(lI`RiWlsVQb;o z;t36=JmxyBr!UiKOsc}wp~A5hnE`%ad7PJVLt%wKP>~-X2TU4l%kML`xNS4Ogd=1v(`7BwZO!KoMyEAVzl!}XN_jhU zGZleUH{yK{o>o*<5%c+|CmRV+rCcA27F88i1nK{9>vVx6xLVo zokMSRJ|ES7S&^n%dSXZJlkm*8>6&(2kxg${sPo&Ej9ipZVNmsnS`ht+db+2C2O^Nps1{A~==3%+S`Mwe9#hpKFBMQ*^C z?Nm6Srs`(u9mR^sLSbPU(m{Zw}1G`$E@H5byL#Te?+#{!|o=Ge4= zHbdMFSC$u3nW0ClB9@K9R#cwwcy^=__PbgOzh`SvSc=f(MnYV?BO!XjL6@}%kATxr zaC}-t0_e-30OG-PVi%4SmeT_k7}_)~P#uf~ODT{PKscKh&utp>s&1ehUcrrh`rKPu zV==;&btAm9u7G3F8lD^~q@v>r1NDMT6jp&U@5>}N%|n)$TjVs=qA?mr9pgNMG{k{s zj$Vcw_g7*+Eva(qw06F|&sBmWE5A}|uRANRLTaz4ZK%!vwUtpqELOdWD8aquQ^q@7 z3AvLuukYra+FiY`pl)knzVu0*VTKe#5N+wfRtybLCw4^JIYoF$i1Xz!EVULTg@Q{CRA+t5Yo@Dtq$ zzO2Bfxf8tS9!!UxWT$Q}poS&V5~ax?nV`z0(GZ#-E-EX=m=w(dRMNCah=wPsLq%mY zh>XE2AB|)?GM*iXV*bFFM-!Y*Tk3AFc<90m^erv1f(9B)T*j1&U2RUPvAi_Hf$7Hg zXkNnI+%0SGEAw8&ekAe76uVbcPpVc;A)lKUUpui zjA1FX%G6|F(Ty`b7p^HPUgqT6@H$vVBT?n_zchW5Wp<-o6%;~gtzWJ+NSb;K zQ@+D|)Xq91pSEj$TDWDFCz^FuQCi1XPh-4pt5mC7(;O^_&xX4nvt_OlsUqY#(EztG z`SsyIxN;ILj-r)?Scyjy>CW$kt>t5>qVyHiH#R$2#^Zy|Ts2jns6DehYEMjtms90R z66cN84$MlHLGLs)Z*EAvxcPBi9cpf|Nam%46qA<CMNj)P=*7SISK|US^RG{J5lZ zG<&5viAHv*RS|Wvse?(yKXsGLOBxEPzk!@7r}svGpolJ@mqOQSNM0T%{i6SxdydzN zjY-eazqCq3RX^Ns)eQgQyUC%M8F~zp>$-pG9Sx%n;{Rlm<@3=}#i}s+`_u<1Yo#7O z4MP<~tLj44Ewqj!zDfWA$`7P?9({i4F-{3bCa=I|Eq(P#OCAbod3}(U^P|&EcQ8Cf z-_f`7>dw{_uaqg;>J`^zaJRO_G>1vyOZDEn62rH=n_ItkbDQP&cs=5doV*`tqm{P! zciMYVS!?5e&70AGPgCjfI`CiDTk~aBhx9cT>l{)x68t_+8)|6#O5&QYWgX&5|L#NY zqEvkqrY==J|4R1Eerc3avg$UhW*{_Oqq>sAN*>l@l`6TI6DXx3;QcbO3U^-erE5lG zSC}45D5i90rexC#ek)b}smG;P;al{mA^mv6cMq88IQ5F_{GdjWXV7!RoYrj9Kalwf zT!Sj=sjs(HQrn_i}6_#ljaX&)}sQ zNmZP8t=d_>CB=s)Z`#rTNY%n`$4+t2%JO(qq|-TQ>xjH6(snoewaBC5OlsrP-mfpk z#${UJl44zGn)L^mBj)uo6__mG?gUNgB2(ODOv{kF1jPfD*g;9_dHr>Y^?*j&_DSlBq8 z-?kE_wU@C}QEqzf>&LjDch00)78#&Do196`#CKt;E;x0eT+`0x@u1ZRR;RbK%9LFJ zi0AZB$RBwr8P-4)(W60Y9cX7~%E_1&^C)<@?Y!(#Yf+kd8gFGg=Tqn0m5Uz7B#-CH ztYkbQD-~q=hAUo0%9c}_!h`eby5lhy`ntR-(|7p`z5CpZ}GR=$v0+syUBUt_YYS?equIuS@Z*`p&0chO&y^ zz%c)RL8YFwf&_JnKYmK4bb^1YdX40vt??D^As>?H@rcWMv|fB8eo`78IsGZk z7+5@=Y}(RC%h6(VET(g~L__^y&K4US63!b=TbwhCGo7=&oma#(gMS%%;Bd61GEfu< zO!VbX300a69059il#e?tHg9gOty-?rAAQc|({>)bh@m-WJWx)5%%u(?Emh`~$~aRJ zBg$CCj9MSlo>OszC>=O7EXS#D=|r?jI?o*&V+F+zQKy5?!_84&cA$vPXrM!%qtO8E z^&%araXJkqj_YxVgOi5OIaC+hRI2GfIZmn`Yo}vbfqneGY;#a4t-{ySzG)mJ=}-#s z#r|b@-0=Wq7f#+LPeui!b-cfp8@c$^bb_y=p)=>{xYfoG9k)&A&efSS(MebnZx3b6 zHPsE!J$reda`I8!NGIUPq%cf3Pg`ay>C{i!rd&tI?uQ~`(mR;)YeIrjgZZN|1arBSqL~LM~^vG}Y6|z5!Zp&2M0y=@%kn z<+$l;+MGz|B62y!51Dj2Zv*X(^U-E)ym61EMXz*_caV+?;q%Xgq}lldi2o!dw$lN3B;xu{HPoWs5=@jVW{ZGRxQF4#~Luug85Iw0j`H7yMd z?YJzRXUStJX>QgUcS&Q{Ydt=hE*n8hM9OGi5L!6TS-ZwmTZOzVdN?32(A1Lh*3Zz? zn7_W-GR>D8q&?kqdNFT2N3)eC5$kG7i8^r9DKKwKuAEO*mMj|{ z>3Nv*xF-))+iXc$gZDH55iboKQ4h0nfroj&UqTmbpbf><(`aQL1vcv8uivL%T`|!P zPocx3J(Q-j`}4GCN<+nDnLk!i?Ua6>k3K5UO#4GkomNgyHgCB12dn9mAI|r5QdMv5 zwwx5Bc01@1xwhD#*rXT*N_z*XQ}dY$guV*X6lNG(G72NLN5ZwKy^6OpzU){qIyt_dEM0VYA3uF# zC)`Y`vJrTpq}DqaYl+gZ0Ij)j8a|%Bh$j#?&AVmW)gJnDIa6xr%QxKnr`L0Osf@HV z)BmS-G~HFHL*mPg(Ys(IK<_E^rh*xl)G657G2FYO`6H&J>TUa@Y0(4mq<| zSt0s(R|B;xbfO6z{#=+Q=|m|d+}<`M(LkG1%JS366gA;UBYi7}P7e$Pn_8L!)K}qd zA?j@e0Y%2Uw(}(>yL4-9cFae0VYDhz-%OF?As=LNs){eud2*xlVI?{^lYLKH>^$|U zU5h^=+=Id=^ki5*ZFF4M`T5y30d7-DeryDlEJ=Mun@VEbk#vPHu1cVr+bTnyb+2dD9O!AM(|cuPxB@|YZbg^<6iA>)YDd!mI3(mi8ty*$vTnTFItU^HB|;dmT^H zys^hGXP|fG>U76Fj#~;8z-Rz9KjmC=h)x)3z&I&=-O6>P&(s(!%fkN$s#~1H-^rf_ z`X?T^^jdliTMj8ETA@xf?e?h-qOqlMgcN?$2Rg}b4xbY}E`}TlCxnUj)fOx54V}8+ zdUVr=i}ssXZzHCc?DOG%OgDfUTzchVBHvlU3$H8NoU}2%P9bompZLnyL6eCOx%1AH zi5AtV1E`ra2ZixbVB~<;OP1>bQn=ojEqflx=>j+y_XwK)p6*&cw@i9e$#u3`h^sto z$tz5X8EomD*Plw3x9*)+t$ zpT=~)$B0{LOG3^|8 z+UD*&tb52z0tx9_x8^ga{-G1?kxBCCXBnCEB}b1Cq!cDUR^GxiNjqnW@w-)Ol|MiE zBsCclF|R9a=-%UyMrxSo=1?g3FnwM|=4xr&A2OxW<=o#fH5byy?DzvTSbvu4LQ=Rs zfJz@WO?z#^Tq^aooY~m0*=*uG8Xke6Zx;G8&B=lEFf@<$dJe3Kkc4tcul@AVD*6O3efHU_NV+WhGGnxpjJ}FYLzI*}dJT#5_^tJ! zDPCP_cR@>G&zJ4gmY86tX&!W-X~doOoX0cYb*0>6W>sBF(`5d1Z@zq$m9FFxLldm@ z$@3_EPLhT>d6L#C!EH2CSmOG6I$0{A$shT`xIg}Gh-uu8r;#+yk=`}Av9{bs;=}0J zm7wLvTIdL-fH^7G*_Kw)h|hA;=Tu!EO7^*;8^2*E^?dm*bdk)8Q)%Ghnb_nORwiG` zB5?&n)X|nk{sJg}k~-@2P7_-`&z8On)9~n^mM0VvHjhspvmbb?Of@nIk@`Vn;Z5{+ z=M!XTc=BlN51Io?Ah~zbwhJW(QWZmWbm=}UQ&DVBI$9h%-)UqV4*A04?fne3%W=nC zuB*04zTEHU?+u3|SRB&OP|05l_wG6&gHbY3ddM)h4JeUXnsOUMvAS@ztDB&y$#s(b zu2z#vCG`TRzth-;LYW(Cs-;;$a-fW=7<}M6hOeB*>qAY`lpu3Kij<|8@pgEsv1o@d z4|((5xxJ$EwP=Nyqu?(Gr%sV(k(|4!3P-dLO7ry2AdzeKR+%_QS)N57Uat-}(nKW= zt)|=L=gj&+u5bcb0FU@{+%yCcqp5WID?O+5d2Xi!#8^P2CLDJ=E7+7YD{KgrKxf;r zV;nRwIS`uk!6)yX{&V8bzO3d5K5a*ze8k!%Qt}ceSs#IhJhlu2PwC93cO=_!#2Ge= z1=}oc4#w)zi}UCk=d|W27@=NzVA*bz)e@%bqOWbzsbTuA1^L+UfV;JbS^g^#a&cVPm3#qls@&3CfL#tbAGJmU*#u{WwHDdeNh&xYMp{_K0J@YiB-j>kjBfWFRP`Ax)Ag+T^gjP z^@|;OED;9REm^lqOFngVq7$+BKrEWamE%FYKxnSfSC8Y7>*Ok9lH>(vNjyVV0cSiS zvjQGg4aiEWEaK_M?|->g6_ftwD~d_&s3s=4W^0yDCDGp0njaS|kLI^i$dV7*CUpolk}jC6J;Q|woz?#!wa z36CGk|60R2j{J2!^~4o=g8rh zO!^1gaY{Sfbsd+g>pCuV*Oh72b7iZ`D$;wD6N4RrrUR@Y9)CI3|vw-;Q2$ z39g8D6PHV``QLT(@h7mI4I>@tmpH9s8b@zh$t>JYZ^`o!|&Sz-erTD$HcjjPNP7xn_OY`P5vEl4fqln1H;kN9xUG+9NOIbqCi^jg> zW}I(K>o{5ax5Uye53{L0VZV6l{o3vv{+GlwUX*;9*yk0fZ3#xIDULb30}>TaxtRc? z*m_FbCfxhoO=Ik=-)?&(aVj&4X-{c?zAZ zSte7GMUE+V021~jIydd!u*+VQC+xp%*VU;LZqvq)r2RXNH{45W`|eer`4)0>IE)S` zWnki_AbTfIa_AH9xC7==M}ej`oqb}CXYmauuFY;q1;Oo2n=Ws2>aL}u=|Yf{Xu?&g z(`r+G$M3&GS)H(Fk*WyfcHE@3If`Bd#s~OF5|8I1b6M*GdVEpYmQy_yUlrlbJX-3t zjMYu*Xw%{qXREQ)ZSgR0WThGKy74quCMM_~wvUe&li_`?i6-ZqH%UC7{;Vi}W1JSO zn=^sTGQOf%NsOlXVp({5aP9N3w;NIJpya#O-gG#<>`Z#CjTO*8iHY**MA$jB9MhtN zeE!5_;=7CNbmE(Bc&7^$SyxbG^9yyEdA|_vh`~xKTHZ=db6+}(H@V1e9}XE;kO`Y^ zXrdxs20JH7f8v%Wa@n zOaMP-<$3>THX>E$X7g*i88_f9yR;KlEaBC^q`{ZT|4^3Z7Sg^Y?nfl-rHY3#$-hF`ZPL@8m)wM3A|b3o z@CUN88tWTqWp=2FsyCc+;<^r-t7_`$SY7kbV_};0Yy;Ax;!(H@s%Z;NO+&EOz0Yq| zx}X2HqU2r~>ksThM}*NP8k8E(uec;_?xXLna4^?!=pwv^@G8NztUU7MBvuL|aKi zXeoC@&{Z@}BO4X6b)y15+eK~Ld*FEQjIYT{WNQ#Rrej5;aqOMvihN9P=kmAUvof6x zA5k2a7l5k)*?z&LEuU6Q$EcW8Q?(H<5YC%5IbTVQP!V=#mRCBn->z3*$2DJ$Y!i^< zh_V!LX_5h)6s}sE2ja^wmwqKhxHM&l*PlgIqS<*6rN@NUv^ueHs@UW`d1!B)Uy?T* z(nMkL_~o~XU4}-~Wcx}6-80o4)?TReI~EcPFuxY0zd*`@>*;Y{;CFAP;K6zHR?_XF z@VFu|4LPyWb=Vwdh*Q9wb1Z$n#`-|YtyP$$2{pAe7SWTjwC^GMGpXp(sjBoL+G;+w3k@xffK2Elx|{NZz1H*|=6uRg zx7xZNE~)zJXuPv7{(~#h@}Q?3EuwcGs_f~USKUl)HdXS9ef)q*zOXLkXp#HnaA^Zm zCaI33;Bi_O?HWkTd$%j}E@=+Gs;{PqV^}OPb?z9OjRa0b)!2w#1WsKcj4JWSerVB<7rAd0sk`8Z{ilU8mBNeEGsvWl)#Bsi1%wh)VgItMm zvX+lK#gumfP^{X&d>lvW#e9gsj}uNZE9{V--6xlJp5l@(HYN& zRcc)yI>)q3ww1fbN$r2CkGZDl#?ZUF`|JnqMzq^ZJXC0GaTp3NTt*#L-dGV&F^3PLDC+EF%p!^WKoNfloA$uQI-GL0 zE0o!Zp~OjD&ZiyXcW9EPXQSPrOp|G&%jm5EduZudqcOMP&9dOcRi=4B#(7b6-Uw2B zPdViy$U|P7VUz?lfApS$1tD}Kf@}VcAIs&Cltj$ke#=NIO+3f}Uf6R>@6ytlYnwXJ z_P=-0Y~o$V?<0>^HFLGbuY#-Mie~ha8l6SaZC%k^fHQ`c%VOqSw$#MSuxV{TELv1X zk4+nwX#1#!r1jXD^rWQ<|ElL{Vu5!pzfJDi^23x;9bX-drLr{CmbBE@tgcRZy{qeO z3@o43#gbq}%%4RAuhCEw&VHLhF|QZ z4yb&)D^X^JqR-MW`^B$eP_a_iS&txe@Dxg)kJehd_014*u?T&v76nF6}x*o zFKF?iLLZn4VErmqI+|+5X+4r}(f#o#&&9yb5>{Qjx^ycpClWpxwe%~~rC&-ftVz9W z$)+62NjedmRt|LPf>Lh#J3UQ_o)fKvG!vHC#XmwIg?njNWMjK9^^KOYE|1J5yotou zJ@}Fqdyvw#wAZC8z`8EyOlLmD+O*cL6&MLeG^bs!gr|-MZL#9m{eI+1Ey1fv(3?-4 zt+1K=ctUs|A_tra85#xh$dh)@wZoX5iJ0@s4=`ld2TGQwaI*O{TAo+o4TCZu zg3Q(BBD|*7^;Eg%;q%<{@I3M%hZ-*BKtZ}avqQsKhTyU<8QlJ-Hk-~#<27j!ssodc zBy-p6et+W3x_m9fd5uk&OZT30r)=@@!P_Ae*r2l-lXhSxmLYeNzI-}>+$^S-8FNm~ zHM5rH>g@Q{JOG3?WqI5Mmje$MxCFRYg_+d?iS@2HoJuXfd*p80dJxNXk%6z%(p={I zOkQKqku?-;yuG$3m3UY2bx})SH6YcRlkZsUPODLCB6JdCW2mu`R6KP|tYPYE?u(Xv!NbkUiI<(cJtITkDVhXVe}Rj{QNPotcm%a_nG+;nhb{BySP zx1f$phr;LN^BC#gXu*WD1cD}T@UN_Bpzp1s{-&3)V&`Rn{)V&ew1G46afiY0{tG6@ zo&8ar>^E(lr+>w7{ywMELS%CMC@(6facxzgxx;_WqLa*v#%5C&EZ}bn7Sero>|ex* zTO`e5I&PicrdsgzrlJ7-Utu7dJ_;SptDF{S3O0t8B{3vL2dp|pktW*;0{aE4OK?o= zh(JzFeIy!#Lxq%=^nV??CnwuS(Dg~rf;)TvKd+rr?*41?IH2fFDd*yJM{bq>dLqXx8eTwk^;Z*5L&@(yd#L_mU=J@<|G3EpT2X?S`bU zsnJQJG#*z^XRFgw$zN1SJ+RbCOM>VF@=0_0E71iB;EHxZSF%XuxY8V0SrKUWl7zw( z%SvbgC7NqAkIAFwPBTvPY9q|Dl2L(*(N0HXWf#Hrt~TkEyO+FgcV5uwSv%($=!&GN zjnt~u(4lh9df1uzm(5EyOTNS^MdH`^pXmhajKr0%2CJ!abNq?f~*52S}> zXz?(2!m1UIMBE2^=t=#wONho!s3%V=fve4_%GLDEkQhx?`N&Brh^|uX9(;0t8Gw>Z zdI}OhX^^-uK0dSk??13hnouW5@jF}fCXIdBk#Lw^&l_5z{5hR}kLS4X|}gXQbeiFV_v{y6%)G z@7vWTq81Y89n&5IOsP-G%bgOzKEikaEcbXH4}Ctg{xBME!upE#`^Z-$wcDsXlN@)& zq_Y+zQ1v#6nkG0TUk-PVn>!wOZ5_$=+&Yp{@AtP$-;psigirS=(dvM73mWK11bo9n zKGRvxiyKnED^NSK{7&!cz*+#Q(8!NGUGpH@UE?EoE8-+Ea;1#i&{M?XBcPbkkFQQF zGj&mi)+k_-3bVwy=3FZIy3TT52*F=v;MEY?Ytj2!#ZR2`nPc~`0Ee=qoJKO8_&ZB+ zQWTUxQSpi>BfPnqnzNK!tL@{wJg0(bR9qS-zxmoW&lo4ylO9vcnNep1oTRVI$d=6T6gn#`vDxsjn##*`Mw6X&w0=oA*g&Ig)zfIRD=oQ-E~5u2 zta`d2!)D35Qj<@i*yk)ID%(+b;>p@ZlXK3sRL1Khm4sc~Z8}s1bq5Yxh&$mNmSnbE zCLO54RgA8kNXXRoPpZ2Q^1!gERPrloDGW8Gu0un3Vqd&S-4kfTDj-_?zLZlpyjKCa z>L+yhuk6rRJdM2+FFE@4IwY}AcuG)sqGFoM`cDcV~gEg>>T<;TQpkLKwVO6G%WRt42C%-G*ng5S?iQK4ZFmb7q;g%!Y#2t zxW@SaQ&L~Nt!C(1a%y<5&k-j?-&&ruEID8qOP>pwPA)Xlr=TbU-0^qbj9eefrP>!Q z6IT=M$Pvm8I#$%=$1u*1cv(nZYioNT%}n~mSj*3%JY!Utm*tbV=(p0WJbfr^bU7Vz zKm$;;NV%ffSyn9H^TpKmczQ`q+WO+WJDD4A^YJ!RXr$sy8c(Eg&G?!x*J3Za#r7V| zQY5{3F52#)vNY?o_^1hbVX3E$H?KD>lh4>@tZb%YO#mt*RyB?_C za6LFUTSRipDY#2LsfV7)gy3SON-*Ix3W=g)Fh17h%jO*zyv(F79G+Y>8iRo}0%lIf zr^Alu?@%Qjmrr@0>>oG&?3drY_Z?+(jSS2Ut!O}UV*m<|D?hPR!o2=@t8 zbm(8ytDK&ewuF^s<57u?PF_XWp_Ir!Cn>G^Xq8~0)Lox4q1Npx!eh}O~2ztTT)zJx~a_R>EZl~5;*;L}+PnrQo0LvR{q zNNH6hEjsE*bzK^u4;FUdUj#Kxbv*{v^~mJvxXeAu|w?iiAI_eDYsZ7-U z&7u|Wxvdx(qw_q+Q`9B~@|+WeGx_U{&N6>~Ir00YK*Sp(-fWe+`_q=5MvgATceY7T ztZ0|1J3oCitg$SwHcRV|dzH726rVAHiVpp|l;TtDS$sN_68V?Z+SBVr>L-heN{5oM zi&(r&!2@TkkV;?DyBbY;C%&v@9}`P8o$tEJmuFp7R5qd`BXEt}jQY*FQJl1oR;C%3np-0D zrHT+et8^THo*rYA|4V0)y}Ik(i_Eda~+z)5{tJ13ee_vUyl{ff#+`q+|3Qp@iU zWwTdE(s0#6GWob1ca%{or8G(HR1--DI%XEO`+&VP+aP#ujVcnF1W&yh-&g25hEcD; z(s*47XO90fOX(#_@dbX%(`P+jk;AmN3DXKc`;b$2R$=Q_0@|g)dkN$`x4?2aiZRD^ z6l0Eii%lUfM>L-sNBJ+M9=4R44()Y7QOI&WS?Xg;u7chPpMxNmsbzQ0ccK^DI@dFt z^nD0YHj*^c}uk|#4~UT`Rql4x@q zF0yrh{r#${8|ZFyV~i9tPc9C;~5j`EThW_`MmW=DiP!z z2P6}8;GyqU1)2s&z%I1^-K>y>(LHlwDk5qlIEvu?Fhc7*en}$;KHHKUo$+v9=dP-Y zxLtFr{YKr4szTrjt2Bbw)iN7J^ot-So=qB4A+SEN$@Car1E!RSeQZTe@&0_(}iKMI(ww>qt+O?WMbl-|Gdui zK_J))=f$n6MigF2aU=S>*3OdaZ5(x$=iakar%CH&|`MPg> zf;{3qW~>-CB>tPv$2;S{)Dz8~qo*qd##ZdHYrNx(>PH3@M~5))CrT9p@go>p1ggWg zh6KM{HLo8XReAkgPxaqqYWB*>RDHJcH<@C>_GczjK4joKpG%RY)si6)ePD{8=L&w! zB6x%m*3CR68CYsC6AcTS%HkBUh!Tuw7^iyaCn||uOn@sJ3is9VcO=JWrhW00cJA9rXY3j8#G@@4@yPpU<@1p&lW_rDP8jkxaONUGmWpn2 zE2&S$@x|%I8XnfodNH3oCMaZQON$L|u5}63Dvl%qzM(V?;3VD&ntaPXjeE|3rT(P| zy`QGz>2!;XLbM6jj~=raZ9DT0s}bRtW2-f=RLeI`%X`@-$B4fqYh7~U4_S?n`a|Mx zl%g5Q7>!d^%W1M>rs;S)OLr)cI-+z5WQ%QcpcwrX|g15D2H4jhixAaI{4OYN12b zM5xIOWoN^Jud*h?cEKnz8t*V@hei*tvsC9F@>kJtdxPfRGl^hPYWO935g-?|Zci(; zEFz3Rip=_9realw^C^mR&-afbahu9fe&iDfLqsRodGa6&qKyO0;Hx&vG;WzL;ibJ- zZ2E{gsy_nC`(w>y$~`{cFuYN%ZkO6wLdFrj)zUaPagFHsaH&5@_tTXS9WcJo!t6tG zB3?T>v{jJou-MO45qVwI`$;38ePxJci6X#@pkt0V14R;2xJ?6WfhbiD<|5q*pN zJAE1unDvo*f^Xk;LRod59Wi>!x&vp;lAOaA=NLeBL-`^2B9I?~Z;JULr(qd#97``O z+^YD|XeNm$q)Ks07(3zYOXAN+)Y%7|c*#!Zztd5?DIf$<5ZlN#D zr6l=-!HcC;xx|-xX|C?l&3J#0`yTdo1*I?|9$i$cgV4O%p{4K| zjcyFPml*<;XHTF$dpQOkvlI%C(2PT3K(uHfs^SYwKO$R1a-}R6ncX6tjQ%|y@3ur< zlx?6tM}1%zFrYY0kefKAH#u{!&yx6RPfw51?f}nmu^PyJs&)-olF%KMK1Sp6-F&?o z8E+;xNsqH44BJOlGqq!&?;7PCMC)<0AQ<hfmdRXkpy^UcN;#Wr76$a)gYu3kx%IR8Z6zU-`m8P|Sgx3_d{&eJGCViy?Rfi` zN)TA+9xX|DCnSc@fL_5yWKU-2>`83oY&G4ifw4ICDDCbisG6h)Et)i_I@D1rArA`b zBL2);%LUa=WPwy<)WG$0)TnwyZ3IEhOCQarEn#@ zRwhjKSz%i7g(hLx1FBOy+k(6iJth+4&Zm-m=_?&k^lyake4(IsT(hv;!~KA>Fdu4d zye9_x{-Mos^8kbj)F0+Ev|7+!&u}7~r2sv{*&mLjld1iHE+y&)q-B-4yapCiWwFd^ zFP&iwZD!$QLJhe9=`!@x0`vQm|1CH6(qJC$OT_R ztynMZj8ItbuC0ZTF%I$iKn&LHZX?L$@&CmLf@deRxU!(w#2upxbXI3a)7s4c*VvI# z6d5$PF#J-vi;%QM1WrrY&a2BWP;)}1J3-GV>`_9yaK3hk!g2#Y2!>lNDe!%#&q!)&@YBHednIIOR}7^BCXg$D;n7~=35EL{sFkIN4-J;tB{4!glX-dzS>UW}Y)3FS2WQeJ|zKXfDl4M4~ z<(!fY$Dfz^1r+tZ*vyR@737`MgkXlAeK%~_$IZ3NhAXFgR#D7d9qcD)2SDZ_1e&u+ zt-?N@S?_w8dlyxMgJrEiF2UN)w&U+96eKxJGiZ_3wUfrQLo1FSPuA8W4lE1A(qW%4 z(&YMi5*vex(1kogq_B>6_soG#EFT5Wn@AnZb+e$F8cWe&sPSmqtInE)lcdmO&22BF zv6tgF_ckdn2RA#WAQL>`csZn8E6lI|P+kt`)QazTPtG-GbXdf1E^}+^Q|2ksd4(r< zo$?Awx!b9Ib6?|AY1{V{%0o5%PDQ6d+wG&_HV{WgUh;{^i(M8f=prIgLtPSjN-JR_ zq_7qydD<4|m~n`_<;~nGaE9Gj2_+KJ=1N2Plf*qgNVRG+;##`TbjFLjHD_P!5;!@Ow)>tqgDz&}8h?Q-9>cUQ ziKHDRj%BlxTxJpO#(8>c3E?;<$;HYiXzjDldq4bWHZq5me&JsVFg^W zC^t;55VbDg74K6D5jDDl+iiBUPVZ+6$PH*lfD%c~N9Eg^gXwwk1a;IPsl3$ekmFP< z!$Im4dvT(+U%P*iSO^7ek_FmuGQ#YjiGU(UKA)L18fqb^fdrs3+be`_`q8#!v1zl> zykW1S3)1tB_yIgP>?yZKTiFU9sI=dEw|T}=6jmhJSLmF+}XDo5;w z=O7~X8{Wq`oBk>T2|$AXWk_zNC)9=wHttA#@f&j*RLh9X4Fz;6jNd(02vknw_>S13 zM475<>D(H}4aaAT8=i~bZMyhmbTgWl!m5=$$XQqm&MoxWb;JF0=a%KysxOGSIBmd> zTywb;CEz~S&Xip=(Emg&h4F@x52}dRQCGyFdag&UsXy&~$e)HU0{%37Q{qp<`p_Ga zXbJaBAv%eF5h$X=j&cfa$H*5=uCG%3C_&_ZO?))@O0corlcpR=!+9KV{rp3qB##ii~4=pl}drwJ%kScgK&Q z*Kksh{MT$NCk@ls>Gdw)ii8P`;=?{ck#k*<^f{2?6LOG{Ljpq#x?mFP#Gf1#uBYO( zJhj|ocp|>A8H6I;sRAx6V$o(ko6Nq?A^5Q-w>?bY%*b73ziz;*658np{F5?ZNHKZK zM|30vu3Ch^S8#t=qfR~sksD0n+3g^^30X%dDugJ$Z*Vz{Cli!HLad1<(xCaUIq&9^ z6v2zL@lrhq;Q~1f^9|=)(!aCd5JD7#(!e+4If#gsxs#aI z^DOIr^l!?c$6XmBhh7qUXdsjmJfNX1b#0Mcd-zM;jCsC~MkdS|B^(0J{O76;)eLE9tc@ijn&_NAmisze+DSfOYg;J}DSHX?hL-(`lq^cxWucHQ;XJ$9U%^rg zIrnwATKRVVoL;I({V<4*bHwU05ij8}#n!BpAIn{-9c)LLhKRu#&Z7*$w2-i$ZK+1j zOIgnS&ZXw2qTB;$<)gG{KQCH?*mxeWpC4}xGJGA=@qVm{o=c%{CD$9AS~|iBrVua3 ziyf6X9(|k$<21wi$pI8a93FDNUT|duQ+}qe2l54RaM%0tq)sB?5>}}k$46xVMDnu8 zR&ykSbhqY=t$}SZOVbsi#g0H;6zWVk39D447BIyZ0Y47D3Hx!5x$(XO6kSjy=2g|6 z#ED$OS(FRgRGS>OgWK(TNr?tGP+ur6g`7zo^{A?jig+Eg#=}qOhYh|6zy{wuGi+ex z!H;%2Nrx5knq*mN2}R}LfCi(lIzze@O`o=<&Fkw zZ2_+j`F*Zj|IQWEW_IwPXC0Zc;&il{&VHoaA!GF6-#c;rB&cE|zi|!>9kr%JN8R`r zcGlH}&HvVe&3{^jjp-wLnxivZ5+QSF&?6*#+O+c7xBbbyP?&gre+5?&MC?AUn6#G$m zpUVyE6`>y(DsO}GXw_}B{6OUVVj1DT82@$gUl0HF3454cMdI?^rKmB88xN`2i$C6e zQj&H-FEzfG2z0UUC9mh5_KoTK;Gick)6c5;*Z!|Gcq z8^bivG5Bx)#{7p7#QNvE%%8Ao@0LI_VAtcn|CC$$^AWIm*y%sa3`D98;Z_KgXJB8PIes*frvB9N2*Q+LSNdz9Gg)8gvzUxl*d z2lVYQ#B4PAbI$X1iHRgE{#oTkV{fgKn!w>cpXS>Tahss7SKhO1K&D7Um1J8rEBL3U zf;w8&wWkkk?X8%RYC-Q@8wxhC6wH65T$7Rvn=lzX*+yAP$)WC)>En*TfigxzTWTiO zMt{82)3^MFB=z}j9asTvDuvY2Bm88F)m#{T#(U&nT)#w8&f$@Tb6o0HmD)w#Q%P`RxU<&VM~V{Z99DCr zE~*Y#XqKlML&UrNG^Nse`a!pG7;Yy>9=znARSFknPqkR#4kIHxU|_@|HOOjmL(l?Y z;1c^lvCiV@^~K-?Bkf@Ac>MvJRk7fFf`GFb^W}p=)YVbF9Gl7>%`E)(+2fi^qJ7Cn zX~kXt-I@H=bS9T8jg*!=zR-(no$+D6PIntFc1poNutG=(HBxu?+s8jaAT?xf{OCLc z;b;_YZggRxFn592@2OGFd_;f_HupEHS9F#cD5@r#K9Demt z*_C_(Rj`1KBm56Pg|O_#L7)IglYYO5slgkLnTou%T*3wR5-#BaeL*@*;MCkeJpHm% z)L{OVSF*4QWyp~$(ZKCM;ZG7Tgai8OHQ2$Gbwa6Ii4n9fgt>?|nqw2N*i;#a;ug6o zFIAz$%Q#XM3Q-H08aby~av~)VZEv`m1*O)d`z;E5>_(Rl9ATMO^%+k;O{ftxn$ zB|fvTSO#B%;4pUU)Z0QNTiA|QcQafe$5mcbF_TLQ$o*^Vl0x6~&y(!90tq!LJ^@D6 znpx)m%OG`j*5*HsJ$J2Pn@J*05S8RG#c3#o$sz`)1qQT85VSh@t0AJRu=2O)%%S8E zRCVJbT@;s#2#l-<&uT}x$0_H`2im`s>|C2k9CKo`FyD6L9h9>0zRuBk%}Z(kGdI2j z0u6myuh`J4uw+RT@mEF``XAYzc(~u+;PodG{z5m-y2^upE0D{vrLuJ+jz2G zBEDj`Lnj$7_D7uGUzGwjg-Mc}Pu2?*21G~(G@Q!{^Gho;Mjf<%g?Y=GvoZ;1a&L$Upt#W@BKq=%!f;}?*e|D<`Xj?XX?aZwlS4jKHh zrVjb;(mgtSgeiiOi*8w{2P?)=m^Sz(vRSKOqZf;* zX#hswfOWdQ`HHGWx^yrIWY^96SE=ik&0JVTRv1I0mf0i8oT*;lfmZC@^(@ln;wp+b zyUB6T@%v`J+%A;98m z_*ORBF76A?FHxl|fbu6_a=y&XJT(5l%MKA zLepv>y(`SzkWszb#9X27EID}u?4Q$%otD;ajpL=ye z^eF)a-aG4MmajnLsaKn*7N7Dl-G*5Gx*WC@pM_Gw02nI5 zwHDb9lRKtjn)j|yvbB+K{njsyQ2?&t?RnMuMW$;!B|i%og90Ka;%Hkj?u-YmeCOuJwV-A6`R+`eN>AZSW==z)&Z+DYpXui58wkM!Tk)4GwqWMz1fGRgbU`4M3VpXPyMxYB2$LHg% zbqERgFa7)iR^=z0e+7zCr4QoR>yj2fi!(b#nKt4O(2MZ`4MZRR5*kY2*_Avhjfzmo zsXEjR_uT~X%JOEc_O1Bh_2gE)r<|Lt>=5a=hhm)@u63%7*L?|IR`pJ0k%_L#HT{tI$5CB6}7m=5K6L@1v&q0yOo*O zs9Ih8ZxxSlX{a2}2n^nI2qI4n2HC#?fLVs~839n(qqV4_CGM49wmJ&9AugNO@*i15 z46->9f_cS%^FII+=e}BTOs|#l)By(3RIbIqq^Ssl;?|!LIYSro8A;VA@O@2rtt$HH zG4>DcG{%|e)~&fRXjuSJj&a_5jU*8^1%gfNK zxT@k}UIAt)2Io6*jt;Qd;wLaNJ7@}|BX_T92b-yU|5^4(T~^=1W?vbpP{Aoj(Tt{s z5k(JBVdvc|7CQ?92v(a5nzpq`QCTH16-|5U+UD_auP&nJh~OPi^I}4jUxW{OK%UQN z8+M3N9bYXe5l7;>6wv<8xW)E%yhu5Q_?MR1Tt**3__lQF(R^NO5 z=$HkS;U9l`)U4xA2w1dT+=WcVUI*=apmrKhhR33>9Cth(zm!+`KStp5ddh+T%xF_a z1Gw}X$*=IBbJp|I15v4dM}Z3X_V43v z$s6LYb%+>Ir81{zIW;rhJ`H0k*>mH(oCa6WFDy57Wcscb7byUYAd6PfE7$IC)RKyf z;!mh>kFhW!1a<*_r3azLNLMt}Fl37tvxF5Lp_($F6eX3)T+DZS*n{IVrn<@Le*JB> z;t$i!dNH3ovTjR7JMCT$+`^R1_Yqe;QYHLhxldeau2M3S@X=oS^{e~ppI-Rw6v35Q zB=(2RVkT~q4}gDfxrw4^z%n$NDePwG=(U>6M#~Qu%T98!q>@Wg;P9l17U$IGu%1Be zbRKXlB($6aV`YY+5$UGfL(ywueYI!(N(gbQsB*E4fihkE*Ta8({5QaVXZY{W(8yld zLm1&c3f2MjAj)5-*Z<_d`5(Z~KOZIrWRa;i zqrsE=0}3-rH(g8vsPW%6O)H{UQ_Qm1v*~;wx$x& zW~br3E>;eLZD_82PwHp`tpl5Z?B7y2!A2oC%q`sEqD0~VQy4sC@?HdiEOuN4hS!e84* z00kU5db8*N>$2(~U-DXD%hno;m7~I;)^Cks)vsCSlK9t(%ay^R?PGM%Cnd_bKfTF=Uh=%zEEm)-RQSmSH`yh3YGuStVl!LJmeiD&$p!_}obHlt*&QIb5{x?!Y$W1KZXvDdTUMr- zV!1vo@%X_L)2aHL(rUb3LvVNVRaz>_k;C;`ULGN_&MvTcl$=S^-40q_-dW4z4cpx= z9d4V*LZ{o}X)uLq5tr#ST3jAcqGr%*!Y-82SFg8$Ez~h46s@3qHwYAplw=|6JST~M z2%~vNHn?Em0tmQEw*NdfIj}I|u>rtA06Tf>nua^BuUBR*7aCe@eD~)$={m}L87j2k zIPud2b?r5tn1Vro=#Z{vap*F0R7hfJ?PP;uO|#WkSk<0<8yf!3J$+NwB>~ctVXBt{ zBIhB%YD?&>hXf*)rNZf~iS`t71g1!N{w^UtD?5pPtc{WA4RyZ-p9gfjWeNWOL62ZcVa!!(eGcX$)-t zRccNjpB6*@B%nAQE4(}&(vIJz4=?@G-P_idaynE(S0Xd}xXA*qORFJqHFSh%$^3Lj z_}7HnQ{r1o#(*-6Kpmr(z=)UK*&8^sS_YiAeR!vC@hML+AR@djN)I`kenQbvq8t59 z@)z2=EBVe0F@f8u0DL+O^om1A83$4K{`m>~vn<~OeSK{^jf;$!l_S6}Lk5Bt6D{(5 z)=`SAJlF;}ua$4J*FaEh$}+=WiWa3hK_X_%-j zrjs8(Q0GOBx!Z4H%Nlv=?1*U$6)Y@+t36yAbvN7fX13iwru*$|7DX~%x*6~9kD87P z4|L~K3^!iw7HnRFDTxIqy%Jm_6eQZf%y(GrVdF!w|B-G8!NxkH`h9eU<*iT+A7;7v zp3QP+Fs;)s$;GGKQXj?X#d`8hmn4eaee<|OZO`jv0f13wzNTuE``M3u)Yado`^U{J z0R&NwDPOfR5JnLQ!5Ag^)gb9G9jzWv6=^%V-fKtp@8|!T-CWPM53}w22jw!5ID&JS zH0P5PfS!$)=?;sYQF*0)8qYV~{bYk1(@(RN65uY$AI{G3d^JWEnFX!Qc(?lwlWIDF zdGlsG`8K+Ie+=3z*(6FejtMdh_h^s}zBz0+>)i}%)6g;2qe(}MN9I3Lv8wjSf{JZM z>G^JQKbsyFvu)H@7FTUuEgLTG0$#AeKFk!%P~d3$Fh_kOa`R}|m8~b*&zJDxpeoXV zUW(Fqc)Q+yA8)5*4OWZABZRsLCM#Ra*?zZUv}GuD2-Y}If;NB9Q~OfT!->LvT`(^v z$(eqjWZC6Y#a-ahcr{Hk%vDg~n@nF2k|y^pOcnLiR42?3b=1r0e5ycY4_XHF!pk`( zHF^!#9EHmka25cc*4sw!*2KbQW5!ds9pI7x<}rqvh#fK3DwG7s;9$=&wZEYka0~Ir zzKGFa_G2=`*Kk=VPhe-VSYrxus0c@kn9E%6V^9~17=yZQ7Gt_ua;ptjr@q@pG{8_7 z8Y6lM($$3BNcP!uw)O-bQ>&}#%J z-yCjlXWLy0u^BhXDI17g%zWM9@c}6~diTSfH zf0!TA)aLKMMDpTCd`O}b`%rtUF^Cg8`>oGs|D}X)5TD;JKL7mgBK`dS{dMvs{qpYT z3pAMlKbR~xG=cTfYNlSIiHA3E(@w^_y|K9LGg({qt6JLX`|^^OGF@Cg(A@ul5PH6u z&nC0)^WBUtKPEm*_VjixRjT-pLCQht=>k4!{+H~k@LW&gMEJ3X@dB$O*#yEahmYiM zdna*Umf-A~FJqw;HZLp0)PV)!F?@;LE;q7|;QCOoG))(G3tB2b<<#&TK+2rot&*$B z)kki^ReU5~>~UffTx>}l(#e7@Gbx3TH0f#jes4P7 zkL64-dpG?fj(RHo3Tofodiy9Otiz~{N6hf7J}%fHCwt~sLqwbK>)Cntby(MGIw$D> z6k|^O#ycs8J7coKOHtKJv_$tUcb)F{_F}L1v;X3_io-y&k^6I=zFR$}1iAC{_jEj& zfL3NO41b9sd+B=}UBkkuFdU*f?l+3d6uuCxN4FUySDrlNOe7;uLVD?5)idQ&&I2d5Y=M(;VUX?f{s=6-W5TUkEKsC$504` zt@Matjv)G!zLX>YsDvB+l!_9bPVT>@w}^f$E>25k@!f32WZJ&hrpRK=s^5#0vJiaD z_ZF6D*XI3L(nzOeADnSv$3xiJ{JY-u@W_wu~w;IVlGPj0ew_AYS9U+0T78*L&e@JU)52@qjcxd*e}k z>=p=EJz^~JJn5{`Ip(;l6>=B9<{s>FUdEaJ_b`XZgXr7rnz}cE?`ioWtZ@kKaPH<# zYAi)VJeglYv_DRA?6X}SCjn(}HPNa;Q~6P34)|%t>CNnJzJjW{pWK5x(I+$5WYzBw zZmmOf>DxePV8WZXp43$G4C|Cr+ot!k#b&l;b;_*GX|siHN7G<+*qQ|?bQR#`qY@l_ zGgpnH(Vmyio+xc1XfDqG5{*FV;0E~!ZBx-a9#Y`^&((K?j;B|%9W<{Z#%lf ztOeW0QgZ~2cYc7OFT*a?uoo3!o^LeKVG?h83qY;4Yz7B7b&lz)?c0jCj- z(D3pP)d1k-188`YqJWm7H|)T~Ef~X7s`-!SE0TakqGk2a^Mt}wwtFB8b(1c~-{5q* zhp_@~&G8+4moU^P4+*R(_>~R?qm(^~N8mAB;i(u_4(N|5Cq1y~JvBc`m+R>q2{2n3 zw7)#S`_@C6!Z+p)c%g$i@3-sksVRucE!)fEgTpF&0Soh~uL?X6HT~Z+!S;Wpud$CP zMDcFV#Eq3)OhRT$4+EzzE&(yu9-=7VX~JjE6qU{75LxdCRdT<*tokTyzjw?ng3TAY z16&4d+JmYSfT>vd1GJ~VUqds>j|Tdi_h6DtjMo|oPU^Qmo?_IK$IO(8c9-fK_SCiZ zmg9}GlAx)85UOgu77FlYJl~E!l5z_Z(_uA->pJr~d-0gE<$Id;Pf@mHKO0H(1&xqi zfH?m*@QQ7UQv03vnvVy&dYnd+?hW$+)5^@6B zpb8Y!*~&vA^hGDi#BoVEO1!p($>lnohZ!{dm2L6O5khT}hk*IIuvjhZRCa$x?0)$} zar~osV$^^q4sllvj%eTdYEj+GXG!JGkLnq>;WG_t@u03GtBfso2SoS583h~F7Pf(O zx?95yvzjj0P*4pR*+1KDU?}-9M|c4LBI9l$PK|dw6iGCsb*20b#euTwmVjbkK?M?Z z?v=ig{4esy~_7^rnxl7E=XvxW<}2-b3$&wK_Zewj?Ecr}Eioq7<2Sm`q;f|UM)Ex9nn)Eor;j_y1 zKS7FbOtEO=5Fvgpwx)e`*od~O2+Q0jpOGX)$4|cSm&V7JTYPczT3 zpP*@u;I$sw<_^oI4;LO*>3Rm_BC&}oQ@pi!3tld(?^7& zAtr}H#`y560c0(n*KW8YiUxTshzs?kY;c7*T<~DXR-^6~IS=IN*AGfalWPvM$D<8m zdrkciT3xVqYmbaayelnx#j`mTmsR$bXQJx2a@p1aoc^L@MvbXuk8~KxC?-%mMkUYo zzTc1Y`_}X*`bMA$T?88qjIo=a^7xuOBgbLL=HjGxaWAC2>Ggg9c8n`FS|mVz9W~fZ zQ~8*q&aLl^=OcMuU~fnKqU!Wyy?GqpEM{88ta{9n9)qLV_!E9CJlcQ^s<*^LQKX1O zKfd%ZWZjP!x9Og*tY@vo*XUv#@fzJiymos)CPVYt8opKnuEK`wduM^RkOEV3sKsYX z&O+teWR;uLdg)@*#qga&;gtr%4^`Gy{l1+2@l!-Hhbhmv$j*A2J9V8-3t^))aF=eZ(CiwYnCkkFL1&~ zXJvKw>x~Fj1|;LIHN6S>hu{)?2_Qp?dnUs?4xHRD-V%Qyl=2ODHl4v=zgAI<#*($p z?0X3p@ACb_f>Nn=@qFqbI)Mj^B*F9G znM&8{;zHCL+IpinhlX+_2}^89U|4Jv&Eiks;^$)zV88Wpy*ad>*`9`2Ta7t#ue1HS zIHN-Yh%1{#VfIOSFGJwv9@2nMc|WT4vL&Nh%&hkO)(|?XiC7f~FOoxK89IF|C4r1& zC>7`;_qpa$o%eRQEtT|8djhb_CI%6`Yofz6uVIOMz>}f>r<;U$wLQW8_^T2h`S;^6^8T+r;UlscpiQPaXVRv=#*l&(uby zr&?yFUY0*y4oq`ci=vbd`fJ5<;|_`V>CJkL)VeI~EV;zx$YFt7p##I!jVLC#)<0~- zT`5(rg~6bIWnpPDT$f~5gy>|ONs%rE;}PdwAq(_oPWdhLmXT+dqrD~x+3Q3nl>Nij z-eJ~*4Dp2xQZhADD6z6c=h&qV6S9mdraY{k?Q@KijuwLvh|@q-jS2pEXCQ|;i~r4N zIp0mh#u0k)jF!ib*^5(_C1VcF5PPfHetJXElN?}F4BDxF#}~R|=?4lPLt30Y9Rj|F zMyyG8rK%?lK+Zb{p+Oh|XXEJ<^*&Iawh<7DuhoP@91$D}lQsZFacVy_f-{u^1o80)-mTBO-}Y@j4nnE&t4XleTx z4aTT$tP5!nkF?oZ`P-S|0aA@9zoM+I<=oJQ>#V22ZU8gXO!uy&_6or6PadNQ3~e4? z4)5q}cYoMV*WXwA#8iTvLHadDctET4g=60BK(*N+3>H!BI&?66+N1|1s0S9n)#A!X zkUk&>TVZF-87|*n!uWXkkRZM-FjcgJWC8OWsW9N7sKH`hF_!tREHT@Hf7RQ$#^bjz zRlTp(t>fNqgg69Y5b>rER>oZey-F^5R?EhLT$9j_Sf{M96LI?w?+tr>e@P24I|K2=@6Iz((Q4f%r9oUHCd&&YSL(lV+hdg|+wzuYVEu|q>q{ObYOmCadPrUmc6uevwrcB1 zq0`f2N6~7%pWi;FC}H(Yr1cK{Go3w9DwabLL)fSB3LdFwvA%B zp(e8rGIYJ2Ar)Uk$Bzj@7-)wGj>vsTL5C9@T>PDJylSYJ2$p2s!-x(#u_7$T{p*N#LZ zjX6!BeWpl4fdkgrVkxR=Ih1KN?@FtBM3d@S_T}9o$5)H9reMlzPBGpip@29(0qyti+)GxM;!QO z42`52*{j%Eh$_fy!M&Eg7T$dMYsbxo`irRfgz{0~m!u`5fM#tT>Ix)23VIU+~$Ms@jS>ENfG>A4JpVfF!jp2w0zF#?*tQ4Ogr z%94xNN3oi26yv5C9;BdTjMe3_kYX|4q1<(Nu;nr&U*?0M^0Fr+sW>Nc#Zm5ezC-nP zSZV_TJynJ}u3R-jm)Kvv(}@a}r>eM|0iK^6wv=e_n4hk2 zqGHKAVchSin$MWv48>AlFbP6GGM>dMsERtmErA>R1MNRX00oCTv0XV|OulrsV`qL_ zBYHephRBXNC52P9J*7_r8n762OV(LR4u_<_hYjek!QWAzZXc;5lkJ>p;f7EuE*T_z zf@N<-11kG(OdWJ}l#RkxU$sA^0bpONHQ8>s67x^DlhuCl(`LR25QegmOC;|SSVU(~ zt>Ed%A$rudnELWaep*_#@FDu2GBG^nEUDqHAV=u!XkZlCms;bJr|)OWg{MO|qwt_@ z&^L)maS7qW@;nuf0X3Z-q|$l1+|4FpeLd!F=-=vXkJ9e$fJzHe_g`}O zFgSN2?+@1-qu)d1CDTb=FhxBMhM;(R0(2Ww*qsO>V2>uXQl&G3w^Z+o>DVrz;yFIc zN%c_V0Td858Y=E14i$W1xtvYskfJz>2?5Mh%wAB&U|S)(UJm){;qqqsfN)H$1SL09zUKlEgOa^}>h0MKYnn zSJk>8)c8rnF$0Hj(b*ySQX59!*e6=TrJV=EEZTXnF5ZWo-HM;r;v*%D`xwagZ|B=x zP-UvMqTc4Pqc&a;4on(N#!^x(DEZsHM_Pb6X2rc*v=D=CBsqaaww+%^m^*G@a79{1 z))=J)c3KN2wGrC&uzLf`q7tXO2BnRZQSY!pm2~O#vpxh5*tuR1y&+qXavT!9bLdt- z>kJ;DX9z_ELJZ9%5R`LeyNie11n7&s(YUXZ(~LnytO;$wT~?oq71Uai)#XM9OB+h} z_!{>>+PJ6?Upu%NyuoA|;`&SHx1)dWwoKNBsNc!K5s-hTNZ&@E2fjb5ZZPhcDx?sO zN3-EG6cZN=JM%E4JpvX>LLQ0Q!G7z)o9V~Jds+4-laS2bQ+(GsvKUE?&2T9o*p;oR zf4hYtDuhI3Z+16fJDT%eew}g}l_XW*$lp?OE{gF1YYX8(aO_o)jpgd~}mAcMmyQ9juIoD(sZEEPA-85?W#Lui;Zq0LEGb)9ZoYGa9)vTc{kY-KH;W~gXbTQ8DXbW; z(q@k{19ft2PrfF-+l5rHt~|aXxko2TaiQSigv?S71iVm98u4@g5M_)#Rvj}{bO+yS zRU#Z8AsKc7TAaBG@zD}ew*No_Lj@I5|MU9J7GWNEJ~)033=W_tZ;keSmJ+S9_sYmq z$$KbG9dJ12FEQ%}=l6QW{1DGyj$(EVDheHjVJ(ys%^3W=hAtdjLY-p~mvZHz%{iF1 z>NkRO)Uv$g!)E0ny~ef1g9Z3?<8i_yzxhx>YflaZ*S#FZqLO!lN0Pr&X5gF$7**p9 zmra7p{`b-{zT4KR;X?Im){H!B#crU+qOzA}))?RLFQzi-DI=pUmwF(6!_>cx4i!pz@Ft-`3u*4!?Ps`*Kx z8_FrOaE+avquimG9Z1F2Cvacq2(FB>IaV-?ai+Z+s`os<{Foi-2`75a2TCIrj?);w zdeexZMnn{R4fi+ICm7lpP;Is+6hUqt4K>FmAHtiPuO~qtsR6IJPr(-)@A#UWWL~Plf?PH$avy}okSD57p@*oj2cSlOj_S~pWq3e(4i~q2ME9C5PPwK< z=Z#fg<~e*f240-+9uaab5WCMnb^`jIZ`L4=6q=LF@58Y7b2wvRy&aWI2kuuMBRyE_ z7-g?A?tMP+37du0h<5vZ3%+^Ub_c=~fd65b$s;w;wJ$-@85wb@nvj&AXzkUb!Cnu_ zfrwdLPLy*)nZ+-!ojjywDkUe@EN_wTWZI`2pB`@8!!%BAmrq5*a}$j&q0_h7;SF@w7$ znetKKYjGLxmp{p`sp^2|6iOzMS!x}N?Tpn?v(gr~rLszF8zr&|b(d{fG+$cxjFyBO zxmzy|6zGQDg775mP()*l!073q$8E9B$Ga46;2YGBV;3>f-lf7WkO5<(UHB`5oM`>+O8JiUv0n%soc)!CUkhTEd6${xdMn;a%;?Ssw%U^WUm#lQT4 z@NnD}LbVkf##{y$<`>i~?Cz*<6@m_akWfze2s$*MJdXAk`zX2Ccd3SSTDAjN45$>N zF<<_4zMx1R&^Vg`JbPmDV7c9mF$lLFp=T(Z@}R*`KjJ<8?Ay?x^IUogV;@Jut=H)Q zbm#bE120=i8O)5;gn*Vm5`@}0fyBIu9zG9YlLMHrRZwx{>pD>JjN+ne z80m9#eR+^JxaAhKGOQ?Jl3pMM_)Rvr@jV-aqlgbyRP+GiCEeBfyQNJU%R}FyEsW5p zBdjWgTI@-oZi2x5)%*c@e~=nzLUXkG>lp41H(2Qe1?)b;`3Fw{aZJsHvqin|u`lPc zyqufY-{JXUw%ft!J6^r&EiTjv0iCa3OuE7PhsOHL zHj7Lyd$aWqKRSa6QDnr7(E7=54tE}1`j%0s1FXR>@=CF$`h1CgX~?3%&cNOQ4cXPEUqH8bG#o*{!OhA z=_J&It~nqJtM8?4E^PK+2rR6obSQ;K;;j&X@MAVXdkbv;aWf;srXzB|Wfppxwv*VFGYR0iHd1DHK#=VCH1yl!OWm~=7Iq) z*GX-DJ*3hj8CB_{*X$sJk?B^_0y8+}XoGUO4iEFP7l>y^FfHiwdvjEw#X zESIW9m~(PkJ??a|H%4+9%$X}&%Gnk*xnNQ$sG4;k;1QS1Gw}#}*4IUe)%=<>ISPun zaiRPr>xLTtO@+!t$?O4&3q!1{J4NL%}8!vPn{R2lRU`WJ<=^krEqaYm5X_dNA7iA{c8dSD|{5e-C@;rjWag!W8 zPn8`}KAk=agYx*sDu(t)NbsL#1lQKUyk0H2=+;{>Dmunyw*b6idiUH8^T_5&W)X+MDTL42}?*8@PcmWI&&2^k{RWm#=Tb%+uG#3vMbg-Eq7x`-6vYYbVa}9XaPzpI?by5s4GdvrN<}v%PE)OF zGKeeoG}%#Mby-SiKFR<|g$S4ejU$pIlEHp_X9%wP2?>SuJrPC7dPGhDr6}&A{v62x zi!r1mDHzE^qtcoxA88I<@gmJw=S2){&WQ9S+0U`>`sVeWf43tLMoRQ}pFYfI-&3TN zq4qc!Es^BmhBKa5(=x!#*`#%l)~LXkM_HC!OL0f7V~bp zd!WQ6Lr{;-utwYg^(-xaG@&+}lY4}Ct5~Lq*s-k_?YX++-DEyb_v^*_JJOIQ#?I=_ zU}$U#%(dUljRT^k%i7N75Y)$u9o@ycLn-%+bJP2CiY1a;U4Nv0ntb~B&&6btAfPLm zm@ttA9y=_6d%f=>WPYS_hH9@V`Sr_WG2P#8QhJ^1Q1f6|&)s}=!f-M`K49Ekpkm!* zeTVG$`#CZeKxCJj@pd)>`r$f<0gM32nkbndkSG!OCO62vf0usgq}M;EUrhg(Kw4A$ z&T{+%jcM+XLua}bt%p#bL)Zec7a`*=m9ao+4VhWG&O1y$ahdwr9m9B_7Tz0Xt0i@Y z*W$T}^qQ>(Hes2k>&!7kBKGzcw_3bkRGp;OZh9Md!-BLa0OS7c0)?VYKUWjs^1#llPCl4@R*1xvJbFzo)Z!q+}5!EJkV;E@T z`6lX6tq_U;eds~4rvT(0>iBH8$Y4=?^!{SmOOio) zO>NbZt9QNhBeyxjarXJakvw5oI7@ehx$GA!;J~XZzM+nm zPR^v2=#QLpDy+k%)g@DSA_2W1@Vj8_;~P|B{y=ZtkFC+bMAvBG!&iEy4#Lu6Y=osZ z4N)`$N`7@|dClLcb5+&I;=nU+k+YpLoWov+U27*t&jcMY1hNKtTIc!|?=O8e)bOl+7{lVUChChfpLos1dR^Xn-@; z2~Xx;yoxbR46k@}I2muolllI!&aV9T!rhUkqW1Y0p3-=%FM?SIK~ulNOZu`jdC3wp z?L~F>lTB=6jh>pm!cJUo15ig^QfT0Sm%i&|AwJqfW{x@BY}fnsWUXo#dfp%0j*(mE zJrG`$3hPsEic-+K-|sgHTG2WEqWfYXBOj$k9(z>2M$Wj|i)Ty7jpMu7&wZA#mF6R0 ztT}hwMZ<;hVWCRhk%@7N;`>yHhiFW*^+X*QGUmt-hwB6tzew2DHRPm+h%x+$qg#V_ z@;)QHY%OCOFo;A3G#(_nE~`qCE-YeLP$s4sFP0sgLE4QsN~g*RxABIaBf6c>7D)4? z(3AnR{~6jDHBngV4}Ofn>CA{Bs%!edEw#8+#ip5vEoW$i$W=EoQKPB(=wjLJcTqGk z*Fu%ra^ygUP8z&j=HY2F8@@i~)8ss0B^7LvF`)6G=jzpw8_Xb8YkLMb>g^E64zryi zai^ZDF_`C~>$}vafKKB+B@am#7YkS9w?B@0O+nU!!NsZ2J9qM9oZcOf;g*hXP}r;N zYM$fV1p{#KAWDJ*-ZvUNdOB8_xS+v0K&98sdOO}e+U7CogCS#_XT){f!ZZY_t zR!S{w3qIW==YWTw2464|#-`&-!-61(9ei&<6;)xLK7~dH2k)V(?{`z}tXNDmIIK`Y z8f9#Oa!Yd_wtWZ{?G8(>(>0s!MoTnzClfjB<6<0#WRxQb!c5`pS>3@lKY_B1 z24bjWOVyx1<9T{ap+{Zjq=}xnEEd*~fS(a$Se0*%Jm9+PK+QgB(F6PNt-Yw(e|?5h zz$k!n47wx{c$bj5F7)jE`Wrfillv^0XxeC9V@q&#-#7x^2H|dcRO|&RTugI;J)GF` z$b%}<^x^K?e3P!f@5l|5Af_E|8?HQXz3<_a9{6K?7N<~^;X~GbRAoQK=LtHW&MCN< zBk)-5$T7JtW>m@%G5NC9NmA`vRp$w&VWeN5k#TkA)hrFHs5e) zJgyT`dzl8#1qO_I62+hogfrcg6U1U3a?g#exo=Y4cq%y2d`@wndoG^|Nq4%<##Hia z?ha*B(YcHQ)sA=>XGy2tH>CQS(~3~9-YkbIM$84jBAqqhpWX0=fHPshZtMs4ViT>X z7-;~VEbEatK%H9;CJ3%%_WFI) zk~r&OL`(`kJ~ay$jT%+Fs0`^(yu5st&ha2fv~LTGJ!u%rc~Fi43R^H^#I|`^ebdAH za0PkSSv7i0|GLo$xBzl&x>U;7jc;>;jG2KvIpDd4!fw}e7p0hVaf7O{_%A`H z0T$qj48yahGH&2JPJKF{+{cqj-Co^#>xCj&@ea@2q_}c)cVR*@*)DE1WQ~cc%`fX*Jw@R)cG}KXAq61+%AP6KSjvbe%3Hx&dZI|=y8nxh}0K67n-?~49VkJB0t)>I#!fM zVl@dY6n&<`2J?LopM-+4LiZfc9WE0;nT_=W$_(NI%aj=dVKWemuz{c(gpf*bDHKkQ zXOoai^SmwWP-2SnrUcp)>_ruCYSPTQe3`HtUiLX@mDs@KTmHUB6bTQ&4UsqF{e4|M zy9fYh2qr}h;MYH_AgUx+_cfIK;^g_;WmvDJOXJwiS$4f8;w)+g^aW;9q(P78ThgF! zQ0HqzWk9ZOE^Z~`cN*777tm?!)SsHTs3`PZ-1Fzk}&TnP9Gui9E3uVOo-M7Ab~Y4 z0TTuMcoyQ-Z18A^&}j6?@1xVm{dy`U6Iqe|;zd`| z6_`+!Px2YF9SJ}w%jaoGK4k)NiRj^**q{(+ZLwa-7m!Q{2q?YIIfA=_CzA}dp|mO4 zjiMJ``5Ct+*+os!{Ds$z?a@LE~aw)7=bSH-LwM6ZifQ;?}j-!6tu2_GweDUXe6AhyJc z+=_w)iFURk5(9Icz^a-nplF+z>;nok zZ#-b5rC3%7Uatagx68efI70$if@-JXAbJdPv`tGfK+E?~-*$NJW{t zlynG48jh}P^cpW7J0tJsm(QQxqf^TCfDE#fS{sk2ISp*E3S}eET)xJe$cKcP7j-IN z#pw|QFmE2CapVAG@$}kNYmBE)qP}hQsb^9r>sSgE)o-$+jL@|)RKBVw-aio+U5y(H zZ)VAcc@!_}`1z+s3Ykp`hqnM@*}2rbZ(}%yg$!qq>2ktk+)ttitt?{%I{T1#syrG? zAqil&Lq*OhwRT#N5ym9HS+J&y|DpE`nXc|Dw!yRZTUPR7_LkTwe9@uQYXiz7<#2L< zs_F1@Uq7d~HuW&sE-8T6GOY6pGMDBVF)WyNUadLH~d~Pa~SyW%3heh|* zj=4^?}3p*M8FO;8VViQ3Z3Kp8LyL;)HQo5IP6O>>}Y4?oedOLKHa)1SUvT&FVH zPuJHM=`UaYjc%ylvwu(a+XX)}S_e| ze3uxlv$8YRQ1a{`aF_Mi*kB`A>GxQ8(z~Zk=y-Z2ULajJDQci^Df2%A4DF)EV+<0c zfxEBHoX>8zvsnq$DmETCkI%eLR57r{89ervRjg+Yv(0p@^Xm z$8x+c1=w@0qZTMH1tuRANXlT?gH}aYSP&(svNi%tM86M;48i3TiKo6J@t`nQR?HSL z&*+Q&P)-Pb0%ux*Ez$pxN#ofauA!>q%GsU>0psFm3z;wMN{*#Cft z!K>8toN4*bqt8U((;L)*Cm#+!LD0nqo@FZF>fwdt#s#vejOGXw zq~*ARwvDqWG(65S9@hwt4V_Tw&fqB4)?2l!XgS|aw7x{~3s$RydhTDubZ6Uw@kyez z>Cl+m5lNv#)};#h5a^qfvOZwPWctU{i3L}BUJAhR7cd#p#pZuA643X~8D>YNU>3zW zZCP)${9a0Za!U?+6}h+2zkz)MUNQ;*hTQg9kuuy&c`7 z9?U&V^F?|ccOc{D7^WIqu@@ucO zxS-IZ=PPuspQGWX%6-G;_VfhEm2E^S(wz51RDv#yQLUEMcY|x8hL|}k&O*vqg zT`XF4_C6jFNtmz5DNZ(Rm3HX!(dh~7H}V_p8y=K`76SnQjxZ~t2FX((lZ!SEJxn;q zS<2L$!OLFUUI``?A3plxH-$wx2`DbC{9<{YSBPpDBT-u|FK8$h8DCD7lr;n(NDjH) zFLnqYDU_|fBGN76t@TjQx!@)Pmp|$~j_kQ!qpU0m2|TZ-Ual<8GrPn{AsVU-tzb>S z3X!UvFj7E<$nIAo;E;x@#kAu_C_m2VE7HT%9gWV6UrxQS@A$$7FHgn1B{!+!&Ev0? ztXK)7d0Y~7Crj8Q5s_<-D{O2WBGq)jMoY0cjI7%(&1q8u?-SM-F0CacnS!B+MM1C3 z(Yj_4Q&un0Y6Zo7DEw+cMYcz{Ob9i#m-d_!TRZl_1lq%gchOmjtVZOZ*`ZeQyrfMl zAaGDFs2P`S?ZPU{wo+BTM47~_`m1I5F0_xOs-pl-ahv-dmA@7^D|)LC{TacGi9xa_ z;9t2OSD3!--F9I)lzm^pD5qd=Au-^?mJlyaLUxd_Is?H7sDX8WbW5laCtp(3X>CHVcr{@*S3b{+Ze4NzS8@_s3VM!{ z$mV(-C!uMSaT2JDoZks9kVaA!`!ia@%={`)u)bmv*{yu;6%?`YnOERJisxQI1_MQZ zm?R!2{Hg?(kI=^PLv)l#pGkHx$2FRxPxg=skyAC~3ZO{2q)1%+j+lnsJ^%X?1juNZ z{=S~3oIwV@w!4P}h|%7qaVPy%)r;jXr~Iw{3UN6wJa1?8hAu+F!<`xEtx9AOm6X~L zl=y3)wNUqs**-ZH?FSzaOQf7j~%Pl6mpHF@U z*-WqD3_SPX83dOM9}P~O;lT6GU&O&@o!^5D&pUs%|5n-3*T02Es!Br=^qT}ljukEN z=dTR}#;>;Y{t~ml=-zXB@Rz@jIcsV|NNkFP`Ro~EdQRa4V&E+*uv1xv(ffxQ7T{k4 z)_=6!BS+z``Lj=2H!*(e1A)?N8|uVo>w|;yw_g>dxdIB4vbHzGwbe^9UHXQ zU)5gUr`P|m_WHu2abqAn;elrq@UM>g88v~q%jbl4)r}${~5&b z*8zYR(#2lO=8Ht{uXFMq0`M2we0K7!irkIy`saXD(@m))7Ni}o)H{`VW->}n$zw$e6Fx<;IZJJ8bfBAFB??VgfcjIetUUd!dKO}rf(_&%_+Rh_=9_P5{-Fx~ z5|3Cn_!Dn9WHhq}brl&MrU=p?GMe9w*R?D~ukvU^kZ%fCZXRL3r)WpxCl7k$&_wDZ z-V^xh#@}$&MrjvapusdKd6j+*H>*}r$vZUEK{~JG^&^O%B9>L7X$W{$4msB-Ks<8? zy1y;4<-4;If9!fA^2&542=Txy6VV$sqQ5z>oSy#1R zsU&H!fI+daN>-bO3uqN$J4cuAk2!%QM=)EZQ9a?G0KtO}E^mcC91+ffrixb(##JP0 zGc)be-c#VrB@}=M6hjms$s9B)-~qx@5&R1e2m=HGO;2Py|a zX~UpTGeXMYuj-d5`m}y`*xz6AflG(nmO^ftDpfBl)$@(LdW|<$@+AI|712078mg(I zE2m~qmX_;>+33m)%8yjagrw}G!oZmK<$NnLYoDO+3dnynP+e%zgjTwiQqdo&iSyaY zX5xz+nx%w0CZFM4Cv?y6!a341g|#MX3!bV*%x6m{?HVk(Yi-Su3L3AvNPHqGDYv6) z^eHNI>4ldrFftZs!(Mojs?P#$@OuuN_YZZBHN5xa!zCmf3IqmsL2I47tz1?RhBNFZ z_Y!WJ&&-9%cF3i}9$VO-(EkB?UZLVe6v_WN|4NbvFTPTbIK%;Gx;&*6>R;-=t%S?k zFKB`f%iAQAnCri7O@}_D`|V*je?U2p8@hU^t+L!KSY907+^eYPEGssgNO^`RZEoqx z^4@TJ!(&k{jILjF`M?eJP@bG5!X7FheWS|lDw$1MuANP9(VTO9mt1`9k_7e%qH+{b z_S_*Sjg&(^BA4cT<0Y;|d{S zcZrq|b1Ikk22e4KcF|kghjkuC7Nx7cbKV) zkv+o>^&L{h^!zinT0da8njyh#w!Rfbpg^jm>@UPV>7T{rE=9e>XWcZURh6hFocqUA zv!M|jH4_{o_%^7N%-c+B=_KPFTBaZc))kbxKq9Z*1|$zz0}y$J17?4huZ$ z+7uVUnsk$!^(!zNLADT&2>pTYasv4cKzT2qKM!eNy)RTqm#>9uh%)w{T_ur|TD%X{ zR;$D@H%aMslyCuK;ZoFNsM zwM}rE1EOW}LjGuWN}oVp42y9TkKrFdJ0b2AqpQ-uHCBMblM$ZFuY`d`>CCzAY*kEp z)Eb=8L={GB@NO5LYmvxmPG(lMQlbqzpVaG`dqH%g;&#DC)#vEJEzchXeDU+r5XT(c!T_fHfGeel&V-%NW{7O-o5;UBQ~u4|WG~ zC?!i(?7!*q^QPTG!IDqsX?nLhq>~>%q9{dw7iuSw&TmKm-XZn~UsCYV{TMoOnqV?f zk#CYgszk<;`vO7@6qhFH%H>70n?l=JS5Vg=k9RnyXa~*B5n}OA^vXk*EI77=%NjmO zG*4X7-I}2ixOiXZQ_cTXIjgw4q^1k#yOQp|OX!UBCmAg-mfbkHSPtSI{_dWi(ch>* zw2b?Mv)-^fi2C>?qECA9dGCC17N656^jtjX4$kA@pa(DIlAr5!;`prFIqTDq(UAX& z;$F8K_u*{=zNn*$dxzDWYILK|0$TntBfL3qis+mko6m(pmp(UHfKt7Voh8W}I-;z( zDH?7r5-v$y6lH3fmIny40kao@s2xITt70{T$d?U@(R5VD#k3Mm_qO7KOjefVx}MC- zo5J8Jayskruz&LHJNgctQBxWU>R^IdrZTmq409Sc%zIy9Z6)=3Ky}3rI~DC{uE4I~ zjaN{Mdf-$k1^diSJ?hyK*NO7nOXGkFfkA6OQP~>PruHmr(25EH<>;dXBjNRX!oPaf zKEj~()>I=#O+;)$5hKF)d9C9FrC<1pnwZT7D#IMn>fO?kr8%MJD#h`$T_uvrZEU{Y zBN8gueIl%bJ9=|wV<_Kic)Z{e$S#{#yf@`z5`=Z&)g*~$|AnPvg6nad5ERAfyTxKX zN$=+f9nhXdL#wE%6FB3DPv&$eGk~>;;s`Jf#v5vlNCohjOq_b^pJ7u{p{GYEPQ%G~ zGoH-%k6L7GDzXmdE2txTI7U=q^qlZDKe9Sewgn4XO?1$3&)3;qj(?1nz|}7)+0Wu5 z@%5Kj6gM0w#y%SByXdtCtRFA9S=O<~VIw`%r^}NT6=P9wGeWgKO!gz}kP}rhE9Nl0 zy5aV0_(~ay#irB$JQ>Dhre$ z)td~b`XRfhdX23)+d<1uspDREJH}G>C?BlXVT7WNZ185|Tl>v6WDw^rHKXQo+0tL7 z&3*P7?DJ=K_+(+TuK?s)C?Ik9O`vhk3g5_rQ6W3ML4H=&+4T*r8&)**Ww`Xv1yB?b zY@!b6^|$oyFy2l7%wnf;(Ucm>m3*&}c6Ahl6tptki!&e1f4*d$tnW5ug7Vp_?2 z$UaN#zE5#cml>~*`e{o1j5&`)@t!gPn07w2#VAi0HOIMjS3AHR2gB9!N^4+_xZkfZ zeHlFyEH4AR&^o>vy6_oeGlb@XkWp&_=&|`ZDdDEMtk6XCL6(x>A4I61MTDW8rO${* zVpZY{X7ukwj4GEwuaRHZlh6j_)?MD&cdW<6!)@Rq7xyWd9eY$g=EX1D#+-a@$w9^R zE5oBqB&`IOyLadc^@)ab+o}%4`t5BISJ)k?=Q$~VO#BXz%}Lk>{*W|{xB*cOGH)+` zeM6Y1c;ib7+|=0|PP>e95fx|))w-4kCQTuDFVQ8Av%(hS(pnGzgqCwGn8KyA{ce{j zL{SH--~u5g|P9n z^nf8!e*K#@w=m73JmcNtZa-T}=Uebxinu~c#_>jm#eD?qbuPGZ1oPEbbPqxWlq@Tp zgT6#;RuHtu2~DY#(=S@@ccu@$2F--w08+93uKs;XdA1&SSePB{ChHCI8%U!OaucC3 z-TVjT1wg40twwBH9%@qcb|n(I2f=HLqxlLn8VzE>WFF>|S-Sqt=>3%EQ9g6kv6R2f zdSmb+`C$<2y^j|}{U33X2$9%5EmF!hf>YOnLh$wEr%levrs>TVJuN5W-QNCqH@{nr z7xri0$Me0PcSZvZCn*TCeT<^Rj>2FVa~P%1ca!_s^spcbNhOC2GQ=BJ%%j^W)DP0b z2oGn*+nsIJ;LkV{y70IlsFoyA!G*VtV>joCqV)}t3v>ZnQw}_`cI-hTENC~`&Nmb) zM=s%+OW98z&c2u@4;RH)@qW90OeqZ>^DEUXfy;lXVL7FJwmEcYZP{s1V|p$o*a- zzSMfXWN&>vJr~y4?atJ)%T|*-T#Dpc+)d<;*T3C@8OvxLg(BD)p&bSRo}j*kUII6i z3!#WRaBqcbRwb(hz+s`MNiIbx^oW^-)uQH>Ci*8Wc4*+U0<5v#YTRbvKnx1paNqFg zjQb$qIC=OIP@7OW4#N4dnPb6*1lN-(9auN8WJUf9Q$CyreuD{+6Hb+S#OH?B8^o&| zqj@-MqcDmFsBPI>N8ey;?vjgJI(b;Ta1X}3?tzmz_1@=Kw3P{J@wj}Zqu5(40;I5d zSWO8l@*$(Xk^yua3W(#El(R-W1lAg>N3T>52aYUx1U?#ALtJpUajDF3y0(rRLe|79 z&=)`bYh8UtkuFj?{j;6Ep_0qnQObsP{^nT0OkZ17J$O*|X32z}(;iPVwZcIq95l1~ z8zd$Dgu*bC83;8qhf|dz2~!f)uiI0jS5^*BRv@J$n83r*1dCIZ%I3w#4Z$P+<&TJL zM-br1j4+j;b^r&uZg)p+4MUAot!qd7i#_7O_Xrb*j(mOx;d{NEk5|#)29~Pn_%U7I zqVCdae-Ef8-KBN~%2u9!#sONiLyt&|(|h_!L3rAjuh&6<*!~=UY}gJ7w2!kfX)*1A zwAO5Gg0t3yErAknjA9##NKHYz;HSXM@|oHMVdb`L@SUTaCXzltmLgbIP_K77-XIfe z^fig%)f&I2tHbgJ+OZ+3mA*5(BfIl_m4%PVwJv)HGC{+4C`zl|igNOzY`5WsoR4>< zo27gp5{nIz#vcKP5UxAS=o~_o76|6UUF0vi4B_b^(_o1#qS%82ZiDC=OFXALEhx#9 zQ&o{OmcaB?u0BuKp>-%ErVn^s^e)S5kR~8ZSRzn9Tih+~L0dPdIz_X!AdTN%3kpo~ z+ITGq5UfMIK%p0++6UsRh=j|FbEpp~=sd@aunAgaLv>-a_%Y)OGt@`i<`Ue1dr zEU}?Q`*k_bFO^2B*X=!+m<6ltY`0!;nwX9}rcHIv{9h_sHy!ji0#Fr18#oVBWygy} z)ZsrMu7hNO*g+(EBa!|a{BDrxk_Gb4GV) z0gSHV4`W>*8d%?KXB+qMx?sc$7=ULJXbI#)GKL`U_3iwQMAs;WpIpAfAY8fK;B~@o zsbBz@i;XOdW#`k~K1F4pJ3oF==CQ~+?&|uekFuNzrZ(GToYvf#m`kWmLrxv@{Xl?d zzMrVsQZ1TNbJfmy)JFZ62X*bGMm+>#Y}bUD6Xof&Af<34L3TD9Mj1Pk%lDV@etG$E zL48=gwEr0nr-ux8DCTe%5&G_^mI%sCNHW9=T;4MlR}f=gx(*x9-J9sU1~0+uumzn| zg7k?$*n_WRVS%h=m5&Ji+su0vr}0pQ$Wgr*gWySSKj>TsMNl^=A+V=32&5WNx@SHS z8CJCE3g7ySmClr?*wSH$dp%)5D<84FLqDo@GKb(BJ|czA(7P zP(OnXwFK9#;s8mqplm!=uv(iKVIYkrevaeR&@J*8$0eD&M$E{F-sZ5o2QX0tltN-8 zwBP}81#wEDXNHhc#=5HUn?eq$c?!!MTMBULLA^tShI}BIiX;&|2h92t*hehmq`5g8 zZaG-QH)|RY>%7(7AX^3TO&Ld}91d6AJp9rE&Nh({oU2tc{scRMKz&p+mq^&JA7?@=;z!B3+9 zjbuyH{ftthAevfzVbt*A`)(i4lEouYST8=g!CgvHL>!@}3Q9u?r$J4dbcc!)uuZWg zYII?&4MO+@C1oNwb3S=YVzVGP;C;Q3cknK|SIS>Sj`{B(A9aG({lUBFugQ*Z1RKC! zjf!!Ia)ssVMCxujPaLzp)Sjolo}PJY5&6KoOzT1K0Q>QF3tx_^T-We6pbA`<%D>?x z0NC5)>K%Sfi582ni7Z+kUaP@%ZNUWHC}yZT74g>&SP zQI=<^1$n)f1)^Yv1hu15+ku-{Az-rs8}z*YI#{7+VK2oFn=j=_SmM#kcmlTQUCZ&T zu{fM&k4j*!dvg(SB)!%oK)XW3#e!O0z_%;>hM4(8;=~xpWAzbp;4oI#M|yBp@Uc#ZvH7NHM-zmeEJFL&)i4 zZq+m(oM-CYAJCg`H~+sGL~i5+;ON{YNh zdl*v~-hT7}xy`C04T|GXaGz?)k-!X%AF?5IJ2!`0^fM*-WX~1PGbCdvX^3Xnd)Tk1 zzz1!qe$F|EoSlIJM5Y!m2lV9I71N_Zl^R}Bi-PHdb5OP$WVej20Q{>PE=5F_shG!q zg(u6dnJI>t(hFx9b@3-&fzP~>pvUBu-=DI?NKZ;7CcP6!h1GUSDvKEXg7K9E! z#=S?CyIhZ%LMi2fy$^(_eE5U;y7`a=;8sf$#-Bwt2>~p&_l8O^i8U{OQ~#FB;WM1 zo>@#jF%8Y4lvI<^C_LiDb9W|%MrTNNTcJHNEJ^?vq0-rO&A(uhtjtU{WNfk*%~zTX zTFP5|Gd9z-asDu?=`woFwe3Dg0)^#6F@%=BQ6&kD11pCere}+7{TGUUrSchU=2rvq z_Wdlp5S!=EJu-0!rJL})fo`bC4TaQX#wOI6%HECV-f+&2SQPqf{*0pKd^Zu}EoC6X zc1sstF=1_bl_L2}35JLg5W^|mLx>QErMSO!;}wIJ8^;!Ra2HT9V+#Jjst~)}*$!xg z%9(*Y?2*zo2{0`&d<}Dpy1iKjj}{wy^L45+7o~O+xN?j{R&#Eh04x{Znhi9^4B*ot zBmfVAz+6J+5!UX)dkCjUjL>TXf3n@<>U+qQEbXOVHoCw8GLA()9;Uxa#_j0x9xcit zu0y22un=pt$;LaTp3{K3mZeH7f*=!wsBZ48(2zj~E%}0AnB2Eng{0Lv%hRZinW{)d zYC#fVk^(T5qW**cY-6f&xHXue2(IX?kTM_7rydLqJXjo2U+s=A3NWYcf=IA;U`}kd zKyMqoEHfLT>!M6rcSKha(UwbMyo-ga1wdMDT8bCrK)6O_I#2;r=sUg^Ed(6jKtd}- z@p7tX$5e>^%&DF&&*xGpD5a&f(P<#nf0C|o6VP&sBrEeYDh1@~0gsZqtB1Aux?wBu zwl|9+s=*330$Y0t4^^8pVjXpDGOEP0c?MiEJuoAAFZGPO^#vH1(6Jyp6W(_CP+Xg)u| zvusbe74)N*$mR4zW$Eyu$Sd^7EdE@ZqKIxFtzbXC8-415v!(HBoLqlQ|DU~gZBOG^ z_P3vpqyYrzr4!>7WXIN$w6n7RZ{|XPEF4Z7kZi3_|J+sGJ=1e!hLfd4x)jHTnLbok zS6A2JSG)b96qx?HKyQu306AaG9eUd5n|?@dy--3u*PT1^Wd;N)@75DQe8N;b<37vm!(a!R`PTxUG+nBr%Tcp zK9Di}&dZp7V`*0YZ_Cn2DWBmn4lRxN9Vm9#F478=zXk%X%M|iOdLT&L0=yU3}`cHA)N`nEz|#I0jyye?EPa0wOpAV!X+Wc5tkM zD#1C9ELH*l|H1PV{4VjWbYvX}B9ZtJwMlIH7`GWn@Qc(QKglW&#(=M zasuiS`x0NIbpob`qb7$ttRa+OPL>J`T2d)+F{h~1WNrt*6&3w~M%& zNv_4BsW?*;f;H_tW9YMZni->nvt?!`T5T@B)|-nTOj*9FT~yP}Vz|J65_8XXv(0Wq zFj&);UMK0WE2(c{DbYKD!8GLF;zdRL3jXxGbnC5j)2iv+-bt88UrE_QJnt(Uo7H{`c z#j&;o5T=~Mnyi?S4T8y5lkf-J;YlrKSAppC?KqMmNL3a=_QhrA&cfdY>j!Q(qkT&V za{wFDNMx$v#&M*+qH@L2ZRRIV*HQcw({((;A#PS~Bjs*|%yn&0=mDo1fOYimpV1ftiij-m2@U37$Dw?K-bicjz&;M&+tNV# zEew$ID8rb&KR6SQlU=Z zbnYI9e}NHZw1!iQ3s>9w__8tX$(m??+EkA&7o({jNh*GOlZk`LsxSHLuaOi-l!&j9 z94|^9Cd=A#S1&V&2sT$Q?SQ#kjGn2&ib+bm1NwliFL^&1Pe(X5jMo5t{zFK}##!&? zxA5kYXZbmqqME4QTVa~VsKx#BbmY3TKguE>{TQmv%STz{Bb|Y@C-+OiMpcYE*{D2_ z+Ng5v){FPAcBA$rkX=601Nh6%my&-MT}u94d@0ed@-HPc_wz0#b@$RgeI1T?`oHao z_@Z8EPn4=2=Hyp%mjrdXqF9YU42~o;thM@;)YwvJ^{WrN7&~SfR3v#7;vvuo-k=>d zzI zY0nEnMc(OoA%RsK9ZwosWyTk@!VjR(OT4{V_Gb7CD;CZM^TAibEv`P0TensHY!;kq z#lz-})UYFcv}K2l_tF$w*8L&CzQ)t&PzU;Gti9gdmBy8`?2_!zzpggmG zy#5uCpE|2jAd$#^r92_KIFT>Hck^e(P5DB&kL9IK%u5f%)>5FTUx|H=x*EdqJ&8Of zUzuD;ab|m?$73H!uGg>^G?m|O&g42UFG8O-BJj-Kje&NoC<<-b&mjeKUA?PFxo@eF z9eKtBDZzm8ogd`cdn*`-0F#~1j~L~z-p&M|lhzyuyPXNt`}znyU*K`oT7Msary39L z2n=jYRj*C2@RY(pU`vT8F~K}GWID0;~J%$ z*PdX@Te((x<;YgoXw7)(PTzHU*4!LxPqJjgU0xCAC>{uIq5uZGUuGc#s%(H_salr4 z#QiwRyeBXuLIjAnU)kF}GJyAXZR(V4-m>nw$VY?6ykI$ln+rGgzrZK3yjgoVRTfLl zKY9GmK+Z?w;ov#OC49dWpOdCOd{)MX+YaLw}-RLoXo8fD5Sd@=2ub0|$I`?+j zd3u-8r!k6fiy&I)aXtaF#TFzFu{dd&TE1zGY;ewq#3O3y=+y(BHTtrffn1Rp{0dZa zEpA`4O`t45XU8sMFVe~3xK%D2m9*e|9G*U+dEhS2TY_~9^piNUHWffDamgl{-=K&Q zp6ud?WvpIT)ip>EFc;s+_6?_l=VX~P}?3Xrlw!nNFucVD@x5<2z zrEoAieRH`aNZkwX%}oK&2Te>r7!z?!KYOWdqm3v7O|FPsT1~56N@FQiBS)lnhU5Ht3*NT zy{UWmU}p!p&5(*S9HVqb4W;j{sEt6CdxL|NAe85_C9?Bl(6^o8Qm`kB&oxplFh_4{S?JlW#$pw33pq78PVr+e}h5jVL!eVKZ*mcPSsh;)k7x>xMRpE?e2$INduzZrb zEe^#3-L~Xvqlc%PL{{6;KsZ&alIRA7PFYPrmp^9LxWq{Ug-B@3)Hzz?P3`{)_H*yH zpN}m0gYJ(w3VArAzV|1JG`yc7F0q!u+cs_~$+)3G1UzX`;3h!;TC7IYp)If4PN#^ltYJb0=bOnseTa4Ro^!LlC{RxP+)r1)KHNWK5{{fp8uI(kGiRU5t299 z70wzzFvmH>$dltwi zt;R*`qTOj+$S3k#qup#@G`j5;@F_EWu65REbedK{R))oeDTRtdISElzT? zc^Ttrt+@|GLu%oH!4)^dg|!Js5NS2J)3WIOiHv-rb+s6J*>m8R4ptcX#VUw>r_wv1 zEx#0k^m-F}0l-n?O~rbJ>Yif&7ymAZEszq-`Ec;~L(mYh4lAr8G7B8$`@vMoOG7Wj zy64U;EsBu9e=N?QrH3Z4@Yt)YMLPz?1I*vt7aUq|%cAR&iTrgT?@xt2rc6~IOc zCV3BLJai^%II?x)V>swg8a0NZj(o)zvC%_BmphVp=6MH#ZOP$cnHo~Z02o8+T2Z06 zLZaG1=eYyPo#lqOcQ;LVZtGe*<%l5A^tuOii7hRJ5iCPr5V<}RX+Y5kpO(TCVy7WA zK3q+{K5PKQlh9eWHfj);JDt&XqAyS6Rqp>DlR-QIPyn!}u^Y?!q{Rs<_^Q@N z1hS_zz7nnqS`1q}_N(x2@yNBTU?~cA&^0h}uWXfWLS!@0g=HdvTt_D?r1+9SJ#^Q+ z3NkMha#H_LLBT1YxS=*_8;Z!P2Z#ZLPn@~YA+z;771J~-*p(@wBEdek)XG&-ve#>o zy4I?l(!B_ zH_g{DE*ArJUx8%K!U;9PKN>+wH*()@U(EM7W-Tuy0*bc3Df(ANt+H(J#q4KKSw(qaAQ%WNvT~kmsfU_5O4oZeWs#MO2mN)eN5~ftNfng$SB!w6v^}V+iOux zq_8b@V}wscp%>`!1kDjL?7zID8r`wr_`LZddsn=-X5W|vDrhF`e>}y?QDzO%(hD<1 z*8GyelwlrBCtv4ySp_Wp@qV8RH$@k1b*JqYZWBl=+AWt=YqdYLXicI>4s6f_S$2l^Ki?UE(qRhX)5_FJZ#YomxDB8Y zH;=2iny}*Nb0WL^47YS~>4Hu*BrLb%g39XRlNT9LNU{rWpp*Hx@zB6E7tPu(!R;Te z#k22}GB*X}%ZSAoZg17IXD|m7Z|I}twF=a>rEuesJeZaw_Bm>sM>TK6`sz* zdFyPqmuvQD6y==;YhT@MuvS60{FA>!boVy`S8dy(JBP$C$W~M5Z)O1xwNc9ZKHYvP zABSU@qHRdqV0CtidrwYDxbRwHG?*f#uJ1W z>*2$A^azv;XWbK4z1)Hm9otA#?6&`nRkHG)+C%m>DSfGa3i_abYDt`juCLye8rO2J z1zlT(b4i&2TRq*?Z_~bl=6JcF(yzuHE>;1gSPxedspTO_gqyKU)xa9rWlzkbfhlD2 z!zLPD2qur-S+LL+4XEgiMCfL5A1&!4MWx`}UZflvlbVQ))*b1udpGH6*NL{aFincU z$K{-KXM3~H(QdMX8%!Q1fU8*JdyEZbS_BTf=u z;|-s^GZtLkGP88TI`>G+jQ5Us*6b|4tk3IGQ3 zxsBu`%(A<4Sy_9hAk>yAWr=h_o+Y7WoFf6o3*yg4gC&mg{eQEH|Az3%o|%r#PZi&1$5a8qxmMb`ER+Sz7VNo zwwp?Ln*r`ge?13`@KO-fGdUOR29*!G`fpALqu44wchabwZ>}ppUyf_8EAP9uYPGX@ zyL=o@2g%j65;8OIgf3RRD!uBIm|gX-?VW5_%eJ=RC{&`QRc*t~*H_xMzgxj;M=ySunjKqyL8hYbdjr9#t%OVU9%4T=+ zzqDuC2p5N_o))5`oqAaCDLzeuGyUNhZTC0=&*+KQzdcgB+Pk*L0}3wG*M|P5&6zA- z)dGWd;v4Fh^05Sr8?yihZj-ZV3b4a?C}9&~E8xS>CIX{c-cP=ACuR_j^T)Mnn5Ky6 z#b^!4?{x5$U}KXgoGV-p)4vl(F(XOZmf-WMJZ|ckM~_6-u^I)(W%8jk8HoRqc}H!i z3VqsjCKG%jI1|nA|0ASDhO@ZmOcAnX&&|WowTjMgn4UU{>@Z#yFmPJyJKHQGT}Ds$ z<557@U3ZBY8`sB1_=mb*4Q{6uDhZ=fAaAWgV89;5CI~=V*J8tIiRXAD8Ob2~hZO52 z_cyGT3Pmhw?r}#V5}Z+vc=HNfN-}bdF5?V`6Hc$==qS@u0B|?Q;!KbVQwOA>*3?~u z#Bm5~4`7J~Jr&Lz^LXx@pHqlTS80BFCbBn63FUNG!nfA|`K_ z#Aa!!Mey_Gz?T%6_kiH77SDYb>U>CXge)?NUe|09iFB4T2f)bVQ`xR4fv?B!thJu} zckG`(WhC4#UOSJ=P-MEvO5Dzf+grR440GG2?yZxMB~Cs)Np~PQkj-kAAwC7h{}*RV zl`Tt6(I2xFz6{CZGWAaGL~oQ5Eu;&rX4K)0vNCQ%4fMCDqgkaRsU^NhCGvbT8UAH! zv%bm+1l`SI!=ku*$pTzT!!NIVI>Qx}k+@R1>Ey_ab5ht;skFY}^ZONMVE7DVsPCX) zQm2FK)-?`aMT_dX$4x3hd;?4Dp>(6b)C~Ws(`LMlDrjdr>cZV6bCH1^PC{P@aE~XW zPcaQGfJ2HGnbPEtN&=r~N>vwTPDQQs!m!qRwLspKT+nIe%7&?1=W4!9QaMKeWPsoL zF&;h=XaL8FxXhTr>bP~wLTq!pbXWdtw*_9>oh^{L-8-{dk>N4-rBZui;Tv{O zgZ0lUk0y+SIg41WDSO|D!eMr9$m;INk-F9sQK6arT(GgyvraAX&do&2?!|fT*;w&{ z1~#MMZn1el!V0>1UYL4jiLLBQ@-g1^_G&CO>?AP0ZKcc2nqxleLB-5&e_vs{GDpgnR0tL9k~bo&vZB>ziWF{ zx_5f0#Tq=Bu#4rITl7_LJ+s%I_!X4I>z6pgY_oR#^iM7gg=*G&S!hn z*K3qyuHk${!A5~>VPr|)W18m{kEoINjCcNZ?CJo-a;Nm^;sZ|DGtschA$tmBcXhRs zE!(Jfq0av2CQRm0_LP@N`akK{i2EWk( zR5)LaO^7CxLp@~+dakX>N%DBCF;_haw~WHEL8(GlFoR;Y-_mLMG^%ZaZC!Xg(eSw~@rJpiTH5>8d*;w>hU0M*ATvq>D79 z?AVmg^SW`p?5KEVdrI-zK7-DUK0QE#PDW>&s;-1)Cw^Q-G~1sO3q{Ik_6=bIcmTD` znNuU^HPuWMFD3fQ?lN>`@VzZ4PV~HWgG+lfHyAw)=EJcxLS!4TccLeiYFauHTjUwD zFIv%dg%-DaLZLR>-vb^Fa7zC*o+}zYYfAzOC=(cxCCj9uj6jK%dTmwoYlFm*(Q(g= ze!X0cN2u17$5=zxmN9~g7mC24!fwf;mUkbtG%z`02GKwO%!$@|D+8#$p$s6%#o^@M zv0}lRWID{=5R%j*(qfNw1QY^2MRfOn$*=NcKtj?I^*Wy9;41|D9DAOK?=xl z^p-h+bb(t39G%PS8LNRLp>cQRP4Zdd6w(z6OV)E-gR#lxT#X7Q?@6 z0zt1qbTdv|-->P4+)kwPG?0XXP5%-s5UV%?I4CjvAut^G1UYjDi#`qo^1jz0yg=$afGiX> zn|v4z6VRCCqk*r#;z^PSJDhVC@1+@ipi$EfyS#w0nO@K!fl&e(OcbPvZiwn?S^`^} zZDLcD0f2E~i~)%Z{|EaIm7q$Chnp&beSo$(TUBvrX#%T;oN7%rS8W^icGli)+zGG(|YsPno3lnl|8I~kgMjZ4>oylfrz#)2z4sSvmC`p;O5Z{9a-1%o~ zVmnq#>n6pMOB7cl$8c_I1%drS2+?~uBtY2?eLCW21yw~Lwq5!x-YnH<8?-D5t+YBK zNvn6YHzjLyl^`G|iqZQJElE{gT0|rd!v*xBXma`+LWFp4#`n1eHg z2qLB4EwGtIzEdL-;^I<{UdWuO3DrOnk%{} z)M+xjyuzm!%g6PD2`?-fbfaO*T1&C$6_<|L+UEZVGnZA{?p;XSo+&+W{Sn|Fo)5h0 z+y)szQdR@#l0GD@wLvH&1_Y zF@FRwZ1DJ1*3X%x^_BDZQ6t)%y$(xs{X&H@=H`;3D8aUn@;mWID`Vr=Oc(flaftqzK4NeNKo$LP|!%h;HA0!w%XETMJkNjY)= z0>mBlxg{B~jrpL*ZrJTN>M!E{IY0b1NBxjLdSPq*c(?A{J%jUtDQ&l&bi>Nf z+fHD82ytWoIT(Es=fnq*rz?#yV}DDftlhdX!a=SNzgl=g*&(h3#!hQ!MQ*8-Qp<9* zj$&lyhl9eq(7nlQWdWs3Ph~b**>xwoo*F#J9>d<*c5p`XjvA=~p;M`^%SAw<4=9)3R!(o*19$ zDw0*m*Ip#6J|<*UUu4xz-S8gV|1u;Mw8LDU!}Nvx{acaB#1{oB6W?T2CRk8oU{o;p zs=blBZ7r#_);J&r$w{c1srYW-hCmkmEVL!IG77LeYW9-<+ceZ%wd{SwEtd&y`AzVE za)03l$v#V;8aYc6oJY!l(c7T`z{TK6hSR|_g6Y$QsH>yS`CWL!uhYs+IhI{y!SCVC zPQSt|C9>e7u*JX9vS6p47+*>ju!O%vtNUAGbw%&kbwZ-2{3Z+l7Zd(6?HJ&Mv%Zc% z)EP}UPwfXFHIf^mB~#~9yCrV^Cq%cp!M9!ZAEV|8Au1d2PtP2eClP|TV~{&EaUC12 zJ|kDrU08poBSTN7Qr3UH$Dq5yTYcLXe1#gwQH9*wTyy1%jmf!3%$tU@g(3ci8cs?kl-XN$vEy*Ri4;~r@CmvClQn#pOh+Ug zo5%!JvBdFa{PE#(AYp?YbG|nxKw}arvj#?efx+Ft00|>oNSQdm0e?riS$n4ma2`30 z+gJ)o=+ce!v-(fRsFzyucbal)&G5hy!9JK`oGf3JCoewWXHjRAzzI7IZIqpN;9}H% z`z1f)CJul)NNk*3ZfC_dSPQ24Co4_Z%vFy4}cv#)uBZW}{*Jm%C0%-DM1o2+q1 z$7D_CBgbBU)4SKj(dZ(U#Em$$jeFf#Rs3mdpu^5{#%!}9Zaq;C;RMs_X#U@L)RVn~ zEX(G8C;c0VNa5Vj@1|A@n*dL_3!$cR6KWbynb|S^ zpsFMQ&^c#)GgLOoY2tvakkkqi18rKCGJ1FCj=FW@!CdXupC%+fV(iob+Fe-dT}5bB zD#lDYlT%q%a;F9*Q6|;k)yK(RO^PEu)#P3co=?Y5W69aTTNiE3+!h7E1MEgQFF0S9 z3&MBJe z4#A07Newnr+zdplF?V2Mhd+Lh*=kQpbHOY**dwyotTk~DaO+Ev@dnGvaKm*Ke@~YP z5t1r53r%I_eUjNU>5Z4(;j9aPUyKZ3lcc#l07Rl+kn8+@d_UlvHeZhPTldw~N-7$x z#_~oS21auoT|%9o3%iqr5G}?o2pxtVPOrcIZSQ3b$8+Iphhq$AWye9{7b78m%)nuf zjSUJ&&0kE!!P{E(dJRdQ^hk8~C`Ud@G{-iJeID;ujrlWh)u-NR-`tA26ZEGI;8+{| zn}#;NZS%@fGGd9-Zt~Z-POoB%b4a-QzWonaII>^~-n~%L-+Kl3WekU6Bh?2|ms9y1 zWnW?r^#Y66-G$KpY*pe#%>G!yt@hgfQn0pK8c)`i2U2S*K$yC=zuL9!39OEMrjqr` z&I;7OixjAT69E|JD^U7Xz5+#aFI|DE%Zu*|R;wMVr{pt1z3J659u#(B&#o3AND@xR z&o`v=^{3PogqT1I}Ss5g$Q-$Y;v%bPHQ17t9&cGH5-o+3Y2EBx>{A4*7xIenVpFC0SQ zY%s^sN8U)Kn@zJopUPnFN1j05NU6ioRfnixAq-qCu$=s;fK(_iy>C&U1*hso_-_8J zB^JNH`WP{D(CvWqVkNDU=`~DLmPR6d)YJ_yqU`^8qD%zitgV6Bzb?IRihnkPfx6W{ z%NwaxF=}ra79II-k(0|G-uC#qf;O3Ohb@<2*l*~AdJz?(yad=gDm5fjX- zz1Gwv+&}%Qf65!x+uAG_)5-9e{Z+|OsHTAY%UmpbPH^+8yKYA^8SZ)M```G|cO4$> z(pQ&O|8)7IrpBWCCc7kY-~8g=H@yS+I2XXWOIdUVlcOo2o1ck-OP|3S(kYs3_6+VN z?w&Q>XNS8kFt(=E56Dw68Nr&fimo8)w&+3iTzkhI+wkd5@9V_LNX2Xn5Ak(?CCJ1o zQXWlEQbDZP@YnfbL&P$bD5z7i4mCxck{*AJ1(u3XoJPQ>TG}tNE}8g7bSd8Ix^J;~ zgKniULW)eCj&Zz};GYjdKR3*)4|i!DTk4odbdRwvkjny^n%EwMla4eEYzRfuQ1e16 zRP!3615gKvY2e)TNnXL#dtRWr78^u(Bc;n4zJyl^I9G!R0~4^BBhu=sm{xq132VK+ zxe9OoU$kYSkhz1qCBDHPuT^`0jqw*JRG?oad5Rqpr_^D+fTBIs$dmXEUgJuaYH|b{ zCN#N(=iu0y+_#>mOmX7M2_D&m6G7_?M$+ds#89pk-~- zTgE|8CFAhJ!zEbU8wc+2F9|4K?Z_RVCSX4iV4nBBU!lF``%&1D7a!n&w<^>{AeUr5 z2b;?!OE_nY#X-6J{o+64yBn%Tj8`9?d{PEBGtA_u*<=?c%7WS@lfQd}5^o?nmss)1 zcDWRv3RpPmdJ-R(e`5^7+sEY;3pjW#6fvUU&asBUC@$?VfKvlZO@?u%YDDbSJWl69 zEgA&9%d2=Psw_m)TZm?IA!GsAO_vhumz}mql0+4wL(X5cN9|r53J$yuRrEwFPRX*e z6;F?&(Yvfd>R3!=gp|%zuA%WIzi%%6m<^F3(%x0FEo1lkt3R4k|L@3 z9fImiD%nSp>W7&~q7%>I$OTfemDxnyus?sIawWJ2QGiJ8?R4(#hDT1mbr$^E7QmI+bLD z+QoBYsh|cK?Eb*fl!u{xx6Fw7#RMmk{~4^dJZ)G}~z zeOnI*t|O_Dx0PGuQKvt7BW#<(kM14qE$GZ1P1csH3Q0z2+`P%7mqxvIjxf{UZ2V(6 zAwN7EY)nRKs!wmB8k2=8o<*8ktkkB+$%G~P;sL`Q72*CGV;e(3o8es7m;i-&aSXs9 z(3ki;Y_Ff!oAHbhu2zeciLR8veJoBlBGrF3fK8~rYW7Uoqrq96Xw|zKW)SEd0Kh#s zGq#$iq{or%-x^UbSa9+&3&!M%D?zx*C$Fwjj4>2{2;t~n?#y0)jSDucMs%2M6|dRL zD{~UGRtwDA%jx4FXf%Nf`_jO!xN>39665>{+txffJ2M=ly$>CZl zIxj)J2ZGA-Sx&CZ(bam`Y?jVsWZC%i5=p$mjLB{}L<8P!J{tj{!SxQN{Mu>s@B7tA$JJ?Z@8vSn?eVluzb` zmY9{~wyb^vz3!R&IG>TpP+*lWGX|^1Y4?Bj@?=v=_4a3OdNB*zLH~Ix#u$;-xxzZW zavgLvIxErw2c#r=T6U0u4eYM5rAQ>by$YMlseHhv-vEIDlGGb_!yGmSpHY@j?zfT? zuORa7Zfn~kQ`2A)x&WO}E}4GCw^NTNu;ZEFJb58w(czY zT-07H1$R`VLuTK2yY{evfnAbGB^%BM$5WQH>Y>N<#~MR~Vt3+xgR{TJ@jR-#F`OPr8WJ|@5CfdkfFGId#N{pW&rqn1RQYCT138BQzpQ`|#tJs&5 z|6q!&+*wq#)w;~)sqa#dDDrq9EB*EQ6D_!h#(OJer%H5Q&ma6HYraVkO8qT02Q3~N zb7yx)UcLNShv~WPcn;3R8@U;*MypM(2&xMpxqkp7(F~ghYC^*F;G?t`h*q-nNbn zP*yyqnzIHd3Cct0`3-)Hg7W-n1r2K@Aa#YX5p(&z13Nx42nZ&VXP+>ul0(2ozfGM zgXI!5WwBIbd?HyQ?a+ao;xSX&i)C{E={-kWB*E}uuyUBZa5L6E3lSo>l42`rBb4N& zTpcHAtE+FD`ze;WlwwwY)Q3~UOs#esc(S7!=JcG{#rSH&J@m|1TUkXG?s4Tc!BRn! zm@OYKsAp3>MPS>2XKSyD3H9>)-0?a43I-bl4xR~cad8?OPs%G8IXGXd$&-b8G65)Y zT;nSke;^gMLJh}Jm^M9uJ(UD-m}9|baT6v;lN)0UnI$3T2FJ^sl96U~*kMQtmIaZo zGYIsS*<#e6eVVlz7p;qSr*YBleVPGDPu_(T%|O9SjXQp&-Du@M+h`F(3p~^eFAK4( z#NVwZcaM@@+n@A^^Aycd>v9lhGtoLsgr`=1DKI(6k{n(A9b0OFSDG_w&5TKV2{%C5 zZ6y0;PC&|7h|p{>Av3M!MTfsPg%XYPcBj>Cwu5t-u6z;^OFoh38trELqS0-)Sc929 z*E(x7I?c1rxilGc^^c&@YBn3^&0dcLuez1vHZ=IcvTY_Mc z#t&vfxDBLQ6xAjh)DDA5S(8a|$F1dHvg&`N$IKwO64{Cw+Xtb*R?Qp-RJ1**vWJIsg>`H;G4TrbL& z2SjPd{O-KoX9G0q@*WvNMBED$fg6S_fFd9o{&A^zSII3=oy8?$Uh|OHDs6idS=p0L zcp}wq-)>Z_F`q`L_<^=yo}4C?S_UX0lcu0C|GjGoKj7O$w$PtkzAmwXySulFFHYs5R|%xE$u-wEQE8NH5{bEdT5(oy?J%(@ z%!MTTL}_FP;)1cQ$~-4f(?Z(Blr62Jn0tpKUO$( zQpa7p#%?!&8P|-#XoCq_|8yxdn>l@?5(WKPaJ7kb#Mg4xy_2#=de$L-ufc5BbRh&&(Xbg0=1vkI!Hg-lhj&lGf{nYL&Hqae$xw<-9Owc9RR(5 zi*jGMeVDhgP8@3D$$;nmhxpz^IUjnIiP-6!QJyJGuIj`{YtNyw<=I3mMDeqVGhw2r z>V_Q3;qlSr6-HSaN9oA(FdU_Q%PHGd)~>`J6muOtorUw(SqZBjtgGVPbbv0ZZHU8l zbPBR<+m1znxf;m>h~FI7=!6Q|_y#9K(uco;Fr(RvY!zP!mp$(yALYU~U3`_gtCCmj zJ~jffw1MNyVh^!jHd-!`M*8DQFXq?X2*2|oP(I-JEI7uPx2;~~9aTOFF^n8mf^X~Z zK!SW*H%`nx%u842U}SXt9Zo(~oMG{9Io#YLA%7xT+Wt;R%Wd1OT=F!Ws2@xvJ?R-Z zF&r!FVwqp?LY{Oldh|#m-dnyGjQ0=%zOd;%C46za)fj(A=CB9I!8UN=G>#?YOFBHL zlrQPWiQU7B`3fB#S~hL(@)UehQKzYSvXss3NTPmwmzJD*$~u39#JQ`=K9LmDAiP)) zAI77{sjBaZi+AhkV;uu^#&Nq|U*yaiw6Juis6JxfmU3HLbOFLgGvrX-$W8}26hfY!0 z+P-H)wqd6;D8j=2roFrlJYkl@ch%0Aymk+&6O?suARVBji({76c3N@@g>unmX6=UQC=Kc@G$>xfRdD2*`PhYgLr= zZ)}2FiP1VEL~nD!Pm&_h9Lop+z6QQIA(x|s=`|jj_e8;zy(j9-Ieu-2Ni*XB&|9Aa z{Pxl-pCgZQ&%s24c`TLnyv3BtK3_!BoF|i%tL`Bz>z-s0qvV#!``zfU^Fh3o!n>Wz z+ncpQY|F)RH)~T@vHZE z3R+g<^uw*4?eB!Nw8GM*ZUAvf>^patmi$dJisEggA8}BSU*3(B1>(4;B7}I`++?iLhW7_(T3~D||qGuM) zK*t2AwoKeiBTe1RkYpC~@z;_!+QS7mUqY1h_PZJ{H6_i%?rt_`lPL(c;~_ez1N06p zvKEO8_mi(yFz0Zdm|NhzrZyh2ST1Lz9!d`*^);*u;&sCgdcBw$%ag^|l`or}{4Xuj zjtIhD>{uhar&}elbQmEh8wblHu~h@NCe&Uk!#BDPe4~h9G#pAUQFYT>wC4MW}LS zib$TBA(qA`9-?joBl}1BG)oGiY0mL5m6i2^C7bi{cfutJ;~gl9pIP*TZBr`;tiu0$ z96!o22MS`>=0^!quP2<06p-x-8uyZXFigm?)s#GN%Ti^nQw648>-KsmSLyH%g(B1l zw<>*6WvNbIOh7POdxY)Ujzo~jVLjl2rk3H864u>Wn0h-}L6>`9joLLSAWQ4j4-nv# zD@Y=+0}}K7evH##@!WR_BW3Gcw`Y@^mttO${^q1d4yI9}UYul6)_F}9jw@6M7!zEe zAVKC*eqlo1y@mg&gKCM-r$9Emf|Dyi9~l0%)`NiqVgam$%dt8ci+5~NqS@?E%H+2@ zcqCBElm`WPW)zo(lT6LL2)^WF&W0mJbe%2IeddYi)Jyh;Vjiereaao@9#)*(_N?&+ z#KQxPFJTzmd$#sI`7l4+0Jkki<;!qBJ&zM~mt-lFZy%N=hsyg-!Wl)%34f?w4reYX zSQB?B7xMVtGpWR=hwA0{^s<{o^f#;{UpEiN6>I`ZRqXPWAG{oYbNfg7Ezu9spBZ_( zg8)xAWcORbJKY&;1$8rKx$X-@c4ZjmOb=`}i9BdrWSS)yV)M9~E8K(J&`c#d4iX>j zJ~;cn8Z6C$LDEx_qUPYKGdckzhoZ5`x3qbE^wtUYt&h3~ceU?krWgAi64v(8fjHrl zV)H8Rxbc@i8*NrE=C#rrwMJz3P^^TKIlWk}fmrAZFvoB)TdIcKCppsyCWE1N4#fF- z4Twe#c4i@@m*5un{%1NI_DBP6^uT4YyAGs)rLmo0*3#IcPyS7_5q??>|FRC%UPJB@ zWXf4(%U4;6CCY3nS#94_D*Gg7o7LGke$|JZb1l~=lNp8)E(wv4W$0>6?W%8O#^jkQ zcAl5p7>TX4^UQYA)H3s`7;;dN+IG*_b2_~z6TGI2oXc$j>d;g=H<1aF zG|h2@n~Xw}_L{tlklxbpu5xxuv-@6e3&>zOyO{5Gp=~TlcfRZv4R;$WRNH{tXl+$- z)qP|2G|PAg>aF}lJy0i3h8^-ET(C;0%H~MEJ&;h?x?Wq^_)tWQglQRPAGouV5z$O8 zE@b^wp>DON3iHbP`(W9p04n!mce|Y2_y1h4uxCWf*_Yi?htsf4RF(R z^4D1Xg5aNsyM4BJ8cVtX_WsA=W^y+L?(SSLor13WcCcQ9!7*@O^?K*zGe2URc2`$k z09n=D9bCFI5c!XS5tX{-lZar_q22d|@5PlR3r#S&B9pOaIyuUXKikWJZ~fU0c2!_w z(VEhavt5+l{_`I%KGUl0&)gNOW(CL&s6ZfYop(yGZGS!zP{kI3oYC30Qj(@>Z{9WB zS_aOHWq>kABa8yRO~BSx*)0AV&qJx8%&Dqrs5YjgDrzz&0`O<7jo??^=NbFN2}c3C zlT@?`s2?jOm>ftUTkp;|cjCbX`95*K7=Q)F^lBqfA`b^S08pyAR&xedZqYFr9DhbJ zc@|CZG@XmPax(&fhxZaeeR(}&=7Mx8HXC&whqhsmPDRp7(SkE4ok1boz1wml&~9V$ z8P;ZgML0_my`fC(Qo_7dV$xuB1I1<1(Ygs(LXAr@*o5NdmlMYHW3DN-)E9v(W}!nx z%oB5O`2x`A``h7kw7Fl(lY+7*g9t%5PMpL8%8;nJt^%ABlM7~H_xVp>6K z_Nb*|LQ{6k9jf}`FPz%Hx(Ab$#?ohRJT)_POR2aRnsn#9muAD*4ly_LD>mxpnJazf z3Y|rpN{xTrCD%A&Kt{~JLowCqU^q5-k~F|)PZ5WCh+`3?nDwqC`Yyu)gQtx}oT+*3 zB_vz!Ea8>Gda_8DX44{6Vim15O=rw38!2%7(MTPC&6a^AIc^4%X>ft9_WJ}k2&HMp zKa?j!=ync1rqsdUuTp@-@?7qe@@Mo*h&YxCZ@bM%d3ZR*IX;}CiS-q-)B87k+aWY{ z{B=b%hAx{_)dLG!@UQuqeW5~G0pC>~5lW$#fSKUk>TM*9k%=}=!T&Al*%mNALdlnF z#WD=AK1Ee+ug0o~Q+sf{lCwu6QzGv2_CeHi9V?07h+&-yu+%kp-CA5(91zC(o^1%oJ$X(W3*@l`Y*Mx~ z=D9-+ToVaL@!$wHus{|v$R?!sS@Ix_cB~;R3Vmrs$FD}F_P3s>i2y9EZfu21XF;i$ z^_qAo0=Si&osrb=onUYil=|FF3`8WG*ses)u>{Fruo`2DWrgyrm2^}cjRFxVl=pqW zG#1N9>30HEM7k5G=kAqoFdSlEjN*Ib6pLHvJzW_^&^z;O6O9HzZzHY2E8)r{p_>GM z&VJ5h4UcqTcd!b;{87C2IhA>5V`$rJ4+R}`Dz)x96qwdDL%HV;1kPW*x@BeXJOw_j zoj$m9E7>QxK~m%-ro#5l!m9^cZHTBz8@3k?9)D;ldukdvw_N{5xt|uUNk+7$HJA)VGNLuEsN;s+7F-H} zd6(J(;^R)~;u6UuRCJ{swM1JG$XoI>*C-UJv#@*j5T!44!j8o#*RO)^>Mkni;I{bH zlgh)MNnmIp7XFgrALIf(FULJm&d$Ql=w8Y_e(inwMwJTD*MdNgY9oiclM!2=6q^2x z!rU4!@<&fUif2ouJ7Lr@w=~H)-aO3KM(&i2trxVXi|uSjA?yyctoN8oqtH-L^BVb~OFV06jvQ#0BK!p}FC zm>yg$G+&AGI0ZS$%W@)z4~xZLrrtV~vBDFbR<>lnmT@k3oBqhWP#%9MHP0E}AfeaZuga|}ReTrkD@ejl>ER4I=R-V_0z0O8iVA*FJId*7%k5{`kcZve6S0BBLfVYMA z*_TP1)u9?qu$$ND9xPlq(0$rWjI%0(GZML*pR3}|U(?l|OF~oMc{=pA?t(M*NUPM| zyGwDbxWX9R#Os!$>$y$aT&}RutTxYDNiGP?6^0wmSY7PmG~cu&YS&aVVpF?yE8aE7 zADEiUqk(C9Ts*L!N1E+Y;U|&C4a{gXZFNJf)itTQ%Z2J+dY_bypr6@NRpu)BINeN) zgP<)XPJn{gZz625QpWo=#I+Qyuc;_ZeZM~kqfcU>y_CJWlBhj?Zhi~J)|&ULS>_(g z7EJ%APF4LR(Ns-!MJDg2+D}aNU+8#ep|@N&AH(b~)8=;S%uN1KJM$LOm2=pyX(m2=}oii+X=3_d|JUfRpK7~9Ohe09RG zfdqkaJ5Zoe$J8hbYC5S-CP7@uB@j-5}?)P z&8Lu%DRsviXNadi{qwlT$<5-k&Yhz{4dOJ5c!nT zbBAqvfbVFxo_Nc273l{YOugt7`hTgRSxj z?;&qK@P5#Amu9t``F2kn9huYmP3%Z0&zsmewAgVf^_$4Cw~oKF$id2=6guiavOa)9 z9p=v>-HtB`bUVJu>UIcJIa1{(Hr>C;Izy2+6-!MpM^&+00EZmGd#O!USeB|zSQ+yS zjVLSxFXbHYh1>79qV2~Q1>28rvfB?W-`seF;c{#TIb#Z%2|EB|_w=K^D7a1Wd>0bd z5hAl%%Gyujm7evvKz+XXe_`iIVF1ti+L>BXc#?Z= z!!bmB>(U?{wUp&@W6JfM$NFh=Q{;e-bn!P{^B4M!*9s(G6<&K2X;mfM8=Dofnf1w9 zB%pts^QkN7$dYQa-D=^^3$+fuDG0t(Vg%L#QB~gdZ_L zgDm(D1&D7{4gS*$cgR&v&Lcy%@g~B=6eRSOO)_InuAr!{S1fL(Hus zc~gy%2A)cgG(Mw}H>E<9=JcROSyTgo@h8ViJ_ehE4CA?RhFf5(yG(9sOo&~`oVEv% z$81)Wp(|2ECegJ%_ow>Ucfg5g@API#aqUhdxmZ#yRim~=ujuIQ$L`HN$Lz$PBN0z%$2G+j5cfz=2QvNTXKB{mV8dHzbpb|d-JebeD@0aN|$NYOpS}t zXgOTKBbd`f+TZVvysOL21@!%--b=T0A~T_wKMGl@muBMN4B2rfQUxq^T?H+>Q7w)og@&k=Y4*^J2=aU9ucJNuZ%jd)spS7EUFPSaXS&w(R1GxO#9S8_B5n zKS#!L1YUDwY8JU7pL~Riy#wvonga^e`T>R+{w0)fhG5zKSeRcZ*L5Agd%J%>c01Yi zPFzo_zM2|xx1pXKr|SM(?ZQTYpR7-VczDuzBEo<6F=elgdmK|Aljqdsp6ArZ%sGYg z;d9kYz`6_es1CST65>t>1-76RKd#7=WmIfl)g@VX}_BLLv1b4)h z{yH}o&U+wX6xyxFEiw@^{9;BL*pX5MDtz&yRP?>xBtc0_>Zm}bUO_VCJ?PS_UBJ`N zj@VnuL6X)4y)6Z(HAr^Y%*X5DU^xaeuH0fHP?tbZ?>CS%X1TDYjGDj@Em)nEj zc!r_b-ePRiya`_g^Pn+0Gr%MS?Nlyc5*-5IoWkZ=&%@nf^jvHhG;gFZZWP}+Xep5o+eoIR)C(vP0vB_NP>SnxEezj-;VK!olO>+Nyj!TAW%hmky+ME0d^5Qx3%>&=oLP^)~^+ zJiu6>codPTU`OL3<{mbFhXRlyFU=P9`)X@n2J2p>msBngb?tD2z^-P@E|?X&T8T21 zUWR8-B@?2{^s2*Q6!qN{5xH0zciPfFP5I=#!Sg-bV6Voo(FFxWES3vfSf}*>?e*`U zG0n(;7o!lD^;hup_y!+1t-T|cKYTyFACQ4-z8vYd?yIX6XvAzsc_W4ip}CGnZi|8^ zv@v!$1+;4fkcjPz_msZS81EiM)by^%!lQxnm^ak?s9V#Y-Dww8N9*Man!&Dhv^j%S zEac2?Sl04L@1Z>m$iart4zR=k)mhsW9H?z-FmzzxZ&!k${>DVd_-{92%7 zA{<3`)Ccl(N(qX2Pc;lQRf$2=95AqQe)>^TcmD4S=1*}Ii2{;Irqxq}smAsVN!4-79{!SJTHD#w$@p7qhlB48T zc`JL<@!kS6JKb0R>ccL&;!H2$Qow10YODhe+EL@n$Cbb}voDy1x$Q?)`tatajIX&dNeC@bsZ>#(~N4->1NTO$`+$LE42D)f=&6#V9q1cqHO9{iMf60?A*{3*! z+?Qxm?v7$<4R0Tp+?Vt+FXgcIXgzK=x;7#$qg%OgEt_o?ZJAwWqFRWPlHM)uh@IK3 z=XG|#t3Jw;E=$goj?qibTuyuaMIFJgA|ltCG>DW^^Dq3ETOtT1i8$(j6!AYVI1#zI}m%_6kruEel%+(A&_$()#MZUn&h!HV$j&AywytY8sAG;zTqZ+geuNFw8i zFd@+PXRr8$TKT0uv{bo4>J?Uu^40*~6T%Z{JgJf=VYo00%NEs&6p8wiI5-8K&$zg< zz{uh7#{ip}dRwf$Fk)lOaXD88QtHI+i7&vI;(9Z4Of$DPL!&UTBg^$idoV`?b@!wf zuTQd)zhh&^m^!rP?Sxk8X|Gf!zTWS&M$DQTv!({FKnGKq(@8jLHc)w%C%&+k(hT}~ z=-GsxQkYR{&5g8rP|6u_VWTK5%e-x+UZVlcTzM)r*PEKf6uMBOeZrU0wdAuLauL3p zKWmnTY)Df%kqd1V>t3_K(vylh7>a$ENCj?eypb~fC`*g!hwNiD_YvN0u-G;FMwA(( zWe_^$VR7I8SZ#Emeb;nw^G0fPkuk1mM~7?Pz7M5Kz}%cY@bfCyM__O`{{nb(cX|E;dUo zzeJlQu6$oS%ddC;#Y^>BT*xlle!sF4KmBef2opqRkgtL9J4FG#xKk@i%Brs_(C)sL6N|PX% z08oNoW^3b`kmogy72Y3Sg^1hp+E#DgNLjs&g&UiE+giOrPBXPDx6k1738e0~*Buvj zl;NM!==vH1{(rXJ^P9$T+Sr`qnpmZ)9NSUiDA>pPXEPIyYJhWnh31~Uos7TzxdDi?gkKs@#@2qPtV{Y zg7_E@W)?Mq1G*79v$SGdQ9bH1Z7}Xb>{I5Y~=>(4BcY56E1>W>5fxBUU z{ZVp*v8oJpSwMJ5_Fzr=sKt$>$Xl^t)3*Zeo1^Rzt z(Tg|0`z5G{s7}VlcWrU8_8YglV+>tvh6bj*wy9X!e;^|Ef&P4Cs?>LrA3Y6l_D;vf znUUt;U5peaZ3D`OEDfhPxuTOFp?q)-hSF?|iA<_W$X}zYnc!Guk6}!VKEPh$7|cEJ z8_E7XoS$@$`6GkSnPKQzYT(F}G|sqi5r$vqk7Qo_2%7Wp_c+Z7^0{X+WaW#Q8e$Fi zr3lNN{sxSuF*(A5EG6JiiN{G+`a|o=n)B5zRe76)ko^vNj=^Jz@v1T_SgJ@Jfv!Oy z%8vzNDpYn(waS53WK60yMT2S_fEqwH{#b$!z77YfSW(hCSFwUu&INQTY_Ff!)E9P8 zkD7mwQtyg1D&Vgz{#I&-U5FNh<~0-GbT&w9_Gr+K?jG-PFR&NjRmHrj(*61O6>b{S zQh{z)I1(cg^7^Oyk4V5G5{R(9h|tpee@f*PdJ$|kP5mMpS*8M?aaxGGM9(~1E#=8e zj5D(A0${Dx;~E$<2W%d=t@8k_Xy{=v<$q?D?J1CACdbhN7F}_+c$+_HET|Q-xHmXR z;`aGCj2Dd>c|V3@g_U#gR&tIdD@7pb6ODLfAv&%sMDhuT*%+T8S5g2WeIULICGSB_ zL^RH(b-%d;&1}MOX&Wxh=M52L>4beK7X3x^zw*6h(;{8D7BO=u3#Acr!H&k&{| zs)8eluW^*U$Nb{=3|Dy|G|r!8Kxs)U5qDt2mo!@*4!VvZ1r#xyPG%E<>M92|tTqfw zqs+r?!}>Z{g9~BK`uoXvI$8@TnsAa#<4fYW&Y4N#6@8)Q-Lt(Hs`e?Ww67xAvXj(b z((;oA=NuVPJC69%H(L%sN9kJwj?jh7!tcT(L<+SisO>}Ljg${np)qw@rs_au!H?Fb z%(I|R&_x8MIcBn22~zNYh%({M#NfF~=W(o3pm*70dt*k35_`Rr?5*%ohfCoq%iX4$ zNSwQE%R-%iIa(rHemNJ_wn!)>$gDK3wP1y-A}E)MVm97%YbDK57e@|k%taz(^ZJX@ zwt~=!8@YSweM>H8g)+m&)fpAuPdAuZwn+{2X)^wv2VbkWQdE7(QSA_?(UIl#Fgr#M znf;#@7%Je;htu&OPUx?A9C}yw>PLK=8;cJfP_Zv_qe$Ota}^)*oee&9Qg;+(z-W$` zD>sw7>9~LOF1#fa7nLBTG+v>Y$GV*1QmZt!g!K?cVGM=@^ek3RJ|oSvKsj!DYI-wi ztTF1b-C*|=h_q_0J9|avZ9Qx@OLvLus%DP|RJ7m`C77ubsJe!TyqVzGnpz4)YIS;I z+ITKoQ1M&hk-(jre4PXL!``T*jN~Z4u4dxhLhERj0Oq?L(=aW*kM!7vp9XMmbjXRO#t(3X2} z0+_kh>lgC+U*qu-1BAdY%N@4&Exmx2F*;9!)kHwAFU^I|+>XFg<(4%0q3351xDSaj zQ~CvM_sI~>Q(?yIhPea52;&lDp3r(~ra)*h$F%jHxC#PQ2F=xob?dTlN`-K^n%;{- zDL=Ad7{ocr0#Yo?Bt4r?^&iRar4_dYY-0xa8@md)efnpnS!$3}|5_f+Z-&t&TPl7u z(MG=h|F&K)c)fCTqWtx`a2sdIUcv^ddq;r1#Mt7#JJ{X>0HCYId28>zi`>0ucb|+` z>#q}Xc`4=U3@(D(oc3$`sqV-08=tXS=Wa_z41}uwsoOHQ$N$(T>OChM(OkUZ+8ZcU+jLH=JjK^T z(?+&rNv1G&^`<+cG1ryFvu~9=Xa~+h?O#0e575IOlB#<}_odt`&6|R%L_%Y>hqAlG zzpdB&FI!0n3cXwf6&C>;SVFe+Cb@mt-c*yny=qc@55F%+wYxaMN@8A~ad4q@Q*;f5#4mVhJbagOk zkErIBJq>iRo&Fm2n$mTxd$%L?>m^q-V_U4#3Oco*>I&uBYoc!-*S&Aze^XPs7g5y) zRdI?%8GKq}CwbMGzrBP<<9O2~$0E^_y&NPxbA{e0i3Kr>E%(Xd8%zU_h+hM>#P?C( z*P*ttpYG|%6GQdq678tu0_;%D5f+u>ril$QJhAW!%NfxIP84_0Gs zu6y@j$MqSDWXFci_OMfaGR9S6Q2z>N0v(|F2_pQ@!p`V^6K)1yd!N2FnTgE7c(2Uh zx|0#{x0B6t|K_98n>b-@E%4}JO0Hgiy!r_4lSIcM^3}#-_F7Ec3k?nvZ;TSvAMw@+ zM-$TS^TW(4PE-m(%w*}FdX$G*O`dFl217n`B=|{@hXq{Fq6ZH3ppk=m;8LQ5g#Iut zlDH2Cswd&Xt+MC~MdLUqIV7|4We~&Wcw#!NObYPg7o+~{)2!9FXkD~Bjf-~g(~SCz z#Jme#m9`w)R5==&;9_q*qnF!_*4DQgO+v@lQJ3TS*U6l!yPsyw2Jf3z znin1Z-jpFU&fA?86sxkDy#k?b6e!TD$+L8lULYQzst zOn%{U-Z-O{={~Jc!JsWeTFfjWb_9+hqD}UZr(Y1lg7P87Vj zM|_E>Fxf<;u=q8d4zqoNHm|DZEa`o>OXreSs6n)ke9lM6L{-NZRf%qbL_jQOfVT)i zbM=UIgPdDc>&%y`i~6I+!o1SCdS^H#YADXKK>Q{-?>7ZAJQh&-4|Wc@QJE!^SP zMbs{Oe1CEmw0sbHZ)tlkf)}?PQ~b7X<$*50(@OQwg3TqUUsOQ3YF?~Kk{>-zHAk8A zsrs8ibG%$&4Pg}}{2z5zj<-Tlle_O<0|-xcs{UnvL+i2}TVE zk3UQb8w0Zes(q(rCk~9%`~ifjHUHidD554w`J^O>ZH1EL^knUX-Vm_%&KIlMU>X_x z-Y>6H>cJBlFdvRhuqm3nBOou1R48`~}?CYO|w z7I)m&`7L_exwf{^FlK+7=4hzA%XCG?C!5kyZE5OkI3;+!MTySFCW> zJGWnce(&6@J76E?K|4IcuwDC4XW_hccElczF?QkzMBhFdZEX8Tq>br8?QP-B5+5Cg z1c@>mtjGON{!!ZqKP>>_;<_*yKKB}+*BZmT>Plco)_T{fQ*~dnyt311?L-UrUD4ep zp$6`EwaII0i?pdYDo=3XmX||lI9P9tXtej5_)~q@Q(IL+gNbF7*1>254i|~sQ3L)( zuH;#5)_=C@Ma>;MxkE_E&EhSbOQ%W=`>+Lv8nKeXG`vk?8N$qU9oIsW{uO}>5}0@v zlC;lEF^!_L4ghHgC-;K<#Suhs1tDVLdH|#umFA^fH+?AzP><|Rq9aW}hRuuHGS=OO z>LHkHY5(JH9S}o%cv{pJ6K!XI9IU=Riu=HdiXHcNPIKNKFC1OvQ$E{%Vxt{ILCNdm zKuStp(@v2%)L~rK$Kej-l3t3X-CoQ5{=iC0?hFpDxRmrF>Od&Lw&25ypGomd7Wn{V-y1t1x>*LT?Uzqdui8ss+ zjWC|N{g$g>O$o}gLf`grzakQD^FWx}cS4x^K!xxtcDV#D(gwBx%_UaSukFwGcls;qJ!7*J_-43eblTcyVmxQK*1AFj^}E zmc;44Z?Vudvz2u68|umKa|({k1E zhGGqzM47QYI)KuSzpgO+>6RAwCIX>J#yB~JqG1tiS1>G*6=FhtiDTmq|6<1`2`Mj? zZY^(YEc_AAcXNR^P^d@lNpBZXPiMPpEcV`Ge3lG)ucmZVzWdY2K9gc;1FSuLEo%$j z@_gn_m5{Bur-+CeIqr(a;OR9WxDo~%!sGrb)>@pReD+O~eX-~&jx4lM8 z%Ou{NS<1sT=Z#L7GEtU?@9tcfFe@vvnr9NBW)f+8d40#?5^)o5?VNv`Ity|U z=!_Qn4`iJ=y`TtpiP*|3mziDKE5FeQdHwya8j%}iGFS!GOmdH=T1vx0&?l;BK*^kH z8zQ`CsP6zlCtRbF#LLIj*=iA2V;VK7H|l)#Y*#7&??ca-rOTd4x^y4Bf+1Nfp$u;@ zlcWe~NwPQzN(%#|n&u621BsI_9RZJ!ZrX{{P4yB`mMmK_M{`Rus%DcI@$k{ptK!-2 zmdG14Cv(EuxCW1j4;UBbx*AlxD{le)o^5JutrX(M`D%POm_q)$+_<5>jP9|V1B~Il?f~63Y-$Fg%{*HHzARa%o~Duu!xbe#M|yFw zxUpg$0k<)@5#fB@7+^q1N+E(_w#c@^hLF>0>)2roPFjB4;oa_R@WVh(Wh^bKkL6M> zI8%>@x;hxQi2id0+ZElYGB)|vAQ*Rmmx4tUqzlw?qk5%Zg1Yk|hXckJHTrsW%$`XwSy8 zJK|dR8#IlUQt}t^g+-LaEyfS_sXqs!Pon(wg+W&9jeOiAOHriJg>G6?-tl7yBtq(! z2Z2f8T=F>6PWq=#^__%oMN>7Ym`vVH^|!=SKV+tQ8Qx@mqo6qKwT^KUYiM-b1|UAB z%+ScJtw|$M)vLQ$VGYGxuZ!+|VaPmq{3<>)7j2-goX3xvGwrx6g1eMox7iHhh}fqu zm05%_1RzHKMXd8cfYarGtRT~TX5T`nW*FoXiZ2QX#W#m06q<7BW&-(-Uq>kelPHw> z?k9X_N9k@<}-R#pDY6xHh=oLGSm-pC7zljV+$7(Y9_LI0aXMK>G zKHvPm5Cl8P`8n&;KEH+&Z1cv#1?Qhhj{SKooI0)FL~$*M!e>59hXrQQW2fPnx{pmA7ZE_}XPVxr4=&C%13CtDj6o$DAspJzG z4Gb9CE=R|w5DV1~zgv!uOF23|q;G*c^`sUwnObyoP*v|;WV-!V!gMQkw;g7qeaKDm zU%Vf5K{M7Eh3OdBc?CQce<18}Y+Eif`;Qz`{KSsNj1GajTeG$YS`u>?y+lk>z-NH> zJ6}^dl#-WzMz2Y9cB)fagE?OB?I8~Rl@-f7;B(?UBw|%1nh(qBo1=5{rFYA?5q+jI z)~njlSyHsf!RSfRVz&)z$5BmtPuFPx>JxI%xz<=Wr%DkA`R=@3^y;+#PtnK40b!t2 zPu1g?7*LtwnktmDE&gogNNtH*q@MKu z91J9#CxU`JC;LZoK8ZCPVk;lXn`%jp6Eeid-E8xm*+EfAy`&CHzzrYU0w;a%&E8!M znrI!rUKYb?)}g2Q5WjjR;dm`}5h}*;mk2t{M&xD62i8|TBJ^WlWFDs{X96;wpFPPV zc{hn6r?jG$2B#oi1n+1wz$p$NGzW9Ab5wHGr<7#ZcsgLm%H9~$Fz;|l4TH{w9orGc zmyNq^xC-}iYXp~V9#?Y>4~pxbb0qk&*4dWG4cu@T+%FhS5ei7oj>wKv$V5INsGyRq z7M#T+(|Mj7K>GW~xt?7i8AxP)d-Tc7@l+!V?8qjhoWHnHQSpRPO%jx{q>Ok%>6;;6 zP=qCrK4kKi@+rl<2W;K#>LMlYh_{n9m#*p9mjbcEaUxwWAQF6EEib9$_4icrE;C9V zmwZgECKGgBGNf;G%ap zW;L@^3~qE{Dkgc6Qh8U?9q)icvK1uW?VaRkx;%}#?h8o|-!?qS>J;>E?T*93;5=-5 zYX{1q_%8jOmzF{)YsOAJTnMJTfdz@~lKE`o$-E^-7@?G$e!)?MkFo7(2*8Er+?Jhs zLE9Whk#rY3T;^hs8b4}+N^Ge{(hVnG`(@i@7aC`7?h++(t}?B61-nYuPO`s+>b!;O z94%DGh+jBPG-N_MFVrw%t^Jrb)F!Imq(TzJ8r;7j2~5Ath6-Xg`O{i(Q%caW_flFn z&>;U*Sh6JNIC7Np+S`8OpJLoR`flS;*bP6TW;-~5dVSHbj%#J2@P|nBQs@KOmFj^ zmXW#>VWY}k&P=>hHJ-@NH<4y%;(~3E8WoMB*r{XNC}_FVvK(7;{0lS+AAH86g6pVT zTr76xECT^=wfL@e-UgfD19uun>s@$q8=s5IA?2k2Ge(@N=jt7hkmY!DrFHrJw@fNO zjFS+B-e{Nk3jM*mY^}c!mIUi3I?X%q(g6p^khdT-Kc4(=7=(7Qd|W?ZD<1wOe0kEX z&~*;6U9`i(@P{KRj&b^wT6^~sTNO6H*n0SNUN36|l9Fxbu?2`#Q`d`MeQOsvT2`71 zg!QO2m(yWh(&-&>lILmun;zSTo2omnosHwzcf#1DB>_Haq)J)J_!#q#xQU!k2;5wG zFiO+MJ?g~?VNMitpXJTd3NOtmY78F)er~(1=sx6kn;*?c1jelRUMcG0rsZdqgp2&drN8gtijumW~3=y?CImA!0arjG%d z4a2J(t+lncsGgD2k&ZYsvFK1Ws@pB8cU3hEZTrM5R-3f}9dTN5X^9ifY+F$$7bbnT z(1f8HahhAHiZ&q5fIW(x`sD*t?PQ&4-uB4dD>gc?u;gpT)v&EWH9GE)iWI9zaWK?; zOl`&;Bf9YK37vCd#5C83q_>_4o{785dwAFWos>RK>*b+cNAS&fK}S_ANs;-)V6VNQ zQbYh0xqm?>AQTGCPXAzlpmS4}2lY_Umu-Om+wHmxyA7`0u3@#CpGUTiWQD=2_=QX#+a8q>|C< zIW)DFlBsmt9!_YlMWcAv4WV_xK(d2YjUus#Pm}TYyhW5K^?>A{Cn`NsqLp>G&#O0! zs+!HsaH;cnQe5qzGyX9iK1wF6@cITkkdA$$Nt?a@#*1D)j5ZdM!A7d8S098Pk7tvG z>zQ#GjPv3_^{%{lG#!yt3YEH&@GljsmJ8Asbr5EvN%qngx@N|Ul>)#hT|lge1`#yf z_of~uyMfUD&T8;I1diAQ(fHY5ySHiDY7wx8S3O1Ny$0#Bg9<@J!3B^?@teV9D&;Q* z#9^3--hFHdg2k?pge)U5Xz=FZxvz)((=~wtq-x3>{jS^E^+vO)71~ook8H=IjHw~= zd^C0#q9)3jghfKKez8NN+=K9slzdnXCYv?I*%AXjk7=bj)B>U&y|EzB$<+B^7Tey_ zWQjvg6j&BVeE2ECATDBoZR1O#!(VfVUNJd6dMeEB=P=;6hypK#{o)aAXTH<(Ogy?O z9Fy1xhuff=uFGcae~<`dUujSn|IQ4Ad#!v(l(L4}=W1Nk){__xeITXInAi@EyPgC! zp9zRN8Si=sx-Ank%)j{pd3E!XU0Ie;H*QG}cc7)3zuEW40u z`vkh0pAc>qeVN356B6=tyfwE9gDjUpDbZ?=7gCc~k|Ne92iQRL_&xk7gT`OSauk*X2g_d{VfveR7%#qbtI3upy zKtZkTG=rUg=W?i*7d17?rGPgRXHM56o1xkm&B+rzi&Ah)e_opm}vg#zD_;y3w51~c}ZY-;4SK02g}KL zIQ~9ak7MIgxVhO_@bpW`rohqB5Bry@!aQ-Yic9f&ggf*GTxbd0W!kb9rme_>h ztS}rr{^-pRErp4AsatZHj7Xo+sg13-QIrlqoW2iMqdn7|YEzy0} z+euzx5D_(O6EqXGyZc(U+ZrhhFq$iber+%DIeG>A=wOObT_yC_0wvU1c|WsF(n_dF zwqW+}trGe{aOiZer`J0=tgXJ5e^wVA)ygd=)X82N?L5Wj#i)Zv3+pWuI|E;sdjiHO6u@2=H8R*BXvp9D#CmCVr@u zinP$|K^|$L*@L|75`g*qaF>9((-iYk!jW$+zLCDd14a7EFIJX=`z&?V_^EsUu$FPp zE(;p8+&o;~(|&bqYumqzt{&J0ohw@Q%PyY$1ys;%H8_2l+$k~GZKHRt)iJYFB4*t2xsgP|F^jN zhO0Yt(SI5k7=_{Kp^tyYB%tMb27kZ!&-m_!=*;oz!;_6;(N2KX)bKLrSjmxC@ zM(E-(`!A2f!kV z`MtdwOLzD#aJ-G@Ln(4%75p#OZ+M7-G`)c|Qv*TS`eK6~vR&bjJsQ~`@F+X73oWv~ z;G=XcA>P)PL8s@T)|3`+rK!KhNCe!NvEjho5OIsgK4^_>$i;|o4T5lq!8KYce=+VP z=RK6Tzl!e@Y~F`2VR=1B)mchkjZoaiQG5%|_MA2^iw4!Z#%+^fUUBPO;`4dGq|}YV z^hXXP;p!S=F21xm3ebmu_gF5zwyc$v35^D^q9ZLGupyRVl?bE`0{P5EiK;rjT&yQQ zLizZd@O!F#^AlW1)ctrip0iRCV!e4@j&nkiU39#*x(fgAajZqeiKOmY-PY>jPe#T2p(~w`|`KXS zF-3g9o{x+!zT7|HbYqoK4&4q&$T^&?vo5yyKS=tDhFstP(SNXNRz9^&0s8)fe$_+R zV&okB2sI0c#}vMw=!mm0G|=wT@%-!N!DNS%&uOF_!s!n!WN!=&uCeG-8MBGHcjW*I z-jHM0Bs7F;>H-pe+H=tiQDYixWV@hIGTEY0lA!uHm%NP0U`iSj)o`qQo`(cZKFl7Z zQ~>9Rh(bcOk$EzHk9FP3bqlgNbbcL5n2lj2)l`Dk?9m3$X_O4oXU0mQV@PrY?3NWo zY?|9}!b4$u{k$eI$JLt=e|dUD=igV@u}oJ5`V5uTY<@{@wf>W_a@+x zu`6NOBoZrzq2?q-d}qs>Q>XDMbzqgAdax)H}@~b7wUV;Pg#| z7+wusxS$bfnRd}luOt(&pykA|+17fIQp#<%+&r7|8zv9$)3-C@RV1hJ|7Y)8`w~Z* zzP}Oy1bn%>lbJys*H!Pm=DFYG7Xg(SP8Xmv-k<*YS5M{&8W7pI$7qKA>v=~C)zmHpTU$2;3W0<4SujY zykB#h#fu*;Y~}e9URKXTrWt~CrYQdbI!n?fGOh|JAm_|Hgly(*6~cnC)iOJ!^zHne z;uE%=HiGOy9D#l*;2tFH&g))x(U4*vdqNJTl?(9$qWmFZEisb$c-X8PxO?Qju z7t?}Zv-yD4r3GqZOE8AMj<%so&6n7wbgev+leBb_EIqfOWM9M{xfo~<=!W_W8 zl5HHWzVNMGCy^|*eKGg)Oja7JK)CCF^V3Pr}!X-_F(1X zA_SSzC++FN^Pmwef?s3UkT}V#e=$7;@~D+7j?{e)m*WrxCC) zD&B%fuU~|Z1h3#@9sSAxY>LGoJRXQEs42xVJ&VWtFtH@gCxWa z9-p9M4VII8+)Dnsgy@MybP+s>3?Ymizsi3k-=|SD?`_CnY;U7wAxD~j#sjEf|5k4l zdYe9E9|o&Sj^zGmVvM-91XlLxb}$)Cki0#Zpkrm9g;?Wn$SIaAn<*s-^4Ik58yEv( z7FQK7PW6F<^#d>D%*pt(6P)3Wrj>98QGV)}O`IWu4y>W|b_jWTggi)mmSkuE%6wbB zYQ7XbLHPhybi#@eBF#Sq@QESS^Z!qMqTJ``(;EiHR+3{EcqTP!-AMP3fpi8H*1!{6 zD;&jeF)7E1kdLGY*`|h#R?l*tW0$mdRt(E6CD? z!E}2|7OOmY)NmKno#|ONe6-$3K_u;e3&Pqh16~=8&g|v~SA-KMS%~4s6<7ntBUG+t)9BolrAU=&A=F1m~ zaA?4v1}6^1L4ZZIBR+!?bi=^>VA|Rap2R!{LIMkeeGc!Yq-lcM;jT9o*!D$C5a-5% z4KjklWDr5NfoQ7)L`5)2QrVW=)lnN2r}0%2Ay0s@T0Xg+_)V zqy5k6o1v45kML5O0j03)gW25=SfOBGor5Z)NSqiuF|jt>hnghmea6Y7&X z|55;@zH($b7bg}OB~wc!GiH&ETX2q|BW40t&f_|=w>Lqd7wk%f7XoR@LPG7pc!Xefq&5=UBCa8VIq2k0P0hMnYdqvJ0 zeLvpH+6f_}Y8O&%@Qn5gAK(?hkme0|M>{olfe-QZIES)K7_4j?S^HQMIvp1*R=fuR z28KtU_JsJ%8Eo5>7J?WMDL1Qzvs>X#A5gq=PL_q<9Sq(tWVRQ#Zf5#2b~dw!WV(jD zbxKe&2~o&QYwx50eU^js+`w}6DX>nj%hdm?$4l1F53CPST*~cj*(dbpvm0#;CRD9e zsafzbl-dGJ5KX>`XyD*=q6Q8{Hlla`+z4jvMwLhPrV7m_-4QA*Q$1K^naPd}o9+U1 z*&*TuX6wiOT?TW`*SHPl{2C!PUkCH4g7+c)h{l~-E<|GRk~hM9RzsjDu34Vd`*Pli z#51KXh3&~oD^=Gb7Uieo>Mb!?g$=+^m(y?(rNjsn+%3n#4l;>I5xjF~dZSz&r`* zW8nz0)Y&!F9>0cK$3dbu3vxux2YT*x_`ud-k||V3JgSkbo5&1a$ilEdjqRj0VENM%^VSqz1 zqs#hHd^4d?b0ZpS5x$j93wes`nNl;ule$A4FPJh6A-{#{fIsy70x0?}LG) zF>nZ&Xi&0n525zEfI0i7#Am`NOvq!NDckAMnOst=}M?aVz%0BvVz)I|g| zv+9d!3lZF0y5c#u>InKImf|(lZGypgwWbpNWe}Il5GP3Ic$h&{P)Q*O8tD1-zskR% z=+7(QfKbttNeqnVP8$#bcl8-m<48y#SeAkL?C?`IrS5z&njwY^@d{}8K}j9m;hg{2 zz}aD!5b8i%0K&)faSBVmSb&G=I^O z_#>nRqCV{7Xz-K#Lb%1A5vDH9kKo9MM4oo<2=Ct1!t*@*_9FCxAp2;iFtC@ z#gr#+Vf6v6d9s{So-AysNIjU%smUnP7|`tKcis}~TY%S83s{aH9@lwu;z;ma-^|U9 zIrFRR-7|o{&#j+Qrpw@QJQ;47_09(FW;1g=C8Ln9TJ9a~B5QFxeVRI_waw>s)$J`s z4%#qH--VNql>6|Wy4H-A;pz#50n#6`7Gc$#sQ|W>b2(|8>#*FiQUTyZ=Mw$y$CP`8u$8~C_*uLIYY)tY}j&~(a zBzGtxMH3M_Vn!KC5Ty8JIIJ<%?UU=h^BV%`@ach)!L;dy-#L-SpqyI7lEqeqzF3OR zMH7^k#kWQiAM6b10KWY|trHkw`hT62#zxU{NX*<4T2M2t-np1bLK9 zKwpWd{272wMpr^YUtsXnl(V0poL|!PM7i+Rs6Ig+9yOyBE}bKv9rtGcie{sXChfB0T$#=vcobj+L%@wP}QWQdlS0 z3VVhXb{pIk#?$|>h^mySkA(jmuw&rA+Pm0=#uQe2_<{s1S1j~cr3pdjRyY_y^3v(S zgm*gmn{!1kAzqO;h3ZyBmJdXhLzY5rH;G_C5;Md(2`9=+J;KQbCo4UTh>OtGC{Q6H z1>5xOtX~xOAc`ZWW)i+YO}H4t^z@b4jN%K0i87KyDt&Jt${)DxI?9`Haf!Gtjj0hG zaW57KDj^>Aw#XLWo$nA0S-4sZy zhRe+Ijq%USP;)Ov9UuUvL{I68&-Kss@OKeLe|ufePshjMH577(34WJ zt523tvC($zG*YoYCsb^TThA`G$Sa~^Q-=vkPp6_NY;z67KCk?kPP^=P#1+gDAV%SI zv9T2}z#9&`EZ)i5WwEt?u>|@8X#sk`K&rb`MA=k*Gg7a{_&_q6z?U4&r>ch`(hS+! z@M45;T||o@_Wl>qKRklkk3*EfB7es#tSu8tNr9C3Uc<{4&W7`8pw#AQ4=^)r0o_5B z3ylLU30zfG8VC>$p9_Ju;=(}MkSPpiwh>|m-i`j-zZz5xM5QvSq~Ka`7YiZd1-*|D z7g`imhBVl09#yPzFvv2A$SC!rZz^+1h%l`|boU@8XKW)X`s4I&JRC2DSZEBx#YZPM zJ4L`74vK(xvJ^oZ5on!|s>zK4tFL?!Sd_KTDt@FEaI{e5TP_Hw%tIr%CCtVjSW9eC zfS_StJ=KW5La0c@8OX6Mr1pectK$~rI|?e@S16v^vp`y={l5ATK76Ux%&~vsR3Keq zLhYoe!k}?^;_XCyKHAlNI6y5v9c1u&`-$iP_75qC@U+=w(u2CnsK*BN3;Y@Zlq>oz zd?5b(HJDIb8^^XG&3cJ)qv{wH)%XY-AF7N@Q6$YeOXbCKalfdTJ*AnR%u!XI;jb7% zD|&%_?NyMR=3z?I=8!^F*+338krla<$!+zSG@IGYgcSZ8TR#(|CL3*72Yh?fDS`N* zN!7Atrky#5=`fi#2gra*prUxV4mrR97ch}>n$75PWi)OaUFSAN8EG|+O@tBIh)wj! zQy5<3;5v4H2O~!*V>?=TGG{Ui6HnFkyPK~c!TE9$RyQ6m%ONN!@@p9`Q15oMSo46; zXQb&%Tsjc9hI-@7mvQ?SHoTg_im7ojEG&b&I@?dD@RS}vbk{cQCP)S_LVPUpeq|M6 zK$ZGAs)89jar1a4(oW7RszbTD2{kBzXhE}W7eb*fK%O>(Tqye1!VaY;kpCa`67cF@ zs=^EkCa8+WurjHZB+(?ABk82 z3yLeMtKL6{<(Kabik+q2f(ZiIZk!Q=)*AsrglDj`psHUyu1%S|s)z!#3boGcT43Dxb$YS4{ExPGJ^ z5!Lfl!6teQuapA#g^I$UZZP-}_A_le5kWk#p1Nm%WQ?S7vve*bfWDchI)o>h)G2yb%^$4{~VS?pabA(-5I7=xd=vPq#_MH zETBu_ph6KKE>bMm#AH+iVbGsQlbZXd8EZ{2@6vrXkm=P{R)5=%I^d;NRd*+lf=i2< zgVZT63PPtOQPYWY^yr8qBVqk|%N`w$1H8E+A>tB2l44|yC1y5kX$99r1f9JW*#nP} zUilVOd6%d#Gw}#+2~7IHc2w2aLyQhbj0@5M-^ci_%bWiVy3%s5(dWpu3&bzeeWDJH z+Ql5-CpPtRMvm2~-~wJo#s$Fc2Mz0&yI$2P)xWt8@U1*yW-`e!1mPH{{!El>+P}F4-qb6|)J_=V&>2fB z64Zl1jKBc)2TSAtY_e_g@`1W;HOR?AjiQ5ijx;Hiom!G6sjw3Wc_hSrBRZw1l|-B& zSDypjAk{&pyKM!xWMPk)FfC{%w*k7}sYOuKiMfNcU3q&hnt~hW!@@lh11-UZj;6eKX3FDyC6(wb zf?Rv`D_j8i%R8Dlk%Q$M<+`Urcm3~Dn(fk);R70;Lq&SJdpTJSRb@J=#dZlQB^|C( zh1aghZ3mgkft%X8m`-ECUQ)T5)U!cVx399#P0}ouN#EMDD_tbmfLAh^02H^zO0@xP$jD|7Er%u0RUgPuu=5FW`Yg^>k;P5?apt4YLmZA$2Av4^7| zOWF{epxI$Seq7hc6ejg9Rb4vh|%+UNk{d1)~_z%mQIEArlrfsV1# zWRFGPzmOaQ-YFex-_NeElE;Cr5pV=6WUBHjTaS3EqYk`-WuR=$jK7NtN4R#nwppT8hVeJ{;6yA0P_{6?RP#)o>h~ z@77e8<2m?p&`MDTDV(~IiuFx1K*_M<)F)8_rCn5kPjh3Tyia%U#}jlg4wtCmk5b*s z`Ev6NmS;B3I7xb9^f>kC^}ncTR|Kp>D4^DMYW?s^L`?>0)^OlJ9I zquuh49Q&uyTzxk){C?<^sc=uu^79(q346UL^>ka{=v2o1c7UZj*W@66k3Bu;?H9Wy727A`-=vDY2`bs|kl$xlr++NNZ;HNcA_4Qx9`Dt>v0XnU^V`~QD7=wv8Q56uA#q;u36c_A)PfEkm+JAT$_a-VqZwM z(26mXG^>-wf~tb!{dhDPq6d!DE2Z{6Bep?wrC*N9=zSiKe%Y!#nn7nWxYvx5fS2JE zxzuOWHuuv)`zJ&nuzi+_rOwuUd>pYMS`0>R{C93I=y7G0_2^-V410KU(jh9kwrPZrrGDf2%S2JsLbweIBmNiE^-L z<+ywvqcM{z&I8?{wH$@W27Vp|2w?w-!Z7M1Fmj{F(sJlv< zagr~vi%~PcpQD%Heu)xKDult~4$c z-J$WJ;^`*NW{CxR_a*yuNT=Vp4d2hU>A}QGd9sfSXG(!_liEe~ku;!HwV>SsBh`zi zC%t%ttW@8E;7A*+UOGcZYIF4=8HbJ18)>|_S)=c_%2a6tU&Ga-G~e3Ft_Obs%CJ${ zIUj}CKp^HX)V~!)HW2hnw-q&1+KV{|IV@1B?kY9P-NjN+xkW9-YeZAdQC~$@9F3!( zlS?>>p~2KsMJ!8^E1>?SPjiJ3+<!KP)8W`7m1;i(gEQXtRyALoT`d^Y>>(x7j&XS$2tQ4-4koTT*d&kv7EbP>j;)U2 zwv=9pTXX?(61O$ZtENk5)xgL;*}s*86NiH|$EO8IC@bf5I`xdZ^&&k#UgQ>eO;8C* zDLT zmYQt{443WXJbFk)DUr@(69#?7Mk4x(->LV3mC4Z?4#uJ0x>}B>)MlJmP}~C=!*w@O zU-TLQGEOG*L5SujLI_rVe9;lOfIbJ{u6;r!9|bb$_e%GjB`7qVxYlGD7myBP545wa z;PaP5@M%+VRuX*hfs`0Xufq&Q5~2vh=<33`VD#~~FDx7y1dV$HIuP9>C7P8JzYa@I zIc!MVM5G%Mkx)I!E+Sp+PeeL2&~2|16p_v}fSG8Y8?wuOjGSeGz7s=3N`TWGrW&6r zyCY(>2@jw$8nglg$`Sd;2U6UEUT1CrnS@Ozs9r%XZhTb~ZmxGL+?0jMpPOKU54d3R zmx2k3ZBzGie8G%IW$`M?RsXo;s(;*a6~ASdt1#SQ$yH^~#wFcAh6Vq$N;mYnL%Q)3 zBHdLa(wW-uX>du3u#Qnr6?~~Cd6%?h)w^jn6hL>)S_CvT-n~@&<2?wLwoSZS*>3Y@ zZQ|9TSfFgH-;ac&t!%F&;Y1FdI)#iMRr&mGjTk?UCX5GL>=pzNiiP?C9%wgUSgfMo zPX9ByyFrZ3X!-fsj1WPIk8m{}1c(S2g;N!O1p8AniaVm;qi`7Fylg8ayCHSkhSy$d zUuCU*o_}@~Pg`WtmN987IVl?|*Wq}A2~WvJ3$c1Se;%P3O~%aF^v{aSma|3Q{~LmU zr`sJx)y3j8j=v>Xfj*Toau%E&p;#)gi^8%J0OmkKWr@y?Uq4vYIWkx+8>)9g2r7Te z31TX36eYiS2aPd+Q1EH3Vg7>|#cQjIp9qGbcF29e3FrqOIWQ91jQH@lc77I0Fg2GO zR2#t4$60b9<6dxJ8CKS0J7wfToKi8Z%-*s9AcOKXY?2uS{|*E48TJqJF#&{xJ0r?T zhaG1F0=bB)iqn%=K=hdohum3X4WsQc5q9W#^qLL@Vzc35z8e1y=;adxu}()s4Dbai zM2B#jh&;;+Gjpt%CtL>qeHuNXi!;S4_EbM9xL09H)|0C>Q2|@{WO+SHZhD7UEu%#! z2WTm1AWGL5xBDwZWaJznl|V*~KOJRwMu%zT8w8z2-qBy zB=?VT;a~2SCR)d%OaTm0uacicBF@xIggv$=y82amBC&brB%WbhXQMit6)>Sc&5XW> zy2frW>t8)isqYhrn=B=ZV$GI#p@23xGqm28>cwzNNkH@TNqLCbSLIXU8Q={lO1zee znKzHA!ic9hwt!1&$Vt{Sss#JHu&Kf_y8^0JX2_#Uf9wTz0e+3WW`IvoI4A0>Scl;NIP8O|E5WE!MkxR1acp zn_}|2U|O7}&7jDe{X&2RLi{tBl5JxBvKaXdsML+1JG_Vc5RF$aezX9k=S#>1o<~vL zgZUJRydz(pZiJJEIX}4aPf#CNZrW$EhD*0V*_kCvv|!svqAT+!kj;Au7r|mg=da!) zl6~zF+ekEd%+5wgi&x<##nEbazm=8#fm8vpi70-6$5JvHL>j!9mM`eR7!@*;HT&0AC9h%B7txqP)QX&biV!xU z=O~zQVDdmguZ|Qq@;v#M%YI3U%Eb?A1C1aXGAV%IToV*@yTqjXDuqkBk;KMyLq0-3AS=aG zMpmyDj+*EIHzh0u_W z7+yzdash(W0v5n1+msQ`Yf23^`GgP63*U5m-8)wkdq^)vITANdQGC;77R-TzgvO;8+}X6?g94?p`p6+0Bwtfmoj}0MM;%cT{zV^3%#=Me zkUoj$U7_g{<-bIDw($gfA6diqOlJ)tUa2ApM$ml@m*Wr_+CWrNy)^=_V3!&H#*|=J z$_lYU;3k@y)5olM-moJ&y0VL2zj4)RL_f{q4RHMu_Xev#>+>_phFK6k3Q0XIY5KbjaL&ccMN!L6!6-8FPB#T=&# z&(r%ooi2i3W3(2J+x{V7WqRuS?E%;-2g*h`rManr4bK~9ih9-HNuUFkg#;k}sm?JT7JYh( zzfB*t4};YuM{<8OF@}i_{-@i)WH3Q4<6r^>S{i#8f7`{hDThSUzNUZQcq|K*BMYaR zETC&W$bzso$^zI8$v|QY0&%MFMg*`~a6}OeTJQ-WOP7$PPm}OjC*M}DGCfE?L3m_E zAFLSJbo|pYq?+-&`b4?U(Wf^IjIAWcF7QlhR1?YaXb##`$9A6G&>SbS?Svj7p79fK ztpYtle5eE0KspF>(0J#Pb2+F-%cmIE;rt2vyOlW=-BL?sL*m$M=$dKq48 zQ(F^j%tPC7$+r{U3>*R()IrT-y~9`omp>Upmis@-5ry4}sb*4@QE|91MGqds$j3OK z7#FfS^Ao8cR1S1(&ya`NXCpxAtpa@=TlI~t`suA=IaK_`>U~KVX!ityE~!;d@HcbJ zRYqzr02UV*a!#DjTS6i-Gak-)yEc}8zMD?h4BR)84pA}(LPDH$i0n)Ojds(urT8hO zZ#~SHFBIp{fPWd@;Q+;RarasgxkI(7A*s;O2h+|Nuwf#7>HNeZ4xr?!Gi;Se4WeCxN587C& zHPixIXa(1Iu=Xn-?SAphET(h~K~HBL9_Ct4+0aC(rG1DHXZW9KHr9Q0i@2b_Rm?^P z>&hHljKj$iZ59{usZsQJJo~{BF2sFLA5e{KPR57c9c`|LHBG8;aux||nn=;|T>z{Ys&2gxHO=k;w&|K|CJPYAhlui- ztr1&jhQm2;Ub!J_p@rre>mi2bh&52d#BjxMtRxsW9drehH5a{0f|#7Ac8ZuBes?6% zKF=<$xOJvliDGJoVi>zMifKV%4=_{L1p+4`P8aZgBV2;Y)F5=Se__`BYz7x7ajOf1 zG?@lU2sYAc*G;4;$4^6NgkNJZ2?ubD34Jsy4qC4>Z2UbPiOiWp(2w$`hEO6}6eN=Z z1zT%g9;)vkoC%Q~X^4=l^lBi{ZccYh``*BymqJLzAtE81q&gZ52Uhb!U~SugE=IlG zYZ!gCiijY3wLD-v**RWiZEUalGq{S=u?}Nhhj)sJqNQ|@1T#8JAQ~d>)^je1g@Tm( z$1rH`%Ek;FDoOH870aZ!>B<}B2&o!X%Ax^4h-q0QDWmXhTPF-~L-Oh2F@}$vLy{Ipx$reX(uT{5k5AxC*iG!oey4GA&j zyQR2_;rf0Nw0Kfm7FCEaDehX4&7|rEv}arRsbp92g9uR}ej*=CMgv-sun?dz37=-1 zp=45J1?>_5B)%c&C*#BsrOl>FH=z6=&h1-!hOIvkb0;{L(pC72Me#>FpPlNDaq zMsPZNq7*brxgvv%foO?&NK{YsW+!jhwk0@e&~%$g@lE3zj*#(;ZbSH1Tg0!pAXGc@ zdpNuxTK6?LxgRf4^OwHp()AJXmflpnEz_@O7YmYGlqLku{?jd-=zJ+1$0YuY@-=W0 zctfBXK0QzhoL2Yvos;;Zf{EB36?Uvv$dUc#xgbR4CVXoUvZsXALsbbHfQwUnvxgsP znZqS=h|x&}ag-Nhsy-Pm7LymoYeB@*?a^T*lq-p+d;K|*Mhzq2!O2B4%dkJPC!ZfPQgr|sxHG+<& ztO&RJBf{h_CML7Pv)VMEpD-OzEjPBl|EH zR&o+5`1K7%XuR_SXwzm*`OXIZH;-nJhVK)t;os#hPnP3{N8OqthiDub^*dnH&~avA zGPO5>)W@#RqJt$Qvpz{?ep)g^RC%TElw^jqs?$g^`<#%>C8K$Z`nkv zxxFAcT!g=!L=v#dhB7#uP9vupGsq1Q!I2;0=e;gLm9Y53LgxExPnknoA6p zQw`-6I@zj4NP#h4Oup_@32accO+DKVwz5F`>;f9zZ~zVORItm(zAu_joX<+iKZ%MvC~8&5d@u#EQ7zkqeZ%CI1pW%! zjWYSQ{UgN^+UyO0xpAmVJpw9b;g_@%A^iZCT}(WX4JLL4NNyM#b~Fy{mZFa8kh-Ip zv`$z%&2XOLGGjz>r9GL?fCk5eSqlT`5XpFNQt)G@N_UOd*dedms zNR+M1+^!S=V`E{_J#dLLaI-krz3hw}czdfcqRP|y*(}%#hZYt%2Nm#ugo!4qohSq- zyIdVcnhI5{I85bLM1h&xY()*S?N($MRQU`7=)Xhw!uw`ek4+psuO^Q&?L)NKd;wW2 z=u-!9=5DKi>JKm`Qz=+>HL1*^ST1%xcaHjilrzh1P)luN7rVe}d~*{B6X~D?>2N+( z3^x*tsoExqq2>L_0Nytk$7gVUQ`5ij3B^@nK%r5KMS)<$EAnjtU*g9UfOa2FAj^?M znwFuYw)91$I#XO0Ib1)2ww7l)%J6pNXZa2US*4!{X#FOUO9a@6h*-NnZ17yr z9cGfiguPkEL)fh$Z^s3Eig1=GS8EkhSiXmlf{zSDu^d&}JVas(xCQ`n_>huvOxpfK zRi(sPekMkiL|g)@NPUW|-)9NyH-@1`s}zq1vjF-~oQpHA*SF-teh%GgZq=#tj1H_1~ayO5p!A~MufOQ5*J%Th9rDe18<$$zJ zR>Do3VD#U2LgsvJyhL3Yiw^*D1KE}h^h%3bo*BJbMJ*wxfTqHvjihg6!Y=AOm!n7c zoq2@C#+V4o>(I@lh}>zQ0|vR|mrYc2hA>m5?rc~TZZqC+xM%Q=+dWeS6+hT@!JD88g)C@Rf_^`&AcP&(ET$j>nlRX0S4K_V!L zE-eJSb)UH$Peb_&hUlVqFOh1IWRb$_CN?3>(`dYT^Xpo1j3}ECV1~oI~L& zm}zsRO+B>A6dwS;{N4i^*HAcxQ(#dhg%5Y_P6iX|z(JQTpCH)<(vN^Vi-axSMy!vF zocP@bW8}!+0EZ^~Ett7Poa~u;K!EwqfQM2@zvN%wJuhyjVrwCZ0$pP!qXNc*4+j|S z*YPA)AXxnhk?9Rl^)&hoqGfrHCRHtl-8JFPu!$i@wddneOEeG+f1>A}3M5tjj$NxY z28GNgj>m-3_mEKPwK!1)@owm{y=LBQ_JBN!ZdRMd$0J5?%~P-6{S%=Z?+;e9v{6q7 zDA>59Ls{R0B_pU>2?AN_sleH!wQ-}^2KNUHL4BA|ukR!)2=&!R4nUfrM8i+ET` z8`NjF4Qhu;3YSVlz%56oEM9UVQvm`Syh>ty6RVrHAmtrp74b0bS&9JZb}6NnUTwn%F((n#uDx!6nUCzd7bQ@-?oLaSUvwFK+=rPVK z%Wim=OE9hc`<+>*h&|4FXf~|!%wA^+sno7lS;Q+rKr$PU?Du+ypqB8uhoCY%Y$}%{ zJ%CGXCF00;>Z7uSR^%x>ReX z>SEJzR-I>(@27nT#g{k#8^mE(G#SU|!9k5ss-_M%j3dn`67|p` zp7I2F;oKvg0yWDvDB^3{ z88p&67IPR;PTLy@VW0uvbU6~FPL9_rN;3COhU0rxB0*{iOa2GBeQY%1XqI_rW|@-} zn*Sp5*6{OXDphpW1LRm!`2!RY;_4$)vV1a;%~qbEjug5erdiZH)g+iJTb&?45orpf zHzvCdKaWSh`U43EvxzCV1O}C`pUOUH}WdaxjdZKwn0psmK}_h|6Ml|Hch zHCkBf5d`s>6&9T&ox!}jnf9}tM3MR3ZAN7|D@DPar{M8YtT-cUH*=bM0n~>1BNe#9 z)_DD!%i;6LJ=y(y$pvI_KOoFB#&f#XgxFq7zpog7*hl_Frm8&TMe`&wgM#Q47!*Dy z3=RiUfqrGV(-qqTPJlUDJ&V_^ag3T6_AUh5fL%|becTAuFYlwH?$`AVB=;R9P{QGm z>SoVT$p!Lspo<`(xpugEG51M4H&bTL zUA|VuDBK6NX%L7tj5yUt&{2Y00pv$Lr-)(pc7{K)Cx`>v1}WCP`==S0`Fz&@{0xh^ zXm5tZ)|~<=L#UR7JC_guQ$pn3eE0$(QdJ4(52S?9HbfwhrArtXFbFz=!FW$PD!%Hu zMz6ewy`4U;X1T*ch0UbIuMP`sm?(Xgt9GUii;MiwVfpH?Q14WF0whpRMPpz^VtrUs z4Y%^7)Q3(1es!V)a}DCt^qBaprBvMAJa`;UNCG8m$=1^{9Yiv5FhKGRt>9FJK1Fml zZ+epF8^H}Uqrn=Lg#<9>Xp%t3wDtCBF+pf40+{<3(}sUWPN@oT^1G-WMTL)1GmB%G zG06RV`72xw`xS>o^b(F4?K77n)=i=+j(y@3QUZq5i(0n<(*L(?0OM{$x4?rrr4Sa0=@#eZtym@5^6hzs7ilOZPzB0;|1?EoJ z;?8c%k3%4ISVTdbc^hOd5~TBvO=MxyfY55BO)Ob8BRcDKqV`vi6p)$14b>E zy&xfB)-Vf;Xs+=+{|v%QTa}iAjbbudlcjv!Sw8~XhtMq6;d%s|rpnJ^9$Kfs*Pz^n z^aBh=t=WzWj042t_OHe zmoL5XY`hNOu>e5H89W3);p%{wV>|`InpSqtqt~>1ge5@uEXKe2qbG19q0^pxJ-e*5 zT{T_{6Z-#WzrEQtb_O_RKJuDY=&~W>+LAFruhPFVyk{nX>4Eg3-5@s{^n&g$I9+kI zWAw|h<36UFd%6u$k17j6JT5?uX7DeUP7#{$2r(SBT2aMUy-Et7lw*%Ev7(N9(U^G$$LX5)+yoYd0p+nx&1f?PZPrA~0&N`o zUk5z}*fnp`9Ocw$wxsuR+#NX_h6}i@nMZl;>60iJ@+lufnN{2qmP&0e;seP%DRxG| zf92;kPp;V_HSK9im5NvR0XzUVA-X!`mv$ z7!74)gBL~sE4k1vnj=$>J<*kn0bNwzDH>4eR`>xSL`;dyTEXH^ITpjg!|WHkH0-+y zNamK{G~K3>Ki#7F6K?Ch1g{XiV!~IVY=6{NJxS)NG*yO6{SgerRHM0r98B!S?!k*q zo_DHd4FT;CUD$T-HBtzYMY-cWd2ik|&+^DuL%fVP*K7~Q%fZtmTm}Y_6IEISAUT90CejiR;Z{OGW2(n|BB1&{ z+IR`s(eX5_M%N(OFdQZ4{0^hl8E7y}RwUZQ*V!Vl!mU>NTaj({E;SlMmB`&0x? zUodnH_`X^)S?P^e5;qNMh`5T;>-V?Y3x!BRh@5CE~rDMHiuIN`Yi3!8aj6`*FQq zQ|ME(tQ6Cd|B;OA0|as`uHd{py8i%u!&(&k+b;Q^ilgTSY2`?_eT?B#ArtGlWVUF zv()Hesnia{Ot1S>b{wTy%kfCx!jU2-$8n_8H7`@YVV zg2s>c72)Y`lC`?;Yx{Vv@AAH$9MAP#o@?iLuJ7_(yT@~Vm*?6mb*`t{`T!RhOcG-5 zp#Y4Xm@9e|SX{|macVVJD9Zw^mlLef5cdq3@R)>vfG@UZsJGRt=1XLT;wLTz*b!-tNCTuPXL>s# z%@JvUG%mXyNB(HrRW}Y#M_=IS-H_9ftsf;I%8k9;U*~%0M z;~gQ|iLxV57664MIK0VP@E}v-Oaj>CiBtsy3VQXs_t+ScFB}m&XaIs#?{s8`#6*=@ zfQq2w`fkp6#@eQ7aj}D($W|3IiIf_tLkD||q$Ct{sw)-f#}@#%jFgMH6i*O*DNc(ew?X>8FV%=oe-ESp(Q9#~@8m z)VjLI;8Akn99ZyZjQNy)`h}R&Az(Dsf;wU}NBUOsFMi-~=oq70pyh`{q+3UnUUDe? zF~VPR0E$QW$0*$`ES^kd61o6LUO){CCMNjRP2rD>`hZgcVjO5!!~yrgi%PR6VYPjv zgGV|jR4M++L<$I+6psy(MQ?TEJJ2b12aZIL65s&lBwW8BXzfj=y2S`^QUw8kLx-T; zoE$U-+$%Ha96_OtINgH=#ZfkV6S85i7+EZ&4(yc>^p4nnlnP^cP%c{`sgT?)Yy;;a z@IiaJ@VxGH5m56RDKpK!n1l}}GL=iZKydf69<8{H_Fy7K3$nEhkU?OrWRR_SK;;G; zE-eXouVm7*r3_#=5{%JdND8>5P4AWx4X$l8BL6|Ef&)rPJ9mS6jGbx;5NMv1u9x9+ zK_H0vmRev(AKIb&&`!`@1jX);eou|pZ=fE5BAt}|Kn}f~k=f%<PM;y5)N)yywZiwDdMJ+}BK(7?)! zj`I4U%j@bQQxk9tD^UUZob)N@}rWzYvZ5$lTsA zbE71fw|yt*W*p|_peu5`C8{9l+w{UJ&e46XTXN_kpxAhIg_VyZ3z9^56bj$N)u!C6 zS7CrwO9A*rZ2QYN4QjYw_F3CGv%1B*gbbOJzUFGxl9R56_Ep!aldM^rd#`TVW>Gh- zYk&EfC8hF8_PX)H2?_6Qz?kw)K)D^^;EQIGzLUh!~l(1IQibbCVVj6$V%Jm;--Ytt8A*FYRUPUjXa7i-TIWigJz#Q9$y!3~>&geHa z{zdy(G+$g()$~(2h4JiujwIwC+#M3I4=m$xcS=kvjYd`DnOa7a{z^5C$h$U2jJJXo z$HQPcpRFJB0BC%w9tg$*)&|1tkmimnopWMH1BiJysM!i`7U;c(22beqN&S?@Gf8>R z7QWDz+3@-FRwG>Z*#J(Bkkbj!C@?^i(u?`vXRw%0#)B81$|363hX=3}-NxtumXalQ zpo}L0`Uy_vM8Nq#sOc5@vetP{C^d3n@__7lNdc5UgDY5zL87oM56c=35zDoj-@ne1 zKv=I;O_l>VJFkH@$?BYZ0uy(Cb1$sqby9tQbwBJ{j~~6s^N$~hq zA`_##dDKyI+@LF8c2^g7h$$1&EkoA_Fq0d1g0pM%BleTb*qEzBmu&nn!xfWA-_kpw zPTO=L%)rLD>0h&no~|T9#jQc5V>217yx#IIlCN}6=BsC}?c@8W=!p4r&O4 zbU^1wT|{BQK}XWr(_|8?Ult=iCnIXp^QLG6JqCXmj(#gv&}z?@SSX;#ud=`hW->*Q z_0@`C8?LB*FbwlDZus;t9nIF7GswhfFE~N7(0xqRq3+1Z(m^}h77pS*5%#*XnNF7D zhe!8RP4nQW=`_T~8A?~_B1=HA^2kDLzO`7+7o+9+g|!7V_zPF#0m%>}Xbn=HU7@3L zelspN7*z;4sNXy=PKyvj{nxD-n|5~5Di>vxbH4T<=&${W_p$&aAE5pKKO zYrf31w|^zH!UzgIMX&H>xSlUpUPu25>WJ>{bJ7FzQKbs{0lu2l;aA^pHdq%6LWn1W zKrewJSZ%isO8a;?Udp!ghq2DjE*Rkr2aNDe7DlRi**UH9uYk|(n}V4v>1j2B`gR|J zp!zr0fqW~L{|~d=sEzCqu-r#mkgpNdq()_(@bGO<5@{ zeSXU`9lxtokiE=hYc5>eSd!+BZRU%Rh=u`o=uR}IE^Rp3Ji4L(J4bH}CKTR32D1hA zgPiHIC2gEe3-u>Nyl($hYJtUyQRkEf1KROS%4Tr;IBUZDyBB!sDW4m`tlik}qF;$d&pBrK<%5>7bt48`ErWR%#kQ^bv~#f6^R%M1JZl}K`iJ) zb_gI1hLaUZihZ;%hX{JhJ*)iy1sP#h>MX`*-46DX&Yx zT>6dFGAQWeN-$t<1H#n+HH}G?K#OMzZ7=q8fS5x80D1BV~#aI~5fS zJ`wX2GaIFMTbIm1lj}gNXJRwnt!>h}=szNh-50}RH<*wM{tL?%=i1~{1(LpubV7+h z29>j5jmtU(9I;#QT*$+Q_dVmoUu5ypa3bRuzQ}HQ#=kKuy1Mx$;8c3lM*~3*b!pi2 zD00@JSfo63&p34BDKLjkx>@_FkBT-h@8E2F0R>7nd5Nva`=#iD=!F{fj1kW8l~Xsok}k-<1K?1FZ(1OmRU>3cxFElg3-U|a z1zBk(EDo^fOZV;yE0yQLroR$YaS|<F|rSB38lICO5s zp-MAhae}VVZAMpi_w@q4RhmYKkcnq8VzHBy_4peY#qx2ZH<&M8f@!#5oN$urH^Ip^ zlIoj_K;4;(V0uLt{AL4qyhix6o(HQ1buVH?00BY*umBM-ONzaKO6rOhcoSel2_}4N zhQJLBSl0ls^cNwuAk;Sr&~xh?9}c0ES?DrPMm}N@VG?0co4vMd%RmHOM}>aQqcFOP zlJ;SUl5qW|4BE+Hu8jl=#eP&wQlG=2p06QMB4GDO!y|E}Cip9xO)`JnLUy-CQ0o7Lyrf^K`M|ObWxbY18RbCiyol|7@hnq}7{l zgCpZ&asoX!qRNQ3k6rJ08I9riJ1uR0nb^DL=Z z}S`h>pY;e>0BkdP^dRd70CDN zEJu^^>?cSrayvP08|Wsc^gO2R@S5&{a|iv3CjJ_%8+S0j_90GR-*E=34>y@A@qjC; z;xF?&t*)4kJ%DXHcDM<4MFbuIi%6LVjGNA%4Ih0UiBBr}k7(GTL_+j*AT4>qYy!XQ zG(Wq=*Bz#oT`I;xhRRDYAS^i%AAvYw1X zTm^~cI!9~=oMl;wZ#%NP!5+WCT9}B*IHNca9VBs<2$H}sVbPH7K8lrm?6%%PdmTCmu;r*IpgD!qhd!lFRB;t8L zM=xA71hqL(n1-HiP}vlIaOIyMFwOIlL>j4EvPPAD^gJH@vMJKIx>PVBB3?9hi%YL) zMC>DkKnkyrv7Fd-BE&w19m-o^GE{Vl3{D3LzbTO?2N2=n9Y3S26q0^`1bx(|=msbJ zI}5r+LF)g{d$(`+qA;b1+6j)F+&p9s@dAL0Ab94}hy!2)~XkP_Z|H!~{FA7?>&t=Z_8 zF6c$YFyv11(t7gneA!kw1siu`Jm(TESfAI@@5>d!MMQ`uPtH!Rolc_st#k@z;V)43 z9;mcb9z=!8aP`QBh3$&T5H<;0r6X2FzV-AEUoGCz{Q%s(nR6I%QYT&Zsm#ZY@t0 zD1O=hj#VWV$7-ih5E7It)sE8Sud6 zk3lrG%{uCKCsY=l|8Sr)>KwLlK$kd2UAasPBJ?_=^PdWxA&C-u$5&D0_@@|h{BH_5 z2!`cuD@b{gpQ_uM;c6mYVZV67b$wfEzo@AtVeha5s7SOeVxNB$hFZldW%9a+4W~IQ z6&_fuQL#X?VTfobWJ&@k$y?!6vEl>^Cf*xyPaHqdhtnaDezihTKQn)w8LbAaF!j8V zU_gLEBKN`I)TLJ>hMLScyN3cI%;o`#vcV=jrNUsaO*9zfh*X>}SU6E;R)!2@8a509 z+N0TUF^5nE@OXg;I=w-N_~G&TB?HmjZv+q=KVE~i;XFiFq3_)k-~5YV`&% zT=&aESeJ1j1M(M$ zgArY3Y8ShjFj0!boUm0$ovCHl&GFEoQ+XW?W|TLF;b!@Q>VLEGIv|CfQ|xL5zfTTv zaEGkU)m7_gVPd}TMD_IAum%zg?P_5ih(Wa8u`r(1t*1R20-P#$kDGHpMg`Vc|LSr2 zNX2?^MH^&?n%9m97iIk%*}vT=wBt|+TyNg3lqR?5qAszg61>S)+mdTeFk z(gE5kL_%z%jEO33RpR6E*!d{BYi~S6C@nY}6)HvT9;JPPaOrX-DAM$OwZw5i{YA~D zJ_sXzoz2&gHll<5^oe=`0#ViZFy9uKebG*M9D7*$p{{T)yB^t&Rb&{3((cwH(% z*Fu4M0;gS!vj%*+$_^D*7M4@WFDgu-CS0afs)brA3P*Zcia0m?t3jFzNo5;G#sd7~ zkATVx8+6;OL?f7cDi{m8EX7Mn8j)>faN55*2kH@i5uZsPm)8c20sLnKN-*fJNI{0? zkb~(afxsTDXzz2(kA&MYn6>p%~0mG2#Ny4xMi%;`z_dPA^2OnjOARN ztfjy5&&F;`$V2Fah;c^+i@J&&N0hpQ%LvT!ZCDR#%)jm;}xyE|xDa>zsiq!1z#{XM2 zyFl%Z#9TQ~ksTnz=wbeSqN-!St%cMr_PR-GON5c>j0L_7-emC>lbbhNY4qpXxxf?a zfY_3+Y6?R*mdg~yP9V4MX$1@B(bn&<^|LYBX6uJY&y7`JQg#v*B!j_EQBE)jR z#sJl1^3q|!F`;6prM30eR-k#~-%N$HZmj6wiYl!Lc`AXaCBUA-R7iA5gdk+KApwj8 zOsH4Ep)cM7c@5;|BqWvzZUZC|>DRI^XgOP*26x=9bUk~v(*QkzjHKIYtWe33oflKD z)9^2*XdNp&oHCNG5>D2<3xVPyiWsN1>OmxT-EKt?E6^PaL1g11!sjZ`ALiaAu1AgQ zPOq1wW{FO3?Syb0>^WI(ndK@$R*I9NrSx|YyOxG`e`al|k5T8(aSc8;t+SaHN0bZN*MIeLO` z<8*<3N3cO6GK7u1Voc~y*=0=T?Tcv>|F`ge8~>ls#Uf#DJ3h+gBqO+#5p&_=%t$rl z`P3jYVM}#3;lJmf`GEe&U~yxMjuY7ayz8a!6oq9yZ%hLRH~-8%?>D|N%ZZg_VfzyW zNn^I26(l!A5%W*`S-tfwy}wgROUO25Qk`{dQ8+k>o>+qZ-kn*(DFz?f)yo{J4R380 zf|B=hf8u{J_TV5o?{lY?Gu1@-?J5wL@W1I@B*Z@GDM+&9pCqoG z!?C0fIl-*zW68+pcgWAvX-T1TvE<(KPRoIs~v1P|QW+_64UyGrcv%l)zGc>+77N@rO zCU~8392tPvj^`B^jd5EJhx;WLWr5Uzu|cxOA1*&+xBJ_4aVWr2A2FMHpwVDEJ=SEt z?*vxvD-(12Z5>`p;ZTBB8HblGjHif&OOaFRVv~an?`4OUGjz!=`X0PmbW9PKh(suI zH#pnry_>zg<8#XhF-4@hgZ7pj-6q;&X5gWyU_lAQw{Ns< zMA;`dy7f(~Frj6K_29XEEoO>Adh^>Nn~ezTy`+ReSG=Ta(IAJFqS(VY8IUtdXn)ckNEdB6BgR^^73*Dma~rdua_L)I za?_|rI~{GF(KFCVY7Bbh-o_Ecj3#jXFd^ehGh_ z-l0-;ITR2C9yJ=}r{KtTl)e-ttuZ>Rc3QW~wOO#QHfmIU-Kkm*gbSxErSF(t)Vop( zOf|l5uQR%CbZwz*Z}sXW!a)x84(2b?f;fkE8R<&Cv0Is5vIWX-=z1EFrhHRBkj18m z$er-d)lLO8o}{ei`!iZ?4+0DJ*G6L;;Mt#fq=?v^^y9sAdr=jDSTIB%?C!*u*R(7Z zQAiXMQ8nbZv_X~7OsqyGR6mUUH)(HS%|x$;1y~TOCX!=c92GCp0f%Ym>{OAcv>Xd` zc7NUMEA8e2uDmv`DoskRn&?>6=!1)zQkN%O`cxkSF7yX;Aqvs2o+hZ_H%rvl?CBDS zarHwU);dgFUU7t~!y!6_osny`85rGCXcfi#FHm_=L_qoej~|e|oUJ23 z`t8Zei|7iAu5isD(-c;>GR>BhOx1ggCb4Hs-jR_NHB_n4>KK*P{!iDLm$HX-4Btex z(_pb)8s#3<32z)7X`Tiar5Uj$PmxF-S$rKs9B}?x}*?+4b}qomjCKX4eg)P=11t#wqkiG>H-GJ)?>0 z;noCdP&xZH6$I=~7XfP04}_K)9b6K);&r&Ty^h_BsvDQ1EiOuur8%z=V?p;G)a4d8 z2ZiG=GuI~JG3jz;Zgk&5Z=6Xuy&HxkpEHMTtyYtcbmzir@b5SpcD0SRM(spfBb?7* zf@huV34p8z@?VIgQnNo^Xi79V4{pv`qRSIr)3wQQmKg;U!zjv1|4^eEYvU8@1CVYH zVP)nH$;X9Wc1BfinjSYjJqK$@Z$Pc}NNf!@{n2Z3=qMKaPc;(aX;rdQ#8c523il07vE%c|l>f4wVylwmo%`2vjM zW*ynP6UJBZO(itI9Fy=nvEh~uQBl_3DOuk?vC)_QIj2AQ6&yfC6R=fZb~ywbdi4H` z-$ejN7-@x9bs!ye@}r%+Epo~KJIWqoi__R8B-v@zB@$=+Zw%ewYC$kqL!*^&OO`QiW4k z%gkeA^5wM1^go1v5%-KxY$;>9VE~zymz#^==38a4;K+K8tf!+`4@2VUxjuTXjd<-zV!y=be%ipH{(@SK!ElxT>E3D9eM06CIh+>}N1oPVTipL+E4+2Nt%2PnmBk z(qq2#(W0ojiWyOv`(tP`SxXcqnMm&hUeSnpx8^K}k{HB%h9$@{v57 zM8!x@>M1sUjD|iYvSpZ$V&ka0d{}JUPBS+Y56ICMzkCRrWyVCQS*fWZMr5>bbh6|z ztKhtV%%PNfB_YR__$OFStqIZ1s3c<|&X4mp%?6`jG#eV*EVm=5nbsYt9H^#UR3hqa6DjEjArDSzpg!)~A`dw{B z{SH8~A}fq$U5au_xFiHP-PbYOp+Q71ape`DqHlJe!?z@xRXfTgRWC_vT{4m*${p{p6L!DT*7SwiA%nVN2=w*6UzxcDG0Ux>Is_pPe7Ro@HqIilyt*%{$?(S#>J>V0M9a3ejiYo=o!Yf@Prvh@A4Sa1*>D*; z=O7pRGm00H_jC5T6Dpb!qp^V44`hnd%=2rcVcD6f#D%PyyPi^}QSPFMW)Y&%mDcg3HX5btw}PF5%~xu+77pQywn(qpY9 zCXvqd6$p(exf14Zd^(9CxOEst-97rJRQ~JR0WpITB*E(Z%{tp_1FnYbI|XY7a_@Y< zE)3ewO*QQ?Vw44*UJ@OUqSYT1_*L@z`T=V9YIxz_cX~fv_*xlch%uq>j|{s5xa)M| zKlr83a2^08lg3--f}fuT!Ncq+82tY2wPvGVK{%Pr2jP0Y>?7K$6*Z(^%;v)p zUTH7F@v?u8fGcSu(OJE$)}yJ{9xa#iWq=1q5A)@Vk7pZCvoRvks2L9Axpw4UYp&MA z#0T5M(S1mbjE1AhXpLF^6cW{&hQB#jPr_=pnA|HdIVZ{d)%5>{Wq_RTd!5#crp-N< zCL%)?2!nf?tw%%YHZ+s&P1(|VT0d6;Ffq`c4iWTk!ajL6E~wZ@Gq{)!eg=#AWITBB zspNm=06YZrFM`qCJa`;U7Sy6@+@LW^&gx&>p?dLO9>uM7jJQ_Mn*t5SXm>Ll{Z{L4 zwdYGT!@@TCRmMZ?e7ZpMs})|o3s)4(2?_sn_B5FU;nTwu5r29cSlqz(PY3e}uoF!P zFzMAaj;Yw$j9z0AE@>U+obgpV34-}z<)L}NdbA7>uo+HP0fH6#=L0F^A9&pWdtFY$ zNifD|_(EPmj|V>D9lEWa)_~ACU_5*14{+za!DG0@g$>8+6*exrXL4{SmGVurvdu84 zH2zTh!#C^RaJ*WF2tMs!#+$WnrP@RkAiYLoBn1(@Q{>coC&TgmJrIQ=U)L{-kqWLj27$hdVM=diY_<5PvIgMsMeYwsZThqU>H4Ty;)qM7iyFrccsI z^7e7ogk4m%{oC#4^pwwyVAgKzchWuWu)z}AUV*Bn+%HgNQVmp#w9;LMzgXZ!{(=`Y zTNc?x-O+r31=JX={?0+NF41#of>f3Hd=m6oZlcR_v!1No44;XwOH5$;d+QNF{DVnW zEWg>Lp=Lu1i>3r75?3#~J$b__im{M}o`#U^M2ch4EfTLQumXycO;cwNqmBo31|w(^ zaPHxI-JXEG(qAMBVZVkVFU0$AhJ?XKQmRnSkvjp@3OqZ2@P-3Gc&Bav0nbwWD+wV# zfO95-koeRILKh4xf|o$(ACtXVFh9N5h!u6Fqv;*QmE9in9|6@~(Fgs99Q2>T_i%VY zI^5Ub5d?e(qB3KmZ{h=Kub%E!-Mg1y7EVXoOh(o#O4+ZvSV9SQnBdB_ zK#@AnB&|;SH@AS={|4P>7MB`*jPLuT6-p{&v#Ci$ygQO^v2_;#RQLwn$dJ}-2$v+T zd^ks3v!3q}*BlgA*&sIKS|fZ~&x6&18e}u-wpjz2hOmar)=Q!kVE1pDRc>YkKGg;k zZtyK&BTByLKyG-bY3~V0pQ!sn#AYN4i%6r{t`M~^_-}}BA@!spG6oYwCo-&Rt3_$( zHQgu%&ql;ZVi5~yi8ma=2HvS#*nr}>rLX}~)l+~%TI3l{uoRcO+ZH z`iN|OYHBCK@ghPmDnRTJ2H6;n5_j&)4vJvL; zHA!O7+9%h$^8K7HR$l_6^u+Np*)9`Yb~D_jr@@-8)7iBcBO)*G>j`8aiNLT4Hvz-Rb&kPi zA>uEPgNUs7?Q^b6+VpYsJy^}a(TjBJj=l#=w3y$UjPBRdIUGfsNc0is$~nG{IOj0L z`lrkB!=r(8O5wC9_@4MCCFXHEt#*^+{FEw>M22oDoKVF*Ds3Ff|J;BsXXGCJ9HiW% z<)?w{!HOkqEpi@yAz!lg9Dhl8j!9FMXUhJ>bMO;D@%K+yo>C$S%{hUBtav_*c7XMukmtqIH2t8XxwZ2&n~Y~ap}Lw# z-3n(foyYkEQRvvYE37If`dVD3oPHbyRBk<}88T^JG!-`AJkfy{;tk{w^8n$-56XdU z_ZGT>GT7&xJp`Q&QUxsA#%?7@_8IwQHRhisFdy+>6u5#!gFcLhq&eY#e6!w!A0GXN zxcX$A9R;umU6hQMJRS7(Zaf?>C8kYBrUO&za>)jLFp2VL$3!UCYP~Tyj?ydtYEs=~ zd`s*hjM~cjiY498m%qX#icIH-EIS6yb{M$HU%kOqCF|+l0%eyEGPlu0OJzv}rnex3BTgni!sOdWrFC+|BQiFo$@#vQ=Tv*8u zDgmPNbA&)cD@i51SE>P z>>{U+9hl&$NHfEYYZH$RrkF?abQ=6rcX+>MA>!gki$y596!Yhi%FgJGhwy@q*Kn@t zSR1VXLEgZqo&ljgYJ-5{Lq51hrsB>25QPtJkT?QM+1;ImtQ#lLVmKRQ9OAjLp~gml zL>p$FnRjpi2e8=&4lhW7Lr$912Z}C&0RJUJj};Nl+e#y`l`=a4fH&-<=)p1@0sfEU z>u)UCQ}`cQ@J!gh&Vrw*)@~BZr?d${<_6tV#^aTLf{p@S7xaCF3p^WA#@S+y>_mir zb%PUr9jIRXZuMPx$m<=gqeRVV1DBzS z9C1ed7#ab*=$;qcJ}syr1Em>#M`+s_miKAF$pmzqZcjGu=(>j-Jjt{=3BHD_$2bYV zjQN^gowMl;pTB>a4JbjY|M?k97Y=IFxam&eiwhyo(!N>TN)o-h`S8U?^ptIxpGeh^ zkp>%R2Zt)o1Y2wmrU0U`kl{^>AlHZ0nYF#~Jec26!dNi+JsKcVloSPAHvJb!bO*uR zzvj!I!ToqPUOkS6GMsFRM{AB3Xhp)w3uuDDNq!i`3tH>J!uLB!cSYu|Z{)5@jas#X z6;gLcYn~PZ@iVC<=-T4Ym5|kaKBlI3I(xgJvbRB#4ZR|<$#%R+bvY@&WjP9QM4-|# zKYEWWpT`^{+X(bJ1bYo&NaC-QV=@4s*~{jw$N;8}Rse$SVYm(j>Okd2@O6X)K2AZo zn0l12o=!st%68U|*x?Z29cv`EBd}fNEfXr-YOyy7CQ3aW4})LJa6t*ubb`$$;;4q; zdSH0tDI8lSlsu1K(>$HXLjy!({F^^|f&@jx{mWNOFwGfT4Z$)>WF#BQ==WNWALmBb zvjP=E7wNFCv;G1HVbAt-3)rYKKwT<&WfOQ0L8I$ zuQfu^gb7mpK@8_h?<5=!)lEnJizhO;kbqk3o>N60NQM0AWgyRk)6&$!)Mq4{%GJJl zBrmW>niP{XFc&Oi7KHujqi=F!FF=|?)Juu_D1lWYpZx9x+|e**HcTdT!&^$Ix)pPsls8nkmPlMB-t&Cp#&zBnS32P!QN1?Bg6Q$#jb{pbQRppIb!KrPvq%(E017H zz@^Bxxk1X0grH*l^N(O446_@2gBThP;Vftgu6D!WGgpa-f;;#~GYz~ZC{V@usxj{~ z1VAk?K=TAgmE>6OWE#HQphN?9mGtU-Fx@QcY#O%1lLRPk0RuR(as=d_4^Udb zH8fxZdpWA%EK$LOks2M$`gbfr6yHr#rWK8Q)0D-BZHW}hr!rb{<3yV%xM>gSI&Oj; zf<6EU)IynzW)CQsz=Cu}oVT7PRZ)R9MWQ_KzJ`l(QyBM%;*VBe6t zRoBo8{bcB3&?t5|CnIQ3OY=zNChDF*(ctSULNDB10=Ldf6Ag&gDRz{4w3K*L4xX% zVNivcX{%xZbJ~Ge1|cK)MJ$8aPGu+I1l`8KC|3|$$&wP;d+h~d?d6KOZaP_0+(zQg zFBr2&OV49QCT41wY&zMpys#+T^R>X;oP>z~Bpur16_$3bado#;%L2&J*21@Fs0TaC zJSBh9U7&8QUippKy0zj|$lrKo_&J18DzxoBhs!a^1ZRY@_7!nn30?8cplu6iwc(ol zowSu!LyOWV&BuO#xXKh6KftpfZgbbwC`Zl&_rWf<$H`Vm4=Il=cBF7(k?!cW2b=-^ ziLHk)=X^8fF!i^hxE-I=RV{)~o31LQHISwjAsienRO5aJDEH=fSs7RDgi28sHlUUw zlZT7=McTf^H{;tIycy!Olb;{smTa@*$~VTv@)c9pBBN0Zm3?bA+MexvXSCxZ0{v+- z8g5|iVA8L|r>o6;z{f(ax*eU5!0J^Gs`3(caANunrG)K0ib+Mdg-G-8ioYMW5fRjn z$3H4>y>|S!i)2dl6*Vo>YK9d-Rf^c5)hff!DnMQ9dw732VoS0EcBt_(%_MBvtY4<| zfzzz&9$&OeVvJ77wCq+(O08{f9OO8rdN&M)BbydgpNmM=v~BzPc{UO~>P4o*h43X3 z8Z^H#Wj12<$N=D<<`HmIX-!8^t0 z`f0+sK0)ooAY83raqgcHXKMvU1Rn(fx6+}J&Imp-=3`5~|KkU)&ofno;kBoX_cpEW zjx!pXfp}uczoV%;ImA^e4!bIk9NxMJFp?aOl@Y^`tvfxU(*K#sb^g4)_RzIwWc)FmD*D-h#^w-nTo z@Py3rOzn9ZV}sd>GUWofYX~$&M7OK( zZE}CjT-i@2@tr<}9&sB2b!dE@-OflA9&X37W+XdoU`+zcB{C_ zRPY3$)frE12&a(UTYHWw&JO>k>a5C7ULYF$k{YgpLS`GTLORlUTi2UzgIff0aA6Hi z8|AIpzZUsQK*AA(g959llt&38P@tp@45y03F8T!iJCzVn(Hf|Nz`;%xl$#NB?{U3e z=&+YWQjNo3YI8G2V8ErBx_w3#W>F6aK>tWabQ8fHZI@z5DDU^epz4fut<`GMp)9I! zD)jtRg6icZ6{ybd=m2$+eU0#RDtyWQYJ7HT_{1G3{|EAxH5*AQ?|3hZOnA{sebFbG zY8ntEfrQD}NbNWHqdd}lwXqVaLlAvUDPF`C$5x1EIzUrSBB_^KQm`V>!<1=@@ejvd zkj-m7wd_&V%Q^h5dB)cwJmVZZ<6BKU!vR4TGjRxjwMS#*@Co$wtcg!J_FnEL)JrmP z&zUzkBT=^I4a@^xd5fAKm{AjNgC8*6cQJ)5dEw;GOIVLeiw`zVpqaq7JisdIC>pGT zz6*<;c8Y+d=3HS!&eU>+?Hf^7$jn*Wq>DR+0vu^pC~!@y(OOzHi3s~P&^m*;b)lMz z&>Vn@PKmyn10>AVkO@!{A^OR`5ImGJKb5ygr^n|Yw%}j=nfIpQQ`n~!MhH$rMUv#f?;*F1ZGdEnw4|5L*FAm}Qy%%d**F;lw(hGg5YI zozDfMJ=+6|!$VUp4o)#W3P*!sGAqZXCQ>CQtI!5*6apCklZlS!ctivyk7KgZ^1Wfb}83ejx!Cu*&+wstd3= z(6FlW4qUXOAYMI8ymO;lUBX`uT#I5E!SoQ2s~+(z7a)A2H{LyvJSpOxYeYkp;2jH4 zIa_hunRbf!89QMS$C-t(GZCi>7fPkN!hDMuPR3IF5c>V)-FPyUZQ9P`ELZqT>WERz zR~LC?5N#4C4(pbw(7*R|-BMe6>-Uas-|+(KDoc-7B!H&GJ~N2r#Kon2HW@b4xWqpF%{m1;ReT#1nh)t(ka<_IY98~_a^AFtG^2-+9aLd?2-K_Z;C$ZCRB z)fJDMkpdYcI@;E>b`VImR^?8M*a;Abfy)RxLEC00s6)sEghNZ=8L6AWs!;|Ta`iQ+ z5Ifb^+{?A&3LO=bbT@2tOyz{6Oxbk;{p-+e`^Q|C zn$wDWA}LKp;^DKG5w@!y?qjQb?4B*(8eK$(Wg5_osmi%y@(=)V+=iD?#o##j-->N^4tH=Ry`)Q+nYD<{ zTAt^%riC z!sBAvzVSEmk}PF_rx$3)!C%wA6FEEY4OAX#O-K1y^+St==?W4_iUX`DXAB&`C8}fz zIa@L+)x5OHB7_N1W?&^Lq;MeL-<$lPTSy^?4AqK~N6m0eL8yr2k$Oal&*GhX9j=Egdn$iwcK69+N_H*`^N49dDWRLB+?!c(PDF>l*g0qeWGp&t zC^i>s!gJ>k-|OF@SRgZg=P0u0r7c80EP z&og^PH1|KDx&P&~37TPqRW$efT~*{+HSa6MfHw22?55HJG|o;+v0F9N({vn!wh+E~ zn^O_5BH=Z@wi5@b`U5MH>COOVnTUUY+SfZ22kjcVQ>ojJ*{{kGRJ?~ZA42JS=n@fM z*=kj!dKTS)azuk`?_DP3BRl>bfLW_Z23$$0^v&!leX1he_6tb22WJ>lUSySzU9RqZ zVG+5vNkn$i)BPFAiqX{c$=57un}H+_E86>oW&I+10-V)Ec9%6x>1~>yDW@cNx8*yY zP9sRw#iLk69b37jm_*M0jbhSQh)Gm-i@Yt)?13$;W_C8U6ChO-WNHSJK6vFb+pJKy z1(}>^@{AUeX*U!XFin#tNFO=+iIfy7Bx>ZW&uZFW6SV22rz9V8RF^VQselI!=i~(| zH*Xp;!mdR_OF%$PR@fLN*-K>$nHOf88?7q2_sapJl&MY=qBM+ZW^87DgM+cX=5Fg} zJ&6q;AnT!XKk^_8eYG-bC$x^tC_(76Wwq0E<C(ED|}8;jnDq1~*J2L(KwL!fI=eH~9!qtzVo=0JdJ6L`IPY z?T8~wO&yWEqWIb_G=PFxHrl*Vg&y}CHg7cA*`Lk3IwUHJGBv{@MU1M8GFd>7=Bcwr z(kPLpDL|1N>$OpP)QI|Y$a$=^Gy4(#sIJ?5*d$tC`M!(70Hp&9OgO~*1wQWc0GQ4kKE$C>Jq8LY@e zt36**?&wfn4QBJYGAXtB0h;Z@ay7EPs2XHSz!5I#LnMsBc}PsW26|Ay;9oDSxb z`I53b(GBcs8W$+t*^FLe5iV&R=A7rK9TNnD-@lOtt#Yp*e~xDl{Q>fWy}@I+4A#qV zy#D{}eQ9^v$ny7BvRI7$awbt`NF2x4_|AXM^QMmsvZD#xfFSYx^v|!VCDD?)g;o&Y z9EPzClDev^YhTvGaj^0Mfr>j(*?5n$V{e6cop1Oq-MRZyIMeB3?D&h-Fc1kkm^xQ$ z_uq{>AII^c2LgoT|&(~XeCWOpJS`<;P?B&;6B5d&6aP0!rfv%4BrgkujF zvfW?^hMfD2_Md+{lRHeLg>dV?c&jy4lLNd@I98Upoug8I>bzlXcV5TFcUV+jaq&En z{~NFWHrihPwd*^t!+V%-Q_-0__UM}^_o9XH^CB9VFXm!1_drW{&{wif{t&(O=6OAM zeiL(N=2m$a@+$#0U@x^^NfIpe5JE?rf$8;Dc)mLuj-G(E|0f1pcZ_p#HN^dG`_7}% zd%|(26P|z_bOIQZgsD9RL915ly8MwOJAU~lX3kQ8PpoJkyW+&p=_lI1=$y*<7M)Wx zK8*g|1PsfyXAfVv+7{o)IF&f0(T@%DjS*$0B}(e{vMCjC>qv1w5;oS3r}>1&kVhwOB7! zzL+i+%i%*YLD(@1LS!oG1tC7rsx(}4P`4n2wOCQHx2KBO>kF}1t}SHp`0h%OH9!j9 z%C@)YX!e(Qx)M(oX=Ee6(-Gpun>$lrcn!0{gWgJLe#dYi4R)rJT?oP1w)0@+{1jlR zLkccK<9z69@Wvu|X42 zkWRqhHSQn{!Y+Xc0$1r%iag(l4Wj29k=*G2=*5I5Ibm1|uX763>{=UdrIi&L)6)E6 z&l?!@o^#NT92nBi#}-@6`C-}BMD}QpDAGA+%J^EoQg6)?FH%r4d@WzJzLru?Vso4x z6?wh>q$xBeHx#UgQjbadV|ET0b_k#$B8g@K45H$S`X+8%cD&g31O2UWD<5$G9k?c7 zXa6Z4N7e`N#5_ABwk(azxW2lSFEK3XP~E3@$$@a8cKji-5kCjmu%F@K8a{$cKi@rD zpiE4)__kM?b^7tMzgfbg8*p{rW4%V&CfCd!#KcYwhN66oPR-|-^A@)uN8|%I@_*>X z92svG{nQcCHA_-%Du#E@34o6e2#FCV3HLkDr12GhwOM;x z*XmM4jjd)GGlZDEdHxXHkFmPZ!)zUVvBB^ zA5HXDTW?HZyerZ1mR*0mwC!tWz4l(`_TxQLzO7q|kMd?a>_FlD<3RM|7Y|<%*Xy@6 z0&LcmyILW>KE@_1f=qqB)$A(j%!8yPY@Mchn z)J0kr{Vk091F1+ISggHY{MG)FKE%rKk3!rTi(>+L@hAmi_+9nsSe8J9(RoJyfA3}Z z_j)z*L1^9@v18w*&=PVISoOBIHawEN$q$G{BC!;E-Bmmo>V|f@a+m;Mk% zqfAhVnH{|BJ}|ishs~`8v`XoZ<+KL40ccUUp4gH=LWBFdKu|Pt2FNW=^)bY`u=>$u z0RsX|=(e7`7o|{eyq4j2CR+#T5c?i5wYMM&XufbMUay?_WHA%YcpQ2jd9er;@3c3` zKEoV0nbRRLMXm0yZm$_^#T?Fafu$a64TDC82uKQ=B#?SDS-|t@PAAM~kx?Lj1WiXt zg2*@#ThINeOFO|K$T--s9Q&AzP?vziz82p>@*wreV(zM|zgd8!7jVoDXEKN$!~?7= znO4Bw!`}oY#PlZai-XY>V0%*+q>6({$QwS4(V>0&O#Tij>%b5>DS@t)+(maeZpw$~ zVT`LFAlE+~INZlTlYg5yA2 zRwnS(25c6GZAe8kxaubSRI*k&hy0P9tf+a!1qh$>;<~S{4|=(>KLgk_CKwdZGf{t6 zE+~-7!@oiNBfrmo9De?|1nGizNFjp*a3k-3>r5t;K(%;*g{kt;!EEC*s|ZGg&@qJ- z^B5!_ot|ES_1ovCR}fUW^@>3E?T05XQX`xxWId9I)?m5Lt2+BeTZ~|;Q~}-Y3e|Kf zqhTCVstdPm59kCVWlrT7hAI@L*-^l4^A!68UGVet3{Q1Hmm_<+SK-lskYYBv6I+xE zce8~041qhr4q+S!68si#__6wYkk6_l))#duqf}Bj;rL#GeXVh{1v%|W^oJtZMnxe_ zWb2BfAu~2pQ;$rgO=SC#NvU(;tL=!baB^kmgrfo&YfGl59JQHEigYM4s^_G#pIH38 z^`I)n`6*Ca$8w@0qW*AH6svs5N=fK$B(gcn8ib=!{#nCMOvtruq`ZUWk}^tDWC_N> z3<@8Kd2+VU9}*V9^J4O*vj|9!wP88g_CNnXkRi$^EGt^r9O)R|Tr8)~8207dgICA? zxJ0;sRP(@G$Pbvl3Q~VuIK!td#e+>2TD}LR=znf}4$KLx{{{vFn?9^L!7PB$pVsju z_2`jom`U3cQ2ohc7qj+sG-p}-R{<;e)dhhi+5m!dq$MGzti~)!(~1I)lAIBAxE?P| z`@q_(H&<2CY&^DO)zZ0QlMhE7)-0DEEHN|_*-@M5A_gy50c{ceX{k3sIAgbf6@Wsc zRMil38$>f`{`DtrtUO>NC03rYUvzq|oatOd&&9@>+(YLC zrN*bFC%6{OG+;K6++E#1ofrhVJxDhvFmVFo#&o!0fnI7iz$nr)LbO|R7J%t?%-CuI z8&?C+IhtsdV4k}9mv9P@mcSnJS!^C^AaKtQQslkCRck9HsWGvZ)O@g`_22+Ulr%JI zETTWBf%~WKo#nlSK<6+u9PdUQ)d(hK6^98KacMJ~Resz1kB= z%Z@!8sS&`E8gVTwzIN!E)`D5vEq3l|hcVQAzZf$x_HC&_^EGTKzO7~D(CA6eN@Qr& ztn;#J!rHDH;NS-<3SNE{t+2MsTCGXt1tlaj{(AM?Krxd&X|mM))@rz{$(q#MH2Uh* zZFhQYS*=!e5Nnvc_5_p_jX7^tT{P(*6o>{j8+IQb(F#7(-J533rssz2^0#`~rJnok z*j1`g>|xkd>PW>{h>V!&JqE31AtsOjS=nQK8ZpCyb0JJ7u1R_u#l6{=SF1@5=gM{D zRf{z;#bZ$S7QOQi;W6eobA6hqiaZZ}V_fYm367)VgR>@a$mUqs& z)`jS0#hHAkT!`}z*H?mAr^^~_&5%hZ!Y_x*;K?Jcat4sk3`E^7>KtMPx%ACw3oH!cHm-JTb9kQSte!=(@0!-*C0o0w7tUmn^v@jKB2OYRv8#gj7H98uMp%=lw5 zme592G;QCTkaXY#g#5G->UHe6yo?NmJToih!3k0r)IQq>bn2S?xd%*PxJMHXEsuc* z8Qp~VJwz4h%T#pys4$u&wp#1HF*$z^nBzbl(Y3gSyBSUce&695KQ-!C;tUwwats+% zoQ$DnHj`HXG6?VdLvgdX{uLmq2>R?r9tkWDo*BqWyz>u92^qk@bISzzVTJI5%99vg z@Kh8%;hleotjs+R5Et@ZL>Ps<4NlmCb+Mb0%DZN#P+o6n&!P$%UBh&KV zlcpmNMnq5vr`I7AmsaOLM417NxM9u|be#w_s79ey-Rg`8%Hj+1_o}FYCl9M`UyJB( z1_ZP#MNDoc zdUSKtHE2u1(65qg2M%e)thb^u`J#815g4>OxS@Q69If>951=NB`sD}3A!xMtn@q$2 zeznnbeYTiHtziK=4Ai4Z>K8}d2lZ4(hnV#^ zwc@eXkZze39ZQSSua$a~o3R(_HC&z9e4;28qO04Nfh`?dWHv1$usl`55S=_i1QtmE zahDu8->%7&nt&i-U|Qu=%3($*`Q*Z^X*B!+G=vSJ^k@Jo>XE$p`L3~%&EdQl@K6v9 zsHL)TJ>nFh>C%jdRHs+HkT>qZ;R>ms`e-F8lp;<~(UtGExa6;}cboZ-rUQ>wRch^i zF%;?j(lp61XKEQ-RtIMX8ci0&sEUX{pE*VDkOEGTf2Ys;lF(sBShRKL2i+t#0qu;4 za=AQt0Y=A)N;Sfg2C6Y2Nk^mGFrXY`2v^QjPIhzV{!os?rlk8AnFcKJjO3mBFP%u1 zcj?Ki0SRkGpRwhxln+KT209+eWNFFNF+U*5W0X0$S)QUNkhhPY(4~j!bN~M%2-c$LtbMa+ucpa^w zS%X(yaam&Y@c88ArSW+(=PfDCWZ*_EKFQF$+j#s^0Pzk+wN5F>Mk#zB{`#!@3GEYt$GlI$T60Z!0}$(9tN=3jlXV$Z8?7Ny#y&bcYMk zmhN>K#a>h}18`dG(~e4&@Wa{1b4zY6+l;3k6V1z>l7d6J0My1Zdz)@YCy4PW?hh9T@K;{K$= z+4;m@@)+cFq1=@-sBa3mP+Ac0&eyj!3xzdUS#Lr@&q3;3Nem zuvixn?sW~B)dEgG2~FJ~Nqz18&&6CIt-ItNm0PibEh-PasWG3s#sw9#_lrLm9el^wUi$RC8!BEu=_3Z}HU`$u)U4rFE&PpK8nc7=Lno zp>idZ?tmywrKNjoI+U`JWB2n{lG3t3#nblN=;)N+hY+62t# ziYjFp2-OGAj8ZVkRB~$VuFx=nfVc%}b7Bk#4vpBTF@%UjE???ZRDU@8oHvDEB#%l?XF`VWWwyvaCE#AB?i9}Im&nbAUY(*uX914&(g{?A_Y^kK7)VAf&XVK}GkVQO69xbg#4aoVE z(P~%(V?r*XY{psjRY;5J*sG{$uT)1verrMhtOCbe>gwq*7`K0dI%`*Lr?@Bot0;dc z9q7_0ccCc=t$<@fu>{|=)XJ%sVuE&=^sasZwN$_9YEug}RXH1y8ia4qMZZW6SLMw} zs1O+{*$M1N0+_nCM4cKC*fE`(mFtXs4~;q2i}4TFM>C++)*HLx*;%{2b*v+h!S=y$ zcH`U&e3lkkrfKdlqVAC@Pkl2{vyd7q5SuKJ)!;2*ryF}_f|bdCGju5Fh)$1S<}RS_ z46TA3-&@Ru|F)!t#!te-m&En@ZSA`=>&jiN7Atfgnz%MK4GNlz(o~0IVwN#zLT}SJ zJv(fs5O#JDnn>kO+eg!-P;Jwjd^ugXeMB+VF!sVT`3(eDV0%Mk;@ zG{d#eA9F1`<%-ubI`C(I+*2nyrsc%d_8PJB-I(OPlrPJ%w_b7)2Uk#)0JQLpXv z2eEI7p1&TB?U92w8@IHC)m^R@OEigm!^f^`Sy1yZXK_itP!l?O`Ms&T zUUN%2T!o8AU%9MRqWos&{1RLk%QJr7aaRC$@ZY^7tti2Llt`lwI#5e zM6z`)AQIqFVAQ_TFG#s0W+nYb#0$Lci1!`C^>GM^;V^J$m>Pugg7Fp^Etn-U73BGu zVrtAY{t7;`a|Y=+^kC)uB*kM$PH<=@X?@BvGi-cc&#>`J6S-LJq z{}p&KPAv$^=o%>d?&|_N{5yWr!p@A}K?;Fz4o3*S65^e1r6r{mb!NO8=oX6F^~#w~ z7Bk_D$DuwaiQai-svbC#?K`6w>UE2$y?uNNl-_&A=noX-o}wsYK*D%hm>cxX@(SY} z^zsTH7~~Z`sasybrI2Z0ATbHA(7^aJQqaEb3=AA^+kS??3bca^ag+eqgN6W42%_mT zclPYAsyZ|Fz`}FoB3KaUB+mA`_}iJ>Aw~r_`!C*V?bDxacs)o3xt(MD-PC!*+U~rL zjqh;q?TWoHu#BF`$#W%cBOed{+V!2+p%mSnxnqyMiE=Mm2tO~v+Z-Xc&D?`S2@m>8 z0l_~+Z@qb551!w|+?lym9)^LkG<2ZkEpQzSPo@w=0=wAS>n+LC*>Lm(to=VR*t%ng zktQjbYf_>QF6AKIIG^B?8tk7>`q_O)h;LX~7xg`||_ z0BhXWCCYlC_qn-HOovw}g}CD{a~n*cl>eUX51rv77>k@*OH)t3h3cSZprV;mK> znZVsMDjf9ml0`1{V4zu=*mr6&vcMprcUgHCLlKWe5)UGSBr;jZnbX9BN)u)!ztauX zIVDsXX!<5s91bVKkxIts;{$SONv_G~;1hqfStAe68ayMa>AHx>!aalG)U!1-D{#k~ z)ieK{Bcnl_^I;qK9j)ul5+P3Uyx7bqV!Bu?ago5iIPxUJq+;v2KXtKEse^|x-s>MO zG;M@m;ip7`+l=odXKl!=SY7I!WzkjiZD-}ON`LeGJy;|e9s_sEIdwMjYws8wRwcxY z!n~Yfcg0r?CNDl~xp)6`!L*BrKg_^fVLlUf z?#;3`NJ|X<%35}(YslJUO6hpZu0LL~-T>pDT+)D@usD27liLjwl{7Dc{X>uuINi79 z(UwwA@$~+i_KiC61v-rVjog^&!Eo?nO5Xepl!MiWheWd?kQ)#K?>l7N&14~7Ja;-_ zy9%bR{4xJ2FeP}}_h0@yg=K`Li}BQk9Q%9_Fr3=SPH4QCO1h;4ya+FC3@+lwFW zoc|bRJ%G@d&hwsC?X#^ecF>!^ryD*n8ozFQuKi3^x$uu@7K|<6yw+IdN32nj=Auj} zfhhgBu}XK{RY29Z(quV~tUgcg6ITr$)j(EBFoGHqYL5%KCA^m~(?@xhb+x0< z|uN-`=kU<2;c{|b$z2w( z>>_1pEZ}Hg&7%GKC$tcEWhJueQ`PUb4Np9S$b@Wa!yItQ-6%Q=g&N8~FkN84POJT^ zOAU3W3p=8F6=D_fE>?hc5irX3zgaqKAIWSJNXx;*g}7b3v9Sb-)QKjtuy%Gsu#K~& zWrfWDcD58hp|u66aHJL@I6NC@t9i7z&=>Qt#g}hLZ3-2?0~OaLQBgzf$ycJ|+~mN1 zB5nT$1Cd075%v>Ff`k)o{L*z%)WCt7);2&#u+YoUQ7<1I5up@e0~|QWM_maVriyD) zVgoq*?YIQ^pA#oQXu;8|O~k058ets_Px4@R$}a>inWxld5a&YX=9NUhE7Qv^bQ zWk@~QSoNqIl{u9_3y(oqu|^{|Dtd3zju~3#YC+t)Wpd;2enNZKKJ_DF{pt^m&`e34 z#Lz)0MKi6I11%B=fpw^w*F|g8GrU%6!GHbN&u+&oNmWcgdyh~2oa2)={~F~iws@*UmH zDDcY8=p+h??dGjOR|IyF6s6-taWE~%#=WB59MW#m@6m3?Hqf+B0lg-nI$T3aK4QtU z=!__RiB+{26dFNP4F5Iqk>r|Xi%2@~MLbm!($9*2P|~TU*M*d*=Oa3;*>;? zg@xvAyc2}W;--_Dh7U^M8RU_=o4HIz9NbZ2C!Ih^iX^Y#AmfgH z^alexG2Rpcp%%Po35AIse%4fx{>P{BbmG4(DIP;HPl^-?#MJ#;cg~R>#NPgPoVmYV zQEcvAtw%zAAJzqE!!_jAa5epCgY;RL8<$#}P*@Iql z;?VR=0Ovu=c1z=OfBM|QJ`!hdgmV^_YXVs zQ13+^J-p$I4`TGl@B9PEKlJj&^o8h2h5cc7vkv@XUHsR>e|`LSjsFJt55eU2`O2tU zTm>IyB6U8Yu39P9C`vJ!TQ&45!P4xHCIc zChurKAG@?lvf=fxYt=rIkyIdDB4m`x1Wg+L={ZQ3>F~}Yq(R0h4P5TI9G|Dg*R*E4 ze$S1%-QnE*Wub4v{xF&HZE%@W3-pNFt2zf93g}eH??ff^j~xP>MHc912Q*7s;8QQw zJ6c+CkC*FoFNUuA%}aK!>qy&>z(EfAm`|DP7-Z1loqtGk%D@1OxLuf00VnzH+Snbc z>7RkLECdq@u2erc0faLx69Xw)79?8q$$l8n8$Lx+3Cs<=^A9-q2Qtg>q(~(w1Y|-d zfEh)}fU-Xgm8%Cp-Vl7h(HlNRmhYnu3}o zoO+2R0wYZ$CNK_88d5P!59$hz%1#;I6VZ|!t(;PvetIFbu=ugj@-J+OL2XrCxkgX4 z$yBNFP^8y)Sj92}(SQZ#Cqe4~Mwl%4OAF80fD5oe;| zu=2~mIu4j#oyS*9{-bxN@pv_TkpeT17c}rx$!^f{vUl$>d~_kj$8vMv)r0FUdgmV` z&gi{viLJGj(ZwqAQ^vfjX?baM5p(7<1TEHF$&sgDpn3j#;0lF@njN9B+PWIEZUC0S z%?^veguGyFa87?DCvDM6F+D2s@0FBl10~_LY{qz*`l?EXr5bMA3MU-B>finh*F89INSFKLa`Gt!Js4*p&=&~`NJnSo1(d8 zAgRFe(@Dx^*KjC|5|-QHG8#adw0TKgoFxoIB8@Icex{UuOP_C0$F{3ricMVCM>J3= z6*PfMfZjFCPUnDWsq!XWSxEpkcLw7!CvL)6w8)8Sm)IoDDssXAh|xV2IRVqM_Q_b} z6rf}mb7`HuThz3b8fV(HJwHh4lwhtbXMe6XW1l=)67E>P%>t4y$^b~#z8;-Bxc?G# zX@8CWs2kxpT=Flg#7REJv`HK#%COkm_X5oj41F)o=*$jWG<m>w_tpXMIW&kYYTYt{4aWmpd6j6Hupu1=&1W z!PHj0B{#r0tMCI}5WqBU!vL_(C^uN3CTm@10|^D;nR!JjVAOjPI`{jqg1Pk2`;JMrT8-h87;uhE~0U1|7RC z3)Y#!a{2pBVFQu2>P$-WSLQ9oA<`ZsJ>TF^O+Fe*v`I-dso4z>QJVQ%Df4WQVV;>u zha<1~4tl61^&B9PCg?dp zLLAtJ`j*#S(q8HUw>=X`z;{iMq#YdE4KX+vdSoMa)w1%yGgkJt(neCp!y0dBvh9ri zx5LFh^W-$smLxTNnLhCu=FhOI*G4atj+{P`7cUiEJa>6qJm28rc_``PXcTyYkxjcl{2DbbPNh*blhaP3<>{w;iOeOWX)ziM`FU^nKPpUvNn1h)9-jf z*&XGMU(No@lVw9H9_sUho(no^@~55)I%@LEo(no^^6#DtI><#)gV+8n&|y&AkinDN z?R@5V802-opDZ2{o?|mf97a zB!l8bsOH>2P6!Uuf{V2$kV&oS_5>+MY$H$C1r6j{;`y8{p=IgT*Yw^q$m|TlG#o2! zlWL>mJ<_Ek|9iYQbyeqkBYUnm!LJKB!7Jp=%DzR%bVh&7U251E(Wq|HS`4{^th7#t z?x7Z3o6Gf33$D#6dZ-20=C(Y@g3YHbcLHje?$tSkNj|<%z~|Q(P7}tY8Lb7pQHNTr z**hZZA+K{M_%%oh2?U$hEIm4>wPxvaIjuEIugGbwS^6zbYt7QLpk4D3Y!8{Eanc5L z41}|=tfhU;mAA(g*GLR&rm%G+kN=U;i-toQsY zZ<`sPf916$3e{wP=3hCfy0xS@9em{!>^i}f8-zb1yqHs;On}#Ca@S_^=Oh#Jl5g9z zXGV60HlY{fmbrMIOwFE*>(~wD=;<#~@|ncwMUVR7(bws-HadUapOSd@`9wBB-L{ z^s?)=qe{)oEZ{B;zRpp65tZDrqnC8eon=WkRcdpww$aI*RoksxZ7{uZI=EZi{-N|a zQXK1Wl^1E?Z&?|h^;Xw=zZZUwZTtG?iZYz7MEU%6Jn@*6K5X-KG=L{I2v2FDpd7O# zQ>r~)V9+7k)rcY|Lh6i0oEctfi*=kCUTTY^oEctfi_@GLUTTX1of%$gi!q%UUIq~P zwM4Ma3@_xqd&dhZ;_@kU1%hzrL?{L$lSaxE2=JXcMiLP?Gg2-WC;LLG+Im?-kmJnA z#f%ymuzg33@4l{_sT?5c%>5zQiwp<9qeg=o8Ek$>jRrL`l>Lqx4Qgbd{2etK)C|Zi z2oG1@cJEOZdNS^NGCsS6LSvRtGRXgq8V%LR6oPluXi!78f$2o$Pp{DSYd|lN-Q=*q zeLr4JU+979su7EZb<-#=OyO&y5lX7p2`{fBI8Qg$^Muz&+Jl#${qT7j0-A;P7cRMK;)34s= zr08>X!Sre!PO4L)3}vTrq0c?mKd)<~{3LA$7FvAQ>Z(tivN5rrQFVfI+s&lgs$dQ)f6AAb5({jBCXSWRRo7w4lKgMEGMBinh|s` z+D@#yOp@A+>Z52ev=u!-_(It@5l+!bL&i}!ksPsH+xGg zGw6Buj-U`h{qbTm_a$ z#8Snf!DHqgzlVyGZOa4mpLfMuI^*FYEP}n3pX3vuzxJ}26F}Z+^1i@-VBk@70AoQY z;#fhIE3+-hv0{aW?YD$0!hEuPrXt*@$Q>$zd_iZLPWHtfZVm94b;>mexR~Rvf&Dbk zJ$oXWi4Ww_!~;D3k%}a|8#F<7Q7(f3*S>_mA;KHyI8ROf>$cFvG**A?Qs>nxF$Fm=cN@ZnwtSS@R|nfjja<`ef< zXv!m7b@?V{__jOuS8qri=!ni$n9wkhUl#Ml#O05yo5gD4uH5toxAP4`-p=NAh8v0H zV(N|GAjvSOrq2)Ldx&Re?IL%kw{*PK5Sb1BK#1|LUzTMBsR?U;<;`D*V}N0ezdI}8 zuN==`56AY%!5g*JKr(E1xmqldvHFIOT^Feoh#il`l76AIRC@Wnsk>g&Z**jF@#t=+ za@^kv@jBnoqx(WEmTRjohDh8HbPdliMA7P#82v@Brz<+=v>+kYiLPXk=gOc?_95(`D8H@&UhT^#5C7i_|B`6(3;bxu*}lTGD$OhV320`q;6@p zN0^~^FJ7TAlc7z*-Iqw1O?@f>d|;1223;l0Cl5r=$>P^DclM0L@rtI=9x#3rh}1@4 z{3d1c@8WM~a)%sc$m>2U-=urmqfaOUv&8M(|1PG^8`gH`b!>cx4X`V=>7Y(NlgW4` zZX=74f9--whVn>v=8ir3Cd$2NA^f}uZ}Y_*oZ3UaJs$LxQr-U$z4hjKJ$QZ-b7$sO zc^I|0`~<9&T`Z9VYdZHM(Z$wYZ^@RN4IydnNB>U@wlK#RtE(aIZ`*esRj(3`JDt#| z-a$v_j6c*L0{a$1>IX#B%L2YAPBDL{x_7E;n(C}8CcEVVqTGHQ{VpCszt>_%jP}Ut z?c;%xchuqv+QaW|1qxvzes|0Kt_=IS6L21tc0jmx}}loB%w z>~upGt2iSA>1$Fn04f}gJ)(H~0maM&V$$+ndlN_;?(4s20sTlqDn3=gONc+HM}52L zW}z++`fpL~>#sIzZwrU#>Y03<(l7h4#df0B`a@@k|KWA_Oht&_p2WA%%{F*htYY?H z*1hOToNJxI2}lErv*ZFFX@xggD+DRYZ7jP)jP;a8F_s98-Y>t2H*YV;}aaD8!-s3vL6)5E~^J)li3D=-`iQO zp^8nh96Sd$ytP>RaHalm=lsXe8xIPBN`VK<%cQGaRvBvi?XuQ4$jGnZ;mN|)zPnm2 zRoVyTvR+AOIB3mJZQGt3TnprhBDxGxTZb7F#!9eBxafar6I%Af1#4Nnji-g`_D=wwJyrjR7$zCONdv_l1I`U7?ly34Kyjyn za|4Q-ES?)s+#WFxP%JUEg=qR-FRc0J_MARTZE&?05 z1{tyy0jpUJJB zUmRvmSI2Lf&kW!mqQvT|ZW0-BDK8@$bxc)VoS7MMZ^;&*&%DCiB6g3nvN;^)7IjBk zVQ!Igv=!!N{YP72ZWf$tg_0Wa-fE6h!z#@OyI$7Q=u+$DL5(i8US`zjQtRbPjV`rb z_SEQ7TgRu&9H3s^tx>GE*@gPMn9NSr7 z7*i@m4_Vgec7z&~mo(+*PhB*mF*$hZaKnU=M5vja+Ch3dCF)1RBhkDmdvYY$){e?z z$?)A**XOi)VkAjGk=|b*pRz#Cym}CE3~^+V{0LNrA*@$|Af+NQpNjwjej?e?->8Uy z8$|_;>yA2Ba>y7nj2#6ADdtINQTd>S7FdW6CX?Qq|x-*fVC0mMdr^A z^UR;U!~EH=G=CNqX??B&q;?U^Z4I=sE0k!RQdHP$7IL$@nPq>LEzX#Wai_9a4ULHi z3{-PuX0R)9>djFPS7wN42$=4SQNc~?ID^!sJ}H7T#?ny#w5u5U47npmQHj+G@X>fTp=+GfR`ijpw&M_R8{bG!CZyNC&IaVZvXbEP7+J^OWx=~}xzl5^?WMZ@SE$_LN3ck7fh zw$%DmJt?(7RlJ8>4>@^2hHLMCD7A+U{ekqu;GKWSsZ=5pJ-SZbBe>d-4JMmYot`!k z1>@8oE>Xp(NJNb*BV@A&@>NIcRIU#RAe%v`r#AeuUd7=We`d`IML)Qov65J8mfX#{ zyH&;|Lg)lCoDjq9dbpidR+U^{zux6t*Sx$z&G)-03vO{W@^03FI@!g4J^a_lf7keL zfdAP2+%^@{ID7({ELeZ^&OgMJ(JxXX#bLu2M*@9G>79Ro5KU;QSxVT6NX}9MleBo> z+{Dqq1zK!%zl+EHI#!xeYLtrASrTaS6xq-|?vrLZ?J>3VlfOyTf=lvvV}*dS;NQ;P zkrRbf&Bx)(ep@^&K}5nUU*Lp5&O za9(@d0SO;$5Jt)GfNUP+HF7GwrcIi0ovJLU#-YDWC9VpZah=N}Rkqafs`L3!^#qwk zYbbPF69F`8gZn!-!wrIffot-H6v5x<4WFWY{bzpXAMofxieOFjU7C|%y_M9pTnwb5 zSK)|!BaPP@*+n&iub0RpT_a7~X_9Wgno)67>rOe4(}>&Uv^CGKE(Nxn9u=F!e5&vt zS-T7{sSd5&VKT3psg=dJb0pE??ln8vhRLWxa*zjp!g4HM$(hiuFlXulJwE9J(5u_p zrxd_Ljf`5a*HMsIyU4wYSUJYe3}5>|*c2w}2s9R$3?EN5o+?k(zFCXAgQg~z1zxyb z*p_m=d3o`E!2~sB`9zjiI#FiGVRejT@(w^JxK!J=cT@ny(hppD1YaXod2a@g3 z3Yan!gTy`MNXfWA#tMj^9%eFdA8RB?us>a5BhZLdmzyaI$V6>Sa3wN#s`l4LZ9 zLzb2yeM%Qv2??b{_G*eybeN;c^G+emUAzBooavg|fSa0me^okV2l!wj3oWbL7_D)d z8fNo=;_TkonacjP;FLz=#cs`8^Nu(Y>$Bp|A$>OX~<4nlrI3 zJ-!gUrlyRcMP7h6C5+)TN3u&bd<+x`%0B|d$vmY2NXUB;W0{$!-l8TAU|DaZlb_u(;cn zP+D~67K~Z45%Or!sokXu>sOJ7 z>;fDs1$Oc7TqP9NmYmx9Twtx;mFgO_p2sxEOVhhTKy3Alb4CSUihHN9C_KtpVa+D5 zVpG8QKj^Ly5NEY3)S4u(wYk5XC&5{l%e*C~EjVz5iL=^TYN?WUYuhQD%pL;3w%@~YjYHygS=ccs6Y<=QNwtiALVQ!ckCn;}Z%jlJQK~-@R zY2~fKP1B=V`EJTKvvkfUy&Q>L1jh@YQg4OOGp1(gQuys7=h1%5h_HT!T|6w|siKk_ zbC9cL5mp*uAHI&~?mG2oE9HKMuO{3|B_pq>yE@6pt2%7_n7*k~jKWr_QYzu#O36%e z*6IaG68282}iEg6O3;i9v2khH>(tJ-NFK0p{I{rye&sLqi@syn*Qh(xM8 z%+8TSig?}h9Q2Gxq`Kqn97&|Q_wI~Hgpg6qH+zmG!cKrn5tVZx5woKhINI+hK1br< zT*~tPu5(05MVIwCqNJkV`y5eH%z1thMsG#m`8iU_UI%=3aO50OGN>4nFgc5YG6kK( zf|MUkHXwPgV-=Bb{v-Jl>q|K`WF{(Ac-)ro?^nn~#d;x#eOG5lW45y3}|v`6^-&H zmsZg@ZgOcAjnpQWR?%2&a%p>ww!Ou5reI8Q9;3BTq#E6bsbX8#Gl{*dog&0A+BXWRP5}zbblLWa|_{?}3b- zs_1Qz^-~DF2Qq`IqPIo%P$Bfvx~m2755gSkh^QA=1(64?_Ut9Z!ksFeclmfPt@{Mf z3#V+winKDZfa>U_b)NuwX|_hSg1;l6*X%U`I5l}qs&d^!xKFCC@eqELs%t!iL#65( z58+vcP+nrP~XN1PwgRF8I?HPO@&WjQ~h zsUE{QYoZy{5ZpLFq9LqQi%gw0(bN$UJ3pc!th^J=N<2Gj_h#u}W|ljj04ISng1y<7 zP*$Jc9fX7#bEeZpPi6#*0`ydWO3HG*_+&P!E{4)I*nw$=o2`4Xj2iSXvQ*zu?RzB3mRO8iEM>O z1NSo;SDVwG&Z?zF6V01THnV`puc4X6F%ulpZ7hl;gybJ|Uqk1GgDlK!P{SBou0>HLZu(>@};KJ^7<(`W*CcL}z zu`8U3v-I7S91q(WFXr%t%I~D)e=-FZ<3WwM#Aj&|!XLF$-L3^5e+zO!(=PeCAN7h) za{nz}M8y<=+wuSTO?LYFru0*it-}H+c1VFU7%Pg8?MOn8#9Jfu_$&>*DO0yc&=2^q z;NZo#Ko;DWO4vm+Gqt2lVncR73o!EWX*`|yFH6y7=lA26C-HDU@^9U_^E`Did42e| ztGO?Y-+z4;|=19H3pF^lk$58wyW z0sK=rfF<=%y-jYM3vxKO&ONNgP;-n8?1a*5bAuH7BK5g~XxkZ6bNR6o;P`GHZi4?8uOj4k!*(#6v5jMa3x-lRjwZZS=32 z1L!V~5c>upM$QVf=533eW)fnJVYj5{sTsem7CjY@YbY>=CV`>)d26;JYht@3Hcnh} z>;vHvLuqh>7G$;9(VkT7jf-I7? zKuQ}xZkMxCroF*;DOJoyLhX9n^+de6`w(knHyG80eJUpvgKwKNXd;v+HM+T{V<5rx)b4EbpYm zsHBcvLb&Qoj$Cy?fKH8ux4|Qs%6R~%+9h-AA*mQUqpXj;Y1v=lcDz50*jY6cmSp}l zBeBK4#eAnLWXFG)9Ef)m3P72FCB)46;fn9Bvm8DINmrIN+f02=c=L(-D>T$lkGg!r zmzU_g& zH+G*N$oJqzKf{sVTRPrqhy&segc$$&Wm%zsTl*_-{yH2J5aaL8O86_s^Vh?%J#z47 z6HLf%XgGkM(Iew>Mh|~tbe|fsg6uXkAkWk zA`Q#ED0I;oh~>F^R~s-1>YI}`01=B!s!g}o-O;GC#HBJ5!;hR z#D-k>ps?mkicj{$U(R&LCdboZG3nAbOBakL&`TG5V302Oq;BbgX~3D%VBlu1S+JE2?``|KSwATPwmk+ffp6j%Q_aV zHj>zXZ?*(|LEvA{+}Sg#V=HP7d$yLRU4&bO+zVD5eiwf`lRGRM+unciR%@TN2;|vO zOZeNl|6NR-H>~Z>>)7}X>nB&N@<2;^CXMP!+(rr&|JwDP*WrB+(23662~Z;uROp>n z!q1EFHebxeX6~V=7!QJZagQX*-g@)A9z4H^xifRCJd9?^kTdI9(8mf9CtdQ&bfn50E7IYo-^3DQ*ZtQ%InJl zuGX!A>N=`IOl7sc7~1M*O^S9*b&utL#kKICuWGSsRCO_8Ywq>9%Q}~Jx33re8BC> zeRie}`V-g>Xi-XE@Afpy8ZTz(U9nyh!z0a2xu6zy*d_!B(E|YybPob;#m;S@8Wkmix;cZ}2VgvYC&44@{5v0k@4M z)OQt($UK@@`pY0N7o8QYc6|_oh@OF zT9*7j6CZ9Gv@TGeZhFeyKUX$srVB~P z6QHd_XG{JFb2$K5+Sanah{nJM)$(aszpcTQtt)r60*1#h{cZcdQ=k?3qQd?Lqush* zxwJIeSU3YfA46z7J^yX#+TS202rntk@k(0QAe70l1Vp_ZMDQsTfT$y`jSwN-PuL$U zlG}`MEcyk*-IX)(IxUAIS3K?2w>TKX!0?nY4GjXvz z4`Ans^usfvd1(5qLH8lW6~#bat)0ORnGsoETi)A?-ilp+z>ZN8*PT zS~zwFgEAQoNGOo~aA`tn6;?O;1D~2w3+@Z1xF8KHWE6Zd;KF3b=k?DOxqhsD6vstU ztnmijAkm1;Uv1Xj)`e1xu0oMfb@Ti^aIQvcHsfpHPF?cs;(S7&T=SjCD;)Y~AGT20 z`LiCY-ZTD;1i}?xGMKzTwn9kX{R2^D&xoyT?d!4ZwW4U~D#Ohi^u!hcgdLz(S3(MW zu`WSNGVjRhg&~QH!{13vY4WN)X2<-Is6TH=f z)-`pgi`IG#!GQ)=e4yw(EGF1~Tz-3E$rT(6D+O0d>O{w)2=A%&C=Xx`kYddyEP^F3 zixqh>Awi6 zbJl)7wK}0DB&z??xiY-5bn9R%`%IBaD?nI((j|)XJ%CE#zOE1mV1El8+h?{Dc89w? zWWm{=`kZSSb;tWIttGF@fupI*v%*oQe-SBYJGMwtXm`(+2m)=(3kiXm<`ZM)(JbMJ zRzpS?aFn(8KNo01hrKI7olfC~(y_q5;Z;EM0ojLFJv71XS}Zi88KCM9#cZb(=PH=~ORBk=d6Yfgv2-+;jDG6>|#m+^Ek zamQ06G7Mp(+qUw%-T^eDW61wz-!PP5=Lw2=26h6T6oew!YMq<+8lyX$>6%g-wH`5v zkPE^CvNLs_LEwQRe)h8rznqpEr>KS*5rr8UG)Czi8RRntWzhJh3}je|loDA|*OK56 zl`rX1RVw9jd@&k6%{;CcffrtbDQ1~cKL~7tq7ls2$dk>H2&U6Ss`|BvVL2^gkWY&+ z?!RoWlTTkr;o~#`m5bOxLFdlOl&GKPpF1CCDc*$D8wI67=(A610WuC|K$i!jc+A&z;SZGZ%w8 zhVS$pb_?zu7+KJ}W$zlVz;je)5aaK8TCdosRE;Lq#5+=(J3t#;SuG@)9!Q4!ou+g= z>C4R?wOW45N{ujWV#9(gU%6xZ;Yq01?!T1gc%3p_0z)C-N5&xRd9$N_K6aGy-58IF z$*WMM=0~~{zWe?%vzj8Y?{?v(BQEut#5G3rZpX-_AttwX<HC+*#k|?sbx9?X|jC3{e0rp9}IlQKY!-$f1}chi&IjJbR4fOrX3VjO5I7ydaEd z@)30|f=XR`9I5R5| zxiu-sFl6&Mw9VmJ&Mi9Se4-<{aLpKvD&~vyb&HarYw(>OerdU#mWN+jZg1h?m!`Vu zVl61HgQ=wHp>aXWm$kqI<`?gm90M);A6mU|+}?Wgg3mC+%+#E@L*wE5!aR;I=++4i zxVQO6qAmDMva27-_(TJ`K?)b`R!Ag+7(-A(o{ zmlM`CjYBU0be^y%L64p63)uF#3ndj(%BjWJrNKUN(+zk?uttW1(j zKa`<1k%vFxA>HbO1D38u^J*a7ypL*>bWCc>fer5a(M>oG zA)LX>!;`fsU@5%0KJ ztmFh~iJK}++QdX!;Oi+*I8vg#BZt{;@>JCz{?3wUjv}iAr+3JtgPvwAKMmubQ$1!! zm|FL z`=9LcCr!F_8%6wSb5P==XU0lXAe{zJCC+*V%yl0ar^03Tz;QC{b`Kn(PI`+|WM)LR(5)`A?=P%t8wGT-w&@SHVvIk4=zNsiKz(0`r zoHn2*O&USxz>$t3>a}4n&^xEEbMmI@AmNS0+tGqP<(Av1IMgqxpNT=eF#6b(bE%dk zvM7*CY{G+7#~LLgZuc4+WP^2}FknI>-|7nCO#a!deevCOmcxf2#mTZ}o2l;!Z$5E< zg>1JsUM%0l%vlO|?yugE9o`Y0tF`;@#+{GdiTtvdFD5R3WZf)Q6L+Qg;C8;5PKC31 zo#94extMz6H_Tw`2&>Nzyng*|;SF9^K_?v2<7d8$NbjWI#J>Z!DJd%aG#Sc=^4lyI#|8#QJbC znkUgu{BJt?kGQ`T;&r~E$M=O;EY}wD;(d1|=sMxyC)?gWiKiX%eT{8UNOuJI_;BcrU}h*Q=3#4N9C(kdV$l&KEw$_WHoH@YcfLEb+~$_ro1N zVP{5u$HHG{i>)ihi#h0EGxj~)Y&iN$;8W`+{M1>my;pt?e*~Gv!#VA3Fv;N&7mV-_ z-Yu&m#GE!$lHU7SNSy`yNsLAkSsfrt@&nq1XJ`E5r?Z*}XL$E)+3%=Jp>FgjY~l=H z5j!OADDLO#o%`yzFDMRm0;7QJz&5OF3|w7|9e)w=dob(_7JK3n>Z2Ip6-ua&5stp> zB-Cxh1Bat8;xq8YA8sE<58~lTd|ZA5)hUG*I^o{!rG+)l_Y4(e>or6;q$Wvqj0M&T zZz*uCtEICdk&d#nrLPt_9ORKQwMHG~t$S>ztAW&;EvNgzvsMe=@ga!Z7vFwBZ5&sX z=E2;kD@HwGs|d1bM`Y$$&CBcr%UpS{-)mhb50nqJ!P}KPej87rfr;ou4!2Aq*Z%jF z>ySeDvS7;TSne-(yrDDuvYA6xFXqB`UTqs&rWC!!?w`8JP4F10T^es`KR^?EfHFB_ zp?>R3CPc%Fmm!FBYuh*xkDy~I@hwt+O`oT=zALHq;ZRJ5;pWN8M2^pfiYZRP+Fh|E zv~MO0!JR0f^YlbN{1LkDKx(2=iTF<)tU^|=K|A9X zYCJ3d*FRUJ%2~;=@3j9&|KRP{-_w+k$<%3enAr+hMQdaIcoEUZ2td1%L=9{|o|Tez zr7@|F)Lay+zgW`Qai$`u{q0772pDuQgIo`T$#i5+)+r7`YG`)yK$qMkP&1OKZar9; zo3*!ftu7G<+gT*{K{f+%^ZY$9_lSrF?$jk?64wndxt_mHhG=LKKH%Wr@n=17tRFBP z=(D$_Yt!KNff!6)eC9}Z|IoZ?(qV0tLAM|AiW2Y{`x(5@w*ez*V4MPHU^+KmaHd!~y8WeO=*@@KJT<{_y$k84bdSJ*vP3H{12}*<0M7r_?>kaL}!t)r70C z@E*Er{`Mpm+LPpH_r8O{T+g3*`tv}RU|^}!pW@yRUqT(J0PyT(8cz*7IlG(X*~xRk z!k#`K&~)|^_4%WM`-;4yVu-WBeM}@O;4ckTrY|wJr`HX@0N@y199+NQQDN#@!GtA~ z)~$!Nw?RUC3onq`M!?kzwACSvknb5oQ){I|Eex_PqAmcY20nK(5ani9Cj*hfbx!IC znW>JOF69v^C$mS~%lxb$;@;x{M6po@4?Nri0xY7!O$d}6_U zOpOQ&-_o{W+rf_n@A=B*uo*nG2+bmFJ49-#GS<8TBBT5ZRhq~6L5X#fZ2m2U)0Av( zost`kN5Kz8&A(a%{5fL8%&*B^LlOAo;kyVzF{357s&QG=l3QQ7EVSfSdM*nsxizQD zLW`<>h9zxt1q6ORJen)g>N^%QUyndf82L>#UdWG`G%r8BlZUtd|otx6XF4B=dQ% zR~>5(pF9LFj|1Z%EZ&Sy9z~aDovqgTBk$b1YmQPhB6O%flthi0baE^BXH7b0{Nxt- z7mAX%lC%1}oq4>$IT3sl9a;tO~il5x7pyaaa~|7b zj}25(bFG$f+{~IXuvJr>p(L`*3(Kv>9vWl0^~XamEUmb4>T4%xyd}a83xYG1Q2i1X z`4~r}p}H(R=MR6>R&$`ZOg~9b&i+FSg+$F0cX_0uwEg)!iIRcaWX%(ZnDURpe;=R5 z(~1AGBmgNXiw4<>heWdg2NfOEFu>}e${!^jTQqmz;{OM#cjb|+R&TcPxw}A6rxn!w z^MxZc7QkJ<(1R200CwpkGWj?_@NNg(`Q#$-&$(R3zp>ZsZ;xe>8t(mDf^Qg_7JX&5&|(-U>VE&%mN!C@W-a0dqPl^6g<%Cy6pWUIVx z6qY)obs(^rBbtFDtt6I>YR2C;=p8a%O<%}sK|9X>^fFp)+;Vz{h#G3VpQg~qRqZro z;PU-g#XW-ZSkZf%&sRteiOC3u@t5Y&fdd4-E5oO}Q>A#ryH@<_qNCSMoUO2!&Z8B> zPfM?G0F7RUf8Kx_{69#GzzQq)zexlx@<)1CHcLYC_{`f%iYk zHBmcToja_owsR}_YLJG+RcC@s|$Ok``5F26N$Be zqvY|Nu#2TORG$Shnx>0dgIIMP67$a0y4(}NZQzIxBZVh}II z!J~m5gUM#n7lW;K*64AYoU~!)B6ZrnY@+VBeS=dSjxaItw_vbP=%+6*O(iv0=z}#R z7Z&b_9LZ18BFFC-9Az7(z8DLOAl=i2MP`wIbPg8T!pW$H^i_rQj zc%*zyMXAL=6(%y$GuV3NKTd+4LcTOhA!mY$>QtfKWaF+NIjFgFTia5qW5skNPRb{s zKUnuoZsBF(tx0;T)p*xa1Px+8f-pWVWGAp^317fDZeCRoo7_c%iqLA`h@M8?S^m_D zRjqNRq0Bc-IF|||usI6UszU{H$fQ~E;TR15Aj{WaKRC9BH1j+A&3t8|&|`dT9*e9%>+MAsw?BaTNnjq70uFRvB zNlO`M(XfZYzNGN%v@0!2)8w7y2Yo5aI!*Q=usI6UYGy^9CcOO9b(-v3z6Sf@vAvNF0(a`p+_^6u1!VqH zC2CPeh1A2EH}xqjO6)rG zx9jhV>BL>3N2@Iej@GAwWGi!h4&0mLO1evVycV{L1mE-yA;nbLG{wO9#n{<6T^KkgWoICbnzRs{Bz{>pX5vojG($Nx^Ig9^Z5o;X;!{`b}5CnN1? zdnM+bp142EvlVFh$=%MOo*akzLs(~e^>gJc9UpilS)BZ^OW7v`4!atKAq8jTs01TLPZn<|&k9J-XqL-Q9N(eS zry}(*PmFW-!^kuZvWB2rp2Cxxg#Cq*e}tQWHa|&Xm37g zd&{Xac0+lm7%!2LCtEkJz5ls4;W!P_`(cvXcWM)ij<{Qle~9H`>W$xQ%rc>~dgy0{ z((2&P4P?`ypKD!-uVUh@my2~^KpbZUE;OOVn>1k#zJeZb@ap=oY^JX7uBhLEe@mT` z*qCWZWLW>KVN5NZ)!JQI1LYUNI`aWUNRAc6nQMb0CC6G6f!d^q$+40+R`QCR{Ws1h zf~ZZC)~xC_YF6!EzBK8YFthSLI}}42b5e`4Z0j+Qer%`0b%kNQG1(Ut2Xc{YcfFp5 zB5$x*x^w&v_Tl+(bgbRMgnS~NpG)E$;z>I>caWz=((MpW)5)~MJvT1N4)Wan^6N0< z`!RGHlB0LJc=g6omi2yqcl?+aRdYbE*om3LpLqMaa$grK&zawn4{^4bH{wJzJtofl zYZQs52R0=4OplAhXA*9PtMf^)133l_YphR9Wj>&UEHd1$CIxhN_ZvDlZ?3ayK*=;b zB~De1oEj5Ubt;Rwpxb|D)TOrc$f!$f;>4uyU+KW8w_}AZaAdM>cH{nX#~b*@dz|;q z(W)zNK@%)}_m^*V!_Nh`$AxoZi<6-oyYhEYn0MtB4JN8?A>+K|S2hTsSHp(o?`HL3 z%>G=geu%a2tl%B0>9*J5ZC{{1ufNxI-y3pyIR5y%jb6!?xOv&k#}wS;O|OdC9-JwD z6F2Tp+!wefG)oQ~%oX;}!nTnQgiioRr>mmjTHZ0H_my8@&3AME-8j>=O&!)PaZ7B0 z?X+6CT|(}A=KSo z*_vAK%baTkdH{1}>jSAymgr%$@iTr(E_Ca;Ke15wu5EO2@gDJw>-F2(cW2g>bjpku z6SuLo5<6VE@Yc@PGWEjtGS_(U1$MDOE@9iDvd_Qo#pR(<*~3b|yAD#8(D z8dlFljL|p#*%|*BK71;Z01GAoorq>^l0nLS97_Zq0hq z`tnin(<7p=XV4Q{wQHf1UT#}yPdt45Xd8g>H^@GO>A0bC0T8I_@Ti*hWOzg~zC^>r z6nJWLBpMzShfC{4)L)s;g@|!4+8`)64~a=BvYR0azIo-qupW5H6Kf!~&eWa1`ro7U z3uWgkrwn50$=kB`Odmq-!x!C*ICQixThsI&mo|%33$ajJclrxk~ zM)gR6eF)hr$+SXn-(BlCXT+Nq8(%D51gbSKEVXuRDS7)iebp&r%y85yV-k;v!=pVf zX(Z=6AqUcqk~>t-nMea&?i8f&vqvk`o+Y|VT$ifj^n@prTu>7cMr@lBPwA(`X|Jxc zlnbibj+FUnap<%s%Xo3V19qUK9~MFB-K*!E8BtK~xEXI((|Ortt4O_W;=GaYX+C34 z|F}4f)p&vmz**-}+V|Kvyrwg?=Ps=}Tzl49lf{H%k?TQ4EvWtXt>fH5`gP&Q_Pv?g zlq(^1|2U=9b+YzAQrl6|Jtq#4_Uvdb%Xe4~r0S)1sU8`m0lS>^(cDPY_E?aH6%8H4 zAG}`zEfWwi?@_iC;%`WtS&xyDwV9%VsV_ehQ6?2HN{v-ixtAu?Q0aE*NI{j`(N3rG z-KyL$N~Evojtv!zP6tJ1TW{=Q$T;ob8}w&y?XR2Cp0l18KiPHhs)CT##=G)7Hx99u zm*u!kKHhdnEjw$M>)~}d&{B@~<+yPjxpy(v$Hw8!K6}QywD9N|*IJ(rE}gl19#ldH-*exZ&Rv+5*^$|qkt$wmfEowR_?$T9 z)p*j3Pi!cJVSHX3LetqYUEZogWm;r?rz!ghC61BlhN=#qY`wNGeLL&g!HYL_gMJ0U zY0CO~vdde}?{dv`)^{!0Jx=yWP`^kmi!6(~k;qPjtb&AyRjrv$7dkM?J+^L;ONg-- ztR2Iqxr%ZMP4-L9tIZZm-WwQgl$I)bq zuZ1cO)xEQonDaSMhSdF3AJgefV6{v=^cy6<3e73mZJ1@TuO;Xy36^YK1=cFQ+D9_= z%q2yOZmuy$>$r8sUe~a2O&LgFTh_7mIF(e`* zwTDVNA0;vEN#w?tQ!dzW6zVd>V(@&H3qn{l@xr|L zml82PJUsq+c#K#&tOV-2HYASF+%vzt$aP0cbZ%Z2tDnwlf-cACU&fwNNS&a~u~GEM zh3uBL|3+p}P~u*RrE-#?jDWcy5g^n)eCAn1C@h249$=t>AbqX_qo?Ov%e>lGg;SapD0#>_qI8V_)M^p zfOkPgb7^UoYLU32^XkH}7&hLCZ`faf9uR_?07{Dz-FN=F2mI9G-Ff!5mPOs(r>KMB z(sAyX$9EcOlx>Uq-lAZD8cr{{Sv;6teVSu@WgVnNs4-BvfRdK|pE^oXs zcC--YJyu3H5W;1&6Y19BMQSKEQ-m!~L@8ZFk8|_1*jY z5&)`q-+TZ4@Ts>`!Bf>+tw`1@(jmLqdwELLja?a8cfFlh$>6`cZcIA@OdntqGLr0V z|9O>&Dlx=H#jxrmQflSPcRq<=SME#}F!fMT9w3P(G@z94hSqSXz zZqfli)ZO!yO{3&_`UcU$upP^Yojt|bX$?}&&I1&$M|2!hYRPH}GbDDk#Oi@6t()p^ zonWr2K*I&;>V@61)2t^SpHJU5v4-@GwLDD^57V3OqZkfVFrl+cv)fSg7rQXMwtPVE zdq_DH?7M4h=_<^$2l0xc*jLL!nZ9i~xYGBb<&jGvw(31=Hc^d+sQK1vrB3YDD&|6z zC!DT(;Vecbsk`sJzZTehi%o#HQ}Hz3%f0K!4dZY+o^nWr54ZiO?E zyl*+!3=1UEjXywLc1%N!xl{|}F7J4Lf>b2mG^f<6D=96vLLxSLJChz2&67;TQV0Mo z@CIM)-Yw33PUI4ekJ~jmcKht3 ztZNM!LZJehG{urG!mL{>-;FYaO!rM?2!&tNX9$I#syria#tJPtG`3>b-xxBCREtY# zn-o}Qxq@7I*A^$6*y`$VxU0LR-aH=+b*w-GV9sQb*p|lwUFl?z*q0UiDKl9lyhvk# z8v>p;641hM&k?gt0?duel-zz5C6>jk{Ni1nypT9NfDhbqMO@t#3>|1XRJj_u5 zNw@kV%!E++l9fbPiVN}b0xvn|IY@0T6I}xzi4jOy=M54B>KVdcXKHc z;=P=izF%3Y;~u01a(M|230KrN9;@u3KH=9uM(v3Om&v>2Onxx#X&C*-E!9FdvvIUj z6HQLyeV$6V-hqEiiM|&1`0rLuuJoN(1yJ+zN!9((q023$G5DVE^W*Y;zgGBmriFIr z4xG4oq)iVKAuDWprA6jtsJM?q=VhFSD2E!p>%r15XRM-5ggPM_`%w~v0 zu~?y7kE-%^TJ-nL9K-3n|G7778ukQR5XU$isW;?_PTNPE=7%RbmF)YxrA+>r^8y3 z0#Q*=P&Am4p^6MZ)CsYCWVetqJyDJSC~2DPNh&-6uaQZ0aCr_Wi}G~^4;uVsrfUFl9DO;Bt>OY;8EY?n=uF%lKYlM(d#@oZ(Q=gwvVKQ8q2X|4Qiz6hn1g$=kMo=Wa z79b9LhT|@>roiD)L~H`du(JRmiNypH6?KRuyoTWbb-JQ$kGIcK*>Cb0G`puKAa0pm z#(1oXs}Zj}AMDnbB|#(M|=-{@%wz)BK;+yVPZjQ)gw18qgZ?Foqrf48mv#Cv&h zR}?^##H6F`+bPCN>=H6n;EKQozxy@cDNfqC$(r4i1lCq@^5Ujlxx5(l(RUw5H=(nS zx>`XvTH*PVtV7oZ6CxRI;G3EAi^Ge7Us83kg#vUSdu)p5H{V?|cE*&jYI3n|10U-S z!ICfFxNT?))^dy_dgr}l+(jO^UH$JNfDt#59cl$2p}qX$XQR}zHKankKob0Mesk+6+hq2uI#&+h5tG@ul5!zM(+*E zCldZn=KIVmkL=@Y)_Q!na9L!<@VJ%n(p1SBg^ss$E zS`g8dHW;bwo@C;jcND4(F+0lY14+=h$&`|{Hn?YQ<#|UX`JNNlJd_4g)Z&5Aa3))- zmkHU|i72m#{lQZZGaw{84fGz?(3p)HBiczchiufSIc4Z7h(dvMc6a2pgenTEuVfyY zV3WbPs)r!IK#jnI%{_?+$m?5oPSuvedl~+{rVi*mZ%t_&usnSa9}FL0)KPL+Kkd7D1~xKFI~cNk9pzWV>N^Q-7Y@(=C{ zM$3BiCQ{u^JKZ#Q>}wQX52BX;FF&7*e(mAX8@oqeGSfBs;;`Pgy5BbU?fuO!1^o81 z$DiIrzje^)4^QKwJAASIpj_|htF7X;yZ;)0%i%wD=PCMSqxf0qZ|+Mp`OPWE@893~ zyZaLV{P|Jrx1d5Nil@&|{;eZ!>~_~*I77M0pE;erq1ODLJmS{-lRI!%__H^jx&2#b zNdG{;v`9U*{j+9O{G(?5TPLLdK(l_c#{^E&qmS9>-lt+p=RUEUR;O?HxjTb@GQiQP z`|0blXoanK`g|+)ODwnllk?1-*foxw*+mOYN`>f5O)ty$>e?&%zL}2Ed(OMw)I2&h zZ{E7RJEv=1!Ct`r>0$j0&&=Ytq@t$&=1lR-*QDrQR*2rh5Zy=dZ;ps=4F8kelzIZ} z-ggjPwnde;c6XTLuWGqVnZ~WIh&m|hCEfJ>T}9>VVMl0uCbQ>HF7fL9NgdVxyp9U> z&Scx+ncFzJts3rxD%g1HXJ*>Z6^y@r7X5ZaO1J1z_OHKpC;qB&=#G19_mMw=U)MWb z6OYXm{c8uA!*R3G@A#n) zk&o>pf8pbkq7TN2-_no0=O8-APU)K99*MF4CM1-MewMD6kZ|hP_~UKyv!b7kqd!SW zaqzx)y6ESV4`z@Gw7NE81H=g$Yv3cd)u0C?TfsrePfeLa;`Pj&do@UJv}L-|+3 zUySYE4*oRwJ>Uz#CxKVTb{_|?2mTB=+qF^Lx9b|!`4)V(>m%deD*to%Y}eT?IC`Xu z{;*wn!P&0j;A~eFaJH*HINNodxclMe&+V$SGkmt|cH@tbe;0hVYZf@$wHTc3S_95@ zy#da4y${ZIeI@RGxORwec69`2 zyKV(vg84iKd@1-m@MYl3!Jh%&4E`+mF7V~xpMtag@4;8VPwzS`dbl6YfmZ_OI#Ub0 zKK92p;Ox&{;H>8!aQ5eHaOSK8XU;Zo=6nIpoV4j<&5gB}ITwO6ry@9Wnt(H>GdObw zfzzJ|&T)APoa6EwIQxG!_yri3j2YtX_4~`br1?^SDzPobw?Wob$Gc zxZmy)ZMPNtS*Y_yaL(uZ!MWYZ;OvKK;OvLj#9ckE%{AiPknkq_ap;HSjB&xa6E8_U zZngu@2R{Y8N!e8YA@Hx^F9rVw{AKWs;BSNP0zU{&|9f!ynKGre>o9UI2=G$kw%^LZ z=ekr2ya4*4Re+B~{wmZn2L89;^T96$-vC|`{2k=G<4ZKq+l>p`bpbfrRScZ% zssPS*T@TKje&Ea*3eKEK;LKSF&YTtC%-IHh1pWL`fFA?j13yES)P8#%JP$bi;^6eF zf`5;kmf%Iedw@^FdfOkI>-?SIT<7P4&p^&|;Ow7G0scNX+j|6@?fnIu{<(>N+@A%& z**}+nvwtoJXaDpA{|Wv1F!(9(`QX#hKYPU8e)=Q)&*0x!CiS>>47@#f=5tcp%W=6B zyjtnhoZ8}UyT4$&_2B;so&ugwJ~iiF@HF6a!0A5=PX8_NvyfBZ+|+iZ1uqAl4!i+) zdhk}@8Ndew_}$=~|D(X!&mRZ)#aUD9uYmq<3C?zP7I)*45p~`TKNI*^@XX-T!Mk7_ z7lL!UtHs@R&qmH3_}uQN;N0$a;N0%7;M^~gvZeM9{mS6%|Ayf7+kw-+9-QMf0{mjM z_bKq=;3vV^Z`re_)?W&KQSj2>)xpbvcK}ZU9|C>}_>16W!QTfj2Ywj5BzTVVQtK}U zURm7Dhy0kg*TOFkzdLvZ@B!cz!5;=^JDF6ZP*t)JU14$ke?1!vAR;2g(};2g(Wz&Vb?z&VbG!K{Zk8mDe&6h&B5z{_XfWL zd>nXP@aMsk!QTU~2mUKK&s&P*O|8E^{L8`F|JMijUEmFnGY_2eb_+P??Q0jr>+$2_ z=k42u`+2+Da6fN95V!O8J8;h1Y!{~X1LtiyaL(IW;GBmY!5g9!O#dh0+ zbGvtfbA1>M&h}0T@aMp}4!i}<{r5|7?!US6r?!{-Z)I@qza7ANpXWC4#^|@v;Jgl+ z9^lW2yLrXyqvzo_LH-f&rr?v)=B1H2+n$nfzz)C&U!ioc+UVI1kO4qf;0cI0DmFCH-NMLJ>YEbC*bsd z1m}5Li^2)9hnpv^PY>$6xm$qu5_j$ZoVSbx?}l}97I;taCry6)SQQBgOW-%d{{Ect zeVwb}4@Nz&gWrPfZZ|o{)ZW7Z{v~qQ-t#Vs7wr4(h;nj^yZ*Tn^%pR{-)<53tiJ*{ zj~6XWj_UdxNbp2(|Na8kUgmFx&zwt&rsgxJ8aQ)Kg0s$2#Zq%NR!Z%MiQw(Qr-`2x{WEte zt;^elAK^2%r-d3Gk`l%sC0poD3!Zc)K@((;pY$Gr_stI#FBP!?kN5=6MV77cNb0Z)fm3;XeY- z{(J#EGy3x*aMphcoPJXDZ{5SyN&k{EF&yJ;*8=b#u%CVgejwVGe?ME6$Z-#s&-tGX zob9R&o&otQ!Rcp?)g9$v*7H9G|^h*^EacOa{@dsIQ#7qaOOV=z7q9cP&swG9LJ{M%()5thZ?Et!! z+x-r_f1T9rmWt+*d)WQ=Ht^->&spHlfiDDSJy+FC&0mH2&;h*d6{+=10%!gSaMs`O z^3;5`cQSZ8a)(~;LI5h&UyGEIM=t21N=*HZudKIZZ}`;)H>O3<-uP?yY2x0 z6#aacxI52rW9*M>cfXMq2K z@uRMYJq{VJ!$iX8;;y|jl2f<)4g7)V&lBM8-}szg#og)MOyp#Y_I>wo?jYpk0O$Rp z!s2dx8%KY9{l&pumUjPyGRWb5q-x;2FHsvgY*zzt=3go9+QsX*tC7!kbwJJt^uw(I zJ^*|yj)Oyx{}A{{_^kf{aOO+^ABlP%K|brB1^*uSO9FfqcvGCGy@>qtFh4iIXZ>5i znez_#g~-{3eAfRV{CiRV*8%<`_+9XSMgBEtSB7W}bPqQ_S^qiU%*hEp3i)}((?vO~ zzYzS<@XH5yb?~9^Ya{%S75Ic>lnK>oGJXZ<(89|Qlk03QrK4E}KBUyu4n z!Dszr!I|?g_(RB_j(pbt82qvDpAGP};H+mo_;BRB0X_?Sn|L}iAKrsM4*q8W{w;Xf z=p5T0M}9#5CE&lnXZ`8xMukL=v!XxDNd%vOda{e#`t!h_2){&tR|Kz&I;$eT5_m26 ztiL`ubDDuqMm<*{pYx#|{D2JdPg&=lN~sDAPUC zMSpmHTNL~R?zdD6@T{?v`22r2@)PyEm43SXJ;*5nz8hR`ABfen6TF~&w|{+r{I1CP z9RB10-voXHzHTqYw)-WxE>mLsYjDNHeht$7ex-k8(*LB1ex`}~Dx04BpN_lpw3nZE zElEi5?X`ZP*xyDO(NE{sUK#h#5qHnrA3xu8J2qD5X6zSd;=cjk*J<;kQ}x)x@xsv` zzuh`<9IM6Ma`ya8a@-v7K6BiyV$Y*4k3HP{`4Ro^uO}S;89ZuA?BVz?;QsvA@sr@p z$$)-f4!LfJ#QNEtZ2LN!#5Wt`>A~9t_z3X4@E-x^{>c5y%?H0-%U%2UaPy}?l<&vG z|DEH7z_aKt-1zeSjpPMUr@to!R&hkkL@ z?{Y4NUkB-0Y6e=4>!K7zm&M^=SIj$5_f)MaL$v^I>33`8adp*x`ERl3C{NB1#g0S{;7Vr z2>C6L@8_NCpR2(Aao=%w4D#!_?@!lnR|kBz4|<>Lf4j&}jQ-9&xSzH~j$fbMcH6`6 zCBS$+3;Db*@au+c*OT!1 zK9*(3=XO`a=l;7Doc*~MIbBibu>e1b9QJ2kcft}qT>V{;!_O6Ae-?$${wxj79DWW7 z`?IQiH?P>AwUEPhCBtWbHi6IWUIX46?dl!iDadF4jDmj?avp?#E4crh6W5>YpXq^| z$G~qx&J)OI|2z$!?RpkI`{zaY{gD3}{2}0b;j_I5;NJ!RNWlLF{!sXtT*pKYH}1p0 z3xE#?uMEz4m<&Dwehcus!EXg02|ifd{a|~C!@mdqWboU;7lL!0TmgPB{B_{>fo}sJ z1-Pk3zn;7CO%KlV-lE`hV%5j% zzaO0EUED6$Np5!&a=3nO1Lt+hhv3Zr9Gv-CFrS%!0XW-R2Au8f2F~pc6nDe_0QzAV z_!w}%F1dbw5Ztd1w*S{*yR3(wUz8C!H8BrE^WiM`XEGnsz~_9p1bhzqKQte>-G5^~ zF#qq&2ey~bz%*i2c{gUu` z-L1C~#g31Upnmql6mY#wGUiVMkN!9IaP67_o(w({oa;6}7o`V$=8P2g{a;4U$H0FS z`HzE_2Y&&4Hu!7c?4NhRtHDo$c}xF1@EY)We#?Hi7=BIomwpAmzykmZ5Y_~A-dB2PA zuj6r+=MSO#;mr9Q`F}Ns^V1#o{c*Z7>gWAo-#`91l`M~SCZnF~kW&x*HgM+q`RVh! z$QuKn`BT7|zXY85>%@Kj3_a)kXFlgY^N$4be=+%c^*jUSIrDiuWquL(tiQ6juRo`r z*M!gfM&Qi9A;5crb3P9RXU_fL^k;(8UjR;jH8_3VSE0W<;2#91e*&EAjD{!n`t_I5 z{LChh1><-W^VaS!{{G#W?l1oS-M_WJ)KfeE>i)7&?RD!R=i6%ZgIgauD*sjZoNsS| za~ux__*dW@ud^^-%sB^~ej#xBrNHU?`=YMBq51G#U_N~NC-b47ws)rUVTk;HYd-k> z^RLc_B^VdZhjrq9{drjR|6A+lBIW;E>*puR|NrY}{Bzg;&iZ*Gu)q9({e`bn&jqiB z`Sv4t9dMp6wE^e(QYUdgKfl-b@^#oZkv{}E^T9`fbKUUg3wGVO5dP1|;rn*jUVnY> za(;r(^GwzgI?v39?QTR4`z`lHuB*V$MvlL~=X~;!@Hvj7!MXldMLmhg=lgBW0lyypx#0c5*>8`6v%SxP zXGP9S;Mu?vQ9s*T9y~kz8sO}Q=HTbS?*h(z|9YS6XXfzrKK3W~3(o(dXxDIzFR$wp zP)}?4)8O|6=RD_pW}SKApO1XjlN+4-%UR&{k;8g8PgoE8AuV#q(}8opX21FSPHug> zAo3Gc$7QHzJ^F|DL+BTS&pLTuB0uUW0iW&7E$-LbQ<^utJ}rzKzg_2Fg!*kxPA5c< zGs#Ipj_)5gKZ{|z%wd0q`q`c9x`)fzg#IiWqjCN^cy)00XFYJ{@IGN#)L8&N>-6ty zb@lLgaV30iw;gzW)XDaq$?+okr?H3a2XgiU=T9}XD+xKQlgBBxw>JD5$l?9in&7?Q zUk=W7nCB0p;q&~#AHV#3E~R-m13u3m{QFN_|FB(a;Isev`YG2*_8a?wocWoNUk~l& z`o4bJ05 zUGOpR$sYk{|2zfG@y#mkrqk6?exjape6PWFIbK|ExeoAiq}iYBf8G!5i2U-X=LT@j z2Y!Ba7x`|Ub3YmY&i&|ar_?oa5LXoag^*qeau7KlA;E>skH9uz~hx2m2k{5AgfID}wWQ z(GQ&G6;r_%AZHr*OW^l{9|FG=oY&W^CmrVVboiHo_W^GKJ_EcH_z-aaK1?@H=&yx8 z6aHJ^mBBv)zY{!-_E(or|9tTA@C$?UxNs>r_ZQAX&IkYcnA_`bU7Xy%WIcbKPcpxnImiKb*;aQ4&7SuiR&0`Qz8ssONF;Zs2plp9G%={tozj z@FU=3z|&$rJOR$vo!w`1`8qFzUmCnNIQ^#J^!@vv-8gdow1?06Qy2AfKC}Yo{+=l=KuIQL)wI-_eZ{W@sxli2Qc;G93bz&Rflf;0bla30@Z z0`HIQz75Xf`#y2Ed%HQDqW%03d>-G=!aBfl&mG_wfpb1o0q1<+b<-l$&-3}G!Fis* z{^a!q>pX^WWPkE?H1-=`Unb{%!TNb!`5De*ufcYgqW)gsW5LIOvt28}>GQbu403W| z9LYO?_kuqSoa@+faGr-80B5^?0;k^{68>~>9(Ono*`J#O{!VcE`@y*`9S3JW zS3v)~fI4~I$Nt|Ae+~Rkz*!Gpr&tUB2vpw64YxxWksXU;|7uOO!! zIM=Hh0nU0}MNV({yxzYPoa^KWaBg=pIM>P9;%+$IasPgeE3c<`+~@Ta`ysb-+&Zux z_49Qi*2DY%^gAGj?dAI$IX`)Sy9c&=3vxDq4+7`>c`U#egL7Uz1I~W%*Tt?MSSQb8 zdEVX;Ijm=7fR6=dJ^ntt+b-(~^;maP>R``E4f7IW|XMb{?xfb zgZ;J*IkzL{C7iDg2j_g_{irI)sSkfM_>JJj!S5D#%g)>2o8a?2^`3-RHceT(D?_j%~!B=A(c|79% zOwRxJki-3G7x)9nXFZj{>DK|Dk9wlW*u#x`e((n3ZXNp&+iefd?J~b8{2Snx0iOtd z0DKiV=LzRC^VvULkuwH4TnC;2XFYF#vz{E{u75s>@)Py^Oz!J`ih8&%O+!EPei!$5 zu2)BpnA;o8f7ZURof6FArT{@|Y@e-wBl@VVfX!PkHv0^bPE zb!IO(_ph9>R>eP`f#ZI;xNGnA$me~|4&Ypu*gyS|!+y9Aoc-_!IQxP77yIE^`0NMH z|8G!dXn(1Q9G=&4ojHb_UhsLHI}V)vwg8;{wgQ~}_AWTrneV`PT+594%;TDWKfW8s zHJJZbz~}Mq<^bpGjGR|@z~{UQ^=DVqpBU)R8Pw(AHu z+r@eHH2SkRe6C~pz&ZcvFGUWIvwXd~6LNk+dxwDE3_cP31UTp0De$-8bN-(I{~12l zd4A4hIm`zh2mSA1xOvENX$k)qRkH0N2 zPgcX{>yjUV&xFr$dRivcuF~haN}uO%^uG$^7sh@{zY;j}uLbAlo$`G< z?4Q3gUgx5JLgRH0+Qso&0?vB;dh2#?-fx);-+i~5@BhW%oL4-59)$V15kBYDC*bVo zli=*<+~{ZeCBf-;1J927`TN(bpTEb%`llj?_47Uh`*|09_VeXv7xz=1SCI2~$2wOd zhwXX;oOSL8XPvx`W1Usuvz}yd*3$|+C;FM|C+isipY=Qj{!i6&{=ZO96d8NC^G5c= z81OvU?kw=U;JltfwDk?x)vb zzo5_kD<8JI4nF%KQeqF=|GaK3fE-?ru>aQ}hyBCrdT#gc^mBgH!}E6b53gU?KNq4O z=<{=*=<{`oLfGykr6zu$oc+cj!WqL z+ZT&TYkeDX5jJie3#=l!el;Qso;ZN72zXOimRb$tc+9Z*k2aGtML0v`vT*P)Za z*#)#3L5=jW*O6?dDvEXqmL z^O^9O^9(q1)`GKtUIAzS6ai=bCBIk7`CZs6sS z-wFH*@OI#QKNa(P!{_*B2JeNO%fNesUkT0}wzn^Q@_qrH0?y;yF6x z$FECK&j94)2>AZExSE==<6Ee0`0a_n+C`9O$=V@y*8Gf5P{%)B*nrKG)$1sFUjh z`cZH= z?H9a{+ECn$Bj2~y7JLilE&G|<<@oaT1@c*FFYBBW;B)^5_xCehdlw)lr`qNClM(mp zL)HLior{qZ+ON5PErDNK`L3Urg5LtZ44l`q9QQ%++5cQOo<$DV=V!p#uF&^dz8Lv` z)IaXLsHoDpZOs_Keo$p@#~)J2ezv{e71}GBgbnPeBN&f&2x^+KD3wn zBkN=i>p7Dgwu}2U-!FY3>L;%%ZjTGRZ;%!Bb3d9NO(uUnGZgt8$G@7->n7%~{!dX4 z+spR@bKG;mXFng0Bwv38)c+LtP2fwwJAp3;zfIisC+`cfKl!@nD&(w3KKr2__z|>= z&R4}vd7{ojJ~_q;OTx{1HfRRR1kw%Z$=_lNnq%pv%% z!Y6+doX695z}e3{zObLKM!VS0JkMl5OoY#V;B`Iwf#0ve_VWEdtg|2T&qF_K1Yd@6 z`2?K5f87!Md*s{+&i1lh%#j|uJU$WS_(vxFkL&KAz?+EM^_lgsz4W<$hWv|^@AgM` zOia;vi!Z_RgW{EqpU)K*J-BYCjZR|xy20aA2Joxm|Lk|iU!EUkf}aeZygPVi_V#58KU;^*=v&De$k*&o_ztd|!|MJp#}DcJDU6=W~(IdU*U|J^VeO0;rRpXF@-l zDi~H%m+_~OzyF4BhMNS=T zmp<2X=F_i%9G~yz3Ga_JMSf%WZNYhetQR=@f#(mkkW(K%Uw`ic-Vi>IkEOsz!)Ff9 zGwZ|WdY%lv89A(<>jS?(t|t6O$l-la=5Tz=!oLDJ&A>UIIqvKS?vJC8!}EO3Th_zZ zr&v#2+1dBP2oQbUJ`sJcxmLU z1!w-p;8!B&EAY#~`Ti5GSNuJ`((voz_{jd@`6u6B)CAk*{@4keImN-R#dewhcl@l% zck78;|NCqGuMY0ML)jl68w5D#KVL__1-{E4ru=^5?kQhK9tzIadHFgB^Bwif3 zPa=o;&w(?a*P+bcZSrR+{{V8Be-xbgzkoB}zfSGzU!eRvm{-hC0%v|@aOO7<_xUT8 z-yA;k`TkPo^Zk&_zsuyWSN^?${0ZR9e;l0o{`UcW{o9q#-$Q5p{Cq0rzaPl=zc=Lb z4=Dc_eC8)OF?u+S`Pso)e-Uw?|AX@V=eWD4%;)FzF~4CT|5}ruCB3UIA>l^&%;)!{ zGk++2)<53l=TbgDM~?aPki&d_{uT2#nf#*4=l7&DpT9@X{G-?|^M5t@<&>W>ZM>gs z|K|W_eiAtIFBNy2aqC-kUQCaOPJ5XMQ7b zpWj#cE#b5N4&cnc6`cA0c_{@I;ocVjfng6B9 zpR4@u;WPg%{k{8Z=H~!szF!Y~{VU~_fzSME;LL9f&iwY`K7WIrcY)9RKH$tB4$l0E z;y(XvJ)aJr`A>i||D^zb9h~Rw`@os=894o)!0Bhm5DirHaN|WkFF5@qaQaoi>0bd( zzYRG3PT=(Wfz!VWoc?%l`ZK`kF9E0j0yzDx;PiKa)BggT{`cVYGiQtqOKe=|=LDyJ zF*yAS;PmT(({BMzzcV=fTfylM2d6&3r4tODx z|E;bQk_`8s8(ZA)ZZ0r-`1`0n|8enh@xMOZ-Ven6>v7)CtMjC)#&0d|@1uHutaxqX zzb>9^czQR1qesK|kJI(k7WelXef}Wv7RG;0ytU!qi2M7KKBu(iLwn=<^SVxkKPJDT zxQi_sb*{f((Hxxf(BE%&elhsI{~f;=oIZa)oqjp^+HCCg<(s1WHrWI?AG+dxP6hqF z+b&;czY?7F`};G_=j*V%zr@#DUkl`10nQx%x}?p&13vTb3-B_yj%NKA!#`Rob-ap+ zyZPX*r#{yBMz{K64_8kQ^qar0>;3QK*9-WZhc5qwd_Ui8zP*3-t9;(SVm%|YU0+W& z9l!4n)HBui1?A5P_)CpnR{qL>ztQ-$ec8*)BMG`21Vs zpC@kn$KD?sEuZ(tSWgX;|DgQjKzrd6zdGRi z@4xW%bdNckDuz(x-{zPZJHJrm^@4EW`YKSzGmfN$@=Es@Xr zZ=4S|n*8PRdj#_Rd8@B~rTn`CzQ6zG{k8IE1pLLOo>%3s2>7oVf3y5I1OA7`e@p(S z0sn;Ycgj!Dd5!yP_Gfl+-`@T5^9KBq#{W`&g@9kz_`k|;67cQ)y0bIKrey-}*RlTo zCjWf-Ljw5^89%T5hXcO9U+4R$p!_8P|7DY3T>hqjzuWi~&{{Ei#JIXH|@N1g{Pa3L`+82uKS$h-Zvo?HK0CgD6$|)P zjGs$>t$^?EANqQV$Zs3)yPNz9^7{t-5yr1C|NekK-T3X~KOXRx8^53YH35IC@rTLx z=UKM@{ryJYKjY+o8OZhAD3S!;FmZ4O8M0Seq-amB>$>_-`V&Z z<=+zU{ryQ_{}%cFJjJ$mvdP~re`X-x-?#Mn@5+BJ;BPef2j%mejT;>*BJj7`5goKeT;ujVtjrM2>7Fof06w00sk@MmzV!!z+Y|r zWcjZK{2j)>MgHD^f7JN*%0C|PGpPUk_&zNETyfj~1&zO0zTeNRU)A`p$oKn=^;;T$ zm;82tdU_cDd-=Bo{JV{x`JDKCcp%`8qE`Ddvec6@V)`}UTT ze__BcZT!meD+T;|#;+m2S-|)6*VmIQ|E7R{hskdte`vt>^VjFMmOmxn`}ynrPV$!p z{8vmpJ>5F}!|C}FP27Uo)-`J)r_A{&+7#ItBmh+uMPOO z7{98X_Y3$Vjo(1e#{~SD#=lz6=LP)djNeJmUkvzf8Q-_(y@3Cj@l*8t>wup|FWmd_ zy;ELhaXY>j82@hh7X|!^#vd=gdcbdL{Q2@*2mG$aUnIX*z`x7*%jDl1@EY=_+^ZrUfZi2 z@ar4@Litw){Eo&?l7Dl+A7K2l@`nZd@y7S_b!xzW()hKN|4hJt)%Y#tzY*~F8vh3Q z2Lt|b#he|CRA;$p0bWXUg&0@$Dc#o46g{ z!p8UePlfZxpc_sMS) z@NY8yRQb0C{GrBQApgFAKgIaZ$e$hXml^*#`Kto{>&Aac{@VfnW8?3Z|3$z*W&BU& zr_1%n$Ln0;em)$NpFiMVV*FF`FAew&j9*amvU$MoWc)Jny9NA##;+rPc)*`v{MPcP z1^lOs-$nkj0e`*m`^(=F@b?*ito%a(|9j(4mH$h?&wBoE*Ylb3bBo*ky}0oolV2|2 z*D=0d2O96+E|8{({YX8qJZpZf`J?eqs4_1Ac4cmzCc>;P*0qRr&n` z{=LSpFaM!{|ETf(apj4CztZ^rc=K|=-){VYs%Lk=|J?W!<$oLS)9M#6{QCTa{4C;j zeDfK9rTnCTU)lH@<<|`OR~mn}{Hp{0&Bos^zjweNX8f<^j|%uxji0D>a8AH~#`x#S ze<9$%Vf+i_zZ38e8o#*wBLV+s<5!lSKJOnNug@3v^P#r<0s+6Q@f*so67U-uzm5DB z0sjW$cb4Bh;NNNdp7KWo{E5czFMoQ#Uu67=@|Oqv4aT1(e`~=1!1xQ~e-iM2F#dA+ zzXtql7yNcTe_4JWal5~lF#g-}%Ln``jQ^4R#sU92<9{u`bHGnA{*Uqp2mA+(f3}Wu zlLP*I!Gk$LQ zeFFY);}@1cI^a(;ent6@1^j1?Use9b@DUh|KsCz zZgD>!{CRD`fM3q|LzG`N;5Rb<-SS%o{2PrwT7Hj!Kgjsw<=-9fCmDZ+{22j%vGJG5 zUlH(MGybdc-wgO48h@kwPXqo57^W#kcf4A|o%RdnCzcv1a z@_!8YS@es|etb*F&mnHt^CaU}lV3XE*ED_|`Sk++)y8im|N4O6+xX4o-x2Uf8NaRk zaRGmh@w>`j81P>(eh>Mt1pIf5-%tLYfPcjJgXA9z_~{G%_Izxp{By+Z_!cn!1M-Un z{3^zuD8E*~Z(;no^4kXd?#B1mw|xWt2;;w^{QCp`bmPA-|M7so-1y(gUlZ`R8b49j zMLPrjC&n)%|I2{?tMSXp&saDi_ORodN8Io4)#Voo_~ngXPkyz4-`M#6y85bs-`V*7 zy84!YKiK#KRL{tOKiT*r<3$Mcl#Z`e_<&2t=R5daMt-6IP1)q z^v8APFx=O9f#GvhXF>6Frp{L2{Jf4U;+u{A{x9p90NxkvdIh{Yc%d}$zi`{7&(CL| z-v>VH(Xtz>hy6*Og6-<U85yUP|26IiPgvys8JkKXTlhaS!MB25%Xo z@y|nG{?+2nAB_AC@R@%*ICBOeX8^W)FE~G!Nw=G0b>0ELIQ-kdD`C5?jmtHEY8&q7 zTLZ&$>OMkKao1k%7j41&VY^y}Vs#D#*JaT*FKJfkcy6p}DuMIx~czbZpw=&?v z;YULkd$@dle$M6Kcf)T3J`%h-ICD6!$o)KV+hu+Va+v=>fQRa@0Y5Y9ycwL|FE$wb zUic4*`*qddznf`hOZHyk9^LbXTa~ncK3j@&g0;t;Ag}-!+8?={Gy`B;kYyh zAC3H*z#jm=Tio@}^yokSbF0P%xb2@$|Db=;BYzdPI|khMj~|!Mm9rl{bG`#-&IRZ< zwwIsJ!}hk2@3;Gt_O!Do*D{1f-r%<-Qy?CYtZoNa-ekHMK!(0txSW#uH{^DbCV6>#RXGdb0jb3J_K z_|Jj1?V19g$pPO{Q)`L?%5flj*K35yZ-Yxe1FzgPI>suNd{*>+$8RE z8Y$-%_{`zw>oUiGj+)PDp&b8t8m@lkEJr?b-Zl03{f?iH!JIDxIf)r#-FEtO$=a%( z?BcebByiT#5uD>Y3Y_CRN!<6twc74f_^f{sIJfIR$IIt*RL)!Qne!1ibNuJi`kXGx z$&Ak*U`{@8=3FN3b9yMJ7JTOL^B}*t6)`M?XE!rSqXg!~OTb>DLBM=e(ji{Pn5Nc}?73*L&{2 z4@1p6opY}fqDP_Fe^cvxTRBOFzbjtcaQ`_D{`$yo*MBcXIpZhm_|5BO*FXMqtg0G+ zwAS|;hHn(FZTPpUGuiNmwVyXMJXr(M)Nud#W-Sc=UcSHXcI|x-*9*Q~Zv4lB`+6K7 z2Y#LM9iIU1?_)SV5qt^!N#Ogyd7XX|oY(1f!I{$?JTvm|3h)QPd3`qjZf#`zS~em-9J)c>iP;w#yv)e>KPNFK)Z6|6bI|@qGe(4(ize&i?-roPJG= z3;q7!9IrXx%;9w?$C3N#*{Jgh)H55rV}SPue-!>a@VVfe=TpJoh0i)Ci@WK#B>IoP z?=dI9g=6dd(~-YYz>S}KxE#mV#Gd{3c;xuY;)(j{{q=gziSu~m_!e=$U-+DD z;yfNXe}{M>{q+7WJx?-xubvk-e7~OidG2!#ikCC~XL|1EgZGb!S2h0Edd}mKtN)mI zZR4NN^JK$M>ABy({C3l89yc|9X7Lt=pCjJd@I>*phWpQzZf|%_`JD{+pEupb@WS%D z8SY=V=xMnBeCj@i7gtV-;ibg~7+zU?u;G=&hZ*kIk&%Yikw41tdg5aYZzMj>@MhwZ z3~wnu#c+TBZieCQd$}*WWdU50wA1 z;X}mN8$Mioli~M>Z!vtN_%_4+=Z5bve7yW!hEEpXYxq?0{f5sJKWO+I@y`sOCw|26 zh2mcuzF7R2;mgEN7`{UMl;Nwy{rQq#Pu7a3(@)RW>3Jr@H|V)vKfJ$L+^-*=Zx#3J zhv(bH{rchgPVs#D>G^Iw_v?q}`^5eCIeLCT+^-*=9}@TLhv$dI%ju`*NA=vVAD(|B zUe)-=^}L4RC-uCx;Xmtnvf*jmhN4G9!_$j5H9WI8Ul(x4opZ!n8{dD=Vq3#=$!~9X zUhz(b`}5!~h8LFa*AMs9)l*cwr}0aO_c7dm|4WMD<>e1Byt4RU!>fr8Gu*!(Fw*ck z@<$n7PkfBw{&Vxk8SbyICmG&UIa3VxuS3i*ytVw_`KhJcpefaa$ssSz>J1<=t`CSFl!FRmAo@X-Lzi%MX z@b-G1&G4J`Jg4Ee>3J^wblabg^Yfn~&S#lZ*BaS&|E~XyUq-(F z9uVvM^~UE{mhaaU>-+uN`}O4y4%9Qr)YDe}jDWw~_}9x{6YzH!znlEM0sols`^!HS z@N?=urTc5w|3l^H6Sw_S$@mlG*9iEnjXzI*`+%Qf{AKb72mDFKe^LI7fWO@MZ^>U1 z@OK!0xBR^U|CsSVmVYYX=X4z$J$(OvFF&97@A}{P>GcMv8Ueqx@r%fBAMjI*Us3+x zfN#J5qK5n#0e`v4uOoj=z~5o~mh$%o{A0%ND*sf#&#C+CzW?u#pHJNVGaipB8Goew z8Ueqx@u$o8-&9PlR@zl;1C0e`vi?~}hK;O{X0 zJo$SA{xRdPmVYYX`|pSK{l8s)KD=K1oB1#9{R8rA1pL;$Rv0dKJPte0{_u1$_{QhJ2SDt^Pd8wGwJeT^5 z*E=q!nmDg_9Pc2`>mA4a_X_7R_4w~))9LK#?QT|1A>$tvPcqz(Uva}nt3kX@boKag zDrfv_e_sW!D_zc1`8AC1zsI7s;r@HIk`14yoQ8%k6zBDz+wNlV7RFyD-rDdL z;=F!zIjh9m8{dC_Rwu*P$?szL2Jvo&Zx-)q_*U^ghHn>7G2DM2)&RqI%O7m`KJj6O z9}pjD_#yF8h94FmW4M1m$~eQnkw3}s{sp2&ZpDAA3@HyhihR+jkX!t_$riL#TZ(;Z{@z#c~5N~VvD)IJ) zuNCiP_&V_}hHnt>X830Do`!D~?_>CO@f5>%iVrY+xAk28FS_$0#@YyX^L_;&Fbh94K7ZFnyo7v>t?SA2ot{lym-?$5jS8@^ci z2Mu2){+Zz`#E%%hO8jfX*NPuA+@E)yFx;Paoif~?cg0Sqqq!6<{qggE<)?EXdU(EF zJd@!&#S;zREuPKred0L{_vc-C4EN_<`3(2xU4;zy=UqvLf1~XdH~hGGX~R#7moxll z@k)lL(SFU}19HdH^x`#)pIN-N;pd3^*Z16CyPWLe{`Ec2bBX)c_dL%l-okY7@HyhU z44)^y*YJhn`wd?#e$en`;-4A5Li~u~tHi%He69E~!`F$QFnojfDZ@95C*XAux6W)8 z=l8`szFj<%@pp>*?~!+#asF=cY{uUwp40FH;&}`|B%aUk!{UVuKPsMN_)J$=^eArl zI`Ptm9}+KTcxFAXWOx}p_rK5R{<-Uimf|&x-(Syb8$MOf{qH6CoVDT&jqks=x2fTI z&yF=Wp@rf3#akO*SiG&_{(C6f8(uv4R~GMScs21phSwBNF}#lW z0K@Bv4>r7!_%OqpiH|hArT8er+lY@bylA?3|Bo}ggZxQ`-ylB4@UG%B4DT*J+wflE za}Dn+zQFMQ;)@I)D8AJ2A>zvoA1=Pq@O#A981BCx^ku^zl)v6^|GlA`44*82i{Vqn zw;4WDe23w4#CI7!PkgW83&r;vzF7RA;mgE7Gkk^k5yMxBe{J|$@neRs6F*`22Jusd zZx)aJLP<1L{W`N%Je}sR=i9|I8NO3I(eT~k*$m$&p40FH;&}`|B%aUk!{UVuKPsMN z_&4Ik4L>ek+VGR&yprK*^u3`~4Nos#!|=@FwGBToe5ZJ#;k(7N8NN?Er{M?0^B8_eJfGo*#S0mJRNQ}#gm2e3;>C@B zT)ed5C&kMd{*Jn&K&j*AX9Jcs=pKhBp!) zW_UC4k%qSvA7ywO@iB(C6CY=I2k}XU-ylB4@UG%B4DT*J+wflEa}Dn+zQFMQ;)@I) zD8AJ2A>zvoA1=Pq@O#A97(QD3Wy2p7UvKz$@lA$L7T;p{RPk+w&lKNb_#E+FhR+k< zYxqL({e~|VKWO+e@y`rjA%4X0RpMV8zE=F0;p@at7`{RLl;NAj6Lj43>*QAPbcSyi z&t&*c@kGOSi)S-@pLkBg4~XY6{E&D)!w-uWGW@7`lHuQo7dQO4I6sHS-M2X@Ue5SG zi&ru{%{f~C4Nos#!|=@FwGBT<+<(rGAD5r?zNUu8FRa%ini^hDyoKT2#akQhUw3P3 zxPRTMz2W|KpH7DR*G;+@o?rLnx*1+ayrdMtX-?IT|d6AUF7AE&vrFJzH8UU zLUGUn`~~E<5x4DX51-q;0Y2N+4LV2(fxiNN7dZV#z(>G;3Y@-w zzmTh+eknHrqlazRU~sl;EI8XWADr8L37qZP2TuPBareW`^VbVS|A`*o!RPz-bLzgm ztN$MKLs4+%SHyjO=2wT${Py6?9|+EMuV$>;cz^Qqj<{~n=jW1bM*aHrx!Ah#26$cM zzX{$E{EzZo|J)3}8vN2&$HoNsYv6AoXE!)=a$p{^o|5qSIbfH8^K-!b?|HlWH(Tae?|LzmB-y8}5NfHNm+puH90v)_2!c@6cP2>4}@&z$6d-vOMT z$MrZkb2bI|X91oC^=w4_XHw7W@Mi_`Uk&h&13W{Z9*#TP%g^g0=jX1Ie;L?rNoEQJFIoss^^FQ3YWsd*64ae!L`~+zqj>6Z1~@XreLLzMu(F2HXG=lt~Vv$g$G1jm;hsOM7f3RovQ zg0o%s1h{{Hjoa=fG=>Ga6bL@7w_SsLF*;lGJG7lP+RJ)F0>z(0Y1 zK6pCxKl!=fx#6D=&ip3e%<-SY>iU_yAN)MX=R9HlQuxeyGr;}t*}3hqfBf&$InI3l zIgz%`if%xnhvhZF*+2gC6m9;U@LB(q0Dlae{qrO^>;Dj(`9BAE#%QkkcCml5f-^rE zocT8fxW7Jf{lNZ7fzSMf;LP6`;M>93KfA%1f37Pedf0I+9pIOOvwtoJXMR6$);~VL zr-QS99tUT>f8U_{Yvz9spF9EAP3)gc;LNWH&YbH4yel~S$G=b0mBswW;4|O9KgYJ~ zb@=R`x51g8F@qc3-;Zz6051#9{;2}a{GQ;f|Na2?ufMx?v48yQ?6!XvBZuu>0nU0} z184njg46#Hob`MPPCuVsSGD6(44idd0?wSy;Ow_v;HY&T>XhS zzGTYeW=VXWK-*a>U^4lW+LhxST^KpD12!6C| z>UqLQaGn=U0so`(B3FOfXl>+qbA)2|EgiqhJ&7KQIn77J@#*U-pR-&%n|^xkU(d^F zxc^>8e_iJNuXOzO*Y%#ab^jAR3dR1LTBrYgx+KG&*E}w6xPKo;)U~mP&-b4<6%EC2 z_@z32R5JWg@v4SrauXnW)QJB$-EV!xYa9N)c(UPjwXQWZ+`n(Kso}ZQ-WG=Y?_Fzc zcm?ezZ4DnH-rn#{;yh0-fO*?PzH3fFa6gY6F9bdtKKV}Y!tf7*Uj%*(ocmGi!Zi9j zw_WZ>i8w#xepFsLZa(D4e&_qy-#!0%w1I0nYyP>$1Cp_7BH}>j}rjueW|4ex!ML z8}d01$AMppdY%sOEdl;5copPuU9AdU80#(jrx0?g!@nZHdxO`2KNP$sILG~R@WS$4 zztsVcPN`xK$FBhIBJReqF1SDLJN_2#+gt^o^ONfp{rlj*i5$OQyL#$jyBrtRa|k)? zXV&>D^3SAB`mFO6V*l~}Ksb0au&UIgdw?jt{%WnX`0P}?R37InwKI?xGoa3UyLu|XP(9h(p!Ry4!jQMQ>{A%#(@N0t? z0KX1A`rp{Y^#ghBIF4<%J^U5{?ypl^PA&NUy3g_M=+D~7VgKX-zY#e_!8?Or4BiQx z>qb}bmhib<*2&j*s>pZ4>CQ6`YoKZ&hp+E&UAh_Dy&gW#Tm1Qh>mR<(GB)7P1*gyR zCEkBn4xjC1KlebLY)-$2`k8zSeCEFg&i&|1 zaQb}zJjaW#(=msy3v>Q+p0Hh{5I%^8Q3qnh`VuV z9Q{d`6bC&2;dS2w^4+*hMgC&&Y2dGbPX~Vsoc;lEUr%&tAA20uPrKb;z-J&oH_j_& zf?o_i3%q%NUklFp&=H*TA&QJWT%F^Q&*RbTi2M1^@qHXVkNb1M>2v$1x+=}}Pd3a$zi#+(Z@Sj~U-UZyvH!-;_j15z{Tq-^ zzB$0R1o&G4{zibm8Q|N&|Bd#(i+r~C{Q%z=;QIr7Z-9Ro;2(ki8}0oR`E2hO0iGC` zCyCL1;jizQlQqE43-IjVyxz_kf=ZIlP~9F8KZM zxsI_P&WBH)Kfosh`kCtzb1py*&)W-v``4}9`cN30em-#KbKIMuy)EIh-*~;i{;3Xs z26CB#ATc0C8q{0-pD_n(XF_LpZcUVF@T{rCNS zjGS56f4>D^j+``U;}g{7a~%2mARI^jo+HPx0dlxMb^+)9I1-%uZ$e}9yl7H+%uXx{FJ&-wEeIOoqV;G91>(nq1u!~Heq&qd&zKgr;nKR1AL z{tO4_{Fw;O^?V^X=a2tfj(Y)cj(bIM-~Ut9|JC7l z#5`;b-U+-nIOl_ZUxRBG=fi{W*$>mjeLep8@;H3X!xzE1U4CB~x9dNz!_~v%%Sq($ z_~Jj;!ugz6`7=f!dN|H`RT`Z0swOzcr4=~Gr6)M^2Z1yH0dV#w-+x8^B7Dv_|NUvM zT|9sL5I+0!2XNMNRwmaw@%~{>9&qNA3-E^E%4vex(7IY zo)7c7#@~N%?dAP6x3;^7<6V*Oug7iwaJ#Jku2@PuhxHT&=XqLmiV=IbeDb>BH=`b2 z2a%70-wpma@b2LAzEx_xElE=k>_ZQmNN>eUQ@|{ypHG!PzeM zKgYc<{3PVu2JYXth|FPqS=XsL)j+_=9E&h>mgc*u`l z!abeC{J+zmGtpl5=PYpc=cC~NRDb@R`e&ma);|ZF^*;vwPu0))P_9hsalwzHKi^(- zaeVyzxO-lyc${Az{}Fq?v|C#`i}QHO&qLsT{W#inruC#K|DUR7fa-Vi{1WuTgWz-j zgF4fdhzI;rb*7*m);SFPKdE!ZKV0XTj$=OUK>t+#^g}b12`}IJe9D z_RQh?lj-k34*ie8>GS;US+tk)fxHfKmc#E0PM_@!U1xE8xm|M3lNH!5&xhIGW~k>m z_+7y1a~)v+OodN=Ci}&6f&F3?>Iv-^>}R%@{mlLe^)vfzHMV;u`_~%y+`pJJ9{ofA zOxCeau#QD9iNqdmJ>fdm2%P8n*MYx;I(vY3LjUu1My_x7!{>SLEO5?SuG=qTyIfCN z`JYHO@daTd;99OmhucG>SUgiGI)$h1JPGr(Q@Z8stX!!Mdp3QK-o^pNLgF3l??E`P399RDb;5UJD z{_}X$JlYBUxJ3Un_HgyP@1FC#W`OhfwIAD+9_yd)qMY6Wu0Hp0`HuVJJ^Rh^<}oYv z{M!42#My7oA0VDnKfQmqp8N9z&xeWoe(?MO@k09P`B*(qGJK4l>$3KAyB-lQZTv}k z{&&`y1L$Y2GarF-o%tC2pIT>l{c;fb$!eb)=R@HBxaRn$;1%G14o?3Fcoz9?T*_hI z`uX7Uzl6_q{wr|KTkaP{P|w%!{rqwH-v#*h0e(EdhvK+dP}_Cw`YH1Ldg0dzx80M- z;c@>I_!Y`=eoyp|UsoLe8Gc{*x56I@-XDBA_%Fy=0?z*YZ>aOvzfoshw2S?D4LJM% z4)BC%a{KjjGiggANRR0*Xw%sUZ3?@pS||l`@8r4j)TvG z-a5IJI=>G6yyzc++ppP~C+EZ8d2)WZ^W*~Xf7d+OlYTuP4hFd%TyH!JACCTM_$c`A zi1SQ%Q}|`@h2Xcs7lz*hUj+UPyczr*xaTjtso|bOT(RqTfu*e`dkKo>vLJS^;rkE zKHI>p&*k9H(BI|ZRrR?6`X$)E^7zL2^W#

7soBj*g-ncb740rE8$?SE)yq4v@4CS=O2^V=uZ} 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